Skip to content

Commit 5f2cd87

Browse files
authored
Merge pull request #609 from components-ai/selector-functions
Add initial support for selector functions
2 parents b7c72ac + 63cc6ff commit 5f2cd87

File tree

7 files changed

+103
-12
lines changed

7 files changed

+103
-12
lines changed

.changeset/modern-zebras-fry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@compai/css-gui': patch
3+
---
4+
5+
Add initial support for selector functions

packages/gui/src/components/Editor/Controls.tsx

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
isValidElement,
77
ReactNode,
88
useMemo,
9+
useState,
910
} from 'react'
1011
import { camelCase, isNil, mapValues, uniq } from 'lodash-es'
1112
import { RefreshCw } from 'react-feather'
@@ -37,7 +38,14 @@ import { SchemaInput } from '../inputs/SchemaInput'
3738
import { EditorDropdown } from '../ui/dropdowns/EditorDropdown'
3839
import { FieldsetDropdown } from '../ui/dropdowns/FieldsetDropdown'
3940
import { tokenize } from '../../lib/parse'
40-
import { addPseudoSyntax } from '../../lib/pseudos'
41+
import {
42+
addPseudoSyntax,
43+
getSelectorFunctionArgument,
44+
getSelectorFunctionName,
45+
isSelectorFunction,
46+
removePseudoSyntax,
47+
stringifySelectorFunction,
48+
} from '../../lib/pseudos'
4149

