@sigx/zero-daisyui 0.4.0 → 0.6.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/README.md +61 -1
- package/dist/audit.json +6963 -3228
- package/dist/components.d.ts +14 -2
- package/dist/components.js +1 -0
- package/dist/css/components/accordion.css +26 -2
- package/dist/css/components/alert.css +2 -0
- package/dist/css/components/avatar.css +2 -0
- package/dist/css/components/badge.css +2 -0
- package/dist/css/components/box.css +2 -0
- package/dist/css/components/breadcrumbs.css +2 -0
- package/dist/css/components/button.css +2 -0
- package/dist/css/components/card.css +2 -0
- package/dist/css/components/carousel.css +2 -0
- package/dist/css/components/center.css +2 -0
- package/dist/css/components/chat.css +2 -0
- package/dist/css/components/checkbox-group.css +98 -0
- package/dist/css/components/checkbox.css +10 -0
- package/dist/css/components/collapsible.css +16 -2
- package/dist/css/components/combobox.css +43 -1
- package/dist/css/components/container.css +2 -0
- package/dist/css/components/countdown.css +2 -0
- package/dist/css/components/dialog.css +2 -0
- package/dist/css/components/diff.css +7 -0
- package/dist/css/components/divider.css +2 -0
- package/dist/css/components/drawer.css +2 -0
- package/dist/css/components/empty-state.css +2 -0
- package/dist/css/components/field.css +2 -0
- package/dist/css/components/file-upload.css +34 -0
- package/dist/css/components/grid.css +2 -0
- package/dist/css/components/indicator.css +2 -0
- package/dist/css/components/input.css +137 -0
- package/dist/css/components/join.css +2 -0
- package/dist/css/components/kbd.css +2 -0
- package/dist/css/components/menu.css +2 -0
- package/dist/css/components/nav-list.css +2 -0
- package/dist/css/components/navbar.css +2 -0
- package/dist/css/components/number-input.css +2 -0
- package/dist/css/components/pagination.css +6 -0
- package/dist/css/components/popover.css +2 -0
- package/dist/css/components/progress.css +2 -0
- package/dist/css/components/radial-progress.css +2 -0
- package/dist/css/components/radio-group.css +10 -4
- package/dist/css/components/rating-group.css +2 -0
- package/dist/css/components/select.css +61 -1
- package/dist/css/components/skeleton.css +2 -0
- package/dist/css/components/slider.css +154 -25
- package/dist/css/components/spacer.css +2 -0
- package/dist/css/components/spinner.css +2 -0
- package/dist/css/components/stack.css +2 -0
- package/dist/css/components/stats.css +68 -16
- package/dist/css/components/status.css +2 -0
- package/dist/css/components/steps.css +2 -0
- package/dist/css/components/swap.css +2 -0
- package/dist/css/components/switch.css +23 -0
- package/dist/css/components/table.css +7 -0
- package/dist/css/components/tabs.css +15 -0
- package/dist/css/components/textarea.css +2 -0
- package/dist/css/components/timeline.css +3 -0
- package/dist/css/components/toast.css +2 -0
- package/dist/css/components/toggle-group.css +2 -0
- package/dist/css/components/toggle.css +2 -0
- package/dist/css/components/tooltip.css +2 -0
- package/dist/css/components/tree-view.css +6 -0
- package/dist/css/index.css +702 -64
- package/dist/css/tokens.css +13 -13
- package/dist/lynx/components/accordion.css +5 -0
- package/dist/lynx/components/alert.css +20 -0
- package/dist/lynx/components/button.css +5 -0
- package/dist/lynx/components/checkbox-group.css +209 -0
- package/dist/lynx/components/checkbox.css +8 -0
- package/dist/lynx/components/combobox.css +53 -0
- package/dist/lynx/components/diff.css +5 -0
- package/dist/lynx/components/drawer.css +4 -0
- package/dist/lynx/components/file-upload.css +47 -0
- package/dist/lynx/components/input.css +178 -0
- package/dist/lynx/components/number-input.css +8 -0
- package/dist/lynx/components/pagination.css +16 -0
- package/dist/lynx/components/progress.css +4 -4
- package/dist/lynx/components/radio-group.css +8 -0
- package/dist/lynx/components/rating-group.css +3 -3
- package/dist/lynx/components/select.css +65 -0
- package/dist/lynx/components/slider.css +106 -25
- package/dist/lynx/components/stats.css +146 -16
- package/dist/lynx/components/steps.css +4 -0
- package/dist/lynx/components/switch.css +4 -0
- package/dist/lynx/components/table.css +5 -0
- package/dist/lynx/components/tabs.css +13 -0
- package/dist/lynx/components/timeline.css +26 -0
- package/dist/lynx/components/toggle.css +5 -0
- package/dist/lynx/components/tree-view.css +4 -0
- package/dist/lynx/index.css +998 -94
- package/dist/lynx/manifest.json +40 -5
- package/dist/lynx/tokens.css +4 -4
- package/dist/manifest.json +38 -3
- package/dist/recipes.d.ts +7 -0
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +658 -60
- package/dist/recipes.js.map +1 -1
- package/dist/register.d.ts +9 -0
- package/dist/report.json +627 -211
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +18 -4
- package/dist/tokens.js.map +1 -1
- package/package.json +5 -5
- package/src/recipes.ts +667 -61
- package/src/tokens.ts +18 -4
package/src/recipes.ts
CHANGED
|
@@ -155,14 +155,48 @@ const disclosurePresence: PartStyles = {
|
|
|
155
155
|
'&::details-content': {
|
|
156
156
|
blockSize: '0',
|
|
157
157
|
overflow: 'hidden',
|
|
158
|
+
},
|
|
159
|
+
// Open only: a close removes `open`, which hides the content before
|
|
160
|
+
// any transition could run — the panel animates that half (#276).
|
|
161
|
+
'&[open]::details-content': {
|
|
162
|
+
blockSize: 'auto',
|
|
158
163
|
transition: 'block-size var(--duration-normal) var(--ease-standard), '
|
|
159
|
-
|
|
164
|
+
+ 'content-visibility var(--duration-normal) allow-discrete',
|
|
160
165
|
},
|
|
161
|
-
'&[open]::details-content': { blockSize: 'auto' },
|
|
162
166
|
},
|
|
163
|
-
at: { 'reduced-motion': { selectors: { '
|
|
167
|
+
at: { 'reduced-motion': { selectors: { '&[open]::details-content': { transition: 'none' } } } },
|
|
164
168
|
};
|
|
165
169
|
|
|
170
|
+
/**
|
|
171
|
+
* The panel's close (#276). Removing `open` hides a `<details>` at once, so
|
|
172
|
+
* the close cannot live on `::details-content` the way the open does: zero
|
|
173
|
+
* flips the panel to `closed` first and holds the element open until the
|
|
174
|
+
* panel's own animation has played. The runtime publishes the panel's
|
|
175
|
+
* measured height as `--<scope>-panel-height`, which is the one value this
|
|
176
|
+
* needs — `auto` is not an endpoint any engine but Chromium can animate.
|
|
177
|
+
* `border-box` so the measured height (content + padding) is the whole box.
|
|
178
|
+
* Web-only: the property is written by the DOM runtime (`RUNTIME_PROPERTIES`).
|
|
179
|
+
*/
|
|
180
|
+
const disclosureExit = (scope: 'collapsible' | 'accordion'): RecipeInput['targets'] => ({
|
|
181
|
+
web: {
|
|
182
|
+
parts: {
|
|
183
|
+
panel: {
|
|
184
|
+
states: {
|
|
185
|
+
closed: {
|
|
186
|
+
boxSizing: 'border-box',
|
|
187
|
+
animation: `${scope}-panel-exit var(--duration-normal) var(--ease-standard)`,
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
at: { 'reduced-motion': { states: { closed: { animation: 'none' } } } },
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
keyframes: {
|
|
194
|
+
[`${scope}-panel-exit`]:
|
|
195
|
+
`from { block-size: var(--${scope}-panel-height); } to { block-size: 0; padding-block: 0; }`,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
166
200
|
/**
|
|
167
201
|
* Merge presence into a part's own styles per KEY, not per block: a recipe
|
|
168
202
|
* that already writes `states: { open: {} }` — the "deliberately unstyled"
|
|
@@ -216,14 +250,33 @@ const withPresence = (presence: PartStyles, styles: PartStyles): PartStyles => (
|
|
|
216
250
|
*/
|
|
217
251
|
export const tabs: RecipeInput = {
|
|
218
252
|
component: 'tabs',
|
|
253
|
+
tokens: {
|
|
254
|
+
// How far the focus ring reaches outside a tab (2px + 2px offset):
|
|
255
|
+
// the list's scroll box pads by it so the ring is never clipped (#45).
|
|
256
|
+
'--tabs-ring-room': '4px',
|
|
257
|
+
},
|
|
219
258
|
parts: {
|
|
220
259
|
root: {
|
|
221
260
|
base: { display: 'flex', flexDirection: 'column', gap: 'var(--space-lg)' },
|
|
222
261
|
},
|
|
223
262
|
list: {
|
|
263
|
+
// The row's own scroll box (#45), Pagination's and Stats' answer:
|
|
264
|
+
// a row of labels wider than its column (a phone, a wide fallback
|
|
265
|
+
// face) scrolls instead of pushing the page sideways.
|
|
266
|
+
// `max-inline-size` (border-box, so it includes the padding)
|
|
267
|
+
// gives the shrink-to-fit list a width to scroll in. The padding is room for the focus ring, which the
|
|
268
|
+
// scroll box would otherwise clip at the row's edges; the
|
|
269
|
+
// matching scroll padding keeps it clear when focus scrolls a
|
|
270
|
+
// tab into view. The box variant's own padding (space-xs, the
|
|
271
|
+
// same 4px) replaces it there.
|
|
224
272
|
base: {
|
|
225
273
|
display: 'inline-flex',
|
|
226
274
|
alignSelf: 'flex-start',
|
|
275
|
+
boxSizing: 'border-box',
|
|
276
|
+
maxInlineSize: '100%',
|
|
277
|
+
overflowX: 'auto',
|
|
278
|
+
padding: 'var(--tabs-ring-room)',
|
|
279
|
+
scrollPaddingInline: 'var(--tabs-ring-room)',
|
|
227
280
|
},
|
|
228
281
|
},
|
|
229
282
|
tab: {
|
|
@@ -261,6 +314,10 @@ export const tabs: RecipeInput = {
|
|
|
261
314
|
...focusRing,
|
|
262
315
|
},
|
|
263
316
|
},
|
|
317
|
+
// daisy draws each flavor's active mark on the tab itself (the
|
|
318
|
+
// underline, the lift, the pill) and none of them slides, so a
|
|
319
|
+
// `Tabs.Indicator` renders nothing here (#283).
|
|
320
|
+
indicator: { base: { display: 'none' } },
|
|
264
321
|
panel: {
|
|
265
322
|
base: { fontSize: 'var(--text-md)' },
|
|
266
323
|
states: { active: {}, inactive: {} },
|
|
@@ -536,6 +593,7 @@ const disclosureSizes: Record<string, Record<string, PartStyles>> = {
|
|
|
536
593
|
// daisy "collapse collapse-arrow" flavor.
|
|
537
594
|
export const collapsible: RecipeInput = {
|
|
538
595
|
component: 'collapsible',
|
|
596
|
+
targets: disclosureExit('collapsible'),
|
|
539
597
|
// Public to a design system derived from this one (#73).
|
|
540
598
|
hooks: {
|
|
541
599
|
properties: {
|
|
@@ -653,6 +711,8 @@ export const switchRecipe: RecipeInput = {
|
|
|
653
711
|
root: {
|
|
654
712
|
base: { display: 'inline-flex', alignItems: 'center', gap: 'var(--space-lg)', cursor: 'pointer' },
|
|
655
713
|
states: {
|
|
714
|
+
// Readonly answers to nothing, so it does not invite a click.
|
|
715
|
+
readonly: { cursor: 'default' },
|
|
656
716
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
657
717
|
checked: {},
|
|
658
718
|
unchecked: {},
|
|
@@ -787,6 +847,29 @@ export const switchRecipe: RecipeInput = {
|
|
|
787
847
|
+ '0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000)',
|
|
788
848
|
transition: 'background-color var(--duration-instant) var(--ease-standard)',
|
|
789
849
|
},
|
|
850
|
+
at: {
|
|
851
|
+
// The knob is `currentColor` background plus shadows:
|
|
852
|
+
// forced colours revalue the one to Canvas and drop the
|
|
853
|
+
// others, leaving two empty pill outlines that read the
|
|
854
|
+
// same on and off (#189). Opt the knob out of forcing
|
|
855
|
+
// and paint it in system colours — CanvasText at rest,
|
|
856
|
+
// Highlight once checked. The noise and the shadows go
|
|
857
|
+
// too: unforced, they would mix the forced ink back in.
|
|
858
|
+
'forced-colors': {
|
|
859
|
+
base: {
|
|
860
|
+
forcedColorAdjust: 'none',
|
|
861
|
+
backgroundColor: 'CanvasText',
|
|
862
|
+
backgroundImage: 'none',
|
|
863
|
+
boxShadow: 'none',
|
|
864
|
+
},
|
|
865
|
+
states: { checked: { backgroundColor: 'Highlight' } },
|
|
866
|
+
// A disabled switch draws in GrayText, the forced palette's
|
|
867
|
+
// own disabled ink, not full-strength ink faded by an
|
|
868
|
+
// author opacity the user's theme cannot style. Keyed off
|
|
869
|
+
// the control's flag, which outranks `checked`.
|
|
870
|
+
selectors: { '[data-scope="switch"][data-part="control"][data-disabled] &': { backgroundColor: 'GrayText' } },
|
|
871
|
+
},
|
|
872
|
+
},
|
|
790
873
|
},
|
|
791
874
|
},
|
|
792
875
|
},
|
|
@@ -1067,6 +1150,23 @@ const floatingPanel: NonNullable<PartStyles['base']> = {
|
|
|
1067
1150
|
boxShadow: 'var(--shadow-lg)',
|
|
1068
1151
|
};
|
|
1069
1152
|
|
|
1153
|
+
/**
|
|
1154
|
+
* daisy's dropdown menu for a listbox, sized by the geometry the
|
|
1155
|
+
* anchored-position strategy publishes (#278): at least the width of the
|
|
1156
|
+
* control that opened it (`--anchor-width`, daisy's 13rem floor otherwise)
|
|
1157
|
+
* and never taller than the room on the side it opened to
|
|
1158
|
+
* (`--available-height`), scrolling past a 20rem cap. Border-box, so both
|
|
1159
|
+
* bounds measure the box the strategy positions. Runtime-published
|
|
1160
|
+
* properties are web-only, so this is each listbox recipe's `targets.web`;
|
|
1161
|
+
* lynx keeps the shared 13rem.
|
|
1162
|
+
*/
|
|
1163
|
+
const anchoredListbox: CssProps = {
|
|
1164
|
+
boxSizing: 'border-box',
|
|
1165
|
+
minWidth: 'var(--anchor-width, 13rem)',
|
|
1166
|
+
maxHeight: 'min(20rem, var(--available-height, 20rem))',
|
|
1167
|
+
overflowY: 'auto',
|
|
1168
|
+
};
|
|
1169
|
+
|
|
1070
1170
|
export const popover: RecipeInput = {
|
|
1071
1171
|
component: 'popover',
|
|
1072
1172
|
parts: {
|
|
@@ -1432,6 +1532,8 @@ export const checkbox: RecipeInput = {
|
|
|
1432
1532
|
root: {
|
|
1433
1533
|
base: { display: 'inline-flex', alignItems: 'center', gap: 'var(--space-md)', cursor: 'pointer' },
|
|
1434
1534
|
states: {
|
|
1535
|
+
// Readonly answers to nothing, so it does not invite a click.
|
|
1536
|
+
readonly: { cursor: 'default' },
|
|
1435
1537
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
1436
1538
|
checked: {}, unchecked: {}, indeterminate: {},
|
|
1437
1539
|
},
|
|
@@ -1504,6 +1606,8 @@ export const checkbox: RecipeInput = {
|
|
|
1504
1606
|
// `invalid` is semantic, not an accent: it stays error under
|
|
1505
1607
|
// every colour variant, on purpose.
|
|
1506
1608
|
invalid: { borderColor: 'var(--color-error)' },
|
|
1609
|
+
// Readonly answers to nothing, so it does not invite a click.
|
|
1610
|
+
readonly: { cursor: 'default' },
|
|
1507
1611
|
disabled: {},
|
|
1508
1612
|
},
|
|
1509
1613
|
},
|
|
@@ -1630,13 +1734,9 @@ export const radioGroup: RecipeInput = {
|
|
|
1630
1734
|
parts: {
|
|
1631
1735
|
root: {
|
|
1632
1736
|
base: { display: 'flex', flexDirection: 'column', gap: 'var(--space-md)' },
|
|
1737
|
+
// `invalid` paints on each `item-control`, which carries the flag
|
|
1738
|
+
// itself (#267) — the root only groups the items.
|
|
1633
1739
|
states: { invalid: {}, required: {} },
|
|
1634
|
-
selectors: {
|
|
1635
|
-
// `invalid` is a fact about the GROUP — `item-control` carries
|
|
1636
|
-
// no flag of its own — and it is semantic: it stays error
|
|
1637
|
-
// under every colour variant.
|
|
1638
|
-
'&[data-invalid] [data-part="item-control"]': { borderColor: 'var(--color-error)' },
|
|
1639
|
-
},
|
|
1640
1740
|
},
|
|
1641
1741
|
label: {
|
|
1642
1742
|
base: { fontSize: 'var(--text-sm)', fontWeight: 'var(--weight-semibold)' },
|
|
@@ -1645,6 +1745,8 @@ export const radioGroup: RecipeInput = {
|
|
|
1645
1745
|
item: {
|
|
1646
1746
|
base: { display: 'inline-flex', alignItems: 'center', gap: 'var(--space-md)', cursor: 'pointer' },
|
|
1647
1747
|
states: {
|
|
1748
|
+
// Readonly answers to nothing, so it does not invite a click.
|
|
1749
|
+
readonly: { cursor: 'default' },
|
|
1648
1750
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
1649
1751
|
checked: {}, unchecked: {},
|
|
1650
1752
|
},
|
|
@@ -1679,6 +1781,10 @@ export const radioGroup: RecipeInput = {
|
|
|
1679
1781
|
animation: 'var(--duration-fast) var(--ease-standard) zero-daisy-radio',
|
|
1680
1782
|
},
|
|
1681
1783
|
unchecked: {},
|
|
1784
|
+
// `invalid` is semantic: it stays error under every colour
|
|
1785
|
+
// variant, and after `checked` so it holds whichever radio is
|
|
1786
|
+
// chosen.
|
|
1787
|
+
invalid: { borderColor: 'var(--color-error)' },
|
|
1682
1788
|
'focus-visible': { outline: '2px solid var(--radio-accent)', outlineOffset: '2px' },
|
|
1683
1789
|
disabled: {},
|
|
1684
1790
|
},
|
|
@@ -1750,6 +1856,48 @@ export const radioGroup: RecipeInput = {
|
|
|
1750
1856
|
},
|
|
1751
1857
|
};
|
|
1752
1858
|
|
|
1859
|
+
/**
|
|
1860
|
+
* CheckboxGroup (#282) — daisy has no group component; its forms stack
|
|
1861
|
+
* `.checkbox` rows under a `fieldset-legend`. The boxes keep the checkbox
|
|
1862
|
+
* recipe; the group owns the stack and the legend, which speaks Field's
|
|
1863
|
+
* label (colour through `roleInk`, #210).
|
|
1864
|
+
*/
|
|
1865
|
+
export const checkboxGroup: RecipeInput = {
|
|
1866
|
+
component: 'checkbox-group',
|
|
1867
|
+
tokens: { '--checkbox-group-accent': 'var(--color-base-content)' },
|
|
1868
|
+
parts: {
|
|
1869
|
+
root: {
|
|
1870
|
+
base: { display: 'flex', flexDirection: 'column', gap: 'var(--space-md)' },
|
|
1871
|
+
// `invalid` and `readonly` paint on each box — the root only
|
|
1872
|
+
// lays the boxes out.
|
|
1873
|
+
states: { invalid: {}, required: {}, readonly: {} },
|
|
1874
|
+
selectors: {
|
|
1875
|
+
'&[data-orientation="horizontal"]': { flexDirection: 'row', flexWrap: 'wrap', columnGap: 'var(--space-lg)' },
|
|
1876
|
+
},
|
|
1877
|
+
},
|
|
1878
|
+
label: {
|
|
1879
|
+
base: { fontSize: 'var(--text-sm)', fontWeight: 'var(--weight-semibold)', color: 'var(--checkbox-group-accent)' },
|
|
1880
|
+
states: { disabled: { opacity: 'var(--disabled-opacity)' } },
|
|
1881
|
+
selectors: {
|
|
1882
|
+
'&[data-required]::after': { content: '" *"', color: roleInk('error') },
|
|
1883
|
+
},
|
|
1884
|
+
},
|
|
1885
|
+
},
|
|
1886
|
+
variants: {
|
|
1887
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
1888
|
+
'--checkbox-group-accent': roleInk(c),
|
|
1889
|
+
} } }])),
|
|
1890
|
+
size: {
|
|
1891
|
+
xs: { root: { base: { gap: 'var(--space-sm)' } }, label: { base: { fontSize: 'var(--text-xs)' } } },
|
|
1892
|
+
sm: { label: { base: { fontSize: 'var(--text-xs)' } } },
|
|
1893
|
+
md: {},
|
|
1894
|
+
lg: { label: { base: { fontSize: 'var(--text-md)' } } },
|
|
1895
|
+
xl: { root: { base: { gap: 'var(--space-lg)' } }, label: { base: { fontSize: 'var(--text-lg)' } } },
|
|
1896
|
+
},
|
|
1897
|
+
},
|
|
1898
|
+
skipStates: { label: ['invalid'] },
|
|
1899
|
+
};
|
|
1900
|
+
|
|
1753
1901
|
// --------------------------------------------------------------------------
|
|
1754
1902
|
// 4. progress — replace the whole export (only the two radius lines move)
|
|
1755
1903
|
// --------------------------------------------------------------------------
|
|
@@ -1875,7 +2023,15 @@ export const progress: RecipeInput = {
|
|
|
1875
2023
|
|
|
1876
2024
|
export const slider: RecipeInput = {
|
|
1877
2025
|
component: 'slider',
|
|
1878
|
-
|
|
2026
|
+
// daisy's `.range` metrics: the thumb is `--range-thumb-size`
|
|
2027
|
+
// (×6 of `--size-selector` at md, ×4…×8 across the ramp — the same
|
|
2028
|
+
// selector ramp checkbox rides) and the track is half of it. Both the
|
|
2029
|
+
// native control and the composed parts read these.
|
|
2030
|
+
tokens: {
|
|
2031
|
+
'--slider-accent': 'var(--color-primary)',
|
|
2032
|
+
'--slider-track-size': 'calc(var(--size-selector) * 3)',
|
|
2033
|
+
'--slider-thumb-size': 'calc(var(--size-selector) * 6)',
|
|
2034
|
+
},
|
|
1879
2035
|
parts: {
|
|
1880
2036
|
root: {
|
|
1881
2037
|
base: { display: 'flex', flexDirection: 'column', gap: 'var(--space-sm)', width: '100%' },
|
|
@@ -1885,30 +2041,106 @@ export const slider: RecipeInput = {
|
|
|
1885
2041
|
base: { fontSize: 'var(--text-sm)', fontWeight: 'var(--weight-semibold)' },
|
|
1886
2042
|
states: { disabled: {} },
|
|
1887
2043
|
},
|
|
2044
|
+
// A custom skin (`appearance: none`), ported from daisyUI 5's
|
|
2045
|
+
// `range.css`: a `--radius-selector` track half the thumb's height,
|
|
2046
|
+
// and daisy's real range thumb — a `base-100` knob inside a
|
|
2047
|
+
// `.25rem` ring of the fill ink (daisy's `--range-p`), carrying the
|
|
2048
|
+
// same `--depth` inset shading the checkbox and radio wear. daisy
|
|
2049
|
+
// throws the elapsed fill as a 100cqw box-shadow off the thumb and
|
|
2050
|
+
// clips it with `overflow: hidden`; here the track reads the
|
|
2051
|
+
// runtime-published `--slider-percent` as a gradient stop instead,
|
|
2052
|
+
// as every other skin does. The fill is deepened toward
|
|
2053
|
+
// `base-content` the way `progressFill` deepens daisy's bar — a raw
|
|
2054
|
+
// role on the base-300 rail is exactly the #210/#228 bug class.
|
|
2055
|
+
//
|
|
2056
|
+
// The rebuild is also the correctness move (see the kit skill): Blink
|
|
2057
|
+
// treats range inputs as ALWAYS `:focus-visible`, even on mouse
|
|
2058
|
+
// focus, so the base kills the native outline and the
|
|
2059
|
+
// `focus-visible` state — compiled to the runtime's keyboard-only
|
|
2060
|
+
// `[data-focus-visible]` flag — draws the ring only when it means it.
|
|
1888
2061
|
control: {
|
|
1889
|
-
base: {
|
|
2062
|
+
base: {
|
|
2063
|
+
appearance: 'none',
|
|
2064
|
+
width: '100%',
|
|
2065
|
+
height: 'var(--slider-thumb-size)',
|
|
2066
|
+
margin: '0',
|
|
2067
|
+
background: 'transparent',
|
|
2068
|
+
cursor: 'pointer',
|
|
2069
|
+
outline: 'none',
|
|
2070
|
+
// Kept for the forced-colors fallback below, where native
|
|
2071
|
+
// rendering takes over and still honours the accent.
|
|
2072
|
+
accentColor: 'var(--slider-accent)',
|
|
2073
|
+
// `--slider-fill` and `--slider-track` live in targets.web
|
|
2074
|
+
// below: the fill is a color-mix() the lynx emitter drops,
|
|
2075
|
+
// and the track reads the runtime-published
|
|
2076
|
+
// `--slider-percent`, a web-only mechanism.
|
|
2077
|
+
},
|
|
1890
2078
|
states: {
|
|
2079
|
+
// Readonly answers to nothing, so it does not invite a click.
|
|
2080
|
+
readonly: { cursor: 'default' },
|
|
1891
2081
|
disabled: { cursor: 'not-allowed' },
|
|
1892
2082
|
'focus-visible': { outline: '2px solid var(--slider-accent)', outlineOffset: '2px' },
|
|
1893
2083
|
// `invalid` is semantic: it stays error under every colour
|
|
1894
|
-
// variant, on purpose
|
|
1895
|
-
|
|
2084
|
+
// variant, on purpose — the accent indirection swaps the
|
|
2085
|
+
// fill, the thumb ring, the focus ring and the forced-colors
|
|
2086
|
+
// fallback together.
|
|
2087
|
+
invalid: { '--slider-accent': 'var(--color-error)' },
|
|
2088
|
+
},
|
|
2089
|
+
selectors: {
|
|
2090
|
+
// Vendor pseudos cannot share a selector list — one unknown
|
|
2091
|
+
// selector invalidates the whole rule — so each engine gets
|
|
2092
|
+
// its own copy reading the shared custom properties.
|
|
2093
|
+
'&::-webkit-slider-runnable-track': {
|
|
2094
|
+
height: 'var(--slider-track-size)',
|
|
2095
|
+
borderRadius: 'var(--radius-selector)',
|
|
2096
|
+
background: 'var(--slider-track)',
|
|
2097
|
+
},
|
|
2098
|
+
'&::-webkit-slider-thumb': {
|
|
2099
|
+
appearance: 'none',
|
|
2100
|
+
boxSizing: 'border-box',
|
|
2101
|
+
width: 'var(--slider-thumb-size)',
|
|
2102
|
+
height: 'var(--slider-thumb-size)',
|
|
2103
|
+
marginTop: 'calc((var(--slider-track-size) - var(--slider-thumb-size)) / 2)',
|
|
2104
|
+
borderRadius: 'calc(var(--radius-selector) + min(0.25rem, var(--radius-selector) * 3))',
|
|
2105
|
+
border: '0.25rem solid var(--slider-fill)',
|
|
2106
|
+
background: 'var(--color-base-100)',
|
|
2107
|
+
boxShadow: '0 -1px var(--depth-shade) inset, 0 8px 0 -4px var(--depth-sheen) inset',
|
|
2108
|
+
},
|
|
2109
|
+
'&::-moz-range-track': {
|
|
2110
|
+
height: 'var(--slider-track-size)',
|
|
2111
|
+
borderRadius: 'var(--radius-selector)',
|
|
2112
|
+
background: 'var(--slider-track)',
|
|
2113
|
+
},
|
|
2114
|
+
'&::-moz-range-thumb': {
|
|
2115
|
+
boxSizing: 'border-box',
|
|
2116
|
+
width: 'var(--slider-thumb-size)',
|
|
2117
|
+
height: 'var(--slider-thumb-size)',
|
|
2118
|
+
borderRadius: 'calc(var(--radius-selector) + min(0.25rem, var(--radius-selector) * 3))',
|
|
2119
|
+
border: '0.25rem solid var(--slider-fill)',
|
|
2120
|
+
background: 'var(--color-base-100)',
|
|
2121
|
+
boxShadow: '0 -1px var(--depth-shade) inset, 0 8px 0 -4px var(--depth-sheen) inset',
|
|
2122
|
+
},
|
|
2123
|
+
},
|
|
2124
|
+
at: {
|
|
2125
|
+
// Native rendering knows forced colors better than a custom
|
|
2126
|
+
// skin; the retained accentColor keeps the fallback branded.
|
|
2127
|
+
'forced-colors': { base: { appearance: 'auto' } },
|
|
1896
2128
|
},
|
|
1897
2129
|
},
|
|
1898
|
-
// The composed range projection (#325)
|
|
1899
|
-
//
|
|
1900
|
-
//
|
|
1901
|
-
// `base-content` the way `progressFill` deepens daisy's bar — a raw
|
|
1902
|
-
// role on the base-300 rail is exactly the #210/#228 bug class.
|
|
2130
|
+
// The composed range projection (#325): the same metrics as the
|
|
2131
|
+
// native control above, restating its accent as real boxes. The
|
|
2132
|
+
// fill is the same deepened ink the native track paints.
|
|
1903
2133
|
track: {
|
|
1904
2134
|
base: {
|
|
1905
|
-
height: '
|
|
1906
|
-
|
|
2135
|
+
height: 'var(--slider-track-size)',
|
|
2136
|
+
// Reserve the thumb's overhang so the row's box matches the
|
|
2137
|
+
// native control's.
|
|
2138
|
+
marginBlock: 'calc((var(--slider-thumb-size) - var(--slider-track-size)) / 2)',
|
|
1907
2139
|
borderRadius: '9999px',
|
|
1908
2140
|
background: 'var(--color-base-300)',
|
|
1909
2141
|
cursor: 'pointer',
|
|
1910
2142
|
},
|
|
1911
|
-
states: { disabled: { cursor: 'not-allowed' } },
|
|
2143
|
+
states: { readonly: { cursor: 'default' }, disabled: { cursor: 'not-allowed' } },
|
|
1912
2144
|
},
|
|
1913
2145
|
range: {
|
|
1914
2146
|
base: {
|
|
@@ -1921,11 +2153,11 @@ export const slider: RecipeInput = {
|
|
|
1921
2153
|
thumb: {
|
|
1922
2154
|
base: {
|
|
1923
2155
|
boxSizing: 'border-box',
|
|
1924
|
-
width: '
|
|
1925
|
-
height: '
|
|
2156
|
+
width: 'var(--slider-thumb-size)',
|
|
2157
|
+
height: 'var(--slider-thumb-size)',
|
|
1926
2158
|
insetBlockStart: '50%',
|
|
1927
2159
|
translate: '0 -50%',
|
|
1928
|
-
marginInlineStart: 'calc(var(--size
|
|
2160
|
+
marginInlineStart: 'calc(var(--slider-thumb-size) / -2)',
|
|
1929
2161
|
borderRadius: '9999px',
|
|
1930
2162
|
background: 'color-mix(in oklab, var(--slider-accent) 90%, var(--color-base-content))',
|
|
1931
2163
|
cursor: 'pointer',
|
|
@@ -1935,12 +2167,14 @@ export const slider: RecipeInput = {
|
|
|
1935
2167
|
states: {
|
|
1936
2168
|
'focus-visible': { outline: '2px solid var(--slider-accent)', outlineOffset: '2px' },
|
|
1937
2169
|
pressed: {},
|
|
2170
|
+
// Readonly answers to nothing, so it does not invite a click.
|
|
2171
|
+
readonly: { cursor: 'default' },
|
|
1938
2172
|
disabled: { cursor: 'not-allowed' },
|
|
1939
2173
|
},
|
|
1940
2174
|
},
|
|
1941
2175
|
mark: {
|
|
1942
2176
|
base: {
|
|
1943
|
-
paddingBlockStart: 'calc(var(--size
|
|
2177
|
+
paddingBlockStart: 'calc(var(--slider-track-size) + var(--space-2xs))',
|
|
1944
2178
|
fontSize: 'var(--text-xs)',
|
|
1945
2179
|
lineHeight: '1',
|
|
1946
2180
|
whiteSpace: 'nowrap',
|
|
@@ -1954,7 +2188,7 @@ export const slider: RecipeInput = {
|
|
|
1954
2188
|
insetBlockStart: '0',
|
|
1955
2189
|
insetInlineStart: '-1px',
|
|
1956
2190
|
width: '2px',
|
|
1957
|
-
height: '
|
|
2191
|
+
height: 'var(--slider-track-size)',
|
|
1958
2192
|
background: 'var(--color-base-content)',
|
|
1959
2193
|
},
|
|
1960
2194
|
},
|
|
@@ -1967,33 +2201,135 @@ export const slider: RecipeInput = {
|
|
|
1967
2201
|
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
1968
2202
|
'--slider-accent': `var(--color-${c})`,
|
|
1969
2203
|
} } }])),
|
|
1970
|
-
//
|
|
1971
|
-
//
|
|
2204
|
+
// daisy's `.range-{xs…xl}` ramp: `--range-thumb-size` at ×4…×8 of
|
|
2205
|
+
// `--size-selector` (1 / 1.25 / 1.5 / 1.75 / 2rem), the track half
|
|
2206
|
+
// of it — so every step moves the whole widget; the un-attributed
|
|
2207
|
+
// (and `md`) render takes the middle step from `tokens:`.
|
|
1972
2208
|
size: {
|
|
1973
|
-
xs: {
|
|
1974
|
-
sm: {
|
|
2209
|
+
xs: { root: { base: { '--slider-track-size': 'calc(var(--size-selector) * 2)', '--slider-thumb-size': 'calc(var(--size-selector) * 4)' } }, label: { base: { fontSize: 'var(--text-xs)' } } },
|
|
2210
|
+
sm: { root: { base: { '--slider-track-size': 'calc(var(--size-selector) * 2.5)', '--slider-thumb-size': 'calc(var(--size-selector) * 5)' } }, label: { base: { fontSize: 'var(--text-sm)' } } },
|
|
2211
|
+
// `md`'s metrics ARE the `tokens:` defaults — one source of truth.
|
|
1975
2212
|
md: { label: { base: { fontSize: 'var(--text-sm)' } } },
|
|
1976
|
-
lg: {
|
|
1977
|
-
xl: {
|
|
2213
|
+
lg: { root: { base: { '--slider-track-size': 'calc(var(--size-selector) * 3.5)', '--slider-thumb-size': 'calc(var(--size-selector) * 7)' } }, label: { base: { fontSize: 'var(--text-md)' } } },
|
|
2214
|
+
xl: { root: { base: { '--slider-track-size': 'calc(var(--size-selector) * 4)', '--slider-thumb-size': 'calc(var(--size-selector) * 8)' } }, label: { base: { fontSize: 'var(--text-lg)' } } },
|
|
1978
2215
|
},
|
|
1979
2216
|
},
|
|
1980
|
-
// Base equals the default — `tokens:` binds the primary accent
|
|
1981
|
-
// only restates the label font-size the
|
|
1982
|
-
//
|
|
2217
|
+
// Base equals the default — `tokens:` binds the primary accent and the
|
|
2218
|
+
// middle-step metrics, and `md` only restates the label font-size the
|
|
2219
|
+
// base already declares — so the twins restate it;
|
|
1983
2220
|
// declared for the manifest (signalxjs/lynx#1070).
|
|
1984
2221
|
defaultVariants: { color: 'primary', size: 'md' },
|
|
1985
2222
|
skipStates: { root: ['invalid', 'focus-visible'] },
|
|
1986
2223
|
// ONE design decision, two paints. On the web the accent reaches the
|
|
1987
|
-
// screen twice
|
|
1988
|
-
//
|
|
1989
|
-
// renders neither
|
|
1990
|
-
// `<input type=range
|
|
1991
|
-
//
|
|
1992
|
-
//
|
|
1993
|
-
//
|
|
1994
|
-
//
|
|
1995
|
-
//
|
|
2224
|
+
// screen twice, both times deepened with `color-mix()`: the native
|
|
2225
|
+
// control's gradient track and thumb ring (via `--slider-fill`, below),
|
|
2226
|
+
// and the composed range/thumb. Lynx renders neither — it has no native
|
|
2227
|
+
// `<input type=range>`, and the emitter drops `color-mix()` as
|
|
2228
|
+
// unresolvable — so the compiled range and thumb once shipped with no
|
|
2229
|
+
// paint at all: grey track, invisible fill, invisible knob (measured on
|
|
2230
|
+
// device, signalxjs/lynx#1075). The geometry all survives (the sizes are
|
|
2231
|
+
// direct calc() over the metric tokens); paint is the only gap, so the
|
|
2232
|
+
// lynx section only restates paint, in lynx-expressible spellings, plus
|
|
2233
|
+
// the physical restatement of the logical geometry.
|
|
1996
2234
|
targets: {
|
|
2235
|
+
web: {
|
|
2236
|
+
parts: {
|
|
2237
|
+
control: {
|
|
2238
|
+
base: {
|
|
2239
|
+
// The fill ink, deepened like the composed range.
|
|
2240
|
+
'--slider-fill': 'color-mix(in oklab, var(--slider-accent) 90%, var(--color-base-content))',
|
|
2241
|
+
// The filled track reads the runtime-published
|
|
2242
|
+
// `--slider-percent` — web-only (`RUNTIME_PROPERTIES`);
|
|
2243
|
+
// the lynx runtime paints its own track/range parts.
|
|
2244
|
+
'--slider-track':
|
|
2245
|
+
'linear-gradient(to right, var(--slider-fill) var(--slider-percent, 50%), var(--color-base-300) 0)',
|
|
2246
|
+
},
|
|
2247
|
+
selectors: {
|
|
2248
|
+
// A vertical native range is spelled through writing
|
|
2249
|
+
// mode by the runtime (#170), so the long axis is the
|
|
2250
|
+
// box's HEIGHT and the channel's WIDTH; the fill
|
|
2251
|
+
// grows from the foot.
|
|
2252
|
+
'&[data-orientation="vertical"]': {
|
|
2253
|
+
width: 'var(--slider-thumb-size)',
|
|
2254
|
+
height: 'var(--slider-length)',
|
|
2255
|
+
'--slider-track':
|
|
2256
|
+
'linear-gradient(to top, var(--slider-fill) var(--slider-percent, 50%), var(--color-base-300) 0)',
|
|
2257
|
+
},
|
|
2258
|
+
'&[data-orientation="vertical"]::-webkit-slider-runnable-track': {
|
|
2259
|
+
width: 'var(--slider-track-size)',
|
|
2260
|
+
height: 'auto',
|
|
2261
|
+
},
|
|
2262
|
+
// Blink lays the thumb against the channel's block
|
|
2263
|
+
// axis — in vertical-lr that is its left edge.
|
|
2264
|
+
'&[data-orientation="vertical"]::-webkit-slider-thumb': {
|
|
2265
|
+
marginTop: '0',
|
|
2266
|
+
marginBlockStart: 'calc((var(--slider-track-size) - var(--slider-thumb-size)) / 2)',
|
|
2267
|
+
},
|
|
2268
|
+
'&[data-orientation="vertical"]::-moz-range-track': {
|
|
2269
|
+
width: 'var(--slider-track-size)',
|
|
2270
|
+
height: '100%',
|
|
2271
|
+
},
|
|
2272
|
+
},
|
|
2273
|
+
},
|
|
2274
|
+
// Vertical (#170): the runtime positions the moving parts by
|
|
2275
|
+
// physical `bottom` percents; these turn the channel upright and
|
|
2276
|
+
// re-centre the knob on the other axis. Web-only — the lynx
|
|
2277
|
+
// runtime ships no vertical slider.
|
|
2278
|
+
root: {
|
|
2279
|
+
selectors: {
|
|
2280
|
+
'&[data-orientation="vertical"]': {
|
|
2281
|
+
width: 'auto',
|
|
2282
|
+
alignItems: 'center',
|
|
2283
|
+
'--slider-length': 'calc(var(--size-selector) * 40)',
|
|
2284
|
+
},
|
|
2285
|
+
},
|
|
2286
|
+
},
|
|
2287
|
+
track: {
|
|
2288
|
+
selectors: {
|
|
2289
|
+
'&[data-orientation="vertical"]': {
|
|
2290
|
+
width: 'var(--slider-track-size)',
|
|
2291
|
+
height: 'var(--slider-length)',
|
|
2292
|
+
marginBlock: '0',
|
|
2293
|
+
marginInline: 'calc((var(--slider-thumb-size) - var(--slider-track-size)) / 2)',
|
|
2294
|
+
},
|
|
2295
|
+
},
|
|
2296
|
+
},
|
|
2297
|
+
range: {
|
|
2298
|
+
selectors: {
|
|
2299
|
+
'&[data-orientation="vertical"]': { insetInlineStart: '0', width: '100%' },
|
|
2300
|
+
},
|
|
2301
|
+
},
|
|
2302
|
+
thumb: {
|
|
2303
|
+
selectors: {
|
|
2304
|
+
'&[data-orientation="vertical"]': {
|
|
2305
|
+
insetBlockStart: 'auto',
|
|
2306
|
+
insetInlineStart: '50%',
|
|
2307
|
+
translate: 'none',
|
|
2308
|
+
marginInlineStart: 'calc(var(--slider-thumb-size) / -2)',
|
|
2309
|
+
marginBlockEnd: 'calc(var(--slider-thumb-size) / -2)',
|
|
2310
|
+
},
|
|
2311
|
+
},
|
|
2312
|
+
},
|
|
2313
|
+
// The label sits beside the channel, centred on its tick.
|
|
2314
|
+
mark: {
|
|
2315
|
+
selectors: {
|
|
2316
|
+
'&[data-orientation="vertical"]': {
|
|
2317
|
+
insetInlineStart: '0',
|
|
2318
|
+
paddingBlockStart: '0',
|
|
2319
|
+
paddingInlineStart: 'calc(var(--slider-track-size) + var(--space-2xs))',
|
|
2320
|
+
translate: '0 50%',
|
|
2321
|
+
},
|
|
2322
|
+
'&[data-orientation="vertical"]::before': {
|
|
2323
|
+
insetBlockStart: '50%',
|
|
2324
|
+
insetInlineStart: '0',
|
|
2325
|
+
width: 'var(--slider-track-size)',
|
|
2326
|
+
height: '2px',
|
|
2327
|
+
translate: '0 -50%',
|
|
2328
|
+
},
|
|
2329
|
+
},
|
|
2330
|
+
},
|
|
2331
|
+
},
|
|
2332
|
+
},
|
|
1997
2333
|
lynx: {
|
|
1998
2334
|
parts: {
|
|
1999
2335
|
track: {
|
|
@@ -2003,14 +2339,14 @@ export const slider: RecipeInput = {
|
|
|
2003
2339
|
// the emitter refuses them. Physical is the lynx
|
|
2004
2340
|
// target's norm — no RTL flow there.
|
|
2005
2341
|
base: {
|
|
2006
|
-
marginTop: 'calc(var(--size-
|
|
2007
|
-
marginBottom: 'calc(var(--size-
|
|
2342
|
+
marginTop: 'calc((var(--slider-thumb-size) - var(--slider-track-size)) / 2)',
|
|
2343
|
+
marginBottom: 'calc((var(--slider-thumb-size) - var(--slider-track-size)) / 2)',
|
|
2008
2344
|
},
|
|
2009
2345
|
},
|
|
2010
2346
|
mark: {
|
|
2011
2347
|
// Same #1084 verdict: the tick labels hang below the
|
|
2012
2348
|
// track off a physical padding.
|
|
2013
|
-
base: { paddingTop: 'calc(var(--size
|
|
2349
|
+
base: { paddingTop: 'calc(var(--slider-track-size) + var(--space-2xs))' },
|
|
2014
2350
|
},
|
|
2015
2351
|
range: {
|
|
2016
2352
|
// The web fill deepens the accent 90/10 toward
|
|
@@ -2048,7 +2384,7 @@ export const slider: RecipeInput = {
|
|
|
2048
2384
|
border: '0.25rem solid var(--slider-accent)',
|
|
2049
2385
|
top: '50%',
|
|
2050
2386
|
transform: 'translateY(-50%)',
|
|
2051
|
-
marginLeft: 'calc(var(--size
|
|
2387
|
+
marginLeft: 'calc(var(--slider-thumb-size) / -2)',
|
|
2052
2388
|
},
|
|
2053
2389
|
},
|
|
2054
2390
|
},
|
|
@@ -2058,12 +2394,15 @@ export const slider: RecipeInput = {
|
|
|
2058
2394
|
|
|
2059
2395
|
export const accordion: RecipeInput = {
|
|
2060
2396
|
component: 'accordion',
|
|
2397
|
+
targets: disclosureExit('accordion'),
|
|
2061
2398
|
// Same accent grammar as collapsible: the open heading's ink, defaulting
|
|
2062
2399
|
// to base-content so the un-attributed render is unchanged.
|
|
2063
2400
|
tokens: { '--accordion-accent': 'var(--color-base-content)' },
|
|
2064
2401
|
parts: {
|
|
2065
2402
|
root: {
|
|
2066
2403
|
base: { display: 'flex', flexDirection: 'column', gap: 'var(--space-md)' },
|
|
2404
|
+
// `orientation="horizontal"` (#276): the cards sit in a row.
|
|
2405
|
+
selectors: { '&[data-orientation="horizontal"]': { flexDirection: 'row', alignItems: 'start' } },
|
|
2067
2406
|
},
|
|
2068
2407
|
item: withPresence(disclosurePresence, {
|
|
2069
2408
|
base: {
|
|
@@ -2073,6 +2412,7 @@ export const accordion: RecipeInput = {
|
|
|
2073
2412
|
overflow: 'hidden',
|
|
2074
2413
|
},
|
|
2075
2414
|
states: { open: {}, closed: {} },
|
|
2415
|
+
selectors: { '[data-scope="accordion"][data-part="root"][data-orientation="horizontal"] > &': { flex: '1 1 0', minInlineSize: '0' } },
|
|
2076
2416
|
}),
|
|
2077
2417
|
trigger: {
|
|
2078
2418
|
base: {
|
|
@@ -2157,14 +2497,21 @@ const fieldControl: NonNullable<PartStyles['base']> = {
|
|
|
2157
2497
|
boxShadow: 'var(--shadow-xs)',
|
|
2158
2498
|
};
|
|
2159
2499
|
|
|
2500
|
+
/**
|
|
2501
|
+
* A select whose clear-trigger is rendered (#280) — anchored on the root, so
|
|
2502
|
+
* a part inside the trigger can make room for the button laid over it.
|
|
2503
|
+
*/
|
|
2504
|
+
const SELECT_CLEARABLE = '[data-scope="select"][data-part="root"]:has(> [data-scope="select"][data-part="clear-trigger"]) &';
|
|
2505
|
+
|
|
2160
2506
|
export const select: RecipeInput = {
|
|
2161
2507
|
component: 'select',
|
|
2162
2508
|
// Accent as text/border ink only — daisy's highlighted item is a neutral
|
|
2163
2509
|
// base-200 wash, so no `-on-accent` content colour is consumed here.
|
|
2164
2510
|
tokens: { '--select-accent': 'var(--color-primary)' },
|
|
2165
2511
|
parts: {
|
|
2512
|
+
// Positioned: the clear-trigger sits over the trigger's inline end.
|
|
2166
2513
|
root: {
|
|
2167
|
-
base: { display: 'inline-flex', flexDirection: 'column' },
|
|
2514
|
+
base: { display: 'inline-flex', flexDirection: 'column', position: 'relative' },
|
|
2168
2515
|
},
|
|
2169
2516
|
trigger: {
|
|
2170
2517
|
base: {
|
|
@@ -2177,6 +2524,8 @@ export const select: RecipeInput = {
|
|
|
2177
2524
|
},
|
|
2178
2525
|
states: {
|
|
2179
2526
|
hover: { borderColor: 'var(--color-base-content)' },
|
|
2527
|
+
// Readonly answers to nothing, so it does not invite a click.
|
|
2528
|
+
readonly: { cursor: 'default' },
|
|
2180
2529
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
2181
2530
|
open: { borderColor: 'var(--select-accent)' },
|
|
2182
2531
|
closed: {},
|
|
@@ -2213,6 +2562,41 @@ export const select: RecipeInput = {
|
|
|
2213
2562
|
indicator: {
|
|
2214
2563
|
base: { opacity: '0.6', transition: 'transform var(--duration-normal) var(--ease-standard)' },
|
|
2215
2564
|
states: { open: { transform: 'rotate(180deg)' }, closed: {} },
|
|
2565
|
+
// Room for the clear-trigger laid over the field (#280).
|
|
2566
|
+
selectors: { [SELECT_CLEARABLE]: { marginInlineStart: 'calc(var(--space-2xl) + var(--space-sm))' } },
|
|
2567
|
+
},
|
|
2568
|
+
// Clears the selection (#280): daisy's ghost-btn over the field's
|
|
2569
|
+
// inline end, just before the chevron — muted like it, the base-200
|
|
2570
|
+
// wash on hover, the base-content ring on focus.
|
|
2571
|
+
'clear-trigger': {
|
|
2572
|
+
base: {
|
|
2573
|
+
appearance: 'none',
|
|
2574
|
+
position: 'absolute',
|
|
2575
|
+
insetBlock: '0',
|
|
2576
|
+
// Centred in the field at WCAG 2.5.8's 24px target, shrinking
|
|
2577
|
+
// only with a field too short to hold it.
|
|
2578
|
+
marginBlock: 'auto',
|
|
2579
|
+
blockSize: 'min(1.5rem, 100%)',
|
|
2580
|
+
minInlineSize: '1.5rem',
|
|
2581
|
+
insetInlineEnd: 'calc(var(--size-field) * 4 + 1em + var(--space-xs))',
|
|
2582
|
+
display: 'inline-flex',
|
|
2583
|
+
alignItems: 'center',
|
|
2584
|
+
justifyContent: 'center',
|
|
2585
|
+
padding: '0 var(--space-xs)',
|
|
2586
|
+
border: 'none',
|
|
2587
|
+
background: 'transparent',
|
|
2588
|
+
color: 'color-mix(in oklab, var(--color-base-content) 70%, transparent)',
|
|
2589
|
+
font: 'inherit',
|
|
2590
|
+
fontSize: 'var(--text-sm)',
|
|
2591
|
+
lineHeight: 'var(--leading-none)',
|
|
2592
|
+
borderRadius: 'var(--radius-field)',
|
|
2593
|
+
cursor: 'pointer',
|
|
2594
|
+
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
2595
|
+
},
|
|
2596
|
+
states: {
|
|
2597
|
+
hover: { background: 'var(--color-base-200)', color: 'var(--color-base-content)' },
|
|
2598
|
+
...focusRing,
|
|
2599
|
+
},
|
|
2216
2600
|
},
|
|
2217
2601
|
popup: withPresence(popupPresence('translateY(-4px)'), {
|
|
2218
2602
|
base: { ...floatingPanel, padding: 'var(--space-md)', minWidth: '13rem' },
|
|
@@ -2228,6 +2612,16 @@ export const select: RecipeInput = {
|
|
|
2228
2612
|
opacity: '0.6',
|
|
2229
2613
|
},
|
|
2230
2614
|
},
|
|
2615
|
+
// A windowed group's heading (#127): the same overline as the label,
|
|
2616
|
+
// as a row of the flat window instead of inside a `group`.
|
|
2617
|
+
'group-heading': {
|
|
2618
|
+
base: {
|
|
2619
|
+
padding: 'var(--space-md) var(--space-lg) var(--space-xs)',
|
|
2620
|
+
fontSize: 'var(--text-xs)',
|
|
2621
|
+
fontWeight: 'var(--weight-bold)',
|
|
2622
|
+
opacity: '0.6',
|
|
2623
|
+
},
|
|
2624
|
+
},
|
|
2231
2625
|
item: {
|
|
2232
2626
|
base: {
|
|
2233
2627
|
display: 'flex',
|
|
@@ -2251,6 +2645,14 @@ export const select: RecipeInput = {
|
|
|
2251
2645
|
base: { fontSize: 'var(--text-xs)', color: 'var(--select-accent)' },
|
|
2252
2646
|
states: { selected: {} },
|
|
2253
2647
|
},
|
|
2648
|
+
// The menu's rule between runs of options (#280).
|
|
2649
|
+
separator: {
|
|
2650
|
+
base: {
|
|
2651
|
+
height: 'var(--border)',
|
|
2652
|
+
margin: 'var(--space-sm) var(--space-md)',
|
|
2653
|
+
background: 'var(--color-base-300)',
|
|
2654
|
+
},
|
|
2655
|
+
},
|
|
2254
2656
|
},
|
|
2255
2657
|
variants: {
|
|
2256
2658
|
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
@@ -2273,8 +2675,12 @@ export const select: RecipeInput = {
|
|
|
2273
2675
|
// The trigger ramp restates `md` too (unlike `btnSizes`), so its lynx
|
|
2274
2676
|
// counterpart does as well.
|
|
2275
2677
|
targets: {
|
|
2678
|
+
web: { parts: { popup: { base: anchoredListbox } } },
|
|
2276
2679
|
lynx: {
|
|
2277
|
-
parts: {
|
|
2680
|
+
parts: {
|
|
2681
|
+
trigger: { base: lynxBtnPad(4) },
|
|
2682
|
+
'clear-trigger': { base: { top: 'var(--space-xs)', bottom: 'var(--space-xs)', right: 'calc(var(--size-field) * 4 + 1em + var(--space-xs))', minWidth: '1.5rem' } },
|
|
2683
|
+
},
|
|
2278
2684
|
variants: {
|
|
2279
2685
|
size: { ...lynxBtnSizes('trigger'), md: { trigger: { base: lynxBtnPad(4) } } },
|
|
2280
2686
|
},
|
|
@@ -2906,6 +3312,23 @@ export const combobox: RecipeInput = {
|
|
|
2906
3312
|
disabled: { cursor: 'not-allowed' },
|
|
2907
3313
|
},
|
|
2908
3314
|
},
|
|
3315
|
+
// Clears the value and the text (#280): the chevron's ghost, one
|
|
3316
|
+
// step before it — muted, full ink on hover.
|
|
3317
|
+
'clear-trigger': {
|
|
3318
|
+
base: {
|
|
3319
|
+
appearance: 'none',
|
|
3320
|
+
border: 'none',
|
|
3321
|
+
background: 'transparent',
|
|
3322
|
+
color: 'inherit',
|
|
3323
|
+
font: 'inherit',
|
|
3324
|
+
opacity: '0.6',
|
|
3325
|
+
padding: '0 var(--space-xs)',
|
|
3326
|
+
lineHeight: 'var(--leading-none)',
|
|
3327
|
+
cursor: 'pointer',
|
|
3328
|
+
transition: 'opacity var(--duration-fast) var(--ease-standard)',
|
|
3329
|
+
},
|
|
3330
|
+
states: { hover: { opacity: '1' } },
|
|
3331
|
+
},
|
|
2909
3332
|
popup: withPresence(popupPresence('translateY(-4px)'), {
|
|
2910
3333
|
base: { ...floatingPanel, padding: 'var(--space-md)', minWidth: '13rem' },
|
|
2911
3334
|
states: { open: {}, closed: {} },
|
|
@@ -2920,6 +3343,16 @@ export const combobox: RecipeInput = {
|
|
|
2920
3343
|
opacity: '0.6',
|
|
2921
3344
|
},
|
|
2922
3345
|
},
|
|
3346
|
+
// A windowed group's heading (#127): the same overline as the label,
|
|
3347
|
+
// as a row of the flat window instead of inside a `group`.
|
|
3348
|
+
'group-heading': {
|
|
3349
|
+
base: {
|
|
3350
|
+
padding: 'var(--space-md) var(--space-lg) var(--space-xs)',
|
|
3351
|
+
fontSize: 'var(--text-xs)',
|
|
3352
|
+
fontWeight: 'var(--weight-bold)',
|
|
3353
|
+
opacity: '0.6',
|
|
3354
|
+
},
|
|
3355
|
+
},
|
|
2923
3356
|
item: {
|
|
2924
3357
|
base: {
|
|
2925
3358
|
display: 'flex',
|
|
@@ -2951,6 +3384,23 @@ export const combobox: RecipeInput = {
|
|
|
2951
3384
|
opacity: '0.6',
|
|
2952
3385
|
},
|
|
2953
3386
|
},
|
|
3387
|
+
// The list still arriving (#280): the empty row's muted grammar.
|
|
3388
|
+
loading: {
|
|
3389
|
+
base: {
|
|
3390
|
+
padding: 'var(--space-lg)',
|
|
3391
|
+
fontSize: 'var(--text-sm)',
|
|
3392
|
+
textAlign: 'center',
|
|
3393
|
+
opacity: '0.6',
|
|
3394
|
+
},
|
|
3395
|
+
},
|
|
3396
|
+
// The menu's rule between runs of options (#280).
|
|
3397
|
+
separator: {
|
|
3398
|
+
base: {
|
|
3399
|
+
height: 'var(--border)',
|
|
3400
|
+
margin: 'var(--space-sm) var(--space-md)',
|
|
3401
|
+
background: 'var(--color-base-300)',
|
|
3402
|
+
},
|
|
3403
|
+
},
|
|
2954
3404
|
},
|
|
2955
3405
|
variants: {
|
|
2956
3406
|
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
@@ -2973,6 +3423,9 @@ export const combobox: RecipeInput = {
|
|
|
2973
3423
|
input: ['focus-visible'],
|
|
2974
3424
|
trigger: ['focus-visible'],
|
|
2975
3425
|
},
|
|
3426
|
+
targets: {
|
|
3427
|
+
web: { parts: { popup: { base: anchoredListbox } } },
|
|
3428
|
+
},
|
|
2976
3429
|
};
|
|
2977
3430
|
|
|
2978
3431
|
// daisy "btn" that stays pressed: the off state is the neutral btn, the on
|
|
@@ -3586,7 +4039,9 @@ export const treeView: RecipeInput = {
|
|
|
3586
4039
|
opacity: '0.6',
|
|
3587
4040
|
transition: 'transform var(--duration-fast) var(--ease-standard)',
|
|
3588
4041
|
},
|
|
3589
|
-
|
|
4042
|
+
// Loading: the chevron halts at daisy's own collapse-arrow angle —
|
|
4043
|
+
// static (no loop), ink unchanged, so contrast is untouched.
|
|
4044
|
+
states: { open: { transform: 'rotate(90deg)' }, closed: {}, loading: { transform: 'rotate(45deg)' } },
|
|
3590
4045
|
// The glyph is element text the runtime renders (`TreeView.tsx`), not
|
|
3591
4046
|
// `content:`, so the `:dir(rtl)` swap the submenu chevron uses is not
|
|
3592
4047
|
// available here — a mirror is its equivalent. `scale` composes
|
|
@@ -3605,6 +4060,9 @@ export const treeView: RecipeInput = {
|
|
|
3605
4060
|
states: { open: {}, closed: {} },
|
|
3606
4061
|
},
|
|
3607
4062
|
},
|
|
4063
|
+
// Open-and-loading content lays out exactly as open content does — the
|
|
4064
|
+
// indicator and the treeitem's aria-busy carry the state.
|
|
4065
|
+
sameAs: { 'branch-content': { loading: 'open' } },
|
|
3608
4066
|
variants: {
|
|
3609
4067
|
// A tree colours one thing: the selected row. Everything else is
|
|
3610
4068
|
// structure, and tinting it would fight the content.
|
|
@@ -3634,6 +4092,33 @@ export const treeView: RecipeInput = {
|
|
|
3634
4092
|
},
|
|
3635
4093
|
};
|
|
3636
4094
|
|
|
4095
|
+
/**
|
|
4096
|
+
* The affordances inside a text field (#281): daisy's in-field label ink and
|
|
4097
|
+
* the ghost circle button, both following the size step.
|
|
4098
|
+
*/
|
|
4099
|
+
const affixInk = 'color-mix(in oklab, var(--color-base-content) 70%, transparent)';
|
|
4100
|
+
const fieldButton: NonNullable<PartStyles['base']> = {
|
|
4101
|
+
appearance: 'none',
|
|
4102
|
+
border: 'none',
|
|
4103
|
+
background: 'transparent',
|
|
4104
|
+
color: affixInk,
|
|
4105
|
+
borderRadius: '9999px',
|
|
4106
|
+
alignSelf: 'center',
|
|
4107
|
+
flex: 'none',
|
|
4108
|
+
order: '1',
|
|
4109
|
+
marginInlineEnd: 'var(--space-xs)',
|
|
4110
|
+
padding: 'var(--space-2xs) var(--space-xs)',
|
|
4111
|
+
fontSize: 'var(--text-sm)',
|
|
4112
|
+
fontWeight: 'var(--weight-semibold)',
|
|
4113
|
+
lineHeight: 'var(--leading-none)',
|
|
4114
|
+
cursor: 'pointer',
|
|
4115
|
+
};
|
|
4116
|
+
const affixSize = (fontSize: string) => ({
|
|
4117
|
+
adornment: { base: { fontSize } },
|
|
4118
|
+
'clear-trigger': { base: { fontSize } },
|
|
4119
|
+
'visibility-trigger': { base: { fontSize } },
|
|
4120
|
+
});
|
|
4121
|
+
|
|
3637
4122
|
/**
|
|
3638
4123
|
* daisy "input" flavor: the shared field box with nothing in it but the text.
|
|
3639
4124
|
* Same `fieldControl` metrics as select's trigger, combobox's control and the
|
|
@@ -3660,6 +4145,7 @@ export const input: RecipeInput = {
|
|
|
3660
4145
|
base: {
|
|
3661
4146
|
display: 'inline-flex',
|
|
3662
4147
|
alignItems: 'stretch',
|
|
4148
|
+
gap: 'var(--space-2xs)',
|
|
3663
4149
|
...fieldControl,
|
|
3664
4150
|
overflow: 'hidden',
|
|
3665
4151
|
transition: 'border-color var(--duration-fast) var(--ease-standard)',
|
|
@@ -3696,6 +4182,43 @@ export const input: RecipeInput = {
|
|
|
3696
4182
|
'&::placeholder': { color: 'color-mix(in oklab, var(--color-base-content) 60%, transparent)' },
|
|
3697
4183
|
},
|
|
3698
4184
|
},
|
|
4185
|
+
// daisy's `label` inside `.input`: a quiet prefix/suffix at one edge,
|
|
4186
|
+
// ordered logically so it flips with the reading direction.
|
|
4187
|
+
adornment: {
|
|
4188
|
+
base: {
|
|
4189
|
+
display: 'inline-flex',
|
|
4190
|
+
alignItems: 'center',
|
|
4191
|
+
flex: 'none',
|
|
4192
|
+
color: affixInk,
|
|
4193
|
+
fontSize: 'var(--text-sm)',
|
|
4194
|
+
lineHeight: 'var(--leading-none)',
|
|
4195
|
+
},
|
|
4196
|
+
states: { disabled: {} },
|
|
4197
|
+
selectors: {
|
|
4198
|
+
'&[data-placement="start"]': { order: '-1', paddingInlineStart: 'var(--space-md)' },
|
|
4199
|
+
'&[data-placement="end"]': { order: '1', paddingInlineEnd: 'var(--space-md)' },
|
|
4200
|
+
},
|
|
4201
|
+
},
|
|
4202
|
+
// `btn btn-ghost btn-circle btn-xs` inside the field.
|
|
4203
|
+
'clear-trigger': {
|
|
4204
|
+
base: fieldButton,
|
|
4205
|
+
states: {
|
|
4206
|
+
hover: { color: 'var(--color-base-content)', background: 'var(--color-base-200)' },
|
|
4207
|
+
disabled: { cursor: 'not-allowed' },
|
|
4208
|
+
...focusRing,
|
|
4209
|
+
},
|
|
4210
|
+
},
|
|
4211
|
+
// Shown: the ghost button's active fill and full ink.
|
|
4212
|
+
'visibility-trigger': {
|
|
4213
|
+
base: fieldButton,
|
|
4214
|
+
states: {
|
|
4215
|
+
on: { color: 'var(--color-base-content)', background: 'var(--color-base-200)' },
|
|
4216
|
+
off: {},
|
|
4217
|
+
hover: { color: 'var(--color-base-content)', background: 'var(--color-base-200)' },
|
|
4218
|
+
disabled: { cursor: 'not-allowed' },
|
|
4219
|
+
...focusRing,
|
|
4220
|
+
},
|
|
4221
|
+
},
|
|
3699
4222
|
},
|
|
3700
4223
|
// The visible ring lives on `control`; the input delegates.
|
|
3701
4224
|
skipStates: { input: ['focus-visible'] },
|
|
@@ -3708,13 +4231,13 @@ export const input: RecipeInput = {
|
|
|
3708
4231
|
// The control carries the height off the shared field ramp; the text
|
|
3709
4232
|
// follows so the box stays proportional.
|
|
3710
4233
|
size: {
|
|
3711
|
-
xs: { control: { base: { height: fieldHeight('xs') } }, input: { base: { fontSize: 'var(--text-xs)', padding: 'var(--space-2xs) var(--space-xs)' } } },
|
|
3712
|
-
sm: { control: { base: { height: fieldHeight('sm') } }, input: { base: { fontSize: 'var(--text-xs)', padding: 'var(--space-xs) var(--space-sm)' } } },
|
|
4234
|
+
xs: { control: { base: { height: fieldHeight('xs') } }, input: { base: { fontSize: 'var(--text-xs)', padding: 'var(--space-2xs) var(--space-xs)' } }, ...affixSize('var(--text-xs)') },
|
|
4235
|
+
sm: { control: { base: { height: fieldHeight('sm') } }, input: { base: { fontSize: 'var(--text-xs)', padding: 'var(--space-xs) var(--space-sm)' } }, ...affixSize('var(--text-xs)') },
|
|
3713
4236
|
// `md` is the un-attributed render: the base already IS the
|
|
3714
4237
|
// middle step.
|
|
3715
4238
|
md: {},
|
|
3716
|
-
lg: { control: { base: { height: fieldHeight('lg') } }, input: { base: { fontSize: 'var(--text-md)', padding: 'var(--space-md) var(--space-lg)' } } },
|
|
3717
|
-
xl: { control: { base: { height: fieldHeight('xl') } }, input: { base: { fontSize: 'var(--text-lg)', padding: 'var(--space-lg) var(--space-xl)' } } },
|
|
4239
|
+
lg: { control: { base: { height: fieldHeight('lg') } }, input: { base: { fontSize: 'var(--text-md)', padding: 'var(--space-md) var(--space-lg)' } }, ...affixSize('var(--text-md)') },
|
|
4240
|
+
xl: { control: { base: { height: fieldHeight('xl') } }, input: { base: { fontSize: 'var(--text-lg)', padding: 'var(--space-lg) var(--space-xl)' } }, ...affixSize('var(--text-lg)') },
|
|
3718
4241
|
},
|
|
3719
4242
|
},
|
|
3720
4243
|
};
|
|
@@ -4442,8 +4965,17 @@ export const stats: RecipeInput = {
|
|
|
4442
4965
|
tokens: { '--stats-accent': 'var(--color-base-content)' },
|
|
4443
4966
|
parts: {
|
|
4444
4967
|
root: {
|
|
4968
|
+
// The row's own scroll box (#43): a value never wraps or shrinks
|
|
4969
|
+
// (`nowrap` below), so a row too wide for its container scrolls
|
|
4970
|
+
// inside the root — daisyUI's own `.stats` behaviour, and Table's
|
|
4971
|
+
// and Pagination's answer — instead of pushing the page sideways.
|
|
4972
|
+
// Items keep their flex automatic minimum — their min-content
|
|
4973
|
+
// width — so the overflow lands here rather than in the next
|
|
4974
|
+
// item. No ring room: a stat is not focusable, and anything a
|
|
4975
|
+
// consumer puts in one sits inside the item's own padding.
|
|
4445
4976
|
base: {
|
|
4446
4977
|
display: 'flex',
|
|
4978
|
+
overflowX: 'auto',
|
|
4447
4979
|
border: 'var(--border) solid var(--color-base-200)',
|
|
4448
4980
|
borderRadius: 'var(--radius-box)',
|
|
4449
4981
|
background: 'var(--color-base-100)',
|
|
@@ -4480,6 +5012,8 @@ export const stats: RecipeInput = {
|
|
|
4480
5012
|
value: {
|
|
4481
5013
|
base: {
|
|
4482
5014
|
gridColumn: '1',
|
|
5015
|
+
// A figure is read whole: `$12 930` never breaks at its space.
|
|
5016
|
+
whiteSpace: 'nowrap',
|
|
4483
5017
|
fontSize: 'var(--text-2xl)',
|
|
4484
5018
|
fontWeight: 'var(--weight-bold)',
|
|
4485
5019
|
fontVariantNumeric: 'tabular-nums',
|
|
@@ -4502,8 +5036,19 @@ export const stats: RecipeInput = {
|
|
|
4502
5036
|
},
|
|
4503
5037
|
},
|
|
4504
5038
|
variants: {
|
|
4505
|
-
|
|
4506
|
-
|
|
5039
|
+
// Keyed on the ITEM, not the root (#161): the item re-carries
|
|
5040
|
+
// `color`, so a stat's own value outranks the row's, and the value
|
|
5041
|
+
// inside it inherits whichever won. A root colour still reaches
|
|
5042
|
+
// every item through the carrier's donut.
|
|
5043
|
+
//
|
|
5044
|
+
// The value is TEXT on the page, so the raw role is not always a
|
|
5045
|
+
// readable ink: measured by the text matrix once the item re-carried
|
|
5046
|
+
// the axis (#161), light warning is 1.76:1 and dark neutral 1.26:1
|
|
5047
|
+
// on base-100. `roleInk` is the shared answer for a role used as ink
|
|
5048
|
+
// (a per-role share of the role over base-content, which flips with
|
|
5049
|
+
// the scheme), the same one the field and button text use.
|
|
5050
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { item: { base: {
|
|
5051
|
+
'--stats-accent': roleInk(c),
|
|
4507
5052
|
} } }])),
|
|
4508
5053
|
size: {
|
|
4509
5054
|
xs: { value: { base: { fontSize: 'var(--text-lg)' } } },
|
|
@@ -4551,7 +5096,11 @@ export const timeline: RecipeInput = {
|
|
|
4551
5096
|
padding: '0',
|
|
4552
5097
|
},
|
|
4553
5098
|
selectors: {
|
|
4554
|
-
|
|
5099
|
+
// A horizontal timeline is a row of steps whose width follows
|
|
5100
|
+
// the step count, not the container: at phone width it scrolls
|
|
5101
|
+
// inside its own box, the answer Table and Pagination give
|
|
5102
|
+
// (#45), rather than widening the page.
|
|
5103
|
+
'&[data-orientation="horizontal"]': { flexDirection: 'row', overflowX: 'auto' },
|
|
4555
5104
|
},
|
|
4556
5105
|
},
|
|
4557
5106
|
/**
|
|
@@ -5252,9 +5801,27 @@ export const pagination: RecipeInput = {
|
|
|
5252
5801
|
'--pg-accent-content': 'var(--color-primary-content)',
|
|
5253
5802
|
'--pg-size': 'calc(var(--size-field) * 10)',
|
|
5254
5803
|
'--pg-font': 'var(--text-sm)',
|
|
5804
|
+
// How far the focus ring reaches outside a control: the root's
|
|
5805
|
+
// scroll box pads by it so the ring is never clipped (#44).
|
|
5806
|
+
'--pg-ring-room': '4px',
|
|
5255
5807
|
},
|
|
5256
5808
|
parts: {
|
|
5257
|
-
root: {
|
|
5809
|
+
root: {
|
|
5810
|
+
// The row's own scroll box (#44): the window is constant-width by
|
|
5811
|
+
// design, so a narrow container scrolls it rather than clipping
|
|
5812
|
+
// it — Table's answer. The padding is room for the focus ring's
|
|
5813
|
+
// 2px + 2px offset, which the scroll box would otherwise clip at
|
|
5814
|
+
// the row's edges; the matching scroll padding keeps it clear
|
|
5815
|
+
// when focus scrolls a page into view.
|
|
5816
|
+
base: {
|
|
5817
|
+
display: 'flex',
|
|
5818
|
+
alignItems: 'center',
|
|
5819
|
+
gap: 'var(--space-2xs)',
|
|
5820
|
+
overflowX: 'auto',
|
|
5821
|
+
padding: 'var(--pg-ring-room)',
|
|
5822
|
+
scrollPaddingInline: 'var(--pg-ring-room)',
|
|
5823
|
+
},
|
|
5824
|
+
},
|
|
5258
5825
|
item: {
|
|
5259
5826
|
base: {
|
|
5260
5827
|
display: 'inline-flex',
|
|
@@ -5748,6 +6315,11 @@ export const table: RecipeInput = {
|
|
|
5748
6315
|
borderRadius: 'var(--radius-box)',
|
|
5749
6316
|
background: 'var(--color-base-100)',
|
|
5750
6317
|
},
|
|
6318
|
+
// The scroll box is a keyboard stop (#270): daisy's ink ring,
|
|
6319
|
+
// drawn inside the box (negative offset) — the root is as wide
|
|
6320
|
+
// as its column, so an outward ring would be clipped by
|
|
6321
|
+
// whatever scrolls around it.
|
|
6322
|
+
states: { 'focus-visible': { outline: '2px solid var(--color-base-content)', outlineOffset: '-2px' } },
|
|
5751
6323
|
// Stacked (Table.Root stack, #55): zero lays each row out as a block
|
|
5752
6324
|
// below the table's breakpoint; the card it becomes is drawn here.
|
|
5753
6325
|
at: tableStackAt(tokens, 'root', { border: '0', borderRadius: '0', background: 'transparent' }),
|
|
@@ -5938,7 +6510,15 @@ export const fileUpload: RecipeInput = {
|
|
|
5938
6510
|
borderRadius: 'var(--radius-field)',
|
|
5939
6511
|
background: 'var(--color-base-100)',
|
|
5940
6512
|
},
|
|
5941
|
-
states: {
|
|
6513
|
+
states: {
|
|
6514
|
+
disabled: { opacity: 'var(--disabled-opacity)' },
|
|
6515
|
+
// A rejected file the app renders through Item: daisy's
|
|
6516
|
+
// error border over a faint error wash.
|
|
6517
|
+
invalid: {
|
|
6518
|
+
borderColor: 'var(--color-error)',
|
|
6519
|
+
background: 'color-mix(in oklab, var(--color-error) 8%, var(--color-base-100))',
|
|
6520
|
+
},
|
|
6521
|
+
},
|
|
5942
6522
|
},
|
|
5943
6523
|
'item-name': {
|
|
5944
6524
|
base: {
|
|
@@ -5974,6 +6554,26 @@ export const fileUpload: RecipeInput = {
|
|
|
5974
6554
|
...focusRing,
|
|
5975
6555
|
},
|
|
5976
6556
|
},
|
|
6557
|
+
// daisy's btn-ghost btn-xs: the remove button's pill, at text size.
|
|
6558
|
+
'clear-trigger': {
|
|
6559
|
+
base: {
|
|
6560
|
+
appearance: 'none',
|
|
6561
|
+
border: 'none',
|
|
6562
|
+
background: 'transparent',
|
|
6563
|
+
color: 'inherit',
|
|
6564
|
+
borderRadius: '9999px',
|
|
6565
|
+
padding: 'var(--space-2xs) var(--space-sm)',
|
|
6566
|
+
fontSize: 'var(--fu-font)',
|
|
6567
|
+
fontWeight: 'var(--weight-semibold)',
|
|
6568
|
+
lineHeight: 'var(--leading-none)',
|
|
6569
|
+
cursor: 'pointer',
|
|
6570
|
+
},
|
|
6571
|
+
states: {
|
|
6572
|
+
hover: { background: 'var(--color-base-200)' },
|
|
6573
|
+
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
6574
|
+
...focusRing,
|
|
6575
|
+
},
|
|
6576
|
+
},
|
|
5977
6577
|
},
|
|
5978
6578
|
variants: {
|
|
5979
6579
|
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
@@ -6318,6 +6918,9 @@ export const diff: RecipeInput = {
|
|
|
6318
6918
|
background: 'var(--color-base-100)',
|
|
6319
6919
|
borderRadius: 'var(--radius-box)',
|
|
6320
6920
|
},
|
|
6921
|
+
// The root holds the images, which are content: disabled is the
|
|
6922
|
+
// handle's to show (#272).
|
|
6923
|
+
states: { disabled: {} },
|
|
6321
6924
|
},
|
|
6322
6925
|
before: {
|
|
6323
6926
|
base: { gridArea: '1 / 1', minWidth: '0' },
|
|
@@ -6347,6 +6950,9 @@ export const diff: RecipeInput = {
|
|
|
6347
6950
|
},
|
|
6348
6951
|
states: {
|
|
6349
6952
|
...focusRing,
|
|
6953
|
+
// daisy's disabled look (#272): the uniform fade, and no grab
|
|
6954
|
+
// cursor — the images are content and stay unfaded.
|
|
6955
|
+
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
6350
6956
|
pressed: { '--diff-accent': 'color-mix(in oklab, var(--color-base-content) 85%, var(--color-base-100))' },
|
|
6351
6957
|
},
|
|
6352
6958
|
selectors: {
|
|
@@ -6421,7 +7027,7 @@ export const diff: RecipeInput = {
|
|
|
6421
7027
|
|
|
6422
7028
|
export const recipes: RecipeInput[] = [
|
|
6423
7029
|
tabs, collapsible, switchRecipe, dialog, popover, tooltip, menu,
|
|
6424
|
-
field, checkbox, radioGroup, progress, slider, accordion, select, button, avatar, toast, combobox,
|
|
7030
|
+
field, checkbox, checkboxGroup, radioGroup, progress, slider, accordion, select, button, avatar, toast, combobox,
|
|
6425
7031
|
toggle, toggleGroup, numberInput, ratingGroup, treeView, input, textarea,
|
|
6426
7032
|
card, alert, emptyState, badge, divider, skeleton, spinner,
|
|
6427
7033
|
kbd, status, indicator, stats, timeline, chat, radialProgress, join,
|