@evolve.ui/theme-system 1.0.1 → 1.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/{allThemes-CFS_9-tk.cjs → allThemes-3d8nhymo.cjs} +12 -2
- package/allThemes-3d8nhymo.cjs.map +1 -0
- package/{allThemes-OV-ayn4C.mjs → allThemes-Abn6KDm7.mjs} +7 -3
- package/allThemes-Abn6KDm7.mjs.map +1 -0
- package/allThemes.cjs +1 -1
- package/allThemes.d.cts +1 -0
- package/allThemes.d.cts.map +1 -1
- package/allThemes.d.mts +1 -0
- package/allThemes.d.mts.map +1 -1
- package/allThemes.mjs +1 -1
- package/brands-CE0wIkKX.d.cts +58 -0
- package/brands-CE0wIkKX.d.cts.map +1 -0
- package/brands-CE0wIkKX.d.mts +58 -0
- package/brands-CE0wIkKX.d.mts.map +1 -0
- package/brands.cjs +16 -13
- package/brands.cjs.map +1 -1
- package/brands.d.cts +2 -2
- package/brands.d.mts +2 -2
- package/brands.mjs +16 -13
- package/brands.mjs.map +1 -1
- package/css/EverydayHarbor.css +377 -0
- package/css/EverydayHarbor.effects.css +650 -0
- package/{default-b-tgfyxR.cjs → default-B6VWF0Js.cjs} +237 -36
- package/default-B6VWF0Js.cjs.map +1 -0
- package/{default-B2n4PnsJ.mjs → default-BogJDhKE.mjs} +238 -37
- package/default-BogJDhKE.mjs.map +1 -0
- package/effects.cjs +26 -0
- package/effects.cjs.map +1 -0
- package/effects.d.cts +22 -0
- package/effects.d.cts.map +1 -0
- package/effects.d.mts +22 -0
- package/effects.d.mts.map +1 -0
- package/effects.mjs +22 -0
- package/effects.mjs.map +1 -0
- package/evolve-ui.entries.json +51 -0
- package/index.cjs +12 -5
- package/index.cjs.map +1 -1
- package/index.d.cts +108 -4
- package/index.d.cts.map +1 -1
- package/index.d.mts +108 -4
- package/index.d.mts.map +1 -1
- package/index.mjs +9 -6
- package/index.mjs.map +1 -1
- package/package.json +5 -1
- package/{plugin-DPtvp4lp.cjs → plugin-B3PF4RMI.cjs} +299 -110
- package/plugin-B3PF4RMI.cjs.map +1 -0
- package/{plugin-D4eCyUJe.mjs → plugin-D6aLCeNB.mjs} +258 -111
- package/plugin-D6aLCeNB.mjs.map +1 -0
- package/src/effects/__fixtures__/fixtureLayer.ts +88 -0
- package/src/effects/brands/EverydayHarbor.ts +392 -0
- package/src/effects/classes.ts +19 -0
- package/src/effects/emit.ts +314 -0
- package/src/effects/hooks.ts +493 -0
- package/src/effects/index.ts +2 -0
- package/src/effects/layers.ts +18 -0
- package/src/effects/rules.ts +90 -0
- package/src/effects/rules.typecheck.ts +50 -0
- package/src/effects/tokens.ts +148 -0
- package/src/effects/validate.ts +683 -0
- package/src/effects/vocabulary.ts +32 -0
- package/src/index.ts +40 -0
- package/src/tailwind/animation.ts +30 -0
- package/src/tailwind/borderRadius.ts +32 -0
- package/src/tailwind/borderWidth.ts +14 -0
- package/src/tailwind/boxShadow.ts +19 -0
- package/src/tailwind/colors.ts +332 -0
- package/src/tailwind/cssVars.ts +301 -0
- package/src/tailwind/fontFamily.ts +14 -0
- package/src/tailwind/plugin.ts +299 -0
- package/src/tailwind/reducedMotion.ts +164 -0
- package/src/tailwind/screens.ts +22 -0
- package/src/tailwind/spacing.ts +20 -0
- package/src/tailwind/text.ts +172 -0
- package/src/tailwind/width.ts +14 -0
- package/src/themeValues.snapshot.ts +6735 -0
- package/src/themes/BasketballTraining.ts +166 -0
- package/src/themes/Default.ts +165 -0
- package/src/themes/EverydayHarbor.ts +147 -0
- package/src/themes/ProfessionalBrand.ts +166 -0
- package/src/themes/allThemes.ts +14 -0
- package/src/themes/brandSources.ts +37 -0
- package/src/themes/brands.ts +63 -0
- package/src/themes/common.ts +65 -0
- package/src/themes/contrastFloors.ts +26 -0
- package/src/themes/generateCSSVars.ts +123 -0
- package/src/themes/inkRamps.ts +43 -0
- package/src/themes/runtime/basketballTraining.ts +3 -0
- package/src/themes/runtime/buildTheme.ts +411 -0
- package/src/themes/runtime/default.ts +3 -0
- package/src/themes/runtime/everydayHarbor.ts +3 -0
- package/src/themes/runtime/professionalBrand.ts +3 -0
- package/src/themes/semanticColorTokenSteps.ts +23 -0
- package/src/themes/themeType.ts +169 -0
- package/src/themes/themeValues.ts +374 -0
- package/src/themes/wcag.ts +48 -0
- package/src/themes/writeToCSSFile.ts +11 -0
- package/src/utils/cn.ts +35 -0
- package/src/utils/color.ts +82 -0
- package/src/utils/wrapVar.ts +24 -0
- package/tailwind/plugin.cjs +5 -1
- package/tailwind/plugin.d.cts +458 -1
- package/tailwind/plugin.d.cts.map +1 -1
- package/tailwind/plugin.d.mts +458 -1
- package/tailwind/plugin.d.mts.map +1 -1
- package/tailwind/plugin.mjs +2 -2
- package/themes/runtime/default.cjs +1 -1
- package/themes/runtime/default.mjs +1 -1
- package/themes/themeType.d.cts +1 -1
- package/themes/themeType.d.mts +1 -1
- package/allThemes-CFS_9-tk.cjs.map +0 -1
- package/allThemes-OV-ayn4C.mjs.map +0 -1
- package/brands-n30rolHQ.d.cts +0 -33
- package/brands-n30rolHQ.d.cts.map +0 -1
- package/brands-n30rolHQ.d.mts +0 -33
- package/brands-n30rolHQ.d.mts.map +0 -1
- package/default-B2n4PnsJ.mjs.map +0 -1
- package/default-b-tgfyxR.cjs.map +0 -1
- package/plugin-D4eCyUJe.mjs.map +0 -1
- package/plugin-DPtvp4lp.cjs.map +0 -1
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook catalog: the only DOM the effect layer may bind to.
|
|
3
|
+
* Selector parts stay separate so the emitter can assemble them and so evidence can cover each token.
|
|
4
|
+
* `structural` records position a literal cannot prove (a child combinator or a last-child). Link 5
|
|
5
|
+
* must witness those hooks in a story play test.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type PseudoSlot = 'before' | 'after';
|
|
9
|
+
|
|
10
|
+
/** States in which the host repaints its own background. `focus-within` can only be acknowledged. */
|
|
11
|
+
export type StateBackground = 'hover' | 'active' | 'focus-within';
|
|
12
|
+
|
|
13
|
+
export type SourceEvidence = {
|
|
14
|
+
readonly kind: 'source';
|
|
15
|
+
/** Path relative to `shared-configs/core-components/src`. */
|
|
16
|
+
readonly file: string;
|
|
17
|
+
readonly literal: string;
|
|
18
|
+
readonly exclusive?: true;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type PackageName = 'react-select' | 'react-toastify' | 'react-tooltip';
|
|
22
|
+
|
|
23
|
+
export type PackageEvidence = {
|
|
24
|
+
readonly kind: 'package';
|
|
25
|
+
readonly package: PackageName;
|
|
26
|
+
readonly literal: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type HookEvidence = SourceEvidence | PackageEvidence;
|
|
30
|
+
|
|
31
|
+
export type EffectHook = {
|
|
32
|
+
readonly component: string;
|
|
33
|
+
readonly host: string;
|
|
34
|
+
readonly is?: string;
|
|
35
|
+
readonly has?: string;
|
|
36
|
+
readonly lacks?: string;
|
|
37
|
+
readonly positioned: boolean;
|
|
38
|
+
readonly taken: readonly PseudoSlot[];
|
|
39
|
+
readonly stateBackgrounds: readonly StateBackground[];
|
|
40
|
+
readonly evidence: readonly [HookEvidence, ...HookEvidence[]];
|
|
41
|
+
readonly structural?: string;
|
|
42
|
+
/** Sibling states whose `is` clauses can never match the same element at once. */
|
|
43
|
+
readonly exclusiveWith?: readonly string[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const src = (file: string, literal: string, exclusive?: true): SourceEvidence =>
|
|
47
|
+
exclusive === true ? { exclusive, file, kind: 'source', literal } : { file, kind: 'source', literal };
|
|
48
|
+
|
|
49
|
+
const pkg = (packageName: PackageName, literal: string): PackageEvidence => ({
|
|
50
|
+
kind: 'package',
|
|
51
|
+
literal,
|
|
52
|
+
package: packageName,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const evidence = <T extends readonly [HookEvidence, ...HookEvidence[]]>(...items: T): T => items;
|
|
56
|
+
|
|
57
|
+
const CHILD_OF_REGION = 'The button is a child of the region; a literal cannot prove the child combinator.';
|
|
58
|
+
const OVERLAY_LAST_CHILD =
|
|
59
|
+
'The overlay div is the last child of the dropzone group; a literal cannot prove the child combinator or that last-child position.';
|
|
60
|
+
|
|
61
|
+
const OVERLAY_TEXT_PATH =
|
|
62
|
+
'The message is a descendant of the overlay (last child of the dropzone group) through a child combinator; a literal cannot prove that path.';
|
|
63
|
+
|
|
64
|
+
const listCardHost = 'div.group\\/list-file-card.rounded-container';
|
|
65
|
+
const gridCardHost = '.group\\/grid-file-card';
|
|
66
|
+
const dropzoneHost = '[role="region"] > [role="button"]';
|
|
67
|
+
|
|
68
|
+
const listCardEvidence = [
|
|
69
|
+
src('FileUpload/components/ListFileCard/ListFileCard.tsx', '<div'),
|
|
70
|
+
src('FileUpload/components/ListFileCard/ListFileCard.tsx', "'group/list-file-card", true),
|
|
71
|
+
src('FileUpload/components/ListFileCard/ListFileCard.tsx', "'rounded-container border'"),
|
|
72
|
+
] as const;
|
|
73
|
+
|
|
74
|
+
const gridCardEvidence = [
|
|
75
|
+
src('FileUpload/components/GridFileCard/GridFileCard.tsx', "'group/grid-file-card", true),
|
|
76
|
+
] as const;
|
|
77
|
+
|
|
78
|
+
const progressCompleteEvidence = [
|
|
79
|
+
src('ProgressCircle/ProgressCircle.tsx', '<svg'),
|
|
80
|
+
src('ProgressCircle/ProgressCircle.tsx', 'role={ariaRole}'),
|
|
81
|
+
src('ProgressCircle/ProgressCircle.tsx', "'status'"),
|
|
82
|
+
src('ProgressCircle/ProgressCircle.tsx', 'aria-valuenow={ariaValueNow}'),
|
|
83
|
+
src('ProgressCircle/ProgressCircle.tsx', 'isComplete ? 100'),
|
|
84
|
+
] as const;
|
|
85
|
+
|
|
86
|
+
const checkboxEvidence = [
|
|
87
|
+
src('ChoiceListItem/Checkbox.tsx', 'type="checkbox"'),
|
|
88
|
+
src('ChoiceListItem/choiceListItem.tsx', '<input'),
|
|
89
|
+
] as const;
|
|
90
|
+
|
|
91
|
+
const regionEvidence = [
|
|
92
|
+
src('FileUpload/components/FileDropzone/FileDropzone.tsx', 'role="region"'),
|
|
93
|
+
src('FileUpload/components/FileDropzone/FileDropzone.tsx', "role: 'button'"),
|
|
94
|
+
] as const;
|
|
95
|
+
|
|
96
|
+
export const effectHooks = {
|
|
97
|
+
modal: {
|
|
98
|
+
component: 'Modal',
|
|
99
|
+
host: 'dialog[data-hui-id="Modal"]',
|
|
100
|
+
positioned: true,
|
|
101
|
+
taken: [],
|
|
102
|
+
stateBackgrounds: [],
|
|
103
|
+
evidence: evidence(
|
|
104
|
+
src('Modal/ComposableModal.tsx', '<dialog'),
|
|
105
|
+
src('Modal/ComposableModal.tsx', 'data-hui-id="Modal"')
|
|
106
|
+
),
|
|
107
|
+
},
|
|
108
|
+
'modal.backdrop': {
|
|
109
|
+
component: 'Modal',
|
|
110
|
+
host: '.hui-modal-backdrop',
|
|
111
|
+
is: '.fixed',
|
|
112
|
+
positioned: true,
|
|
113
|
+
taken: [],
|
|
114
|
+
stateBackgrounds: [],
|
|
115
|
+
evidence: evidence(
|
|
116
|
+
src('Modal/components/Backdrop/Backdrop.tsx', 'hui-modal-backdrop opacity-0'),
|
|
117
|
+
src(
|
|
118
|
+
'Modal/components/Backdrop/Backdrop.tsx',
|
|
119
|
+
'fixed bottom-0 left-0 right-0 top-0 z-[1001] flex items-center justify-center bg-background-overlay'
|
|
120
|
+
)
|
|
121
|
+
),
|
|
122
|
+
},
|
|
123
|
+
drawer: {
|
|
124
|
+
component: 'Drawer',
|
|
125
|
+
host: '[data-hui-id="Drawer"]',
|
|
126
|
+
positioned: true,
|
|
127
|
+
taken: [],
|
|
128
|
+
stateBackgrounds: [],
|
|
129
|
+
evidence: evidence(src('Drawer/Drawer.tsx', 'data-hui-id="Drawer"', true)),
|
|
130
|
+
},
|
|
131
|
+
bottomSheet: {
|
|
132
|
+
component: 'BottomSheet',
|
|
133
|
+
host: '[role="dialog"][aria-modal="true"]',
|
|
134
|
+
is: '.rounded-t-xl',
|
|
135
|
+
positioned: true,
|
|
136
|
+
taken: ['before'],
|
|
137
|
+
stateBackgrounds: [],
|
|
138
|
+
evidence: evidence(
|
|
139
|
+
src(
|
|
140
|
+
'BottomSheet/components/BottomSheetOverlay/BottomSheetOverlayTranslate3d.tsx',
|
|
141
|
+
'aria-modal="true"',
|
|
142
|
+
true
|
|
143
|
+
),
|
|
144
|
+
src('BottomSheet/components/BottomSheetOverlay/BottomSheetOverlayTranslate3d.tsx', 'role="dialog"'),
|
|
145
|
+
src('BottomSheet/components/BottomSheetOverlay/BottomSheetOverlayTranslate3d.tsx', 'rounded-t-xl')
|
|
146
|
+
),
|
|
147
|
+
},
|
|
148
|
+
menu: {
|
|
149
|
+
component: 'Menu',
|
|
150
|
+
host: '.Menu[role="menu"]',
|
|
151
|
+
positioned: true,
|
|
152
|
+
taken: [],
|
|
153
|
+
stateBackgrounds: [],
|
|
154
|
+
evidence: evidence(src('Menu/Menu.tsx', "'Menu relative", true), src('Menu/Menu.tsx', "role: 'menu'")),
|
|
155
|
+
},
|
|
156
|
+
'select.menu': {
|
|
157
|
+
component: 'Select',
|
|
158
|
+
host: 'div',
|
|
159
|
+
has: '> [role="listbox"][id^="react-select-"]',
|
|
160
|
+
positioned: true,
|
|
161
|
+
taken: [],
|
|
162
|
+
stateBackgrounds: [],
|
|
163
|
+
structural: 'The listbox is a child of the menu div; a literal cannot prove the child combinator.',
|
|
164
|
+
evidence: evidence(
|
|
165
|
+
src('Select/Select.tsx', 'instanceId={defaultInstanceId}'),
|
|
166
|
+
pkg('react-select', "role: 'listbox'"),
|
|
167
|
+
pkg('react-select', "id: _this4.getElementId('listbox')"),
|
|
168
|
+
pkg('react-select', "'react-select-'"),
|
|
169
|
+
pkg('react-select', 'createElement("div"')
|
|
170
|
+
),
|
|
171
|
+
},
|
|
172
|
+
tooltip: {
|
|
173
|
+
exclusiveWith: ['tooltip.inverse'],
|
|
174
|
+
component: 'Tooltip',
|
|
175
|
+
host: '[role="tooltip"].hui-text-body-secondary',
|
|
176
|
+
is: ':not(.\\!bg-background-inverse)',
|
|
177
|
+
positioned: true,
|
|
178
|
+
taken: [],
|
|
179
|
+
stateBackgrounds: [],
|
|
180
|
+
evidence: evidence(
|
|
181
|
+
src('Tooltip/Tooltip.tsx', 'hui-text-body-secondary !bg-background-primary !shadow-03'),
|
|
182
|
+
src('Tooltip/Tooltip.tsx', "'!bg-background-inverse': isInverse"),
|
|
183
|
+
pkg('react-tooltip', "role = 'tooltip'")
|
|
184
|
+
),
|
|
185
|
+
},
|
|
186
|
+
'tooltip.inverse': {
|
|
187
|
+
exclusiveWith: ['tooltip'],
|
|
188
|
+
component: 'Tooltip',
|
|
189
|
+
host: '[role="tooltip"].hui-text-body-secondary',
|
|
190
|
+
is: '.\\!bg-background-inverse',
|
|
191
|
+
positioned: true,
|
|
192
|
+
taken: [],
|
|
193
|
+
stateBackgrounds: [],
|
|
194
|
+
evidence: evidence(
|
|
195
|
+
src('Tooltip/Tooltip.tsx', 'hui-text-body-secondary !bg-background-primary !shadow-03'),
|
|
196
|
+
src('Tooltip/Tooltip.tsx', "'!bg-background-inverse': isInverse"),
|
|
197
|
+
pkg('react-tooltip', "role = 'tooltip'")
|
|
198
|
+
),
|
|
199
|
+
},
|
|
200
|
+
'datePicker.popover': {
|
|
201
|
+
// Binds through data-testid, which is not a styling contract.
|
|
202
|
+
component: 'DatePicker',
|
|
203
|
+
host: '[data-testid="date-picker-popover"]',
|
|
204
|
+
positioned: true,
|
|
205
|
+
taken: [],
|
|
206
|
+
stateBackgrounds: [],
|
|
207
|
+
evidence: evidence(src('DateTimeInput/datePicker/DatePicker.tsx', 'data-testid="date-picker-popover"', true)),
|
|
208
|
+
},
|
|
209
|
+
'timePicker.popover': {
|
|
210
|
+
// Binds through data-testid, which is not a styling contract.
|
|
211
|
+
component: 'TimePicker',
|
|
212
|
+
host: '[data-testid="time-picker-popover"]',
|
|
213
|
+
positioned: true,
|
|
214
|
+
taken: [],
|
|
215
|
+
stateBackgrounds: [],
|
|
216
|
+
evidence: evidence(src('DateTimeInput/timePicker/TimePicker.tsx', 'data-testid="time-picker-popover"', true)),
|
|
217
|
+
},
|
|
218
|
+
toast: {
|
|
219
|
+
component: 'ToastContainer',
|
|
220
|
+
host: '.Toastify__toast-container > *',
|
|
221
|
+
positioned: true,
|
|
222
|
+
taken: [],
|
|
223
|
+
stateBackgrounds: [],
|
|
224
|
+
structural:
|
|
225
|
+
'The toast node is a child of the container. toastClassName is a function, so the toast class is dropped; a literal cannot prove the child combinator.',
|
|
226
|
+
evidence: evidence(
|
|
227
|
+
src('Toast/components/ToastContainer/ToastContainer.tsx', 'toastClassName={(context) =>', true),
|
|
228
|
+
pkg('react-toastify', 'Toastify__toast-container')
|
|
229
|
+
),
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
dropzone: {
|
|
233
|
+
component: 'FileDropzone',
|
|
234
|
+
host: `${dropzoneHost}[aria-disabled]`,
|
|
235
|
+
positioned: true,
|
|
236
|
+
taken: [],
|
|
237
|
+
stateBackgrounds: ['hover', 'active'],
|
|
238
|
+
structural: CHILD_OF_REGION,
|
|
239
|
+
evidence: evidence(
|
|
240
|
+
...regionEvidence,
|
|
241
|
+
src('FileUpload/components/FileDropzone/FileDropzone.tsx', "'aria-disabled': isDisabled")
|
|
242
|
+
),
|
|
243
|
+
},
|
|
244
|
+
'dropzone.disabled': {
|
|
245
|
+
component: 'FileDropzone',
|
|
246
|
+
host: dropzoneHost,
|
|
247
|
+
is: '[aria-disabled="true"]',
|
|
248
|
+
positioned: true,
|
|
249
|
+
taken: [],
|
|
250
|
+
stateBackgrounds: [],
|
|
251
|
+
structural: CHILD_OF_REGION,
|
|
252
|
+
evidence: evidence(
|
|
253
|
+
...regionEvidence,
|
|
254
|
+
src('FileUpload/components/FileDropzone/FileDropzone.tsx', "'aria-disabled': isDisabled")
|
|
255
|
+
),
|
|
256
|
+
},
|
|
257
|
+
'dropzone.dragging': {
|
|
258
|
+
component: 'FileDropzone',
|
|
259
|
+
host: dropzoneHost,
|
|
260
|
+
is: '.outline-4',
|
|
261
|
+
positioned: true,
|
|
262
|
+
taken: [],
|
|
263
|
+
stateBackgrounds: [],
|
|
264
|
+
structural: CHILD_OF_REGION,
|
|
265
|
+
evidence: evidence(
|
|
266
|
+
...regionEvidence,
|
|
267
|
+
src('FileUpload/utils/styles.ts', 'outline outline-4 outline-offset-0 outline-focus-primary')
|
|
268
|
+
),
|
|
269
|
+
},
|
|
270
|
+
'dropzone.error': {
|
|
271
|
+
component: 'FileDropzone',
|
|
272
|
+
host: dropzoneHost,
|
|
273
|
+
is: '.bg-background-critical-light',
|
|
274
|
+
positioned: true,
|
|
275
|
+
taken: [],
|
|
276
|
+
stateBackgrounds: ['hover', 'active'],
|
|
277
|
+
structural: CHILD_OF_REGION,
|
|
278
|
+
evidence: evidence(
|
|
279
|
+
...regionEvidence,
|
|
280
|
+
src(
|
|
281
|
+
'FileUpload/utils/styles.ts',
|
|
282
|
+
'bg-background-critical-light outline-dashed outline-1 -outline-offset-1 outline-border-critical'
|
|
283
|
+
)
|
|
284
|
+
),
|
|
285
|
+
},
|
|
286
|
+
'dropzoneOverlay.accept': {
|
|
287
|
+
exclusiveWith: ['dropzoneOverlay.reject'],
|
|
288
|
+
component: 'FileDropzoneOverlay',
|
|
289
|
+
host: '.group\\/dropzone > div:last-child',
|
|
290
|
+
is: '.outline-4:not(.outline-border-critical)',
|
|
291
|
+
positioned: true,
|
|
292
|
+
taken: [],
|
|
293
|
+
stateBackgrounds: [],
|
|
294
|
+
structural: OVERLAY_LAST_CHILD,
|
|
295
|
+
evidence: evidence(
|
|
296
|
+
src('FileUpload/components/FileDropzoneOverlay/FileDropzoneOverlay.tsx', "'group/dropzone'", true),
|
|
297
|
+
src('FileUpload/components/FileDropzoneOverlay/FileDropzoneOverlay.tsx', '<div'),
|
|
298
|
+
src(
|
|
299
|
+
'FileUpload/components/FileDropzoneOverlay/styles.ts',
|
|
300
|
+
'outline outline-4 outline-offset-0 outline-focus-primary'
|
|
301
|
+
),
|
|
302
|
+
src(
|
|
303
|
+
'FileUpload/components/FileDropzoneOverlay/styles.ts',
|
|
304
|
+
'bg-background-critical-light/50 outline-border-critical'
|
|
305
|
+
)
|
|
306
|
+
),
|
|
307
|
+
},
|
|
308
|
+
'dropzoneOverlay.reject': {
|
|
309
|
+
exclusiveWith: ['dropzoneOverlay.accept'],
|
|
310
|
+
component: 'FileDropzoneOverlay',
|
|
311
|
+
host: '.group\\/dropzone > div:last-child',
|
|
312
|
+
is: '.outline-4.outline-border-critical',
|
|
313
|
+
positioned: true,
|
|
314
|
+
taken: [],
|
|
315
|
+
stateBackgrounds: [],
|
|
316
|
+
structural: OVERLAY_LAST_CHILD,
|
|
317
|
+
evidence: evidence(
|
|
318
|
+
src('FileUpload/components/FileDropzoneOverlay/FileDropzoneOverlay.tsx', "'group/dropzone'", true),
|
|
319
|
+
src('FileUpload/components/FileDropzoneOverlay/FileDropzoneOverlay.tsx', '<div'),
|
|
320
|
+
src(
|
|
321
|
+
'FileUpload/components/FileDropzoneOverlay/styles.ts',
|
|
322
|
+
'outline outline-4 outline-offset-0 outline-focus-primary'
|
|
323
|
+
),
|
|
324
|
+
src(
|
|
325
|
+
'FileUpload/components/FileDropzoneOverlay/styles.ts',
|
|
326
|
+
'bg-background-critical-light/50 outline-border-critical'
|
|
327
|
+
)
|
|
328
|
+
),
|
|
329
|
+
},
|
|
330
|
+
'overlayDragText.valid': {
|
|
331
|
+
// The message div of a valid drag. The invalid message uses text-text-critical and needs no hook.
|
|
332
|
+
component: 'FileDropzoneOverlay',
|
|
333
|
+
host: '.group\\/dropzone > div:last-child .text-center > .text-xl.font-medium',
|
|
334
|
+
is: '.text-blue-600',
|
|
335
|
+
positioned: false,
|
|
336
|
+
taken: [],
|
|
337
|
+
stateBackgrounds: [],
|
|
338
|
+
structural: OVERLAY_TEXT_PATH,
|
|
339
|
+
evidence: evidence(
|
|
340
|
+
src('FileUpload/components/FileDropzoneOverlay/FileDropzoneOverlay.tsx', "'group/dropzone'", true),
|
|
341
|
+
src('FileUpload/components/FileDropzoneOverlay/FileDropzoneOverlay.tsx', '<div'),
|
|
342
|
+
src(
|
|
343
|
+
'FileUpload/components/FileDropzoneOverlay/components/OverlayDropzoneContent.tsx',
|
|
344
|
+
'className="text-center"'
|
|
345
|
+
),
|
|
346
|
+
src(
|
|
347
|
+
'FileUpload/components/FileDropzoneOverlay/components/OverlayDropzoneDragText.tsx',
|
|
348
|
+
"'text-xl font-medium'"
|
|
349
|
+
),
|
|
350
|
+
src(
|
|
351
|
+
'FileUpload/components/FileDropzoneOverlay/components/OverlayDropzoneDragText.tsx',
|
|
352
|
+
"validation.dragValidationError ? 'text-text-critical' : 'text-blue-600'"
|
|
353
|
+
)
|
|
354
|
+
),
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
listFileCard: {
|
|
358
|
+
component: 'ListFileCard',
|
|
359
|
+
host: listCardHost,
|
|
360
|
+
positioned: false,
|
|
361
|
+
taken: [],
|
|
362
|
+
stateBackgrounds: ['hover'],
|
|
363
|
+
evidence: evidence(...listCardEvidence),
|
|
364
|
+
},
|
|
365
|
+
'listFileCard.uploading': {
|
|
366
|
+
component: 'ListFileCard',
|
|
367
|
+
host: listCardHost,
|
|
368
|
+
is: '[aria-busy="true"]',
|
|
369
|
+
positioned: false,
|
|
370
|
+
taken: [],
|
|
371
|
+
stateBackgrounds: ['hover'],
|
|
372
|
+
evidence: evidence(
|
|
373
|
+
...listCardEvidence,
|
|
374
|
+
src('FileUpload/components/ListFileCard/ListFileCard.tsx', 'aria-busy={isProcessing || undefined}')
|
|
375
|
+
),
|
|
376
|
+
},
|
|
377
|
+
'listFileCard.selected': {
|
|
378
|
+
component: 'ListFileCard',
|
|
379
|
+
host: listCardHost,
|
|
380
|
+
has: 'input[type="checkbox"]:checked',
|
|
381
|
+
positioned: false,
|
|
382
|
+
taken: [],
|
|
383
|
+
stateBackgrounds: ['hover'],
|
|
384
|
+
evidence: evidence(...listCardEvidence, ...checkboxEvidence),
|
|
385
|
+
},
|
|
386
|
+
'listFileCard.error': {
|
|
387
|
+
component: 'ListFileCard',
|
|
388
|
+
host: listCardHost,
|
|
389
|
+
is: '.bg-background-critical-light',
|
|
390
|
+
positioned: false,
|
|
391
|
+
taken: [],
|
|
392
|
+
stateBackgrounds: ['hover'],
|
|
393
|
+
evidence: evidence(
|
|
394
|
+
...listCardEvidence,
|
|
395
|
+
src(
|
|
396
|
+
'FileUpload/components/ListFileCard/ListFileCard.tsx',
|
|
397
|
+
"'bg-background-critical-light': isError && !isDragging"
|
|
398
|
+
)
|
|
399
|
+
),
|
|
400
|
+
},
|
|
401
|
+
'listFileCard.complete': {
|
|
402
|
+
// A card showing a status icon instead of the progress circle is not matched.
|
|
403
|
+
component: 'ListFileCard',
|
|
404
|
+
host: listCardHost,
|
|
405
|
+
has: 'svg[role="status"][aria-valuenow="100"]',
|
|
406
|
+
positioned: false,
|
|
407
|
+
taken: [],
|
|
408
|
+
stateBackgrounds: ['hover'],
|
|
409
|
+
evidence: evidence(...listCardEvidence, ...progressCompleteEvidence),
|
|
410
|
+
},
|
|
411
|
+
|
|
412
|
+
gridFileCard: {
|
|
413
|
+
component: 'GridFileCard',
|
|
414
|
+
host: gridCardHost,
|
|
415
|
+
positioned: true,
|
|
416
|
+
taken: [],
|
|
417
|
+
stateBackgrounds: ['hover', 'focus-within'],
|
|
418
|
+
evidence: evidence(...gridCardEvidence),
|
|
419
|
+
},
|
|
420
|
+
'gridFileCard.uploading': {
|
|
421
|
+
component: 'GridFileCard',
|
|
422
|
+
host: gridCardHost,
|
|
423
|
+
is: '[aria-busy="true"]',
|
|
424
|
+
positioned: true,
|
|
425
|
+
taken: [],
|
|
426
|
+
stateBackgrounds: ['hover', 'focus-within'],
|
|
427
|
+
evidence: evidence(
|
|
428
|
+
...gridCardEvidence,
|
|
429
|
+
src('FileUpload/components/GridFileCard/GridFileCard.tsx', 'aria-busy={isProcessing || undefined}')
|
|
430
|
+
),
|
|
431
|
+
},
|
|
432
|
+
'gridFileCard.selected': {
|
|
433
|
+
component: 'GridFileCard',
|
|
434
|
+
host: gridCardHost,
|
|
435
|
+
has: 'input[type="checkbox"]:checked',
|
|
436
|
+
positioned: true,
|
|
437
|
+
taken: [],
|
|
438
|
+
stateBackgrounds: ['hover', 'focus-within'],
|
|
439
|
+
evidence: evidence(...gridCardEvidence, ...checkboxEvidence),
|
|
440
|
+
},
|
|
441
|
+
'gridFileCard.error': {
|
|
442
|
+
component: 'GridFileCard',
|
|
443
|
+
host: gridCardHost,
|
|
444
|
+
is: '.ring-border-critical',
|
|
445
|
+
positioned: true,
|
|
446
|
+
taken: [],
|
|
447
|
+
stateBackgrounds: ['hover', 'focus-within'],
|
|
448
|
+
evidence: evidence(
|
|
449
|
+
...gridCardEvidence,
|
|
450
|
+
src('FileUpload/components/GridFileCard/GridFileCard.tsx', 'ring-border-critical')
|
|
451
|
+
),
|
|
452
|
+
},
|
|
453
|
+
'gridFileCard.complete': {
|
|
454
|
+
// A card showing a status icon instead of the progress circle is not matched.
|
|
455
|
+
component: 'GridFileCard',
|
|
456
|
+
host: gridCardHost,
|
|
457
|
+
has: 'svg[role="status"][aria-valuenow="100"]',
|
|
458
|
+
positioned: true,
|
|
459
|
+
taken: [],
|
|
460
|
+
stateBackgrounds: ['hover', 'focus-within'],
|
|
461
|
+
evidence: evidence(...gridCardEvidence, ...progressCompleteEvidence),
|
|
462
|
+
},
|
|
463
|
+
} as const satisfies Record<string, EffectHook>;
|
|
464
|
+
|
|
465
|
+
export type EffectHookId = keyof typeof effectHooks;
|
|
466
|
+
|
|
467
|
+
const hookRecord = (id: EffectHookId): EffectHook => effectHooks[id];
|
|
468
|
+
|
|
469
|
+
/** `host + is + :has(has) + :not(:has(lacks))`, unscoped. */
|
|
470
|
+
export const hookSelector = (id: EffectHookId): string => {
|
|
471
|
+
const hook = hookRecord(id);
|
|
472
|
+
const is = hook.is ?? '';
|
|
473
|
+
const has = hook.has !== undefined ? `:has(${hook.has})` : '';
|
|
474
|
+
const lacks = hook.lacks !== undefined ? `:not(:has(${hook.lacks}))` : '';
|
|
475
|
+
return `${hook.host}${is}${has}${lacks}`;
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
/** `'listFileCard.selected'` -> `'listFileCard'`. An id with no dot is returned unchanged. */
|
|
479
|
+
export const rootOf = (id: EffectHookId): string => {
|
|
480
|
+
const dot = id.indexOf('.');
|
|
481
|
+
return dot === -1 ? id : id.slice(0, dot);
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
/** First pseudo slot the component does not paint itself. */
|
|
485
|
+
export const freePseudoSlot = (taken: readonly PseudoSlot[]): PseudoSlot | undefined => {
|
|
486
|
+
if (!taken.includes('before')) {
|
|
487
|
+
return 'before';
|
|
488
|
+
}
|
|
489
|
+
if (!taken.includes('after')) {
|
|
490
|
+
return 'after';
|
|
491
|
+
}
|
|
492
|
+
return undefined;
|
|
493
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which brands have an effect layer. EverydayHarbor is the first.
|
|
3
|
+
* Locked brands are `?: never`, so they cannot be registered without leaving `LOCKED_BRANDS`.
|
|
4
|
+
*/
|
|
5
|
+
import type { Brands } from '../themes/brands';
|
|
6
|
+
import type { LOCKED_BRANDS } from '../themes/themeValues';
|
|
7
|
+
import type { EffectLayer } from './rules';
|
|
8
|
+
|
|
9
|
+
import { everydayHarborEffects } from './brands/EverydayHarbor';
|
|
10
|
+
|
|
11
|
+
export type LockedBrand = (typeof LOCKED_BRANDS)[number];
|
|
12
|
+
export type EffectBrand = Exclude<Brands, LockedBrand>;
|
|
13
|
+
|
|
14
|
+
export type EffectLayerRegistry = { readonly [B in EffectBrand]?: EffectLayer } & {
|
|
15
|
+
readonly [B in LockedBrand]?: never;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const effectLayers = { EverydayHarbor: everydayHarborEffects } as const satisfies EffectLayerRegistry;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effect rules are data. Authors name a hook or an opt-in class and the values to apply.
|
|
3
|
+
* The emitter chooses the selector, the layer, and which declarations are enforced.
|
|
4
|
+
*
|
|
5
|
+
* Enforced (layered `!important`): material, shadow, paint, and every pseudo-element declaration.
|
|
6
|
+
* Default (layered, normal): `vars`, radius, and the emitter's `position: relative`.
|
|
7
|
+
* `PaintProperty`, `When`, and `vars` leave focus, outlines, borders, and type metrics to their own conventions.
|
|
8
|
+
* `vars` is limited to text and icon colour channels plus `--effect-font-accent`. Focus, outline, and border colours are not remappable.
|
|
9
|
+
* A `vars` remap inherits into the hook's whole subtree, including any text field inside a modal, drawer, sheet, or menu.
|
|
10
|
+
* `shadow` is a shadow token name. The emitter prepends the Tailwind ring variables.
|
|
11
|
+
*/
|
|
12
|
+
import type { CSSVariables } from '../themes/generateCSSVars';
|
|
13
|
+
import type { EffectClass } from './classes';
|
|
14
|
+
import type { EffectHookId, StateBackground } from './hooks';
|
|
15
|
+
import type { EffectTokenRef, EffectTokenValues, TokensOfKind } from './tokens';
|
|
16
|
+
|
|
17
|
+
export type EffectMaterial = 'surface' | 'raised' | 'scrim';
|
|
18
|
+
|
|
19
|
+
/** Token names each material expands to. `edge: true` adds the masked 1px edge. */
|
|
20
|
+
export const materials = {
|
|
21
|
+
surface: { fill: 'surface-fill', filter: 'surface-filter', shadow: 'surface-shadow', edge: true },
|
|
22
|
+
raised: { fill: 'raised-fill', filter: 'raised-filter', shadow: 'raised-shadow', edge: true },
|
|
23
|
+
scrim: { fill: 'scrim-fill', filter: 'scrim-filter', shadow: null, edge: false },
|
|
24
|
+
} as const satisfies Record<
|
|
25
|
+
EffectMaterial,
|
|
26
|
+
{
|
|
27
|
+
fill: TokensOfKind<'paint'>;
|
|
28
|
+
filter: TokensOfKind<'filter'>;
|
|
29
|
+
shadow: TokensOfKind<'shadow'> | null;
|
|
30
|
+
edge: boolean;
|
|
31
|
+
}
|
|
32
|
+
>;
|
|
33
|
+
|
|
34
|
+
export type PaintProperty =
|
|
35
|
+
| 'background'
|
|
36
|
+
| 'backgroundColor'
|
|
37
|
+
| 'backgroundImage'
|
|
38
|
+
| 'backdropFilter'
|
|
39
|
+
| 'color'
|
|
40
|
+
| 'fontFamily';
|
|
41
|
+
|
|
42
|
+
export type When = 'hover' | 'active';
|
|
43
|
+
|
|
44
|
+
type PaintFor<P extends PaintProperty> = P extends 'fontFamily'
|
|
45
|
+
? EffectTokenRef<TokensOfKind<'font'>>
|
|
46
|
+
: P extends 'backdropFilter'
|
|
47
|
+
? EffectTokenRef<TokensOfKind<'filter'>>
|
|
48
|
+
: EffectTokenRef<TokensOfKind<'paint'>>;
|
|
49
|
+
|
|
50
|
+
export type Decoration = {
|
|
51
|
+
readonly inset?: `${number}px`;
|
|
52
|
+
readonly borderRadius?: `${number}px` | 'inherit';
|
|
53
|
+
readonly borderWidth?: `${number}px`;
|
|
54
|
+
readonly borderStyle?: 'dashed' | 'dotted' | 'solid';
|
|
55
|
+
readonly borderColor?: EffectTokenRef<TokensOfKind<'paint'>>;
|
|
56
|
+
readonly background?: EffectTokenRef<TokensOfKind<'paint'>>;
|
|
57
|
+
readonly boxShadow?: EffectTokenRef<TokensOfKind<'shadow'>>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type EffectVars = {
|
|
61
|
+
readonly [K in
|
|
62
|
+
| Extract<CSSVariables, `--color-text-${string}` | `--color-icon-${string}`>
|
|
63
|
+
| '--effect-font-accent']?: K extends '--effect-font-accent'
|
|
64
|
+
? EffectTokenRef<TokensOfKind<'font'>>
|
|
65
|
+
: EffectTokenRef<TokensOfKind<'channels'>>;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type EffectRule = {
|
|
69
|
+
readonly on: EffectHookId | EffectClass;
|
|
70
|
+
readonly when?: When;
|
|
71
|
+
readonly unless?: string;
|
|
72
|
+
readonly material?: EffectMaterial;
|
|
73
|
+
readonly shadow?: TokensOfKind<'shadow'>;
|
|
74
|
+
readonly paint?: { readonly [P in PaintProperty]?: PaintFor<P> };
|
|
75
|
+
readonly vars?: EffectVars;
|
|
76
|
+
readonly radius?: TokensOfKind<'length'>;
|
|
77
|
+
readonly before?: Decoration;
|
|
78
|
+
readonly after?: Decoration;
|
|
79
|
+
readonly acknowledge?: readonly StateBackground[];
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** Brand layers bind catalog hooks. Opt-in classes belong to the vocabulary. */
|
|
83
|
+
export type BrandEffectRule = Omit<EffectRule, 'on'> & {
|
|
84
|
+
readonly on: EffectHookId;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export type EffectLayer = {
|
|
88
|
+
readonly tokens: EffectTokenValues;
|
|
89
|
+
readonly rules: readonly BrandEffectRule[];
|
|
90
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-time guard rails. `ts-check` covers this file; it is not a published entry.
|
|
3
|
+
* Each `@ts-expect-error` is one line so the error stays on that line.
|
|
4
|
+
*/
|
|
5
|
+
import type { EffectLayerRegistry } from './layers';
|
|
6
|
+
import type { BrandEffectRule, EffectRule } from './rules';
|
|
7
|
+
|
|
8
|
+
import { fx, type EffectTokenValues } from './tokens';
|
|
9
|
+
|
|
10
|
+
declare const readyTokens: EffectTokenValues;
|
|
11
|
+
|
|
12
|
+
// @ts-expect-error a brand must provide every token that has no registry default
|
|
13
|
+
export const incompleteTokens: EffectTokenValues = { 'solid-fill': '#000' };
|
|
14
|
+
|
|
15
|
+
// @ts-expect-error rules carry token refs, never literal colours
|
|
16
|
+
export const literalColour: EffectRule = { on: 'modal', paint: { background: '#E3B341' } };
|
|
17
|
+
|
|
18
|
+
// @ts-expect-error outline is outside PaintProperty
|
|
19
|
+
export const outlinePaint: EffectRule = { on: 'modal', paint: { outlineColor: fx.dash } };
|
|
20
|
+
|
|
21
|
+
// @ts-expect-error focus states are not a `when`
|
|
22
|
+
export const focusState: EffectRule = { on: 'modal', when: 'focus-visible' };
|
|
23
|
+
|
|
24
|
+
// @ts-expect-error only catalogued hooks and vocabulary classes are targets
|
|
25
|
+
export const uncatalogued: EffectRule = { on: 'textInput', material: 'surface' };
|
|
26
|
+
|
|
27
|
+
// @ts-expect-error shadow takes a shadow-kind token name
|
|
28
|
+
export const wrongKind: EffectRule = { on: 'modal', shadow: 'surface-fill' };
|
|
29
|
+
|
|
30
|
+
// @ts-expect-error channel remaps take channels tokens
|
|
31
|
+
export const paintIntoChannels: EffectRule = { on: 'modal', vars: { '--color-text-inverse': fx.ctaFill } };
|
|
32
|
+
|
|
33
|
+
// @ts-expect-error --effect-font-accent takes a font token
|
|
34
|
+
export const fontAccentChannels: EffectRule = { on: 'modal', vars: { '--effect-font-accent': fx.ctaInkRgb } };
|
|
35
|
+
|
|
36
|
+
// @ts-expect-error focus colours are not remappable
|
|
37
|
+
export const focusColourVar: EffectRule = { on: 'modal', vars: { '--color-focus-primary': fx.ctaInkRgb } };
|
|
38
|
+
|
|
39
|
+
// @ts-expect-error border colours are not remappable
|
|
40
|
+
export const borderColourVar: EffectRule = { on: 'modal', vars: { '--color-border-primary': fx.ctaInkRgb } };
|
|
41
|
+
|
|
42
|
+
// @ts-expect-error locked brands cannot register an effect layer
|
|
43
|
+
export const lockedBrand: EffectLayerRegistry = { Default: { rules: [], tokens: readyTokens } };
|
|
44
|
+
|
|
45
|
+
// @ts-expect-error brand rules bind hooks, not opt-in classes
|
|
46
|
+
export const classTarget: BrandEffectRule = { on: 'hui-fx-glass', material: 'surface' };
|
|
47
|
+
|
|
48
|
+
export const selectedAccent: EffectRule = { on: 'listFileCard.selected', shadow: 'selected' };
|
|
49
|
+
export const surfaceFillRef: 'var(--effect-surface-fill)' = fx.surfaceFill;
|
|
50
|
+
export const serifSwitch: EffectRule = { on: 'modal', vars: { '--effect-font-accent': fx.fontSerif } };
|