@graphprotocol/gds-css 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/component-registry.d.ts +112 -40
- package/dist/component-registry.d.ts.map +1 -1
- package/dist/component-registry.js +42 -7
- package/dist/component-registry.js.map +1 -1
- package/dist/css-props/index.d.ts +1 -1
- package/dist/css-props/index.d.ts.map +1 -1
- package/dist/css-props/index.js +1 -1
- package/dist/css-props/index.js.map +1 -1
- package/dist/css-props/parseCSSPropValue.js +1 -1
- package/dist/css-props/parseCSSPropValue.js.map +1 -1
- package/dist/css-props/setupCSSProps.d.ts +7 -0
- package/dist/css-props/setupCSSProps.d.ts.map +1 -0
- package/dist/css-props/{registerCSSProps.js → setupCSSProps.js} +30 -42
- package/dist/css-props/setupCSSProps.js.map +1 -0
- package/dist/css-states/index.d.ts +1 -1
- package/dist/css-states/index.d.ts.map +1 -1
- package/dist/css-states/index.js +1 -1
- package/dist/css-states/index.js.map +1 -1
- package/dist/css-states/setupCSSStates.d.ts +20 -0
- package/dist/css-states/setupCSSStates.d.ts.map +1 -0
- package/dist/css-states/{registerCSSStates.js → setupCSSStates.js} +21 -18
- package/dist/css-states/setupCSSStates.js.map +1 -0
- package/dist/css-states/states.d.ts +1 -0
- package/dist/css-states/states.d.ts.map +1 -1
- package/dist/css-states/states.js +2 -1
- package/dist/css-states/states.js.map +1 -1
- package/dist/design-tokens.generated.d.ts +44 -24
- package/dist/design-tokens.generated.d.ts.map +1 -1
- package/dist/design-tokens.generated.js +82 -10
- package/dist/design-tokens.generated.js.map +1 -1
- package/dist/tailwind-customizations/index.d.ts +2 -2
- package/dist/tailwind-customizations/index.d.ts.map +1 -1
- package/dist/tailwind-customizations/index.js +2 -2
- package/dist/tailwind-customizations/index.js.map +1 -1
- package/dist/tailwind-customizations/setupComponents.d.ts +8 -0
- package/dist/tailwind-customizations/setupComponents.d.ts.map +1 -0
- package/dist/tailwind-customizations/setupComponents.js +75 -0
- package/dist/tailwind-customizations/setupComponents.js.map +1 -0
- package/dist/tailwind-customizations/setupVariants.d.ts +8 -0
- package/dist/tailwind-customizations/setupVariants.d.ts.map +1 -0
- package/dist/tailwind-customizations/{registerVariants.js → setupVariants.js} +7 -7
- package/dist/tailwind-customizations/setupVariants.js.map +1 -0
- package/dist/tailwind-customizations/variants.d.ts.map +1 -1
- package/dist/tailwind-customizations/variants.js +2 -0
- package/dist/tailwind-customizations/variants.js.map +1 -1
- package/dist/tailwind-plugin.js +7 -7
- package/dist/tailwind-plugin.js.map +1 -1
- package/dist/utils/cssUnescape.d.ts +5 -3
- package/dist/utils/cssUnescape.d.ts.map +1 -1
- package/dist/utils/cssUnescape.js +5 -3
- package/dist/utils/cssUnescape.js.map +1 -1
- package/package.json +5 -5
- package/src/component-registry.ts +205 -71
- package/src/css-props/index.ts +1 -1
- package/src/css-props/parseCSSPropValue.ts +1 -1
- package/src/css-props/{registerCSSProps.ts → setupCSSProps.ts} +31 -46
- package/src/css-states/index.ts +1 -1
- package/src/css-states/{registerCSSStates.ts → setupCSSStates.ts} +20 -17
- package/src/css-states/states.ts +2 -1
- package/src/design-tokens.generated.ts +82 -10
- package/src/tailwind-customizations/index.ts +2 -2
- package/src/tailwind-customizations/setupComponents.ts +87 -0
- package/src/tailwind-customizations/{registerVariants.ts → setupVariants.ts} +6 -6
- package/src/tailwind-customizations/variants.ts +11 -15
- package/src/tailwind-plugin.ts +7 -7
- package/src/utils/cssUnescape.ts +5 -3
- package/styles/global.css +4 -3
- package/styles/theme.css +16 -12
- package/styles/typography.css +27 -14
- package/styles/utilities.css +9 -0
- package/dist/css-props/registerCSSProps.d.ts +0 -7
- package/dist/css-props/registerCSSProps.d.ts.map +0 -1
- package/dist/css-props/registerCSSProps.js.map +0 -1
- package/dist/css-states/registerCSSStates.d.ts +0 -23
- package/dist/css-states/registerCSSStates.d.ts.map +0 -1
- package/dist/css-states/registerCSSStates.js.map +0 -1
- package/dist/tailwind-customizations/registerUtilities.d.ts +0 -9
- package/dist/tailwind-customizations/registerUtilities.d.ts.map +0 -1
- package/dist/tailwind-customizations/registerUtilities.js +0 -59
- package/dist/tailwind-customizations/registerUtilities.js.map +0 -1
- package/dist/tailwind-customizations/registerVariants.d.ts +0 -8
- package/dist/tailwind-customizations/registerVariants.d.ts.map +0 -1
- package/dist/tailwind-customizations/registerVariants.js.map +0 -1
- package/src/tailwind-customizations/registerUtilities.ts +0 -65
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { objectEntries } from 'ts-extras'
|
|
2
|
-
|
|
3
1
|
import { camelToKebab, type DeepRecord } from '@graphprotocol/gds-utils'
|
|
4
2
|
|
|
5
3
|
import type {
|
|
@@ -14,45 +12,85 @@ import type {
|
|
|
14
12
|
|
|
15
13
|
export type CSSPropsConfig = Record<string, CSSPropDefinition>
|
|
16
14
|
|
|
17
|
-
type CSSProp<
|
|
15
|
+
type CSSProp<Def extends CSSPropDefinition = CSSPropDefinition> = Def & {
|
|
18
16
|
readonly name: string
|
|
19
17
|
readonly kebabName: string
|
|
20
|
-
|
|
18
|
+
/** The container name to use in style queries for this prop. */
|
|
19
|
+
readonly containerName: string
|
|
20
|
+
/** The CSS custom property that holds this prop's value (e.g. `--gds-button-size`). */
|
|
21
|
+
readonly cssProperty: string
|
|
22
|
+
/** The initial value of this prop's CSS custom property. */
|
|
23
|
+
readonly initialValue: CSSPropType<Def>
|
|
24
|
+
/** Whether this prop is defined or overridden in this component (not inherited via `extends`). */
|
|
25
|
+
readonly own: boolean
|
|
26
|
+
/** The data attribute used by `useCSSPropsPolyfill` (e.g. `data-gds-prop-polyfill-size`). */
|
|
27
|
+
readonly polyfillAttribute: string
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
type CSSProps<
|
|
24
|
-
readonly [K in keyof
|
|
30
|
+
type CSSProps<Props extends CSSPropsConfig> = {
|
|
31
|
+
readonly [K in keyof Props]: Props[K] & CSSProp<Props[K]>
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
type
|
|
34
|
+
type IsolateOption = true | 'allow-inheritance' | false
|
|
35
|
+
|
|
28
36
|
type VarCSS = string | DeepRecord<string>
|
|
37
|
+
type VarsConfig = Record<string, VarCSS>
|
|
29
38
|
|
|
30
|
-
export class GDSComponent<
|
|
39
|
+
export class GDSComponent<
|
|
40
|
+
Props extends CSSPropsConfig = CSSPropsConfig,
|
|
41
|
+
AddonCompatible extends boolean = boolean,
|
|
42
|
+
> {
|
|
31
43
|
readonly name: string
|
|
32
44
|
readonly kebabName: string
|
|
33
45
|
readonly className: string
|
|
34
|
-
readonly containerName: string
|
|
35
|
-
readonly
|
|
36
|
-
readonly
|
|
46
|
+
readonly containerName: string
|
|
47
|
+
readonly extends: GDSComponent | undefined
|
|
48
|
+
readonly isolate: IsolateOption
|
|
49
|
+
readonly addonCompatible: AddonCompatible
|
|
50
|
+
readonly cssProps: CSSProps<Props>
|
|
37
51
|
readonly vars: VarsConfig | undefined
|
|
38
52
|
|
|
39
53
|
constructor(
|
|
40
54
|
name: string,
|
|
41
55
|
config?: {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
extends?: GDSComponent | undefined
|
|
57
|
+
isolate?: IsolateOption | undefined
|
|
58
|
+
addonCompatible?: AddonCompatible | undefined
|
|
59
|
+
cssProps?: Props | undefined
|
|
45
60
|
vars?: VarsConfig | undefined
|
|
46
61
|
},
|
|
47
62
|
) {
|
|
48
63
|
this.name = name
|
|
49
64
|
this.kebabName = camelToKebab(name)
|
|
50
65
|
this.className = `gds-${this.kebabName}`
|
|
51
|
-
this.containerName =
|
|
52
|
-
this.
|
|
66
|
+
this.containerName = `gds-${this.kebabName}`
|
|
67
|
+
this.extends = config?.extends
|
|
68
|
+
this.isolate = config?.isolate ?? (this.extends ? false : true)
|
|
69
|
+
this.addonCompatible = (config?.addonCompatible ?? false) as AddonCompatible
|
|
70
|
+
this.vars = config?.vars
|
|
71
|
+
|
|
72
|
+
// Merge `cssProps` from parent with own, allowing `null` to remove inherited props
|
|
73
|
+
const parentCSSProps = this.extends?.cssProps ?? {}
|
|
74
|
+
const ownCSSProps = (config?.cssProps ?? {}) as Props
|
|
75
|
+
const ownPropNames = new Set(Object.keys(ownCSSProps))
|
|
76
|
+
const mergedCSSProps = new Map<string, CSSPropDefinition>()
|
|
77
|
+
for (const [propName, prop] of Object.entries(parentCSSProps)) {
|
|
78
|
+
if (ownCSSProps[propName] !== null) {
|
|
79
|
+
mergedCSSProps.set(propName, prop)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const [propName, prop] of Object.entries(ownCSSProps)) {
|
|
83
|
+
if (prop !== null) {
|
|
84
|
+
mergedCSSProps.set(propName, prop)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
53
88
|
this.cssProps = Object.freeze(
|
|
54
89
|
Object.fromEntries(
|
|
55
|
-
|
|
90
|
+
[...mergedCSSProps.entries()].map(([propName, prop]) => {
|
|
91
|
+
const kebabName = camelToKebab(propName)
|
|
92
|
+
const own = ownPropNames.has(propName)
|
|
93
|
+
const parentProp = parentCSSProps[propName]
|
|
56
94
|
const initialValue =
|
|
57
95
|
prop.defaultValue ??
|
|
58
96
|
(() => {
|
|
@@ -74,21 +112,35 @@ export class GDSComponent<P extends CSSPropsConfig = CSSPropsConfig, A extends b
|
|
|
74
112
|
{
|
|
75
113
|
...prop,
|
|
76
114
|
name: propName,
|
|
77
|
-
kebabName
|
|
115
|
+
kebabName,
|
|
116
|
+
// Inherited props keep their parent's `containerName` and `cssProperty`
|
|
117
|
+
containerName: (!own && parentProp?.containerName) || this.containerName,
|
|
118
|
+
cssProperty:
|
|
119
|
+
(!own && parentProp?.cssProperty) || `--gds-${this.kebabName}-${kebabName}`,
|
|
78
120
|
initialValue,
|
|
121
|
+
own,
|
|
122
|
+
polyfillAttribute: `data-gds-prop-polyfill-${kebabName}`,
|
|
79
123
|
},
|
|
80
124
|
]
|
|
81
125
|
}),
|
|
82
126
|
),
|
|
83
|
-
) as CSSProps<
|
|
84
|
-
this.vars = config?.vars
|
|
127
|
+
) as unknown as CSSProps<Props>
|
|
85
128
|
}
|
|
86
129
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
130
|
+
/** Returns the chain of container names from the `extends` hierarchy as an array. */
|
|
131
|
+
getContainerNameChain() {
|
|
132
|
+
const containerNames = [this.containerName]
|
|
133
|
+
let parent = this.extends
|
|
134
|
+
while (parent) {
|
|
135
|
+
containerNames.unshift(parent.containerName)
|
|
136
|
+
parent = parent.extends
|
|
91
137
|
}
|
|
138
|
+
return containerNames
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
getCSSPropByName<Name extends keyof Props & string>(name: Name): CSSProps<Props>[Name] {
|
|
142
|
+
const prop = this.cssProps[name]
|
|
143
|
+
if (!prop) throw new Error(`Unknown CSS prop "${name}" for component "${this.name}"`)
|
|
92
144
|
return prop
|
|
93
145
|
}
|
|
94
146
|
|
|
@@ -102,81 +154,163 @@ export class GDSComponent<P extends CSSPropsConfig = CSSPropsConfig, A extends b
|
|
|
102
154
|
}
|
|
103
155
|
}
|
|
104
156
|
|
|
105
|
-
/**
|
|
106
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Validates a CSS prop definition. Ensures:
|
|
159
|
+
*
|
|
160
|
+
* - `values` type props have at least one value.
|
|
161
|
+
* - `defaultValue` (if provided) is one of the `values`
|
|
162
|
+
*/
|
|
163
|
+
type ValidateCSSPropDefinition<Def> = Def extends {
|
|
107
164
|
type: 'values'
|
|
108
|
-
values: readonly unknown[]
|
|
165
|
+
values: readonly [unknown, ...unknown[]]
|
|
166
|
+
defaultValue: infer Default
|
|
109
167
|
}
|
|
110
|
-
?
|
|
111
|
-
?
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
168
|
+
? Default extends Def['values'][number]
|
|
169
|
+
? Def
|
|
170
|
+
: Omit<Def, 'defaultValue'> & { defaultValue: Def['values'][number] }
|
|
171
|
+
: Def extends { type: 'values'; values: readonly [] }
|
|
172
|
+
? never
|
|
173
|
+
: Def
|
|
174
|
+
|
|
175
|
+
/** Validates all CSS props in an object. Allows `null` to remove inherited props. */
|
|
176
|
+
type ValidateCSSPropsConfig<Config extends Record<string, unknown>> = {
|
|
177
|
+
[K in keyof Config]: Config[K] extends null ? null : ValidateCSSPropDefinition<Config[K]>
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Validates that `vars` names don't conflict with `cssProps` names. */
|
|
181
|
+
type ValidateVarsConfig<CSSPropsKeys, Vars extends VarsConfig> = {
|
|
182
|
+
[K in keyof Vars]: K extends CSSPropsKeys
|
|
183
|
+
? `Var "${K & string}" conflicts with cssProps. Choose a different name.`
|
|
184
|
+
: Vars[K]
|
|
124
185
|
}
|
|
125
186
|
|
|
126
|
-
/**
|
|
127
|
-
|
|
128
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Merges parent `cssProps` with own. Own props override parent props, `null` removes inherited
|
|
189
|
+
* props.
|
|
190
|
+
*/
|
|
191
|
+
type MergeCSSProps<
|
|
192
|
+
ParentProps extends CSSPropsConfig | undefined,
|
|
193
|
+
OwnProps extends Record<string, CSSPropDefinition | null>,
|
|
194
|
+
> = ParentProps extends CSSPropsConfig
|
|
195
|
+
? {
|
|
196
|
+
// Keys from parent that aren't overridden with `null`
|
|
197
|
+
[K in keyof ParentProps as K extends keyof OwnProps
|
|
198
|
+
? OwnProps[K] extends null
|
|
199
|
+
? never
|
|
200
|
+
: K
|
|
201
|
+
: K]: K extends keyof OwnProps
|
|
202
|
+
? OwnProps[K] extends CSSPropDefinition
|
|
203
|
+
? OwnProps[K]
|
|
204
|
+
: ParentProps[K]
|
|
205
|
+
: ParentProps[K]
|
|
206
|
+
} & {
|
|
207
|
+
// Keys from own that aren't in parent and aren't `null`
|
|
208
|
+
[K in keyof OwnProps as K extends keyof ParentProps
|
|
209
|
+
? never
|
|
210
|
+
: OwnProps[K] extends null
|
|
211
|
+
? never
|
|
212
|
+
: K]: OwnProps[K] extends CSSPropDefinition ? OwnProps[K] : never
|
|
213
|
+
}
|
|
129
214
|
: {
|
|
130
|
-
[K in keyof
|
|
131
|
-
?
|
|
132
|
-
:
|
|
215
|
+
[K in keyof OwnProps as OwnProps[K] extends null
|
|
216
|
+
? never
|
|
217
|
+
: K]: OwnProps[K] extends CSSPropDefinition ? OwnProps[K] : never
|
|
133
218
|
}
|
|
134
219
|
|
|
220
|
+
/** Computed merged `cssProps` type for `createComponent`. */
|
|
221
|
+
type MergedCSSProps<
|
|
222
|
+
Extends extends GDSComponent | undefined,
|
|
223
|
+
OwnProps extends Record<string, CSSPropDefinition | null>,
|
|
224
|
+
> = MergeCSSProps<
|
|
225
|
+
Extends extends GDSComponent<infer ParentProps> ? ParentProps : undefined,
|
|
226
|
+
OwnProps
|
|
227
|
+
>
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Empty object type without index signature (unlike `Record<string, never>` which has `keyof
|
|
231
|
+
* Record<string, never>` = `string`, not `never`).
|
|
232
|
+
*/
|
|
233
|
+
type EmptyObject = { [K in never]: never }
|
|
234
|
+
|
|
135
235
|
/** Helper function to create a `GDSComponent` instance with proper type inference. */
|
|
136
236
|
export function createComponent<
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
const
|
|
237
|
+
const Extends extends GDSComponent | undefined = undefined,
|
|
238
|
+
const OwnCSSProps extends Record<string, CSSPropDefinition | null> = EmptyObject,
|
|
239
|
+
const Vars extends VarsConfig = EmptyObject,
|
|
240
|
+
const AddonCompatible extends boolean = false,
|
|
140
241
|
>(
|
|
141
242
|
name: string,
|
|
142
|
-
config?:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
243
|
+
config?: {
|
|
244
|
+
/**
|
|
245
|
+
* The parent component that this component extends. When set:
|
|
246
|
+
*
|
|
247
|
+
* - The component inherits all parent container names for CSS container queries (e.g. if
|
|
248
|
+
* `Checkbox` extends `Checkable`, the following CSS will be generated: `.gds-checkbox {
|
|
249
|
+
* container-name: gds-checkable gds-checkbox; }`).
|
|
250
|
+
* - The component inherits all parent `cssProps`, which can be overridden or removed (with
|
|
251
|
+
* `null`).
|
|
252
|
+
* - The `isolate` option defaults to `false` since the parent already handles isolation (if not,
|
|
253
|
+
* it can be set to `true` or `'allow-inheritance'`, but `isolate: false` will not cancel/undo
|
|
254
|
+
* the parent's isolation).
|
|
255
|
+
*/
|
|
256
|
+
extends?: Extends
|
|
257
|
+
/**
|
|
258
|
+
* - `true`: add `isolation: isolate` and reset all inherited font/text properties.
|
|
259
|
+
* - `'allow-inheritance'`: only add `isolation: isolate` (keep inheriting font/text properties).
|
|
260
|
+
* - `false`: do nothing.
|
|
261
|
+
*
|
|
262
|
+
* @default true // or if `extends` is set, `false`
|
|
263
|
+
*/
|
|
264
|
+
isolate?: IsolateOption
|
|
265
|
+
/**
|
|
266
|
+
* Whether the component can be used as an addon (e.g. `<Button addonBefore={Component}>`).
|
|
267
|
+
* Setting this to `true` also includes this component's class in the selector generated by the
|
|
268
|
+
* `addon-compatible` custom variant.
|
|
269
|
+
*
|
|
270
|
+
* @default false
|
|
271
|
+
*/
|
|
272
|
+
addonCompatible?: AddonCompatible
|
|
273
|
+
cssProps?: ValidateCSSPropsConfig<OwnCSSProps>
|
|
274
|
+
vars?: ValidateVarsConfig<keyof MergedCSSProps<Extends, OwnCSSProps>, Vars>
|
|
146
275
|
},
|
|
147
|
-
)
|
|
148
|
-
return new GDSComponent(
|
|
276
|
+
) {
|
|
277
|
+
return new GDSComponent(
|
|
278
|
+
name,
|
|
279
|
+
config as ConstructorParameters<typeof GDSComponent>[1],
|
|
280
|
+
) as unknown as GDSComponent<MergedCSSProps<Extends, OwnCSSProps>, AddonCompatible>
|
|
149
281
|
}
|
|
150
282
|
|
|
151
|
-
/**
|
|
152
|
-
export type ComponentCSSPropsConfig<
|
|
153
|
-
|
|
283
|
+
/** Extracts the CSS props config type from a `GDSComponent` instance. */
|
|
284
|
+
export type ComponentCSSPropsConfig<Component extends GDSComponent> =
|
|
285
|
+
Component extends GDSComponent<infer Props> ? Props : never
|
|
154
286
|
|
|
155
287
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
288
|
+
* Extracts CSS props types from a component for use in React props. Props without a `defaultValue`
|
|
289
|
+
* are required, those with a `defaultValue` are optional. Explicitly includes `undefined` in the
|
|
290
|
+
* type to work with `exactOptionalPropertyTypes`.
|
|
159
291
|
*/
|
|
160
|
-
export type ComponentCSSProps<
|
|
161
|
-
|
|
292
|
+
export type ComponentCSSProps<Component extends GDSComponent> = {
|
|
293
|
+
// Required props (no `defaultValue`)
|
|
294
|
+
[K in keyof ComponentCSSPropsConfig<Component> as string extends K
|
|
162
295
|
? never
|
|
163
|
-
: 'defaultValue' extends keyof ComponentCSSPropsConfig<
|
|
296
|
+
: 'defaultValue' extends keyof ComponentCSSPropsConfig<Component>[K]
|
|
164
297
|
? never
|
|
165
|
-
: K]: CSSPropType<ComponentCSSPropsConfig<
|
|
298
|
+
: K]: CSSPropType<ComponentCSSPropsConfig<Component>[K]>
|
|
166
299
|
} & {
|
|
167
|
-
|
|
300
|
+
// Optional props (have `defaultValue`)
|
|
301
|
+
[K in keyof ComponentCSSPropsConfig<Component> as string extends K
|
|
168
302
|
? never
|
|
169
|
-
: 'defaultValue' extends keyof ComponentCSSPropsConfig<
|
|
303
|
+
: 'defaultValue' extends keyof ComponentCSSPropsConfig<Component>[K]
|
|
170
304
|
? K
|
|
171
|
-
: never]?: CSSPropType<ComponentCSSPropsConfig<
|
|
305
|
+
: never]?: CSSPropType<ComponentCSSPropsConfig<Component>[K]> | undefined
|
|
172
306
|
}
|
|
173
307
|
|
|
174
308
|
/**
|
|
175
309
|
* Combined props type for GDS components. Includes CSS props and, for addon-compatible components,
|
|
176
310
|
* a hidden prop to mark them as such.
|
|
177
311
|
*/
|
|
178
|
-
export type GDSComponentProps<
|
|
179
|
-
(
|
|
312
|
+
export type GDSComponentProps<Component extends GDSComponent> = ComponentCSSProps<Component> &
|
|
313
|
+
(Component extends GDSComponent<CSSPropsConfig, true>
|
|
180
314
|
? { __addonCompatible?: undefined }
|
|
181
315
|
: { __addonCompatible?: never })
|
|
182
316
|
|
package/src/css-props/index.ts
CHANGED
|
@@ -47,7 +47,7 @@ export function parseCSSPropValue<P extends CSSPropDefinition>(
|
|
|
47
47
|
const numberValue = parseNumber(rawValue.slice(0, -2)) // Remove 'px'
|
|
48
48
|
return pxToTw(numberValue ?? 0) satisfies CSSPropLength as CSSPropType<P>
|
|
49
49
|
}
|
|
50
|
-
// Otherwise keep as-is (e.g
|
|
50
|
+
// Otherwise keep as-is (e.g. '1rem' or '100%')
|
|
51
51
|
return rawValue as CSSPropLength as CSSPropType<P>
|
|
52
52
|
}
|
|
53
53
|
case 'color': {
|
|
@@ -12,10 +12,10 @@ import { getCSSPropRawValue } from './getCSSPropRawValue.ts'
|
|
|
12
12
|
import type { CSSPropColor } from './types.ts'
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* Sets up the CSS props system via the Tailwind plugin. This includes custom properties, utilities,
|
|
16
|
+
* and variants for prop-based styling.
|
|
17
17
|
*/
|
|
18
|
-
export function
|
|
18
|
+
export function setupCSSProps(api: PluginAPI) {
|
|
19
19
|
const componentsByCSSProp = getRegisteredComponents(true).reduce((outerMap, component) => {
|
|
20
20
|
for (const cssProp of Object.values(component.cssProps)) {
|
|
21
21
|
const innerMap =
|
|
@@ -46,9 +46,13 @@ export function registerCSSProps(api: PluginAPI) {
|
|
|
46
46
|
})
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/** Register a custom property for each component-prop pair to restrict the allowed values. */
|
|
50
49
|
for (const component of getRegisteredComponents()) {
|
|
51
|
-
|
|
50
|
+
// Only process own props (not inherited via `extends`); inherited props are handled by the parent component
|
|
51
|
+
const ownCSSProps = Object.values(component.cssProps).filter((cssProp) => cssProp.own)
|
|
52
|
+
if (ownCSSProps.length === 0) continue
|
|
53
|
+
|
|
54
|
+
/** Register a custom property for each component-prop pair to restrict the allowed values. */
|
|
55
|
+
for (const cssProp of ownCSSProps) {
|
|
52
56
|
const syntax = (() => {
|
|
53
57
|
switch (cssProp.type) {
|
|
54
58
|
case 'values':
|
|
@@ -66,7 +70,7 @@ export function registerCSSProps(api: PluginAPI) {
|
|
|
66
70
|
}
|
|
67
71
|
})()
|
|
68
72
|
api.addBase({
|
|
69
|
-
[`@property
|
|
73
|
+
[`@property ${cssProp.cssProperty}`]: {
|
|
70
74
|
syntax,
|
|
71
75
|
/**
|
|
72
76
|
* We only need this property to inherit if the type is not `values`, because `values` CSS
|
|
@@ -89,26 +93,20 @@ export function registerCSSProps(api: PluginAPI) {
|
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
/**
|
|
92
|
-
*
|
|
93
|
-
* applying the value passed via the React prop, if any. See `
|
|
94
|
-
*
|
|
96
|
+
* Add base styles for each component-prop pair, defining the default value of each CSS prop and
|
|
97
|
+
* applying the value passed via the React prop, if any. See `getCSSPropRawValue` for details on
|
|
98
|
+
* how each prop type is handled.
|
|
95
99
|
*/
|
|
96
100
|
api.addBase({
|
|
97
101
|
'@layer components': {
|
|
98
|
-
[`.${component.className}`]:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
[`--gds-${component.kebabName}-${cssProp.kebabName}`]: `var(--gds-prop-${cssProp.kebabName}, var(--gds-passed-or-default-${cssProp.kebabName}))`,
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
{} as Record<string, string>,
|
|
110
|
-
),
|
|
111
|
-
},
|
|
102
|
+
[`.${component.className}`]: ownCSSProps.reduce<Record<string, string>>(
|
|
103
|
+
(rules, cssProp) => ({
|
|
104
|
+
...rules,
|
|
105
|
+
[`--gds-passed-or-default-${cssProp.kebabName}`]: `var(--gds-passed-${cssProp.kebabName}, var(--gds-default-${cssProp.kebabName}, ${getCSSPropRawValue(cssProp, cssProp.initialValue)}))`,
|
|
106
|
+
[cssProp.cssProperty]: `var(--gds-prop-${cssProp.kebabName}, var(--gds-passed-or-default-${cssProp.kebabName}))`,
|
|
107
|
+
}),
|
|
108
|
+
{},
|
|
109
|
+
),
|
|
112
110
|
},
|
|
113
111
|
})
|
|
114
112
|
}
|
|
@@ -200,19 +198,12 @@ export function registerCSSProps(api: PluginAPI) {
|
|
|
200
198
|
variant,
|
|
201
199
|
(cssPropKebabNameAndValue, { modifier: componentKebabName }) => {
|
|
202
200
|
/**
|
|
203
|
-
* If the modifier is
|
|
204
|
-
*
|
|
205
|
-
|
|
206
|
-
if (componentKebabName === 'dummy') return []
|
|
207
|
-
/**
|
|
208
|
-
* Default a missing modifier to `dummy` instead of considering the variant invalid so that
|
|
209
|
-
* it's suggested by Tailwind IntelliSense.
|
|
210
|
-
*/
|
|
211
|
-
const component = getRegisteredComponent(componentKebabName ?? 'dummy')
|
|
212
|
-
/**
|
|
213
|
-
* If no component is found (i.e. the modifier is not a valid component name), consider the
|
|
214
|
-
* variant invalid.
|
|
201
|
+
* If the modifier is missing, return some dummy CSS so that the variant is suggested by
|
|
202
|
+
* Tailwind IntelliSense. If a modifier is present but is not a valid component name, then
|
|
203
|
+
* return an empty array (no CSS is generated and the variant is considered invalid).
|
|
215
204
|
*/
|
|
205
|
+
if (componentKebabName === null) return '@container dummy'
|
|
206
|
+
const component = getRegisteredComponent(componentKebabName)
|
|
216
207
|
if (!component) return []
|
|
217
208
|
try {
|
|
218
209
|
const { cssPropKebabName, value } = JSON.parse(cssPropKebabNameAndValue)
|
|
@@ -223,20 +214,14 @@ export function registerCSSProps(api: PluginAPI) {
|
|
|
223
214
|
return []
|
|
224
215
|
const cssProp = component.findCSSPropByKebabName(cssPropKebabName)
|
|
225
216
|
/**
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
* to list all variants in IntelliSense regardless of the component).
|
|
217
|
+
* If no such prop exists on the component, or if one exists but is not of type `values`
|
|
218
|
+
* or doesn't support the specified value, consider the variant invalid.
|
|
229
219
|
*/
|
|
230
|
-
if (
|
|
231
|
-
(!cssProp || cssProp.type !== 'values' || !cssProp.values.includes(value)) &&
|
|
232
|
-
component.kebabName !== 'dummy'
|
|
233
|
-
) {
|
|
234
|
-
return []
|
|
235
|
-
}
|
|
220
|
+
if (!cssProp || cssProp.type !== 'values' || !cssProp.values.includes(value)) return []
|
|
236
221
|
const rawValue = cssProp ? getCSSPropRawValue(cssProp, value) : String(value)
|
|
237
222
|
return [
|
|
238
223
|
/** Style query selector for modern browsers. */
|
|
239
|
-
`@container ${
|
|
224
|
+
`@container ${cssProp.containerName} ${variant === '@prop-not' ? 'not' : ''} style(${cssProp.cssProperty}: ${rawValue})`,
|
|
240
225
|
/**
|
|
241
226
|
* Fallback selector for browsers without style query support, which unfortunately
|
|
242
227
|
* cannot be queried, but happens to be the same browsers that don't support view
|
|
@@ -246,7 +231,7 @@ export function registerCSSProps(api: PluginAPI) {
|
|
|
246
231
|
* polyfill attributes are present even when the polyfill is not needed (see
|
|
247
232
|
* `useCSSPropsPolyfill` for an explanation why).
|
|
248
233
|
*/
|
|
249
|
-
`@supports (not (view-transition-name: none)) or (-moz-orient: inline) { &:where(.${component.className}${variant === '@prop-not' ? ':not' : ':is'}([
|
|
234
|
+
`@supports (not (view-transition-name: none)) or (-moz-orient: inline) { &:where(.${component.className}${variant === '@prop-not' ? ':not' : ':is'}([${cssProp.polyfillAttribute}=${cssesc(rawValue, { isIdentifier: true })}]) *) }`,
|
|
250
235
|
]
|
|
251
236
|
} catch {
|
|
252
237
|
/**
|
package/src/css-states/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { cssStates, cssStateVariables, type CSSState, type CSSStateValue } from './states.ts'
|
|
2
|
-
export {
|
|
2
|
+
export { setupCSSStates } from './setupCSSStates.ts'
|
|
@@ -5,9 +5,8 @@ import type { PluginAPI } from '../types.ts'
|
|
|
5
5
|
import { cssStates, cssStateVariables, getCSSStateVariable } from './states.ts'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* This system abstracts away CSS's native states in order to:
|
|
8
|
+
* Sets up the CSS states system via the Tailwind plugin. This system abstracts away CSS's native
|
|
9
|
+
* states in order to:
|
|
11
10
|
*
|
|
12
11
|
* - Allow overriding the state of components (e.g. `<Button className="state-active" />`).
|
|
13
12
|
* - Allow reading the state of components from their root element even when the native state is
|
|
@@ -18,24 +17,24 @@ import { cssStates, cssStateVariables, getCSSStateVariable } from './states.ts'
|
|
|
18
17
|
* `@state-[highlighted]:bg-strong` on a child; also supports key-values like `state-[foo=bar]
|
|
19
18
|
* lg:state-[foo=baz]`).
|
|
20
19
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* attribute, `view-transition-name` to target browsers that don't support style queries, etc.),
|
|
25
|
-
* though the CSS props polyfill is not used. See `useCSSState` for more details.
|
|
20
|
+
* Like CSS props, this system leverages style queries and uses a similar fallback mechanism
|
|
21
|
+
* (querying by data attribute + using `view-transition-name` to target browsers that don't support
|
|
22
|
+
* style queries), though the CSS props polyfill is not used. See `useCSSState` for more details.
|
|
26
23
|
*/
|
|
27
|
-
export function
|
|
24
|
+
export function setupCSSStates(api: PluginAPI) {
|
|
28
25
|
/**
|
|
29
|
-
* Register a couple custom properties for each state variable.
|
|
26
|
+
* Register a couple custom properties for each non-arbitrary state variable.
|
|
30
27
|
*
|
|
31
28
|
* - All of them are non-inherited to prevent leaking.
|
|
32
29
|
* - Normally, we would only need `--gds-exposed-*` and `--gds-state-*` (with no `initial-value` on
|
|
33
30
|
* `--gds-exposed-*`), but due to a limitation in the `style-observer` library
|
|
34
31
|
* (https://github.com/LeaVerou/style-observer/issues/133), we need to set an `initial-value` on
|
|
35
|
-
* any custom property we want to observe in JS
|
|
36
|
-
*
|
|
37
|
-
* `--gds-clickable-*`). So we register an
|
|
38
|
-
* guaranteed to have a valid value and can be
|
|
32
|
+
* any custom property we want to observe in JS (and we want to observe both in `useCSSState`).
|
|
33
|
+
* But we also need `--gds-state-*` to NOT have an initial value so that we can define a
|
|
34
|
+
* fallback when a state is not set at all (see `--gds-clickable-*`). So we register an
|
|
35
|
+
* additional `--gds-observed-*` property that is guaranteed to have a valid value and can be
|
|
36
|
+
* observed. Note that non-registered `--gds-state-*` properties (i.e. arbitrary states) don't
|
|
37
|
+
* have this problem, so arbitrary state utilities don't need to set `--gds-observed-*`.
|
|
39
38
|
*/
|
|
40
39
|
for (const [variableName, { values, defaultValue }] of Object.entries(cssStateVariables)) {
|
|
41
40
|
api.addBase({
|
|
@@ -69,14 +68,18 @@ export function registerCSSStates(api: PluginAPI) {
|
|
|
69
68
|
return {
|
|
70
69
|
[`--gds-exposed-${stateVariable.name}`]: stateVariable.value,
|
|
71
70
|
[`--gds-state-${stateVariable.name}`]: stateVariable.value,
|
|
72
|
-
|
|
71
|
+
...(!stateVariable.isArbitrary && {
|
|
72
|
+
[`--gds-observed-${stateVariable.name}`]: stateVariable.value,
|
|
73
|
+
}),
|
|
73
74
|
}
|
|
74
75
|
},
|
|
75
76
|
state: (state) => {
|
|
76
77
|
const stateVariable = getCSSStateVariable(state)
|
|
77
78
|
return {
|
|
78
79
|
[`--gds-state-${stateVariable.name}`]: stateVariable.value,
|
|
79
|
-
|
|
80
|
+
...(!stateVariable.isArbitrary && {
|
|
81
|
+
[`--gds-observed-${stateVariable.name}`]: stateVariable.value,
|
|
82
|
+
}),
|
|
80
83
|
}
|
|
81
84
|
},
|
|
82
85
|
},
|
|
@@ -108,7 +111,7 @@ export function registerCSSStates(api: PluginAPI) {
|
|
|
108
111
|
.join(' or ')})`,
|
|
109
112
|
]
|
|
110
113
|
if (containerName) {
|
|
111
|
-
// Fallback selector for browsers without style query support; see `
|
|
114
|
+
// Fallback selector for browsers without style query support; see `setupCSSProps` for more details
|
|
112
115
|
selectors.push(
|
|
113
116
|
`@supports (not (view-transition-name: none)) or (-moz-orient: inline) { &:where(.${containerName}${variant === '@state-not' ? ':not' : ':is'}(${stateVariables
|
|
114
117
|
.map(
|
package/src/css-states/states.ts
CHANGED
|
@@ -101,13 +101,14 @@ export function getCSSStateVariable(state: string) {
|
|
|
101
101
|
name: variableName,
|
|
102
102
|
value: String(value),
|
|
103
103
|
isDefault: value === defaultValue,
|
|
104
|
+
isArbitrary: false,
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
// Support arbitrary states in the format `state-[foo]` or `state-[foo=bar]`
|
|
109
110
|
const [stateName, stateValue] = state.split('=')
|
|
110
|
-
return { name: stateName!, value: stateValue ?? 'true', isDefault: false }
|
|
111
|
+
return { name: stateName!, value: stateValue ?? 'true', isDefault: false, isArbitrary: true }
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
export function getPointerStateSelector(state: 'idle' | 'hover' | 'active') {
|