Skip to content

Commit

Permalink
Merge pull request #609 from components-ai/selector-functions
Browse files Browse the repository at this point in the history
Add initial support for selector functions
  • Loading branch information
johno authored Sep 9, 2022
2 parents b7c72ac + 63cc6ff commit 5f2cd87
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 12 deletions.
5 changes: 5 additions & 0 deletions .changeset/modern-zebras-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@compai/css-gui': patch
---

Add initial support for selector functions
54 changes: 49 additions & 5 deletions packages/gui/src/components/Editor/Controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
isValidElement,
ReactNode,
useMemo,
useState,
} from 'react'
import { camelCase, isNil, mapValues, uniq } from 'lodash-es'
import { RefreshCw } from 'react-feather'
Expand Down Expand Up @@ -37,7 +38,14 @@ import { SchemaInput } from '../inputs/SchemaInput'
import { EditorDropdown } from '../ui/dropdowns/EditorDropdown'
import { FieldsetDropdown } from '../ui/dropdowns/FieldsetDropdown'
import { tokenize } from '../../lib/parse'
import { addPseudoSyntax } from '../../lib/pseudos'
import {
addPseudoSyntax,
getSelectorFunctionArgument,
getSelectorFunctionName,
isSelectorFunction,
removePseudoSyntax,
stringifySelectorFunction,
} from '../../lib/pseudos'