4250
export const getPropertyFromField = (field: KeyArg) => {
4351
if (Array.isArray(field)) {
@@ -219,7 +227,7 @@ export const Editor = ({
219227
<div sx={{ ml: 'auto', display: 'flex' }}>
220228
<IconButton
221229
onClick={() => onChange(regenerateAll())}
222-
sx={{ ml: 'auto', }}
230+
sx={{ ml: 'auto' }}
223231
>
224232
<RefreshCw size={15} />
225233
</IconButton>
@@ -320,10 +328,13 @@ type FieldsetControlProps = {
320328
field: string
321329
}
322330
const FieldsetControl = ({ field }: FieldsetControlProps) => {
323-
const { getField, removeField } = useEditor()
331+
const { getField, removeField, setFields } = useEditor()
332+
const [argument, setArgument] = useState(getSelectorFunctionArgument(field))
333+
324334
const styles = getField(field)
325335
const properties = Object.keys(styles)
326336
const label = addPseudoSyntax(field)
337+
const rawFieldsetName = getSelectorFunctionName(field)
327338

328339
return (
329340
<section
@@ -350,12 +361,45 @@ const FieldsetControl = ({ field }: FieldsetControlProps) => {
350361
mb: 0,
351362
}}
352363
>
353-
{removeInternalCSSClassSyntax(label)}
364+
{rawFieldsetName}
365+
{isSelectorFunction(rawFieldsetName) ? (
366+
<>
367+
{'('}
368+
<input
369+
value={argument}
370+
sx={{
371+
width: 64,
372+
}}
373+
onChange={(e) => {
374+
setArgument(e.target.value)
375+
}}
376+
onBlur={() => {
377+
setFields(
378+
{
379+
[stringifySelectorFunction(rawFieldsetName, argument)]:
380+
styles,
381+
},
382+
[field]
383+
)
384+
}}
385+
/>
386+
{')'}
387+
</>
388+
) : null}
354389
</h3>
355390
<FieldsetDropdown onRemove={() => removeField(field)} />
356391
</div>
357392
<GenericFieldset field={field}>
358-
<div sx={{ mb: 3, p: 3, borderWidth: '1px', borderStyle: 'solid', borderColor: 'border', borderRadius: '6px', }}>
393+
<div
394+
sx={{
395+
mb: 3,
396+
p: 3,
397+
borderWidth: '1px',
398+
borderStyle: 'solid',
399+
borderColor: 'border',
400+
borderRadius: '6px',
401+
}}
402+
>
359403
<AddPropertyControl
360404
field={field}
361405
styles={styles}

packages/gui/src/data/pseudo-classes.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,25 @@ export const inputPseudoClasses = [
4343
export const treeStructuralPseudoClasses = [
4444
'root',
4545
'empty',
46-
'nth-child',
47-
'nth-last-child',
4846
'first-child',
4947
'last-child',
5048
'only-child',
51-
'nth-of-type',
52-
'nth-last-of-type',
5349
'only-of-type',
5450
] as const
51+
export const selectorFunctionPseudoClasses = [
52+
'dir',
53+
'has',
54+
'host-context',
55+
'host',
56+
'is',
57+
'lang',
58+
'not',
59+
'nth-child',
60+
'nth-last-child',
61+
'nth-last-of-type',
62+
'nth-of-type',
63+
'where',
64+
]
5565
export const pseudoClasses = [
5666
...linquisticPseudoClasses,
5767
...locationPseudoClasses,
@@ -60,4 +70,5 @@ export const pseudoClasses = [
6070
...resourceStatePseudoClasses,
6171
...inputPseudoClasses,
6272
...treeStructuralPseudoClasses,
73+
...selectorFunctionPseudoClasses,
6374
] as const

packages/gui/src/data/pseudo-elements.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
export const selectorFunctionPseudoElements = ['part', 'slotted']
2+
13
export const pseudoElements = [
24
'after',
35
'backdrop',
@@ -13,4 +15,5 @@ export const pseudoElements = [
1315
'selection',
1416
'spelling-error',
1517
'target-text',
18+
...selectorFunctionPseudoElements,
1619
] as const
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { selectorFunctionPseudoClasses } from './pseudo-classes'
2+
import { selectorFunctionPseudoElements } from './pseudo-elements'
3+
4+
export const selectorFunctions = [
5+
...selectorFunctionPseudoClasses,
6+
...selectorFunctionPseudoElements,
7+
] as const

packages/gui/src/lib/pseudos.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { pseudoClasses } from '../data/pseudo-classes'
22
import { pseudoElements } from '../data/pseudo-elements'
3+
import { selectorFunctions } from '../data/selector-functions'
34

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

13+
export const isSelectorFunction = (str: string): boolean => {
14+
return !!selectorFunctions.filter((value) => str.startsWith(value)).length
15+
}
16+
1217
export const isPseudo = (str: string): boolean => {
13-
return isPseudoClass(str) || isPseudoElement(str)
18+
return isPseudoClass(str) || isPseudoElement(str) || isSelectorFunction(str)
1419
}
1520

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

29+
export const getSelectorFunctionArgument = (str: string): string => {
30+
return str.match(/\(([^)]+)\)/)?.[1] ?? ''
31+
}
32+
33+
export const getSelectorFunctionName = (str: string): string => {
34+
return str.split('(')[0]
35+
}
36+
37+
export const stringifySelectorFunction = (
38+
functionName: string,
39+
argument: string
40+
): string => {
41+
return `${addPseudoSyntax(functionName)}(${argument})`
42+
}
43+
2444
export const addPseudoSyntax = (str: string): string => {
25-
if (isPseudoClass(str)) {
45+
if (isPseudoClass(str) || isSelectorFunction(str)) {
2646
return ':' + str
2747
} else if (isPseudoElement(str)) {
2848
return '::' + str

packages/gui/src/lib/util.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isPseudoClass, isPseudoElement } from './pseudos'
1+
import { isPseudoClass, isPseudoElement, isSelectorFunction } from './pseudos'
22
import { isElement } from './elements'
33
import { lowerCase, startCase, upperFirst } from 'lodash-es'
44
import { EditorProps, EditorPropsWithLabel } from '../types/editor'
@@ -31,6 +31,7 @@ export function isNestedSelector(selector: string): boolean {
3131
isElement(selector) ||
3232
isPseudoClass(selector) ||
3333
isPseudoElement(selector) ||
34+
isSelectorFunction(selector) ||
3435
isInternalCSSClass(selector) ||
3536
false
3637
)

0 commit comments

Comments
 (0)