export const getPropertyFromField = (field: KeyArg) => {
if (Array.isArray(field)) {
Expand Down Expand Up @@ -219,7 +227,7 @@ export const Editor = ({
<div sx={{ ml: 'auto', display: 'flex' }}>
<IconButton
onClick={() => onChange(regenerateAll())}
sx={{ ml: 'auto', }}
sx={{ ml: 'auto' }}
>
<RefreshCw size={15} />
</IconButton>
Expand Down Expand Up @@ -320,10 +328,13 @@ type FieldsetControlProps = {
field: string
}
const FieldsetControl = ({ field }: FieldsetControlProps) => {
const { getField, removeField } = useEditor()
const { getField, removeField, setFields } = useEditor()
const [argument, setArgument] = useState(getSelectorFunctionArgument(field))

const styles = getField(field)
const properties = Object.keys(styles)
const label = addPseudoSyntax(field)
const rawFieldsetName = getSelectorFunctionName(field)

return (
<section
Expand All @@ -350,12 +361,45 @@ const FieldsetControl = ({ field }: FieldsetControlProps) => {
mb: 0,
}}
>
{removeInternalCSSClassSyntax(label)}
{rawFieldsetName}
{isSelectorFunction(rawFieldsetName) ? (
<>
{'('}
<input
value={argument}
sx={{
width: 64,
}}
onChange={(e) => {
setArgument(e.target.value)
}}
onBlur={() => {
setFields(
{
[stringifySelectorFunction(rawFieldsetName, argument)]:
styles,
},
[field]
)
}}
/>
{')'}
</>
) : null}
</h3>
<FieldsetDropdown onRemove={() => removeField(field)} />
</div>
<GenericFieldset field={field}>
<div sx={{ mb: 3, p: 3, borderWidth: '1px', borderStyle: 'solid', borderColor: 'border', borderRadius: '6px', }}>
<div
sx={{
mb: 3,
p: 3,
borderWidth: '1px',
borderStyle: 'solid',
borderColor: 'border',
borderRadius: '6px',
}}
>
<AddPropertyControl
field={field}
styles={styles}
Expand Down
19 changes: 15 additions & 4 deletions packages/gui/src/data/pseudo-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,25 @@ export const inputPseudoClasses = [
export const treeStructuralPseudoClasses = [
'root',
'empty',
'nth-child',
'nth-last-child',
'first-child',
'last-child',
'only-child',
'nth-of-type',
'nth-last-of-type',
'only-of-type',
] as const
export const selectorFunctionPseudoClasses = [
'dir',
'has',
'host-context',
'host',
'is',
'lang',
'not',
'nth-child',
'nth-last-child',
'nth-last-of-type',
'nth-of-type',
'where',
]
export const pseudoClasses = [
...linquisticPseudoClasses,
...locationPseudoClasses,
Expand All @@ -60,4 +70,5 @@ export const pseudoClasses = [
...resourceStatePseudoClasses,
...inputPseudoClasses,
...treeStructuralPseudoClasses,
...selectorFunctionPseudoClasses,
] as const
3 changes: 3 additions & 0 deletions packages/gui/src/data/pseudo-elements.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const selectorFunctionPseudoElements = ['part', 'slotted']

export const pseudoElements = [
'after',
'backdrop',
Expand All @@ -13,4 +15,5 @@ export const pseudoElements = [
'selection',
'spelling-error',
'target-text',
...selectorFunctionPseudoElements,
] as const
7 changes: 7 additions & 0 deletions packages/gui/src/data/selector-functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { selectorFunctionPseudoClasses } from './pseudo-classes'
import { selectorFunctionPseudoElements } from './pseudo-elements'

export const selectorFunctions = [
...selectorFunctionPseudoClasses,
...selectorFunctionPseudoElements,
] as const
24 changes: 22 additions & 2 deletions packages/gui/src/lib/pseudos.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { pseudoClasses } from '../data/pseudo-classes'
import { pseudoElements } from '../data/pseudo-elements'
import { selectorFunctions } from '../data/selector-functions'

export const isPseudoClass = (str: string): boolean => {
return !!pseudoClasses.filter((value) => value === str).length
Expand All @@ -9,8 +10,12 @@ export const isPseudoElement = (str: string): boolean => {
return !!pseudoElements.filter((value) => value === str).length
}

export const isSelectorFunction = (str: string): boolean => {
return !!selectorFunctions.filter((value) => str.startsWith(value)).length
}

export const isPseudo = (str: string): boolean => {
return isPseudoClass(str) || isPseudoElement(str)
return isPseudoClass(str) || isPseudoElement(str) || isSelectorFunction(str)
}

export const hasPseudoSyntax = (str: string): boolean => {
Expand All @@ -21,8 +26,23 @@ export const removePseudoSyntax = (str: string): string => {
return str.replace(/^:+/, '')
}

export const getSelectorFunctionArgument = (str: string): string => {
return str.match(/\(([^)]+)\)/)?.[1] ?? ''
}

export const getSelectorFunctionName = (str: string): string => {
return str.split('(')[0]
}

export const stringifySelectorFunction = (
functionName: string,
argument: string
): string => {
return `${addPseudoSyntax(functionName)}(${argument})`
}

export const addPseudoSyntax = (str: string): string => {
if (isPseudoClass(str)) {
if (isPseudoClass(str) || isSelectorFunction(str)) {
return ':' + str
} else if (isPseudoElement(str)) {
return '::' + str
Expand Down
3 changes: 2 additions & 1 deletion packages/gui/src/lib/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isPseudoClass, isPseudoElement } from './pseudos'
import { isPseudoClass, isPseudoElement, isSelectorFunction } from './pseudos'
import { isElement } from './elements'
import { lowerCase, startCase, upperFirst } from 'lodash-es'
import { EditorProps, EditorPropsWithLabel } from '../types/editor'
Expand Down Expand Up @@ -31,6 +31,7 @@ export function isNestedSelector(selector: string): boolean {
isElement(selector) ||
isPseudoClass(selector) ||
isPseudoElement(selector) ||
isSelectorFunction(selector) ||
isInternalCSSClass(selector) ||
false
)
Expand Down

1 comment on commit 5f2cd87

@vercel
Copy link

@vercel vercel bot commented on 5f2cd87 Sep 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

css-gui – ./

css-gui.vercel.app
css-gui-git-main-components-ai.vercel.app
css-gui-components-ai.vercel.app

Please sign in to comment.