@sigx/zero-daisyui 0.5.0 → 0.7.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 +62 -3
- package/dist/audit.json +8018 -2748
- package/dist/components.d.ts +23 -2
- package/dist/components.js +2 -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 +36 -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/fieldset.css +91 -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 +68 -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 +74 -0
- package/dist/css/components/progress.css +2 -4
- 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 +54 -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 +77 -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 +67 -0
- package/dist/css/components/tree-view.css +6 -0
- package/dist/css/index.css +1053 -70
- package/dist/css/tokens.css +13 -13
- package/dist/lynx/components/accordion.css +9 -0
- package/dist/lynx/components/alert.css +20 -0
- package/dist/lynx/components/button.css +58 -1
- package/dist/lynx/components/checkbox-group.css +209 -0
- package/dist/lynx/components/checkbox.css +8 -0
- package/dist/lynx/components/combobox.css +46 -0
- package/dist/lynx/components/dialog.css +19 -0
- package/dist/lynx/components/diff.css +5 -0
- package/dist/lynx/components/drawer.css +4 -0
- package/dist/lynx/components/fieldset.css +219 -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/popover.css +37 -0
- package/dist/lynx/components/progress.css +4 -24
- package/dist/lynx/components/radio-group.css +8 -0
- package/dist/lynx/components/rating-group.css +3 -3
- package/dist/lynx/components/select.css +70 -1
- package/dist/lynx/components/slider.css +391 -28
- package/dist/lynx/components/stats.css +146 -16
- package/dist/lynx/components/steps.css +4 -0
- package/dist/lynx/components/switch.css +6 -0
- package/dist/lynx/components/table.css +57 -0
- package/dist/lynx/components/tabs.css +115 -93
- package/dist/lynx/components/timeline.css +84 -1
- package/dist/lynx/components/toast.css +41 -6
- package/dist/lynx/components/toggle.css +5 -0
- package/dist/lynx/components/tree-view.css +4 -0
- package/dist/lynx/index.css +1912 -262
- package/dist/lynx/manifest.json +67 -5
- package/dist/lynx/tokens.css +4 -4
- package/dist/manifest.json +65 -3
- package/dist/recipes.d.ts +17 -0
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +1141 -110
- package/dist/recipes.js.map +1 -1
- package/dist/register.d.ts +18 -0
- package/dist/report.json +902 -270
- 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 +1156 -111
- package/src/tokens.ts +18 -4
package/dist/recipes.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { axisRoles, tableStackAt } from '@sigx/zero-kit/define';
|
|
1
|
+
import { axisRoles, popupArrow, popupArrowHost, tableStackAt } from '@sigx/zero-kit/define';
|
|
2
2
|
import { roles, tokens } from './tokens.js';
|
|
3
3
|
/**
|
|
4
4
|
* Every role a consumer can pass as `color`, derived from the declaration
|
|
@@ -140,13 +140,45 @@ const disclosurePresence = {
|
|
|
140
140
|
'&::details-content': {
|
|
141
141
|
blockSize: '0',
|
|
142
142
|
overflow: 'hidden',
|
|
143
|
+
},
|
|
144
|
+
// Open only: a close removes `open`, which hides the content before
|
|
145
|
+
// any transition could run — the panel animates that half (#276).
|
|
146
|
+
'&[open]::details-content': {
|
|
147
|
+
blockSize: 'auto',
|
|
143
148
|
transition: 'block-size var(--duration-normal) var(--ease-standard), '
|
|
144
149
|
+ 'content-visibility var(--duration-normal) allow-discrete',
|
|
145
150
|
},
|
|
146
|
-
'&[open]::details-content': { blockSize: 'auto' },
|
|
147
151
|
},
|
|
148
|
-
at: { 'reduced-motion': { selectors: { '
|
|
152
|
+
at: { 'reduced-motion': { selectors: { '&[open]::details-content': { transition: 'none' } } } },
|
|
149
153
|
};
|
|
154
|
+
/**
|
|
155
|
+
* The panel's close (#276). Removing `open` hides a `<details>` at once, so
|
|
156
|
+
* the close cannot live on `::details-content` the way the open does: zero
|
|
157
|
+
* flips the panel to `closed` first and holds the element open until the
|
|
158
|
+
* panel's own animation has played. The runtime publishes the panel's
|
|
159
|
+
* measured height as `--<scope>-panel-height`, which is the one value this
|
|
160
|
+
* needs — `auto` is not an endpoint any engine but Chromium can animate.
|
|
161
|
+
* `border-box` so the measured height (content + padding) is the whole box.
|
|
162
|
+
* Web-only: the property is written by the DOM runtime (`RUNTIME_PROPERTIES`).
|
|
163
|
+
*/
|
|
164
|
+
const disclosureExit = (scope) => ({
|
|
165
|
+
web: {
|
|
166
|
+
parts: {
|
|
167
|
+
panel: {
|
|
168
|
+
states: {
|
|
169
|
+
closed: {
|
|
170
|
+
boxSizing: 'border-box',
|
|
171
|
+
animation: `${scope}-panel-exit var(--duration-normal) var(--ease-standard)`,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
at: { 'reduced-motion': { states: { closed: { animation: 'none' } } } },
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
keyframes: {
|
|
178
|
+
[`${scope}-panel-exit`]: `from { block-size: var(--${scope}-panel-height); } to { block-size: 0; padding-block: 0; }`,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
});
|
|
150
182
|
/**
|
|
151
183
|
* Merge presence into a part's own styles per KEY, not per block: a recipe
|
|
152
184
|
* that already writes `states: { open: {} }` — the "deliberately unstyled"
|
|
@@ -191,14 +223,33 @@ const withPresence = (presence, styles) => ({
|
|
|
191
223
|
*/
|
|
192
224
|
export const tabs = {
|
|
193
225
|
component: 'tabs',
|
|
226
|
+
tokens: {
|
|
227
|
+
// How far the focus ring reaches outside a tab (2px + 2px offset):
|
|
228
|
+
// the list's scroll box pads by it so the ring is never clipped (#45).
|
|
229
|
+
'--tabs-ring-room': '4px',
|
|
230
|
+
},
|
|
194
231
|
parts: {
|
|
195
232
|
root: {
|
|
196
233
|
base: { display: 'flex', flexDirection: 'column', gap: 'var(--space-lg)' },
|
|
197
234
|
},
|
|
198
235
|
list: {
|
|
236
|
+
// The row's own scroll box (#45), Pagination's and Stats' answer:
|
|
237
|
+
// a row of labels wider than its column (a phone, a wide fallback
|
|
238
|
+
// face) scrolls instead of pushing the page sideways.
|
|
239
|
+
// `max-inline-size` (border-box, so it includes the padding)
|
|
240
|
+
// gives the shrink-to-fit list a width to scroll in. The padding is room for the focus ring, which the
|
|
241
|
+
// scroll box would otherwise clip at the row's edges; the
|
|
242
|
+
// matching scroll padding keeps it clear when focus scrolls a
|
|
243
|
+
// tab into view. The box variant's own padding (space-xs, the
|
|
244
|
+
// same 4px) replaces it there.
|
|
199
245
|
base: {
|
|
200
246
|
display: 'inline-flex',
|
|
201
247
|
alignSelf: 'flex-start',
|
|
248
|
+
boxSizing: 'border-box',
|
|
249
|
+
maxInlineSize: '100%',
|
|
250
|
+
overflowX: 'auto',
|
|
251
|
+
padding: 'var(--tabs-ring-room)',
|
|
252
|
+
scrollPaddingInline: 'var(--tabs-ring-room)',
|
|
202
253
|
},
|
|
203
254
|
},
|
|
204
255
|
tab: {
|
|
@@ -236,6 +287,12 @@ export const tabs = {
|
|
|
236
287
|
...focusRing,
|
|
237
288
|
},
|
|
238
289
|
},
|
|
290
|
+
// daisy draws each flavor's active mark on the tab itself (the
|
|
291
|
+
// underline, the lift, the pill) and none of them slides, so a
|
|
292
|
+
// `Tabs.Indicator` renders nothing here (#283). On lynx, which has
|
|
293
|
+
// no pseudo-elements, the `border` flavor's underline is drawn on
|
|
294
|
+
// this part instead (see `targets.lynx`).
|
|
295
|
+
indicator: { base: { display: 'none' } },
|
|
239
296
|
panel: {
|
|
240
297
|
base: { fontSize: 'var(--text-md)' },
|
|
241
298
|
states: { active: {}, inactive: {} },
|
|
@@ -436,11 +493,17 @@ export const tabs = {
|
|
|
436
493
|
targets: {
|
|
437
494
|
lynx: {
|
|
438
495
|
parts: {
|
|
439
|
-
//
|
|
440
|
-
//
|
|
496
|
+
// No hover on a touch platform: the held tab takes the ink
|
|
497
|
+
// daisy's hover gives it.
|
|
498
|
+
tab: { states: { pressed: { color: 'var(--color-base-content)' } } },
|
|
499
|
+
// The list is the indicator's containing block.
|
|
500
|
+
list: { base: { position: 'relative' } },
|
|
501
|
+
// The underline's ink (read by the `border` flavor below) —
|
|
502
|
+
// base-content by default, exactly what the shared
|
|
503
|
+
// `states.active.color` paints on the tab. One class, so any
|
|
441
504
|
// color-axis definition below (host + two classes, baked per
|
|
442
505
|
// theme) wins on specificity.
|
|
443
|
-
|
|
506
|
+
indicator: { base: { '--tab-active-ink': 'var(--color-base-content)' } },
|
|
444
507
|
},
|
|
445
508
|
variants: {
|
|
446
509
|
// `currentColor` never resolves on lynx (measured on device
|
|
@@ -450,18 +513,28 @@ export const tabs = {
|
|
|
450
513
|
// theme colors through color-mix(), which the emitter bakes
|
|
451
514
|
// once per theme — the same per-color/per-theme literal the
|
|
452
515
|
// active `color:` rules already land as.
|
|
453
|
-
color: Object.fromEntries(ROLES.map((c) => [c, {
|
|
516
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { indicator: { base: { '--tab-active-ink': roleInk(c) } } }])),
|
|
454
517
|
variant: {
|
|
455
518
|
// The underline is a ::before on the web, which lynx
|
|
456
|
-
// drops
|
|
457
|
-
//
|
|
458
|
-
// the
|
|
459
|
-
//
|
|
460
|
-
//
|
|
519
|
+
// drops. It is redrawn on the `indicator` part (zero#324),
|
|
520
|
+
// which lynx-zero renders in every list and positions
|
|
521
|
+
// over the active tab from measured geometry (the web's
|
|
522
|
+
// `--tabs-indicator-*` properties are web-only). The part
|
|
523
|
+
// IS the tab's box, so daisy's bar is its bottom edge:
|
|
524
|
+
// 3px, 80% wide and centred, like `left: 10%; width: 80%`
|
|
525
|
+
// on the web. The ink rides `--tab-active-ink` — a plain
|
|
526
|
+
// var() chain, device-proven — instead of `currentColor`,
|
|
527
|
+
// which lynx never resolves.
|
|
461
528
|
border: {
|
|
462
|
-
|
|
463
|
-
base: {
|
|
464
|
-
|
|
529
|
+
indicator: {
|
|
530
|
+
base: {
|
|
531
|
+
display: 'flex',
|
|
532
|
+
boxSizing: 'border-box',
|
|
533
|
+
borderBottomWidth: '3px',
|
|
534
|
+
borderBottomStyle: 'solid',
|
|
535
|
+
borderBottomColor: 'var(--tab-active-ink)',
|
|
536
|
+
transform: 'scaleX(0.8)',
|
|
537
|
+
},
|
|
465
538
|
},
|
|
466
539
|
},
|
|
467
540
|
// `min()` does not exist on lynx (signalxjs/lynx#1066) and
|
|
@@ -505,6 +578,7 @@ const disclosureSizes = {
|
|
|
505
578
|
// daisy "collapse collapse-arrow" flavor.
|
|
506
579
|
export const collapsible = {
|
|
507
580
|
component: 'collapsible',
|
|
581
|
+
targets: disclosureExit('collapsible'),
|
|
508
582
|
// Public to a design system derived from this one (#73).
|
|
509
583
|
hooks: {
|
|
510
584
|
properties: {
|
|
@@ -621,6 +695,8 @@ export const switchRecipe = {
|
|
|
621
695
|
root: {
|
|
622
696
|
base: { display: 'inline-flex', alignItems: 'center', gap: 'var(--space-lg)', cursor: 'pointer' },
|
|
623
697
|
states: {
|
|
698
|
+
// Readonly answers to nothing, so it does not invite a click.
|
|
699
|
+
readonly: { cursor: 'default' },
|
|
624
700
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
625
701
|
checked: {},
|
|
626
702
|
unchecked: {},
|
|
@@ -753,6 +829,29 @@ export const switchRecipe = {
|
|
|
753
829
|
+ '0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000)',
|
|
754
830
|
transition: 'background-color var(--duration-instant) var(--ease-standard)',
|
|
755
831
|
},
|
|
832
|
+
at: {
|
|
833
|
+
// The knob is `currentColor` background plus shadows:
|
|
834
|
+
// forced colours revalue the one to Canvas and drop the
|
|
835
|
+
// others, leaving two empty pill outlines that read the
|
|
836
|
+
// same on and off (#189). Opt the knob out of forcing
|
|
837
|
+
// and paint it in system colours — CanvasText at rest,
|
|
838
|
+
// Highlight once checked. The noise and the shadows go
|
|
839
|
+
// too: unforced, they would mix the forced ink back in.
|
|
840
|
+
'forced-colors': {
|
|
841
|
+
base: {
|
|
842
|
+
forcedColorAdjust: 'none',
|
|
843
|
+
backgroundColor: 'CanvasText',
|
|
844
|
+
backgroundImage: 'none',
|
|
845
|
+
boxShadow: 'none',
|
|
846
|
+
},
|
|
847
|
+
states: { checked: { backgroundColor: 'Highlight' } },
|
|
848
|
+
// A disabled switch draws in GrayText, the forced palette's
|
|
849
|
+
// own disabled ink, not full-strength ink faded by an
|
|
850
|
+
// author opacity the user's theme cannot style. Keyed off
|
|
851
|
+
// the control's flag, which outranks `checked`.
|
|
852
|
+
selectors: { '[data-scope="switch"][data-part="control"][data-disabled] &': { backgroundColor: 'GrayText' } },
|
|
853
|
+
},
|
|
854
|
+
},
|
|
756
855
|
},
|
|
757
856
|
},
|
|
758
857
|
},
|
|
@@ -783,6 +882,10 @@ export const switchRecipe = {
|
|
|
783
882
|
// No currentColor on this target — the ink is named
|
|
784
883
|
// directly, and `checked` moves border + fill below.
|
|
785
884
|
border: 'var(--border) solid var(--switch-ink)',
|
|
885
|
+
// daisy's 1px inset depth line along the track's top
|
|
886
|
+
// edge — `currentColor` at `--depth` × 10% on the
|
|
887
|
+
// web, the theme's own depth shade here.
|
|
888
|
+
boxShadow: '0 1px var(--depth-shade) inset',
|
|
786
889
|
userSelect: 'none',
|
|
787
890
|
transition: 'border-color var(--duration-slow) var(--ease-standard), '
|
|
788
891
|
+ 'background-color var(--duration-normal) var(--ease-standard)',
|
|
@@ -803,6 +906,12 @@ export const switchRecipe = {
|
|
|
803
906
|
height: 'calc(var(--switch-size) - (var(--border) + var(--switch-p)) * 2)',
|
|
804
907
|
borderRadius: 'var(--radius-selector)',
|
|
805
908
|
backgroundColor: 'var(--switch-ink)',
|
|
909
|
+
// daisy's knob relief (`.toggle:before`): a shade
|
|
910
|
+
// along the bottom edge, a sheen across the top, and
|
|
911
|
+
// a 1px drop — the web's `currentColor` drop spelled
|
|
912
|
+
// as the theme's depth shade. The noise layer has no
|
|
913
|
+
// lynx spelling and stays web-only.
|
|
914
|
+
boxShadow: '0 -1px var(--depth-shade) inset, 0 8px 0 -4px var(--depth-sheen) inset, 0 1px var(--depth-shade)',
|
|
806
915
|
transition: 'transform var(--duration-normal) var(--ease-standard), '
|
|
807
916
|
+ 'background-color var(--duration-instant) var(--ease-standard)',
|
|
808
917
|
},
|
|
@@ -909,6 +1018,21 @@ const lynxBtnSizes = (part) => ({
|
|
|
909
1018
|
lg: { [part]: { base: lynxBtnPad(5) } },
|
|
910
1019
|
xl: { [part]: { base: lynxBtnPad(6) } },
|
|
911
1020
|
});
|
|
1021
|
+
/**
|
|
1022
|
+
* The press rendering of the overlay btns (dialog, popover), for their
|
|
1023
|
+
* `targets.lynx` sections. The web answers a pointer with `:hover`, which
|
|
1024
|
+
* the lynx emitter drops (a touch platform), and these parts' `pressed`
|
|
1025
|
+
* flag — stamped by the lynx runtime on every touch — had no rule, so a
|
|
1026
|
+
* tap showed nothing (signalxjs/lynx#1146). daisy's `:hover` wash plus the
|
|
1027
|
+
* btn's 1px sink, the same pair `button` paints for its own `pressed`. A
|
|
1028
|
+
* colour variant restates the background after the states at equal
|
|
1029
|
+
* specificity, so a coloured trigger keeps its fill and sinks.
|
|
1030
|
+
*/
|
|
1031
|
+
const lynxBtnPressed = {
|
|
1032
|
+
background: 'var(--color-base-300)',
|
|
1033
|
+
transform: 'translateY(1px)',
|
|
1034
|
+
boxShadow: 'none',
|
|
1035
|
+
};
|
|
912
1036
|
export const dialog = {
|
|
913
1037
|
component: 'dialog',
|
|
914
1038
|
// Public to a design system derived from this one (#73).
|
|
@@ -1000,14 +1124,16 @@ export const dialog = {
|
|
|
1000
1124
|
// reach and the trigger is the whole story here.
|
|
1001
1125
|
variants: { color: btnColors(), size: btnSizes },
|
|
1002
1126
|
// The btn paddings and the footer's push-down, restated physically —
|
|
1003
|
-
// see `lynxBtnPad` for the #1084 verdict this answers
|
|
1127
|
+
// see `lynxBtnPad` for the #1084 verdict this answers — and the btns'
|
|
1128
|
+
// press rendering (`lynxBtnPressed`). The footer states its row: on lynx
|
|
1129
|
+
// a row container says so next to `display: flex`.
|
|
1004
1130
|
targets: {
|
|
1005
1131
|
lynx: {
|
|
1006
1132
|
parts: {
|
|
1007
|
-
trigger: { base: lynxBtnPad(4) },
|
|
1008
|
-
close: { base: lynxBtnPad(4) },
|
|
1009
|
-
cancel: { base: lynxBtnPad(4) },
|
|
1010
|
-
footer: { base: { marginTop: 'var(--space-2xl)' } },
|
|
1133
|
+
trigger: { base: lynxBtnPad(4), states: { pressed: lynxBtnPressed } },
|
|
1134
|
+
close: { base: lynxBtnPad(4), states: { pressed: lynxBtnPressed } },
|
|
1135
|
+
cancel: { base: lynxBtnPad(4), states: { pressed: lynxBtnPressed } },
|
|
1136
|
+
footer: { base: { marginTop: 'var(--space-2xl)', flexDirection: 'row' } },
|
|
1011
1137
|
},
|
|
1012
1138
|
variants: { size: lynxBtnSizes('trigger') },
|
|
1013
1139
|
},
|
|
@@ -1021,6 +1147,37 @@ const floatingPanel = {
|
|
|
1021
1147
|
borderRadius: 'var(--radius-box)',
|
|
1022
1148
|
boxShadow: 'var(--shadow-lg)',
|
|
1023
1149
|
};
|
|
1150
|
+
/**
|
|
1151
|
+
* The arrow a floating surface grows toward its anchor (#279), in the
|
|
1152
|
+
* surface's own paint: the dropdown's base-100 and border for popover and
|
|
1153
|
+
* menu, the tooltip's borderless neutral for the tooltip — daisy's
|
|
1154
|
+
* `tooltip` draws exactly that tail under its bubble. Web-only (placed by
|
|
1155
|
+
* the runtime's `--arrow-x`/`--arrow-y`), so it rides `targets.web` with
|
|
1156
|
+
* the popup's permission to let it out.
|
|
1157
|
+
*/
|
|
1158
|
+
const floatingArrow = (scope, paint, size = '0.625rem') => ({
|
|
1159
|
+
parts: {
|
|
1160
|
+
popup: { selectors: popupArrowHost(scope) },
|
|
1161
|
+
arrow: popupArrow(scope, { size, paint }),
|
|
1162
|
+
},
|
|
1163
|
+
});
|
|
1164
|
+
const panelArrowPaint = { background: 'var(--color-base-100)', border: 'var(--border) solid var(--color-base-300)' };
|
|
1165
|
+
/**
|
|
1166
|
+
* daisy's dropdown menu for a listbox, sized by the geometry the
|
|
1167
|
+
* anchored-position strategy publishes (#278): at least the width of the
|
|
1168
|
+
* control that opened it (`--anchor-width`, daisy's 13rem floor otherwise)
|
|
1169
|
+
* and never taller than the room on the side it opened to
|
|
1170
|
+
* (`--available-height`), scrolling past a 20rem cap. Border-box, so both
|
|
1171
|
+
* bounds measure the box the strategy positions. Runtime-published
|
|
1172
|
+
* properties are web-only, so this is each listbox recipe's `targets.web`;
|
|
1173
|
+
* lynx keeps the shared 13rem.
|
|
1174
|
+
*/
|
|
1175
|
+
const anchoredListbox = {
|
|
1176
|
+
boxSizing: 'border-box',
|
|
1177
|
+
minWidth: 'var(--anchor-width, 13rem)',
|
|
1178
|
+
maxHeight: 'min(20rem, var(--available-height, 20rem))',
|
|
1179
|
+
overflowY: 'auto',
|
|
1180
|
+
};
|
|
1024
1181
|
export const popover = {
|
|
1025
1182
|
component: 'popover',
|
|
1026
1183
|
parts: {
|
|
@@ -1041,6 +1198,14 @@ export const popover = {
|
|
|
1041
1198
|
title: {
|
|
1042
1199
|
base: { margin: '0 0 var(--space-md)', fontSize: 'var(--text-md)', fontWeight: 'var(--weight-bold)' },
|
|
1043
1200
|
},
|
|
1201
|
+
// The modal's muted body line, at the dropdown's tighter rhythm.
|
|
1202
|
+
description: {
|
|
1203
|
+
base: {
|
|
1204
|
+
margin: '0 0 var(--space-lg)',
|
|
1205
|
+
fontSize: 'var(--text-sm)',
|
|
1206
|
+
color: 'color-mix(in oklab, var(--color-base-content) 75%, transparent)',
|
|
1207
|
+
},
|
|
1208
|
+
},
|
|
1044
1209
|
close: {
|
|
1045
1210
|
base: { ...btn, height: 'calc(var(--size-field) * 8)', paddingInline: 'calc(var(--size-field) * 3)', fontSize: 'var(--text-xs)' },
|
|
1046
1211
|
states: {
|
|
@@ -1052,12 +1217,14 @@ export const popover = {
|
|
|
1052
1217
|
},
|
|
1053
1218
|
// Trigger-carried axes — same wiring as dialog, same reason.
|
|
1054
1219
|
variants: { color: btnColors(), size: btnSizes },
|
|
1055
|
-
// The btn paddings, restated physically — see `lynxBtnPad` (#1084)
|
|
1220
|
+
// The btn paddings, restated physically — see `lynxBtnPad` (#1084) —
|
|
1221
|
+
// and the btns' press rendering (`lynxBtnPressed`).
|
|
1056
1222
|
targets: {
|
|
1223
|
+
web: floatingArrow('popover', panelArrowPaint),
|
|
1057
1224
|
lynx: {
|
|
1058
1225
|
parts: {
|
|
1059
|
-
trigger: { base: lynxBtnPad(4) },
|
|
1060
|
-
close: { base: lynxBtnPad(3) },
|
|
1226
|
+
trigger: { base: lynxBtnPad(4), states: { pressed: lynxBtnPressed } },
|
|
1227
|
+
close: { base: lynxBtnPad(3), states: { pressed: lynxBtnPressed } },
|
|
1061
1228
|
},
|
|
1062
1229
|
variants: { size: lynxBtnSizes('trigger') },
|
|
1063
1230
|
},
|
|
@@ -1099,6 +1266,7 @@ export const tooltip = {
|
|
|
1099
1266
|
variants: { color: btnColors(), size: btnSizes },
|
|
1100
1267
|
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
1101
1268
|
targets: {
|
|
1269
|
+
web: floatingArrow('tooltip', { background: 'var(--color-neutral)' }, '0.5rem'),
|
|
1102
1270
|
lynx: {
|
|
1103
1271
|
parts: { trigger: { base: lynxBtnPad(4) } },
|
|
1104
1272
|
variants: { size: lynxBtnSizes('trigger') },
|
|
@@ -1251,6 +1419,7 @@ export const menu = {
|
|
|
1251
1419
|
variants: { color: btnColors(), size: btnSizes },
|
|
1252
1420
|
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
1253
1421
|
targets: {
|
|
1422
|
+
web: floatingArrow('menu', panelArrowPaint),
|
|
1254
1423
|
lynx: {
|
|
1255
1424
|
parts: { trigger: { base: lynxBtnPad(4) } },
|
|
1256
1425
|
variants: { size: lynxBtnSizes('trigger') },
|
|
@@ -1306,6 +1475,60 @@ export const field = {
|
|
|
1306
1475
|
},
|
|
1307
1476
|
},
|
|
1308
1477
|
};
|
|
1478
|
+
/**
|
|
1479
|
+
* Fieldset (#285), daisyUI 5's `.fieldset` / `.fieldset-legend`: a
|
|
1480
|
+
* one-column grid with a tight row gap and a little block padding, no frame
|
|
1481
|
+
* of its own (daisy draws one only when the app adds `border`), and a
|
|
1482
|
+
* semibold legend with block padding standing in for its margin. The UA
|
|
1483
|
+
* `min-inline-size: min-content` is undone so a wide control cannot push the
|
|
1484
|
+
* group past its column. Colour accents the legend through `roleInk`, the
|
|
1485
|
+
* Field way.
|
|
1486
|
+
*/
|
|
1487
|
+
export const fieldset = {
|
|
1488
|
+
component: 'fieldset',
|
|
1489
|
+
tokens: { '--fieldset-accent': 'var(--color-base-content)' },
|
|
1490
|
+
parts: {
|
|
1491
|
+
root: {
|
|
1492
|
+
base: {
|
|
1493
|
+
display: 'grid',
|
|
1494
|
+
gridTemplateColumns: '1fr',
|
|
1495
|
+
gridAutoRows: 'max-content',
|
|
1496
|
+
gap: 'var(--space-sm)',
|
|
1497
|
+
minInlineSize: '0',
|
|
1498
|
+
margin: '0',
|
|
1499
|
+
paddingBlock: 'var(--space-xs)',
|
|
1500
|
+
paddingInline: '0',
|
|
1501
|
+
border: 'none',
|
|
1502
|
+
},
|
|
1503
|
+
},
|
|
1504
|
+
legend: {
|
|
1505
|
+
base: {
|
|
1506
|
+
paddingBlock: 'var(--space-sm)',
|
|
1507
|
+
paddingInline: '0',
|
|
1508
|
+
fontSize: 'var(--text-sm)',
|
|
1509
|
+
fontWeight: 'var(--weight-semibold)',
|
|
1510
|
+
color: 'var(--fieldset-accent)',
|
|
1511
|
+
},
|
|
1512
|
+
states: {
|
|
1513
|
+
disabled: { opacity: 'var(--disabled-opacity)' },
|
|
1514
|
+
invalid: { color: roleInk('error') },
|
|
1515
|
+
},
|
|
1516
|
+
},
|
|
1517
|
+
},
|
|
1518
|
+
variants: {
|
|
1519
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
1520
|
+
'--fieldset-accent': roleInk(c),
|
|
1521
|
+
} } }])),
|
|
1522
|
+
size: {
|
|
1523
|
+
xs: { legend: { base: { fontSize: 'var(--text-xs)' } } },
|
|
1524
|
+
sm: { legend: { base: { fontSize: 'var(--text-xs)' } } },
|
|
1525
|
+
// `md` is the un-attributed render.
|
|
1526
|
+
md: {},
|
|
1527
|
+
lg: { legend: { base: { fontSize: 'var(--text-md)' } } },
|
|
1528
|
+
xl: { legend: { base: { fontSize: 'var(--text-lg)' } } },
|
|
1529
|
+
},
|
|
1530
|
+
},
|
|
1531
|
+
};
|
|
1309
1532
|
/**
|
|
1310
1533
|
* daisy's tick, drawn by a clip-path that GROWS.
|
|
1311
1534
|
*
|
|
@@ -1380,6 +1603,8 @@ export const checkbox = {
|
|
|
1380
1603
|
root: {
|
|
1381
1604
|
base: { display: 'inline-flex', alignItems: 'center', gap: 'var(--space-md)', cursor: 'pointer' },
|
|
1382
1605
|
states: {
|
|
1606
|
+
// Readonly answers to nothing, so it does not invite a click.
|
|
1607
|
+
readonly: { cursor: 'default' },
|
|
1383
1608
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
1384
1609
|
checked: {}, unchecked: {}, indeterminate: {},
|
|
1385
1610
|
},
|
|
@@ -1452,6 +1677,8 @@ export const checkbox = {
|
|
|
1452
1677
|
// `invalid` is semantic, not an accent: it stays error under
|
|
1453
1678
|
// every colour variant, on purpose.
|
|
1454
1679
|
invalid: { borderColor: 'var(--color-error)' },
|
|
1680
|
+
// Readonly answers to nothing, so it does not invite a click.
|
|
1681
|
+
readonly: { cursor: 'default' },
|
|
1455
1682
|
disabled: {},
|
|
1456
1683
|
},
|
|
1457
1684
|
},
|
|
@@ -1576,13 +1803,9 @@ export const radioGroup = {
|
|
|
1576
1803
|
parts: {
|
|
1577
1804
|
root: {
|
|
1578
1805
|
base: { display: 'flex', flexDirection: 'column', gap: 'var(--space-md)' },
|
|
1806
|
+
// `invalid` paints on each `item-control`, which carries the flag
|
|
1807
|
+
// itself (#267) — the root only groups the items.
|
|
1579
1808
|
states: { invalid: {}, required: {} },
|
|
1580
|
-
selectors: {
|
|
1581
|
-
// `invalid` is a fact about the GROUP — `item-control` carries
|
|
1582
|
-
// no flag of its own — and it is semantic: it stays error
|
|
1583
|
-
// under every colour variant.
|
|
1584
|
-
'&[data-invalid] [data-part="item-control"]': { borderColor: 'var(--color-error)' },
|
|
1585
|
-
},
|
|
1586
1809
|
},
|
|
1587
1810
|
label: {
|
|
1588
1811
|
base: { fontSize: 'var(--text-sm)', fontWeight: 'var(--weight-semibold)' },
|
|
@@ -1591,6 +1814,8 @@ export const radioGroup = {
|
|
|
1591
1814
|
item: {
|
|
1592
1815
|
base: { display: 'inline-flex', alignItems: 'center', gap: 'var(--space-md)', cursor: 'pointer' },
|
|
1593
1816
|
states: {
|
|
1817
|
+
// Readonly answers to nothing, so it does not invite a click.
|
|
1818
|
+
readonly: { cursor: 'default' },
|
|
1594
1819
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
1595
1820
|
checked: {}, unchecked: {},
|
|
1596
1821
|
},
|
|
@@ -1625,6 +1850,10 @@ export const radioGroup = {
|
|
|
1625
1850
|
animation: 'var(--duration-fast) var(--ease-standard) zero-daisy-radio',
|
|
1626
1851
|
},
|
|
1627
1852
|
unchecked: {},
|
|
1853
|
+
// `invalid` is semantic: it stays error under every colour
|
|
1854
|
+
// variant, and after `checked` so it holds whichever radio is
|
|
1855
|
+
// chosen.
|
|
1856
|
+
invalid: { borderColor: 'var(--color-error)' },
|
|
1628
1857
|
'focus-visible': { outline: '2px solid var(--radio-accent)', outlineOffset: '2px' },
|
|
1629
1858
|
disabled: {},
|
|
1630
1859
|
},
|
|
@@ -1695,6 +1924,47 @@ export const radioGroup = {
|
|
|
1695
1924
|
},
|
|
1696
1925
|
},
|
|
1697
1926
|
};
|
|
1927
|
+
/**
|
|
1928
|
+
* CheckboxGroup (#282) — daisy has no group component; its forms stack
|
|
1929
|
+
* `.checkbox` rows under a `fieldset-legend`. The boxes keep the checkbox
|
|
1930
|
+
* recipe; the group owns the stack and the legend, which speaks Field's
|
|
1931
|
+
* label (colour through `roleInk`, #210).
|
|
1932
|
+
*/
|
|
1933
|
+
export const checkboxGroup = {
|
|
1934
|
+
component: 'checkbox-group',
|
|
1935
|
+
tokens: { '--checkbox-group-accent': 'var(--color-base-content)' },
|
|
1936
|
+
parts: {
|
|
1937
|
+
root: {
|
|
1938
|
+
base: { display: 'flex', flexDirection: 'column', gap: 'var(--space-md)' },
|
|
1939
|
+
// `invalid` and `readonly` paint on each box — the root only
|
|
1940
|
+
// lays the boxes out.
|
|
1941
|
+
states: { invalid: {}, required: {}, readonly: {} },
|
|
1942
|
+
selectors: {
|
|
1943
|
+
'&[data-orientation="horizontal"]': { flexDirection: 'row', flexWrap: 'wrap', columnGap: 'var(--space-lg)' },
|
|
1944
|
+
},
|
|
1945
|
+
},
|
|
1946
|
+
label: {
|
|
1947
|
+
base: { fontSize: 'var(--text-sm)', fontWeight: 'var(--weight-semibold)', color: 'var(--checkbox-group-accent)' },
|
|
1948
|
+
states: { disabled: { opacity: 'var(--disabled-opacity)' } },
|
|
1949
|
+
selectors: {
|
|
1950
|
+
'&[data-required]::after': { content: '" *"', color: roleInk('error') },
|
|
1951
|
+
},
|
|
1952
|
+
},
|
|
1953
|
+
},
|
|
1954
|
+
variants: {
|
|
1955
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
1956
|
+
'--checkbox-group-accent': roleInk(c),
|
|
1957
|
+
} } }])),
|
|
1958
|
+
size: {
|
|
1959
|
+
xs: { root: { base: { gap: 'var(--space-sm)' } }, label: { base: { fontSize: 'var(--text-xs)' } } },
|
|
1960
|
+
sm: { label: { base: { fontSize: 'var(--text-xs)' } } },
|
|
1961
|
+
md: {},
|
|
1962
|
+
lg: { label: { base: { fontSize: 'var(--text-md)' } } },
|
|
1963
|
+
xl: { root: { base: { gap: 'var(--space-lg)' } }, label: { base: { fontSize: 'var(--text-lg)' } } },
|
|
1964
|
+
},
|
|
1965
|
+
},
|
|
1966
|
+
skipStates: { label: ['invalid'] },
|
|
1967
|
+
};
|
|
1698
1968
|
// --------------------------------------------------------------------------
|
|
1699
1969
|
// 4. progress — replace the whole export (only the two radius lines move)
|
|
1700
1970
|
// --------------------------------------------------------------------------
|
|
@@ -1756,9 +2026,13 @@ export const progress = {
|
|
|
1756
2026
|
transition: 'width var(--duration-slow) var(--ease-standard)',
|
|
1757
2027
|
},
|
|
1758
2028
|
states: {
|
|
1759
|
-
// `complete`
|
|
1760
|
-
//
|
|
1761
|
-
|
|
2029
|
+
// `complete` keeps the colour axis. daisyUI 5 has no complete
|
|
2030
|
+
// tint — a full `progress-primary` stays primary — and a
|
|
2031
|
+
// `color="secondary"` bar that turns green at 100% reads as a
|
|
2032
|
+
// bug, not as meaning (signalxjs/lynx#1144). A caller who wants
|
|
2033
|
+
// "done" to look like success says so: `color="success"`.
|
|
2034
|
+
// Declared equal to `loading` in `sameAs` below.
|
|
2035
|
+
complete: {},
|
|
1762
2036
|
loading: {},
|
|
1763
2037
|
indeterminate: { width: '40%', animation: 'zero-daisy-indeterminate 1.2s ease-in-out infinite' },
|
|
1764
2038
|
},
|
|
@@ -1794,6 +2068,8 @@ export const progress = {
|
|
|
1794
2068
|
// so the manifest states the default for runtimes with no `:not()`
|
|
1795
2069
|
// fallback (the lynx target; signalxjs/lynx#1070).
|
|
1796
2070
|
defaultVariants: { color: 'primary', size: 'md' },
|
|
2071
|
+
// A finished bar is a full bar of the same ink (see `complete` above).
|
|
2072
|
+
sameAs: { root: { complete: 'loading' }, range: { complete: 'loading' } },
|
|
1797
2073
|
keyframes: {
|
|
1798
2074
|
// Logical, so the sweep runs the way the bar fills — `margin-inline-start`
|
|
1799
2075
|
// is animatable and direction-aware on its own. The determinate `width`,
|
|
@@ -1814,9 +2090,19 @@ export const progress = {
|
|
|
1814
2090
|
},
|
|
1815
2091
|
},
|
|
1816
2092
|
};
|
|
2093
|
+
/** The slider's deepened fill — the web's `--slider-fill` mix, for a given accent. */
|
|
2094
|
+
const sliderFill = (accent) => `color-mix(in oklab, ${accent} 90%, var(--color-base-content))`;
|
|
1817
2095
|
export const slider = {
|
|
1818
2096
|
component: 'slider',
|
|
1819
|
-
|
|
2097
|
+
// daisy's `.range` metrics: the thumb is `--range-thumb-size`
|
|
2098
|
+
// (×6 of `--size-selector` at md, ×4…×8 across the ramp — the same
|
|
2099
|
+
// selector ramp checkbox rides) and the track is half of it. Both the
|
|
2100
|
+
// native control and the composed parts read these.
|
|
2101
|
+
tokens: {
|
|
2102
|
+
'--slider-accent': 'var(--color-primary)',
|
|
2103
|
+
'--slider-track-size': 'calc(var(--size-selector) * 3)',
|
|
2104
|
+
'--slider-thumb-size': 'calc(var(--size-selector) * 6)',
|
|
2105
|
+
},
|
|
1820
2106
|
parts: {
|
|
1821
2107
|
root: {
|
|
1822
2108
|
base: { display: 'flex', flexDirection: 'column', gap: 'var(--space-sm)', width: '100%' },
|
|
@@ -1826,30 +2112,106 @@ export const slider = {
|
|
|
1826
2112
|
base: { fontSize: 'var(--text-sm)', fontWeight: 'var(--weight-semibold)' },
|
|
1827
2113
|
states: { disabled: {} },
|
|
1828
2114
|
},
|
|
2115
|
+
// A custom skin (`appearance: none`), ported from daisyUI 5's
|
|
2116
|
+
// `range.css`: a `--radius-selector` track half the thumb's height,
|
|
2117
|
+
// and daisy's real range thumb — a `base-100` knob inside a
|
|
2118
|
+
// `.25rem` ring of the fill ink (daisy's `--range-p`), carrying the
|
|
2119
|
+
// same `--depth` inset shading the checkbox and radio wear. daisy
|
|
2120
|
+
// throws the elapsed fill as a 100cqw box-shadow off the thumb and
|
|
2121
|
+
// clips it with `overflow: hidden`; here the track reads the
|
|
2122
|
+
// runtime-published `--slider-percent` as a gradient stop instead,
|
|
2123
|
+
// as every other skin does. The fill is deepened toward
|
|
2124
|
+
// `base-content` the way `progressFill` deepens daisy's bar — a raw
|
|
2125
|
+
// role on the base-300 rail is exactly the #210/#228 bug class.
|
|
2126
|
+
//
|
|
2127
|
+
// The rebuild is also the correctness move (see the kit skill): Blink
|
|
2128
|
+
// treats range inputs as ALWAYS `:focus-visible`, even on mouse
|
|
2129
|
+
// focus, so the base kills the native outline and the
|
|
2130
|
+
// `focus-visible` state — compiled to the runtime's keyboard-only
|
|
2131
|
+
// `[data-focus-visible]` flag — draws the ring only when it means it.
|
|
1829
2132
|
control: {
|
|
1830
|
-
base: {
|
|
2133
|
+
base: {
|
|
2134
|
+
appearance: 'none',
|
|
2135
|
+
width: '100%',
|
|
2136
|
+
height: 'var(--slider-thumb-size)',
|
|
2137
|
+
margin: '0',
|
|
2138
|
+
background: 'transparent',
|
|
2139
|
+
cursor: 'pointer',
|
|
2140
|
+
outline: 'none',
|
|
2141
|
+
// Kept for the forced-colors fallback below, where native
|
|
2142
|
+
// rendering takes over and still honours the accent.
|
|
2143
|
+
accentColor: 'var(--slider-accent)',
|
|
2144
|
+
// `--slider-fill` and `--slider-track` live in targets.web
|
|
2145
|
+
// below: the fill is a color-mix() the lynx emitter drops,
|
|
2146
|
+
// and the track reads the runtime-published
|
|
2147
|
+
// `--slider-percent`, a web-only mechanism.
|
|
2148
|
+
},
|
|
1831
2149
|
states: {
|
|
2150
|
+
// Readonly answers to nothing, so it does not invite a click.
|
|
2151
|
+
readonly: { cursor: 'default' },
|
|
1832
2152
|
disabled: { cursor: 'not-allowed' },
|
|
1833
2153
|
'focus-visible': { outline: '2px solid var(--slider-accent)', outlineOffset: '2px' },
|
|
1834
2154
|
// `invalid` is semantic: it stays error under every colour
|
|
1835
|
-
// variant, on purpose
|
|
1836
|
-
|
|
2155
|
+
// variant, on purpose — the accent indirection swaps the
|
|
2156
|
+
// fill, the thumb ring, the focus ring and the forced-colors
|
|
2157
|
+
// fallback together.
|
|
2158
|
+
invalid: { '--slider-accent': 'var(--color-error)' },
|
|
2159
|
+
},
|
|
2160
|
+
selectors: {
|
|
2161
|
+
// Vendor pseudos cannot share a selector list — one unknown
|
|
2162
|
+
// selector invalidates the whole rule — so each engine gets
|
|
2163
|
+
// its own copy reading the shared custom properties.
|
|
2164
|
+
'&::-webkit-slider-runnable-track': {
|
|
2165
|
+
height: 'var(--slider-track-size)',
|
|
2166
|
+
borderRadius: 'var(--radius-selector)',
|
|
2167
|
+
background: 'var(--slider-track)',
|
|
2168
|
+
},
|
|
2169
|
+
'&::-webkit-slider-thumb': {
|
|
2170
|
+
appearance: 'none',
|
|
2171
|
+
boxSizing: 'border-box',
|
|
2172
|
+
width: 'var(--slider-thumb-size)',
|
|
2173
|
+
height: 'var(--slider-thumb-size)',
|
|
2174
|
+
marginTop: 'calc((var(--slider-track-size) - var(--slider-thumb-size)) / 2)',
|
|
2175
|
+
borderRadius: 'calc(var(--radius-selector) + min(0.25rem, var(--radius-selector) * 3))',
|
|
2176
|
+
border: '0.25rem solid var(--slider-fill)',
|
|
2177
|
+
background: 'var(--color-base-100)',
|
|
2178
|
+
boxShadow: '0 -1px var(--depth-shade) inset, 0 8px 0 -4px var(--depth-sheen) inset',
|
|
2179
|
+
},
|
|
2180
|
+
'&::-moz-range-track': {
|
|
2181
|
+
height: 'var(--slider-track-size)',
|
|
2182
|
+
borderRadius: 'var(--radius-selector)',
|
|
2183
|
+
background: 'var(--slider-track)',
|
|
2184
|
+
},
|
|
2185
|
+
'&::-moz-range-thumb': {
|
|
2186
|
+
boxSizing: 'border-box',
|
|
2187
|
+
width: 'var(--slider-thumb-size)',
|
|
2188
|
+
height: 'var(--slider-thumb-size)',
|
|
2189
|
+
borderRadius: 'calc(var(--radius-selector) + min(0.25rem, var(--radius-selector) * 3))',
|
|
2190
|
+
border: '0.25rem solid var(--slider-fill)',
|
|
2191
|
+
background: 'var(--color-base-100)',
|
|
2192
|
+
boxShadow: '0 -1px var(--depth-shade) inset, 0 8px 0 -4px var(--depth-sheen) inset',
|
|
2193
|
+
},
|
|
2194
|
+
},
|
|
2195
|
+
at: {
|
|
2196
|
+
// Native rendering knows forced colors better than a custom
|
|
2197
|
+
// skin; the retained accentColor keeps the fallback branded.
|
|
2198
|
+
'forced-colors': { base: { appearance: 'auto' } },
|
|
1837
2199
|
},
|
|
1838
2200
|
},
|
|
1839
|
-
// The composed range projection (#325)
|
|
1840
|
-
//
|
|
1841
|
-
//
|
|
1842
|
-
// `base-content` the way `progressFill` deepens daisy's bar — a raw
|
|
1843
|
-
// role on the base-300 rail is exactly the #210/#228 bug class.
|
|
2201
|
+
// The composed range projection (#325): the same metrics as the
|
|
2202
|
+
// native control above, restating its accent as real boxes. The
|
|
2203
|
+
// fill is the same deepened ink the native track paints.
|
|
1844
2204
|
track: {
|
|
1845
2205
|
base: {
|
|
1846
|
-
height: '
|
|
1847
|
-
|
|
2206
|
+
height: 'var(--slider-track-size)',
|
|
2207
|
+
// Reserve the thumb's overhang so the row's box matches the
|
|
2208
|
+
// native control's.
|
|
2209
|
+
marginBlock: 'calc((var(--slider-thumb-size) - var(--slider-track-size)) / 2)',
|
|
1848
2210
|
borderRadius: '9999px',
|
|
1849
2211
|
background: 'var(--color-base-300)',
|
|
1850
2212
|
cursor: 'pointer',
|
|
1851
2213
|
},
|
|
1852
|
-
states: { disabled: { cursor: 'not-allowed' } },
|
|
2214
|
+
states: { readonly: { cursor: 'default' }, disabled: { cursor: 'not-allowed' } },
|
|
1853
2215
|
},
|
|
1854
2216
|
range: {
|
|
1855
2217
|
base: {
|
|
@@ -1862,11 +2224,11 @@ export const slider = {
|
|
|
1862
2224
|
thumb: {
|
|
1863
2225
|
base: {
|
|
1864
2226
|
boxSizing: 'border-box',
|
|
1865
|
-
width: '
|
|
1866
|
-
height: '
|
|
2227
|
+
width: 'var(--slider-thumb-size)',
|
|
2228
|
+
height: 'var(--slider-thumb-size)',
|
|
1867
2229
|
insetBlockStart: '50%',
|
|
1868
2230
|
translate: '0 -50%',
|
|
1869
|
-
marginInlineStart: 'calc(var(--size
|
|
2231
|
+
marginInlineStart: 'calc(var(--slider-thumb-size) / -2)',
|
|
1870
2232
|
borderRadius: '9999px',
|
|
1871
2233
|
background: 'color-mix(in oklab, var(--slider-accent) 90%, var(--color-base-content))',
|
|
1872
2234
|
cursor: 'pointer',
|
|
@@ -1876,12 +2238,14 @@ export const slider = {
|
|
|
1876
2238
|
states: {
|
|
1877
2239
|
'focus-visible': { outline: '2px solid var(--slider-accent)', outlineOffset: '2px' },
|
|
1878
2240
|
pressed: {},
|
|
2241
|
+
// Readonly answers to nothing, so it does not invite a click.
|
|
2242
|
+
readonly: { cursor: 'default' },
|
|
1879
2243
|
disabled: { cursor: 'not-allowed' },
|
|
1880
2244
|
},
|
|
1881
2245
|
},
|
|
1882
2246
|
mark: {
|
|
1883
2247
|
base: {
|
|
1884
|
-
paddingBlockStart: 'calc(var(--size
|
|
2248
|
+
paddingBlockStart: 'calc(var(--slider-track-size) + var(--space-2xs))',
|
|
1885
2249
|
fontSize: 'var(--text-xs)',
|
|
1886
2250
|
lineHeight: '1',
|
|
1887
2251
|
whiteSpace: 'nowrap',
|
|
@@ -1895,7 +2259,7 @@ export const slider = {
|
|
|
1895
2259
|
insetBlockStart: '0',
|
|
1896
2260
|
insetInlineStart: '-1px',
|
|
1897
2261
|
width: '2px',
|
|
1898
|
-
height: '
|
|
2262
|
+
height: 'var(--slider-track-size)',
|
|
1899
2263
|
background: 'var(--color-base-content)',
|
|
1900
2264
|
},
|
|
1901
2265
|
},
|
|
@@ -1908,57 +2272,222 @@ export const slider = {
|
|
|
1908
2272
|
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
1909
2273
|
'--slider-accent': `var(--color-${c})`,
|
|
1910
2274
|
} } }])),
|
|
1911
|
-
//
|
|
1912
|
-
//
|
|
2275
|
+
// daisy's `.range-{xs…xl}` ramp: `--range-thumb-size` at ×4…×8 of
|
|
2276
|
+
// `--size-selector` (1 / 1.25 / 1.5 / 1.75 / 2rem), the track half
|
|
2277
|
+
// of it — so every step moves the whole widget; the un-attributed
|
|
2278
|
+
// (and `md`) render takes the middle step from `tokens:`.
|
|
1913
2279
|
size: {
|
|
1914
|
-
xs: {
|
|
1915
|
-
sm: {
|
|
2280
|
+
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)' } } },
|
|
2281
|
+
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)' } } },
|
|
2282
|
+
// `md`'s metrics ARE the `tokens:` defaults — one source of truth.
|
|
1916
2283
|
md: { label: { base: { fontSize: 'var(--text-sm)' } } },
|
|
1917
|
-
lg: {
|
|
1918
|
-
xl: {
|
|
2284
|
+
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)' } } },
|
|
2285
|
+
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)' } } },
|
|
1919
2286
|
},
|
|
1920
2287
|
},
|
|
1921
|
-
// Base equals the default — `tokens:` binds the primary accent
|
|
1922
|
-
// only restates the label font-size the
|
|
1923
|
-
//
|
|
2288
|
+
// Base equals the default — `tokens:` binds the primary accent and the
|
|
2289
|
+
// middle-step metrics, and `md` only restates the label font-size the
|
|
2290
|
+
// base already declares — so the twins restate it;
|
|
1924
2291
|
// declared for the manifest (signalxjs/lynx#1070).
|
|
1925
2292
|
defaultVariants: { color: 'primary', size: 'md' },
|
|
1926
2293
|
skipStates: { root: ['invalid', 'focus-visible'] },
|
|
1927
2294
|
// ONE design decision, two paints. On the web the accent reaches the
|
|
1928
|
-
// screen twice
|
|
1929
|
-
//
|
|
1930
|
-
// renders neither
|
|
1931
|
-
// `<input type=range
|
|
1932
|
-
//
|
|
1933
|
-
//
|
|
1934
|
-
//
|
|
1935
|
-
//
|
|
1936
|
-
//
|
|
2295
|
+
// screen twice, both times deepened with `color-mix()`: the native
|
|
2296
|
+
// control's gradient track and thumb ring (via `--slider-fill`, below),
|
|
2297
|
+
// and the composed range/thumb. Lynx renders neither — it has no native
|
|
2298
|
+
// `<input type=range>`, and the emitter drops `color-mix()` as
|
|
2299
|
+
// unresolvable — so the compiled range and thumb once shipped with no
|
|
2300
|
+
// paint at all: grey track, invisible fill, invisible knob (measured on
|
|
2301
|
+
// device, signalxjs/lynx#1075). The geometry all survives (the sizes are
|
|
2302
|
+
// direct calc() over the metric tokens); paint is the only gap, so the
|
|
2303
|
+
// lynx section only restates paint, in lynx-expressible spellings, plus
|
|
2304
|
+
// the physical restatement of the logical geometry.
|
|
1937
2305
|
targets: {
|
|
2306
|
+
web: {
|
|
2307
|
+
parts: {
|
|
2308
|
+
control: {
|
|
2309
|
+
base: {
|
|
2310
|
+
// The fill ink, deepened like the composed range.
|
|
2311
|
+
'--slider-fill': 'color-mix(in oklab, var(--slider-accent) 90%, var(--color-base-content))',
|
|
2312
|
+
// The filled track reads the runtime-published
|
|
2313
|
+
// `--slider-percent` — web-only (`RUNTIME_PROPERTIES`);
|
|
2314
|
+
// the lynx runtime paints its own track/range parts.
|
|
2315
|
+
'--slider-track': 'linear-gradient(to right, var(--slider-fill) var(--slider-percent, 50%), var(--color-base-300) 0)',
|
|
2316
|
+
},
|
|
2317
|
+
selectors: {
|
|
2318
|
+
// A vertical native range is spelled through writing
|
|
2319
|
+
// mode by the runtime (#170), so the long axis is the
|
|
2320
|
+
// box's HEIGHT and the channel's WIDTH; the fill
|
|
2321
|
+
// grows from the foot.
|
|
2322
|
+
'&[data-orientation="vertical"]': {
|
|
2323
|
+
width: 'var(--slider-thumb-size)',
|
|
2324
|
+
height: 'var(--slider-length)',
|
|
2325
|
+
'--slider-track': 'linear-gradient(to top, var(--slider-fill) var(--slider-percent, 50%), var(--color-base-300) 0)',
|
|
2326
|
+
},
|
|
2327
|
+
'&[data-orientation="vertical"]::-webkit-slider-runnable-track': {
|
|
2328
|
+
width: 'var(--slider-track-size)',
|
|
2329
|
+
height: 'auto',
|
|
2330
|
+
},
|
|
2331
|
+
// Blink lays the thumb against the channel's block
|
|
2332
|
+
// axis — in vertical-lr that is its left edge.
|
|
2333
|
+
'&[data-orientation="vertical"]::-webkit-slider-thumb': {
|
|
2334
|
+
marginTop: '0',
|
|
2335
|
+
marginBlockStart: 'calc((var(--slider-track-size) - var(--slider-thumb-size)) / 2)',
|
|
2336
|
+
},
|
|
2337
|
+
'&[data-orientation="vertical"]::-moz-range-track': {
|
|
2338
|
+
width: 'var(--slider-track-size)',
|
|
2339
|
+
height: '100%',
|
|
2340
|
+
},
|
|
2341
|
+
},
|
|
2342
|
+
},
|
|
2343
|
+
// Vertical (#170): the runtime positions the moving parts by
|
|
2344
|
+
// physical `bottom` percents; these turn the channel upright and
|
|
2345
|
+
// re-centre the knob on the other axis. Web-only — the lynx
|
|
2346
|
+
// runtime ships no vertical slider.
|
|
2347
|
+
root: {
|
|
2348
|
+
selectors: {
|
|
2349
|
+
'&[data-orientation="vertical"]': {
|
|
2350
|
+
width: 'auto',
|
|
2351
|
+
alignItems: 'center',
|
|
2352
|
+
'--slider-length': 'calc(var(--size-selector) * 40)',
|
|
2353
|
+
},
|
|
2354
|
+
},
|
|
2355
|
+
},
|
|
2356
|
+
track: {
|
|
2357
|
+
selectors: {
|
|
2358
|
+
'&[data-orientation="vertical"]': {
|
|
2359
|
+
width: 'var(--slider-track-size)',
|
|
2360
|
+
height: 'var(--slider-length)',
|
|
2361
|
+
marginBlock: '0',
|
|
2362
|
+
marginInline: 'calc((var(--slider-thumb-size) - var(--slider-track-size)) / 2)',
|
|
2363
|
+
},
|
|
2364
|
+
},
|
|
2365
|
+
},
|
|
2366
|
+
range: {
|
|
2367
|
+
selectors: {
|
|
2368
|
+
'&[data-orientation="vertical"]': { insetInlineStart: '0', width: '100%' },
|
|
2369
|
+
},
|
|
2370
|
+
},
|
|
2371
|
+
thumb: {
|
|
2372
|
+
selectors: {
|
|
2373
|
+
'&[data-orientation="vertical"]': {
|
|
2374
|
+
insetBlockStart: 'auto',
|
|
2375
|
+
insetInlineStart: '50%',
|
|
2376
|
+
translate: 'none',
|
|
2377
|
+
marginInlineStart: 'calc(var(--slider-thumb-size) / -2)',
|
|
2378
|
+
marginBlockEnd: 'calc(var(--slider-thumb-size) / -2)',
|
|
2379
|
+
},
|
|
2380
|
+
},
|
|
2381
|
+
},
|
|
2382
|
+
// The label sits beside the channel, centred on its tick.
|
|
2383
|
+
mark: {
|
|
2384
|
+
selectors: {
|
|
2385
|
+
'&[data-orientation="vertical"]': {
|
|
2386
|
+
insetInlineStart: '0',
|
|
2387
|
+
paddingBlockStart: '0',
|
|
2388
|
+
paddingInlineStart: 'calc(var(--slider-track-size) + var(--space-2xs))',
|
|
2389
|
+
translate: '0 50%',
|
|
2390
|
+
},
|
|
2391
|
+
'&[data-orientation="vertical"]::before': {
|
|
2392
|
+
insetBlockStart: '50%',
|
|
2393
|
+
insetInlineStart: '0',
|
|
2394
|
+
width: 'var(--slider-track-size)',
|
|
2395
|
+
height: '2px',
|
|
2396
|
+
translate: '0 -50%',
|
|
2397
|
+
},
|
|
2398
|
+
},
|
|
2399
|
+
},
|
|
2400
|
+
},
|
|
2401
|
+
},
|
|
1938
2402
|
lynx: {
|
|
2403
|
+
// The web's deepened fill, theme-baked: `color-mix()` over theme
|
|
2404
|
+
// colours cannot resolve on device, but the emitter restates a
|
|
2405
|
+
// colour function per theme as literals (as `progress` does for
|
|
2406
|
+
// its accent), so each colour step gets the same 90/10 mix the
|
|
2407
|
+
// web track and knob ring wear. Bound on the root like
|
|
2408
|
+
// `--slider-accent`, rebound by `color` below and by `invalid`
|
|
2409
|
+
// on the control (which wraps the rail on lynx).
|
|
2410
|
+
tokens: { '--slider-fill': sliderFill('var(--color-primary)') },
|
|
2411
|
+
variants: {
|
|
2412
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
2413
|
+
'--slider-fill': sliderFill(`var(--color-${c})`),
|
|
2414
|
+
} } }])),
|
|
2415
|
+
},
|
|
1939
2416
|
parts: {
|
|
2417
|
+
// Vertical (lynx#1144): the lynx runtime positions the moving
|
|
2418
|
+
// parts itself — `top` percentages, the knob centred by
|
|
2419
|
+
// `left: 50%` plus the base's `translateY(-50%)` and
|
|
2420
|
+
// negative `margin-left` — so the class rules only turn the
|
|
2421
|
+
// channel upright: the control is a thumb-wide column of
|
|
2422
|
+
// daisy's rail length, the track fills it.
|
|
2423
|
+
root: {
|
|
2424
|
+
selectors: {
|
|
2425
|
+
'&[data-orientation="vertical"]': { width: 'auto', alignItems: 'center' },
|
|
2426
|
+
},
|
|
2427
|
+
},
|
|
2428
|
+
control: {
|
|
2429
|
+
states: {
|
|
2430
|
+
invalid: { '--slider-fill': sliderFill('var(--color-error)') },
|
|
2431
|
+
},
|
|
2432
|
+
selectors: {
|
|
2433
|
+
'&[data-orientation="vertical"]': {
|
|
2434
|
+
display: 'flex',
|
|
2435
|
+
flexDirection: 'row',
|
|
2436
|
+
justifyContent: 'center',
|
|
2437
|
+
width: 'var(--slider-thumb-size)',
|
|
2438
|
+
height: 'calc(var(--size-selector) * 40)',
|
|
2439
|
+
},
|
|
2440
|
+
},
|
|
2441
|
+
},
|
|
1940
2442
|
track: {
|
|
1941
2443
|
// The web track's `margin-block` breathing room,
|
|
1942
2444
|
// restated physically: logical spellings resolve on iOS
|
|
1943
2445
|
// but not on Android (measured, signalxjs/lynx#1084), so
|
|
1944
2446
|
// the emitter refuses them. Physical is the lynx
|
|
1945
2447
|
// target's norm — no RTL flow there.
|
|
2448
|
+
//
|
|
2449
|
+
// daisy's channel radius is the selector radius (the
|
|
2450
|
+
// native control's runnable-track), not a pill.
|
|
1946
2451
|
base: {
|
|
1947
|
-
marginTop: 'calc(var(--size-
|
|
1948
|
-
marginBottom: 'calc(var(--size-
|
|
2452
|
+
marginTop: 'calc((var(--slider-thumb-size) - var(--slider-track-size)) / 2)',
|
|
2453
|
+
marginBottom: 'calc((var(--slider-thumb-size) - var(--slider-track-size)) / 2)',
|
|
2454
|
+
borderRadius: 'var(--radius-selector)',
|
|
2455
|
+
},
|
|
2456
|
+
selectors: {
|
|
2457
|
+
'&[data-orientation="vertical"]': {
|
|
2458
|
+
width: 'var(--slider-track-size)',
|
|
2459
|
+
height: '100%',
|
|
2460
|
+
marginTop: '0',
|
|
2461
|
+
marginBottom: '0',
|
|
2462
|
+
},
|
|
1949
2463
|
},
|
|
1950
2464
|
},
|
|
1951
2465
|
mark: {
|
|
1952
|
-
//
|
|
1953
|
-
//
|
|
1954
|
-
|
|
2466
|
+
// The web draws the tick with `::before`, which lynx has
|
|
2467
|
+
// no projection for — the mark was an empty, invisible
|
|
2468
|
+
// box. Here the mark IS the tick: a 2px rule across the
|
|
2469
|
+
// channel, centred on its runtime-set `left` percent.
|
|
2470
|
+
// (Lynx marks carry no label text.)
|
|
2471
|
+
base: {
|
|
2472
|
+
top: '0',
|
|
2473
|
+
width: '2px',
|
|
2474
|
+
height: 'var(--slider-track-size)',
|
|
2475
|
+
marginLeft: '-1px',
|
|
2476
|
+
paddingTop: '0',
|
|
2477
|
+
backgroundColor: 'var(--color-base-content)',
|
|
2478
|
+
},
|
|
2479
|
+
selectors: {
|
|
2480
|
+
'&[data-orientation="vertical"]': {
|
|
2481
|
+
left: '0',
|
|
2482
|
+
width: 'var(--slider-track-size)',
|
|
2483
|
+
height: '2px',
|
|
2484
|
+
marginLeft: '0',
|
|
2485
|
+
marginTop: '-1px',
|
|
2486
|
+
},
|
|
2487
|
+
},
|
|
1955
2488
|
},
|
|
1956
2489
|
range: {
|
|
1957
|
-
|
|
1958
|
-
// base-content (`progressFill`'s recipe); the plain
|
|
1959
|
-
// accent — a var() chain, proven working on device — is
|
|
1960
|
-
// the closest lynx-expressible equivalent.
|
|
1961
|
-
base: { background: 'var(--slider-accent)' },
|
|
2490
|
+
base: { background: 'var(--slider-fill)', borderRadius: 'var(--radius-selector)' },
|
|
1962
2491
|
},
|
|
1963
2492
|
thumb: {
|
|
1964
2493
|
// daisy's real range thumb, ported from daisyUI 5
|
|
@@ -1969,13 +2498,16 @@ export const slider = {
|
|
|
1969
2498
|
// `--range-thumb` defaults to `var(--color-base-100)`,
|
|
1970
2499
|
// `--range-p` is daisy's literal `.25rem` (deliberately
|
|
1971
2500
|
// not the theme's size token), and the border ink is the
|
|
1972
|
-
//
|
|
1973
|
-
//
|
|
1974
|
-
//
|
|
1975
|
-
//
|
|
1976
|
-
//
|
|
1977
|
-
//
|
|
1978
|
-
//
|
|
2501
|
+
// deepened fill the web's native thumb ring reads. daisy
|
|
2502
|
+
// paints the progress fill itself as a 100cqw box-shadow
|
|
2503
|
+
// thrown off this thumb — a native-input trick with no
|
|
2504
|
+
// lynx equivalent; the composed `.zx-slider__range` box
|
|
2505
|
+
// above IS that fill here, so the knob only needs its own
|
|
2506
|
+
// paints. Its radius is daisy's formula minus the min()
|
|
2507
|
+
// clamp lynx cannot evaluate — exact for every shipped
|
|
2508
|
+
// theme (all set `--radius-selector` ≥ .25rem / 3) — and
|
|
2509
|
+
// it wears daisy's depth shading (`currentColor`'s 10%
|
|
2510
|
+
// outer shadow spelled as the theme's shade).
|
|
1979
2511
|
//
|
|
1980
2512
|
// The centering geometry is restated physically beside
|
|
1981
2513
|
// the paint: `inset-block-start`/`translate`/
|
|
@@ -1986,10 +2518,12 @@ export const slider = {
|
|
|
1986
2518
|
// proven on both platforms.
|
|
1987
2519
|
base: {
|
|
1988
2520
|
background: 'var(--color-base-100)',
|
|
1989
|
-
border: '0.25rem solid var(--slider-
|
|
2521
|
+
border: '0.25rem solid var(--slider-fill)',
|
|
2522
|
+
borderRadius: 'calc(var(--radius-selector) + 0.25rem)',
|
|
2523
|
+
boxShadow: '0 -1px var(--depth-shade) inset, 0 8px 0 -4px var(--depth-sheen) inset, 0 1px var(--depth-shade)',
|
|
1990
2524
|
top: '50%',
|
|
1991
2525
|
transform: 'translateY(-50%)',
|
|
1992
|
-
marginLeft: 'calc(var(--size
|
|
2526
|
+
marginLeft: 'calc(var(--slider-thumb-size) / -2)',
|
|
1993
2527
|
},
|
|
1994
2528
|
},
|
|
1995
2529
|
},
|
|
@@ -1998,12 +2532,26 @@ export const slider = {
|
|
|
1998
2532
|
};
|
|
1999
2533
|
export const accordion = {
|
|
2000
2534
|
component: 'accordion',
|
|
2535
|
+
targets: {
|
|
2536
|
+
...disclosureExit('accordion'),
|
|
2537
|
+
lynx: {
|
|
2538
|
+
parts: {
|
|
2539
|
+
// No hover on a touch platform: the held trigger takes daisy's
|
|
2540
|
+
// hover fill. The ::after chevron has no lynx counterpart (no
|
|
2541
|
+
// pseudo-elements, and the anatomy declares no part for it) —
|
|
2542
|
+
// an app draws its own glyph in the trigger.
|
|
2543
|
+
trigger: { states: { pressed: { background: 'var(--color-base-200)' } } },
|
|
2544
|
+
},
|
|
2545
|
+
},
|
|
2546
|
+
},
|
|
2001
2547
|
// Same accent grammar as collapsible: the open heading's ink, defaulting
|
|
2002
2548
|
// to base-content so the un-attributed render is unchanged.
|
|
2003
2549
|
tokens: { '--accordion-accent': 'var(--color-base-content)' },
|
|
2004
2550
|
parts: {
|
|
2005
2551
|
root: {
|
|
2006
2552
|
base: { display: 'flex', flexDirection: 'column', gap: 'var(--space-md)' },
|
|
2553
|
+
// `orientation="horizontal"` (#276): the cards sit in a row.
|
|
2554
|
+
selectors: { '&[data-orientation="horizontal"]': { flexDirection: 'row', alignItems: 'start' } },
|
|
2007
2555
|
},
|
|
2008
2556
|
item: withPresence(disclosurePresence, {
|
|
2009
2557
|
base: {
|
|
@@ -2013,6 +2561,7 @@ export const accordion = {
|
|
|
2013
2561
|
overflow: 'hidden',
|
|
2014
2562
|
},
|
|
2015
2563
|
states: { open: {}, closed: {} },
|
|
2564
|
+
selectors: { '[data-scope="accordion"][data-part="root"][data-orientation="horizontal"] > &': { flex: '1 1 0', minInlineSize: '0' } },
|
|
2016
2565
|
}),
|
|
2017
2566
|
trigger: {
|
|
2018
2567
|
base: {
|
|
@@ -2095,14 +2644,20 @@ const fieldControl = {
|
|
|
2095
2644
|
borderRadius: 'var(--radius-field)',
|
|
2096
2645
|
boxShadow: 'var(--shadow-xs)',
|
|
2097
2646
|
};
|
|
2647
|
+
/**
|
|
2648
|
+
* A select whose clear-trigger is rendered (#280) — anchored on the root, so
|
|
2649
|
+
* a part inside the trigger can make room for the button laid over it.
|
|
2650
|
+
*/
|
|
2651
|
+
const SELECT_CLEARABLE = '[data-scope="select"][data-part="root"]:has(> [data-scope="select"][data-part="clear-trigger"]) &';
|
|
2098
2652
|
export const select = {
|
|
2099
2653
|
component: 'select',
|
|
2100
2654
|
// Accent as text/border ink only — daisy's highlighted item is a neutral
|
|
2101
2655
|
// base-200 wash, so no `-on-accent` content colour is consumed here.
|
|
2102
2656
|
tokens: { '--select-accent': 'var(--color-primary)' },
|
|
2103
2657
|
parts: {
|
|
2658
|
+
// Positioned: the clear-trigger sits over the trigger's inline end.
|
|
2104
2659
|
root: {
|
|
2105
|
-
base: { display: 'inline-flex', flexDirection: 'column' },
|
|
2660
|
+
base: { display: 'inline-flex', flexDirection: 'column', position: 'relative' },
|
|
2106
2661
|
},
|
|
2107
2662
|
trigger: {
|
|
2108
2663
|
base: {
|
|
@@ -2115,6 +2670,8 @@ export const select = {
|
|
|
2115
2670
|
},
|
|
2116
2671
|
states: {
|
|
2117
2672
|
hover: { borderColor: 'var(--color-base-content)' },
|
|
2673
|
+
// Readonly answers to nothing, so it does not invite a click.
|
|
2674
|
+
readonly: { cursor: 'default' },
|
|
2118
2675
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
2119
2676
|
open: { borderColor: 'var(--select-accent)' },
|
|
2120
2677
|
closed: {},
|
|
@@ -2151,6 +2708,41 @@ export const select = {
|
|
|
2151
2708
|
indicator: {
|
|
2152
2709
|
base: { opacity: '0.6', transition: 'transform var(--duration-normal) var(--ease-standard)' },
|
|
2153
2710
|
states: { open: { transform: 'rotate(180deg)' }, closed: {} },
|
|
2711
|
+
// Room for the clear-trigger laid over the field (#280).
|
|
2712
|
+
selectors: { [SELECT_CLEARABLE]: { marginInlineStart: 'calc(var(--space-2xl) + var(--space-sm))' } },
|
|
2713
|
+
},
|
|
2714
|
+
// Clears the selection (#280): daisy's ghost-btn over the field's
|
|
2715
|
+
// inline end, just before the chevron — muted like it, the base-200
|
|
2716
|
+
// wash on hover, the base-content ring on focus.
|
|
2717
|
+
'clear-trigger': {
|
|
2718
|
+
base: {
|
|
2719
|
+
appearance: 'none',
|
|
2720
|
+
position: 'absolute',
|
|
2721
|
+
insetBlock: '0',
|
|
2722
|
+
// Centred in the field at WCAG 2.5.8's 24px target, shrinking
|
|
2723
|
+
// only with a field too short to hold it.
|
|
2724
|
+
marginBlock: 'auto',
|
|
2725
|
+
blockSize: 'min(1.5rem, 100%)',
|
|
2726
|
+
minInlineSize: '1.5rem',
|
|
2727
|
+
insetInlineEnd: 'calc(var(--size-field) * 4 + 1em + var(--space-xs))',
|
|
2728
|
+
display: 'inline-flex',
|
|
2729
|
+
alignItems: 'center',
|
|
2730
|
+
justifyContent: 'center',
|
|
2731
|
+
padding: '0 var(--space-xs)',
|
|
2732
|
+
border: 'none',
|
|
2733
|
+
background: 'transparent',
|
|
2734
|
+
color: 'color-mix(in oklab, var(--color-base-content) 70%, transparent)',
|
|
2735
|
+
font: 'inherit',
|
|
2736
|
+
fontSize: 'var(--text-sm)',
|
|
2737
|
+
lineHeight: 'var(--leading-none)',
|
|
2738
|
+
borderRadius: 'var(--radius-field)',
|
|
2739
|
+
cursor: 'pointer',
|
|
2740
|
+
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
2741
|
+
},
|
|
2742
|
+
states: {
|
|
2743
|
+
hover: { background: 'var(--color-base-200)', color: 'var(--color-base-content)' },
|
|
2744
|
+
...focusRing,
|
|
2745
|
+
},
|
|
2154
2746
|
},
|
|
2155
2747
|
popup: withPresence(popupPresence('translateY(-4px)'), {
|
|
2156
2748
|
base: { ...floatingPanel, padding: 'var(--space-md)', minWidth: '13rem' },
|
|
@@ -2199,6 +2791,14 @@ export const select = {
|
|
|
2199
2791
|
base: { fontSize: 'var(--text-xs)', color: 'var(--select-accent)' },
|
|
2200
2792
|
states: { selected: {} },
|
|
2201
2793
|
},
|
|
2794
|
+
// The menu's rule between runs of options (#280).
|
|
2795
|
+
separator: {
|
|
2796
|
+
base: {
|
|
2797
|
+
height: 'var(--border)',
|
|
2798
|
+
margin: 'var(--space-sm) var(--space-md)',
|
|
2799
|
+
background: 'var(--color-base-300)',
|
|
2800
|
+
},
|
|
2801
|
+
},
|
|
2202
2802
|
},
|
|
2203
2803
|
variants: {
|
|
2204
2804
|
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
@@ -2221,8 +2821,28 @@ export const select = {
|
|
|
2221
2821
|
// The trigger ramp restates `md` too (unlike `btnSizes`), so its lynx
|
|
2222
2822
|
// counterpart does as well.
|
|
2223
2823
|
targets: {
|
|
2824
|
+
web: { parts: { popup: { base: anchoredListbox } } },
|
|
2224
2825
|
lynx: {
|
|
2225
|
-
parts: {
|
|
2826
|
+
parts: {
|
|
2827
|
+
// daisy's `.select` is `width: clamp(3rem, 20rem, 100%)`: 20rem,
|
|
2828
|
+
// never wider than its container. lynx has no `clamp()`, so the
|
|
2829
|
+
// same bounds are spelled out — and the base's 13rem floor
|
|
2830
|
+
// comes down to daisy's 3rem, or the trigger overflowed any
|
|
2831
|
+
// cell narrower than 208px (signalxjs/lynx#1146). The root
|
|
2832
|
+
// caps too, so a shrink-wrapped root cannot push it back out.
|
|
2833
|
+
root: { base: { maxWidth: '100%' } },
|
|
2834
|
+
trigger: {
|
|
2835
|
+
base: { ...lynxBtnPad(4), width: '20rem', maxWidth: '100%', minWidth: '3rem' },
|
|
2836
|
+
// The web's `:hover` border is dropped on a touch
|
|
2837
|
+
// platform; a press gets a wash instead — a background,
|
|
2838
|
+
// so it never fights the open/invalid border inks.
|
|
2839
|
+
states: { pressed: { background: 'var(--color-base-200)' } },
|
|
2840
|
+
},
|
|
2841
|
+
// A touch list has no highlight: the pressed item takes the
|
|
2842
|
+
// highlighted item's wash.
|
|
2843
|
+
item: { states: { pressed: { background: 'var(--color-base-200)' } } },
|
|
2844
|
+
'clear-trigger': { base: { top: 'var(--space-xs)', bottom: 'var(--space-xs)', right: 'calc(var(--size-field) * 4 + 1em + var(--space-xs))', minWidth: '1.5rem' } },
|
|
2845
|
+
},
|
|
2226
2846
|
variants: {
|
|
2227
2847
|
size: { ...lynxBtnSizes('trigger'), md: { trigger: { base: lynxBtnPad(4) } } },
|
|
2228
2848
|
},
|
|
@@ -2244,6 +2864,22 @@ export const select = {
|
|
|
2244
2864
|
*/
|
|
2245
2865
|
const BTN_STEPS = { xs: 6, sm: 8, md: 10, lg: 12, xl: 14 };
|
|
2246
2866
|
const btnHeight = (step) => `calc(var(--size-field) * ${BTN_STEPS[step]})`;
|
|
2867
|
+
/**
|
|
2868
|
+
* daisy 5's disabled btn, for the lynx target (signalxjs/lynx#1143). The web
|
|
2869
|
+
* fades the whole button (`opacity: var(--disabled-opacity)`, 0.3 on the
|
|
2870
|
+
* dark themes), which on device left a solid button's label barely legible.
|
|
2871
|
+
* daisy's own `.btn:disabled` paints instead: a base-content wash (not on
|
|
2872
|
+
* ghost/link), a transparent border, no shadow, and a muted base-content
|
|
2873
|
+
* label. daisy mutes that label to 20%; this keeps it at 50%, still plainly
|
|
2874
|
+
* inert but readable on a phone at arm's length.
|
|
2875
|
+
*/
|
|
2876
|
+
const lynxBtnDisabled = {
|
|
2877
|
+
opacity: '1',
|
|
2878
|
+
background: 'color-mix(in oklab, var(--color-base-content) 10%, transparent)',
|
|
2879
|
+
color: 'color-mix(in oklab, var(--color-base-content) 50%, transparent)',
|
|
2880
|
+
borderColor: 'transparent',
|
|
2881
|
+
boxShadow: 'none',
|
|
2882
|
+
};
|
|
2247
2883
|
export const button = {
|
|
2248
2884
|
component: 'button',
|
|
2249
2885
|
// Public to a design system derived from this one (#73).
|
|
@@ -2457,11 +3093,39 @@ export const button = {
|
|
|
2457
3093
|
},
|
|
2458
3094
|
defaultVariants: { color: 'primary', variant: 'solid', size: 'md' },
|
|
2459
3095
|
keyframes: { 'zero-daisyui-btn-spin': 'to { transform: rotate(360deg) }' },
|
|
2460
|
-
// The icon chips' zeroed inline padding, restated physically: logical
|
|
2461
|
-
// spellings resolve on iOS but not on Android (measured,
|
|
2462
|
-
// signalxjs/lynx#1084), so the emitter refuses them.
|
|
2463
3096
|
targets: {
|
|
2464
3097
|
lynx: {
|
|
3098
|
+
parts: {
|
|
3099
|
+
// The ring, restated: `currentColor` never resolves on lynx
|
|
3100
|
+
// (signalxjs/lynx#1079) and the logical sizes/edge are
|
|
3101
|
+
// unproven there, so the lynx ring is physical longhands in
|
|
3102
|
+
// the variant's own ink (the variants below pick it).
|
|
3103
|
+
spinner: {
|
|
3104
|
+
base: {
|
|
3105
|
+
width: '1em',
|
|
3106
|
+
height: '1em',
|
|
3107
|
+
border: 'calc(var(--border) * 2) solid var(--btn-ink)',
|
|
3108
|
+
borderTopColor: 'transparent',
|
|
3109
|
+
},
|
|
3110
|
+
},
|
|
3111
|
+
root: { states: { disabled: lynxBtnDisabled } },
|
|
3112
|
+
},
|
|
3113
|
+
variants: {
|
|
3114
|
+
// The baked disabled paint above outranks every variant's fill
|
|
3115
|
+
// (the theme host adds a class), but its plain declarations
|
|
3116
|
+
// do not: outline/dash restate the border, ghost/link drop
|
|
3117
|
+
// the wash (as in daisy).
|
|
3118
|
+
variant: {
|
|
3119
|
+
solid: { spinner: { base: { borderColor: 'var(--btn-on-accent)', borderTopColor: 'transparent' } } },
|
|
3120
|
+
outline: { root: { states: { disabled: { borderColor: 'transparent' } } } },
|
|
3121
|
+
dash: { root: { states: { disabled: { borderColor: 'transparent' } } } },
|
|
3122
|
+
ghost: { root: { states: { disabled: { background: 'transparent' } } } },
|
|
3123
|
+
link: { root: { states: { disabled: { background: 'transparent' } } } },
|
|
3124
|
+
},
|
|
3125
|
+
},
|
|
3126
|
+
// The icon chips' zeroed inline padding, restated physically: logical
|
|
3127
|
+
// spellings resolve on iOS but not on Android (measured,
|
|
3128
|
+
// signalxjs/lynx#1084), so the emitter refuses them.
|
|
2465
3129
|
modifiers: {
|
|
2466
3130
|
square: { root: { base: { paddingLeft: '0', paddingRight: '0' } } },
|
|
2467
3131
|
circle: { root: { base: { paddingLeft: '0', paddingRight: '0' } } },
|
|
@@ -2700,21 +3364,66 @@ export const toast = {
|
|
|
2700
3364
|
// signalxjs/lynx#1070. No `color` default: the un-attributed toast is a
|
|
2701
3365
|
// neutral base-200 surface, outside the color vocabulary.
|
|
2702
3366
|
defaultVariants: { size: 'md' },
|
|
2703
|
-
// The corner placements' physical anchors: logical inset spellings
|
|
2704
|
-
// resolve on iOS but not on Android (measured, signalxjs/lynx#1084), so
|
|
2705
|
-
// the emitter refuses them. Physical is the lynx target's norm — no RTL
|
|
2706
|
-
// flow there, so `start` IS left and `end` IS right.
|
|
2707
3367
|
targets: {
|
|
2708
3368
|
lynx: {
|
|
2709
3369
|
parts: {
|
|
3370
|
+
// The viewport is a full-width strip on lynx (the runtime pins
|
|
3371
|
+
// it to the outlet's top or bottom edge). What the web gets
|
|
3372
|
+
// from `:popover-open` and `min(24rem, 100vw)` is restated:
|
|
3373
|
+
// flex (a lynx view defaults to linear layout, which ignores
|
|
3374
|
+
// flex-direction and gap), and the cards aligned to the
|
|
3375
|
+
// placement's edge. The centred pair's `left: 50%` +
|
|
3376
|
+
// `translateX(-50%)` pull-back is for a shrink-wrapped popover;
|
|
3377
|
+
// on the strip it shifted everything half a width left, so it
|
|
3378
|
+
// is cancelled. The corner anchors are physical: logical
|
|
3379
|
+
// insets resolve on iOS but not on Android (measured,
|
|
3380
|
+
// signalxjs/lynx#1084), and lynx has no RTL flow, so `start`
|
|
3381
|
+
// IS left and `end` IS right.
|
|
2710
3382
|
viewport: {
|
|
3383
|
+
base: { display: 'flex', width: 'auto' },
|
|
2711
3384
|
selectors: {
|
|
2712
|
-
'&[data-placement="top-start"]': { left: '0' },
|
|
2713
|
-
'&[data-placement="top
|
|
2714
|
-
'&[data-placement="
|
|
2715
|
-
'&[data-placement="bottom-
|
|
3385
|
+
'&[data-placement="top-start"]': { left: '0', alignItems: 'flex-start' },
|
|
3386
|
+
'&[data-placement="top"]': { left: '0', right: '0', transform: 'none', alignItems: 'center' },
|
|
3387
|
+
'&[data-placement="top-end"]': { right: '0', alignItems: 'flex-end' },
|
|
3388
|
+
'&[data-placement="bottom-start"]': { left: '0', alignItems: 'flex-start' },
|
|
3389
|
+
'&[data-placement="bottom"]': { left: '0', right: '0', transform: 'none', alignItems: 'center' },
|
|
3390
|
+
'&[data-placement="bottom-end"]': { right: '0', alignItems: 'flex-end' },
|
|
2716
3391
|
},
|
|
2717
3392
|
},
|
|
3393
|
+
// No grid on lynx (signalxjs/lynx#1075): the card is a flex
|
|
3394
|
+
// column (title, description, then the action), with the
|
|
3395
|
+
// close button pinned to the top-right corner and the title
|
|
3396
|
+
// kept clear of it. The web's 24rem viewport cap becomes the
|
|
3397
|
+
// card's own cap. The `^="top"` entry offset has no class
|
|
3398
|
+
// form, so each top placement restates it.
|
|
3399
|
+
root: {
|
|
3400
|
+
base: {
|
|
3401
|
+
display: 'flex',
|
|
3402
|
+
flexDirection: 'column',
|
|
3403
|
+
alignItems: 'flex-start',
|
|
3404
|
+
position: 'relative',
|
|
3405
|
+
boxSizing: 'border-box',
|
|
3406
|
+
width: '100%',
|
|
3407
|
+
maxWidth: '24rem',
|
|
3408
|
+
},
|
|
3409
|
+
selectors: {
|
|
3410
|
+
'&[data-placement="top-start"]': { '--toast-from': '-8px' },
|
|
3411
|
+
'&[data-placement="top"]': { '--toast-from': '-8px' },
|
|
3412
|
+
'&[data-placement="top-end"]': { '--toast-from': '-8px' },
|
|
3413
|
+
},
|
|
3414
|
+
},
|
|
3415
|
+
title: { base: { paddingRight: '1.75em' } },
|
|
3416
|
+
// The 75% ink mix over a recipe-local property cannot bake;
|
|
3417
|
+
// the same ink at 75% opacity reads the same on the soft fill.
|
|
3418
|
+
description: { base: { color: 'var(--toast-ink)', opacity: '0.75' } },
|
|
3419
|
+
// The 25% ink border cannot bake either: it takes the full ink.
|
|
3420
|
+
action: {
|
|
3421
|
+
base: {
|
|
3422
|
+
marginTop: 'var(--space-xs)',
|
|
3423
|
+
border: 'var(--border) solid var(--toast-ink)',
|
|
3424
|
+
},
|
|
3425
|
+
},
|
|
3426
|
+
close: { base: { position: 'absolute', top: 'var(--space-xs)', right: 'var(--space-xs)' } },
|
|
2718
3427
|
},
|
|
2719
3428
|
},
|
|
2720
3429
|
},
|
|
@@ -2845,6 +3554,23 @@ export const combobox = {
|
|
|
2845
3554
|
disabled: { cursor: 'not-allowed' },
|
|
2846
3555
|
},
|
|
2847
3556
|
},
|
|
3557
|
+
// Clears the value and the text (#280): the chevron's ghost, one
|
|
3558
|
+
// step before it — muted, full ink on hover.
|
|
3559
|
+
'clear-trigger': {
|
|
3560
|
+
base: {
|
|
3561
|
+
appearance: 'none',
|
|
3562
|
+
border: 'none',
|
|
3563
|
+
background: 'transparent',
|
|
3564
|
+
color: 'inherit',
|
|
3565
|
+
font: 'inherit',
|
|
3566
|
+
opacity: '0.6',
|
|
3567
|
+
padding: '0 var(--space-xs)',
|
|
3568
|
+
lineHeight: 'var(--leading-none)',
|
|
3569
|
+
cursor: 'pointer',
|
|
3570
|
+
transition: 'opacity var(--duration-fast) var(--ease-standard)',
|
|
3571
|
+
},
|
|
3572
|
+
states: { hover: { opacity: '1' } },
|
|
3573
|
+
},
|
|
2848
3574
|
popup: withPresence(popupPresence('translateY(-4px)'), {
|
|
2849
3575
|
base: { ...floatingPanel, padding: 'var(--space-md)', minWidth: '13rem' },
|
|
2850
3576
|
states: { open: {}, closed: {} },
|
|
@@ -2900,6 +3626,23 @@ export const combobox = {
|
|
|
2900
3626
|
opacity: '0.6',
|
|
2901
3627
|
},
|
|
2902
3628
|
},
|
|
3629
|
+
// The list still arriving (#280): the empty row's muted grammar.
|
|
3630
|
+
loading: {
|
|
3631
|
+
base: {
|
|
3632
|
+
padding: 'var(--space-lg)',
|
|
3633
|
+
fontSize: 'var(--text-sm)',
|
|
3634
|
+
textAlign: 'center',
|
|
3635
|
+
opacity: '0.6',
|
|
3636
|
+
},
|
|
3637
|
+
},
|
|
3638
|
+
// The menu's rule between runs of options (#280).
|
|
3639
|
+
separator: {
|
|
3640
|
+
base: {
|
|
3641
|
+
height: 'var(--border)',
|
|
3642
|
+
margin: 'var(--space-sm) var(--space-md)',
|
|
3643
|
+
background: 'var(--color-base-300)',
|
|
3644
|
+
},
|
|
3645
|
+
},
|
|
2903
3646
|
},
|
|
2904
3647
|
variants: {
|
|
2905
3648
|
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
@@ -2922,6 +3665,9 @@ export const combobox = {
|
|
|
2922
3665
|
input: ['focus-visible'],
|
|
2923
3666
|
trigger: ['focus-visible'],
|
|
2924
3667
|
},
|
|
3668
|
+
targets: {
|
|
3669
|
+
web: { parts: { popup: { base: anchoredListbox } } },
|
|
3670
|
+
},
|
|
2925
3671
|
};
|
|
2926
3672
|
// daisy "btn" that stays pressed: the off state is the neutral btn, the on
|
|
2927
3673
|
// state is the solid accent fill.
|
|
@@ -3520,7 +4266,9 @@ export const treeView = {
|
|
|
3520
4266
|
opacity: '0.6',
|
|
3521
4267
|
transition: 'transform var(--duration-fast) var(--ease-standard)',
|
|
3522
4268
|
},
|
|
3523
|
-
|
|
4269
|
+
// Loading: the chevron halts at daisy's own collapse-arrow angle —
|
|
4270
|
+
// static (no loop), ink unchanged, so contrast is untouched.
|
|
4271
|
+
states: { open: { transform: 'rotate(90deg)' }, closed: {}, loading: { transform: 'rotate(45deg)' } },
|
|
3524
4272
|
// The glyph is element text the runtime renders (`TreeView.tsx`), not
|
|
3525
4273
|
// `content:`, so the `:dir(rtl)` swap the submenu chevron uses is not
|
|
3526
4274
|
// available here — a mirror is its equivalent. `scale` composes
|
|
@@ -3539,6 +4287,9 @@ export const treeView = {
|
|
|
3539
4287
|
states: { open: {}, closed: {} },
|
|
3540
4288
|
},
|
|
3541
4289
|
},
|
|
4290
|
+
// Open-and-loading content lays out exactly as open content does — the
|
|
4291
|
+
// indicator and the treeitem's aria-busy carry the state.
|
|
4292
|
+
sameAs: { 'branch-content': { loading: 'open' } },
|
|
3542
4293
|
variants: {
|
|
3543
4294
|
// A tree colours one thing: the selected row. Everything else is
|
|
3544
4295
|
// structure, and tinting it would fight the content.
|
|
@@ -3567,6 +4318,32 @@ export const treeView = {
|
|
|
3567
4318
|
},
|
|
3568
4319
|
},
|
|
3569
4320
|
};
|
|
4321
|
+
/**
|
|
4322
|
+
* The affordances inside a text field (#281): daisy's in-field label ink and
|
|
4323
|
+
* the ghost circle button, both following the size step.
|
|
4324
|
+
*/
|
|
4325
|
+
const affixInk = 'color-mix(in oklab, var(--color-base-content) 70%, transparent)';
|
|
4326
|
+
const fieldButton = {
|
|
4327
|
+
appearance: 'none',
|
|
4328
|
+
border: 'none',
|
|
4329
|
+
background: 'transparent',
|
|
4330
|
+
color: affixInk,
|
|
4331
|
+
borderRadius: '9999px',
|
|
4332
|
+
alignSelf: 'center',
|
|
4333
|
+
flex: 'none',
|
|
4334
|
+
order: '1',
|
|
4335
|
+
marginInlineEnd: 'var(--space-xs)',
|
|
4336
|
+
padding: 'var(--space-2xs) var(--space-xs)',
|
|
4337
|
+
fontSize: 'var(--text-sm)',
|
|
4338
|
+
fontWeight: 'var(--weight-semibold)',
|
|
4339
|
+
lineHeight: 'var(--leading-none)',
|
|
4340
|
+
cursor: 'pointer',
|
|
4341
|
+
};
|
|
4342
|
+
const affixSize = (fontSize) => ({
|
|
4343
|
+
adornment: { base: { fontSize } },
|
|
4344
|
+
'clear-trigger': { base: { fontSize } },
|
|
4345
|
+
'visibility-trigger': { base: { fontSize } },
|
|
4346
|
+
});
|
|
3570
4347
|
/**
|
|
3571
4348
|
* daisy "input" flavor: the shared field box with nothing in it but the text.
|
|
3572
4349
|
* Same `fieldControl` metrics as select's trigger, combobox's control and the
|
|
@@ -3593,6 +4370,7 @@ export const input = {
|
|
|
3593
4370
|
base: {
|
|
3594
4371
|
display: 'inline-flex',
|
|
3595
4372
|
alignItems: 'stretch',
|
|
4373
|
+
gap: 'var(--space-2xs)',
|
|
3596
4374
|
...fieldControl,
|
|
3597
4375
|
overflow: 'hidden',
|
|
3598
4376
|
transition: 'border-color var(--duration-fast) var(--ease-standard)',
|
|
@@ -3629,6 +4407,43 @@ export const input = {
|
|
|
3629
4407
|
'&::placeholder': { color: 'color-mix(in oklab, var(--color-base-content) 60%, transparent)' },
|
|
3630
4408
|
},
|
|
3631
4409
|
},
|
|
4410
|
+
// daisy's `label` inside `.input`: a quiet prefix/suffix at one edge,
|
|
4411
|
+
// ordered logically so it flips with the reading direction.
|
|
4412
|
+
adornment: {
|
|
4413
|
+
base: {
|
|
4414
|
+
display: 'inline-flex',
|
|
4415
|
+
alignItems: 'center',
|
|
4416
|
+
flex: 'none',
|
|
4417
|
+
color: affixInk,
|
|
4418
|
+
fontSize: 'var(--text-sm)',
|
|
4419
|
+
lineHeight: 'var(--leading-none)',
|
|
4420
|
+
},
|
|
4421
|
+
states: { disabled: {} },
|
|
4422
|
+
selectors: {
|
|
4423
|
+
'&[data-placement="start"]': { order: '-1', paddingInlineStart: 'var(--space-md)' },
|
|
4424
|
+
'&[data-placement="end"]': { order: '1', paddingInlineEnd: 'var(--space-md)' },
|
|
4425
|
+
},
|
|
4426
|
+
},
|
|
4427
|
+
// `btn btn-ghost btn-circle btn-xs` inside the field.
|
|
4428
|
+
'clear-trigger': {
|
|
4429
|
+
base: fieldButton,
|
|
4430
|
+
states: {
|
|
4431
|
+
hover: { color: 'var(--color-base-content)', background: 'var(--color-base-200)' },
|
|
4432
|
+
disabled: { cursor: 'not-allowed' },
|
|
4433
|
+
...focusRing,
|
|
4434
|
+
},
|
|
4435
|
+
},
|
|
4436
|
+
// Shown: the ghost button's active fill and full ink.
|
|
4437
|
+
'visibility-trigger': {
|
|
4438
|
+
base: fieldButton,
|
|
4439
|
+
states: {
|
|
4440
|
+
on: { color: 'var(--color-base-content)', background: 'var(--color-base-200)' },
|
|
4441
|
+
off: {},
|
|
4442
|
+
hover: { color: 'var(--color-base-content)', background: 'var(--color-base-200)' },
|
|
4443
|
+
disabled: { cursor: 'not-allowed' },
|
|
4444
|
+
...focusRing,
|
|
4445
|
+
},
|
|
4446
|
+
},
|
|
3632
4447
|
},
|
|
3633
4448
|
// The visible ring lives on `control`; the input delegates.
|
|
3634
4449
|
skipStates: { input: ['focus-visible'] },
|
|
@@ -3641,13 +4456,13 @@ export const input = {
|
|
|
3641
4456
|
// The control carries the height off the shared field ramp; the text
|
|
3642
4457
|
// follows so the box stays proportional.
|
|
3643
4458
|
size: {
|
|
3644
|
-
xs: { control: { base: { height: fieldHeight('xs') } }, input: { base: { fontSize: 'var(--text-xs)', padding: 'var(--space-2xs) var(--space-xs)' } } },
|
|
3645
|
-
sm: { control: { base: { height: fieldHeight('sm') } }, input: { base: { fontSize: 'var(--text-xs)', padding: 'var(--space-xs) var(--space-sm)' } } },
|
|
4459
|
+
xs: { control: { base: { height: fieldHeight('xs') } }, input: { base: { fontSize: 'var(--text-xs)', padding: 'var(--space-2xs) var(--space-xs)' } }, ...affixSize('var(--text-xs)') },
|
|
4460
|
+
sm: { control: { base: { height: fieldHeight('sm') } }, input: { base: { fontSize: 'var(--text-xs)', padding: 'var(--space-xs) var(--space-sm)' } }, ...affixSize('var(--text-xs)') },
|
|
3646
4461
|
// `md` is the un-attributed render: the base already IS the
|
|
3647
4462
|
// middle step.
|
|
3648
4463
|
md: {},
|
|
3649
|
-
lg: { control: { base: { height: fieldHeight('lg') } }, input: { base: { fontSize: 'var(--text-md)', padding: 'var(--space-md) var(--space-lg)' } } },
|
|
3650
|
-
xl: { control: { base: { height: fieldHeight('xl') } }, input: { base: { fontSize: 'var(--text-lg)', padding: 'var(--space-lg) var(--space-xl)' } } },
|
|
4464
|
+
lg: { control: { base: { height: fieldHeight('lg') } }, input: { base: { fontSize: 'var(--text-md)', padding: 'var(--space-md) var(--space-lg)' } }, ...affixSize('var(--text-md)') },
|
|
4465
|
+
xl: { control: { base: { height: fieldHeight('xl') } }, input: { base: { fontSize: 'var(--text-lg)', padding: 'var(--space-lg) var(--space-xl)' } }, ...affixSize('var(--text-lg)') },
|
|
3651
4466
|
},
|
|
3652
4467
|
},
|
|
3653
4468
|
};
|
|
@@ -4363,8 +5178,17 @@ export const stats = {
|
|
|
4363
5178
|
tokens: { '--stats-accent': 'var(--color-base-content)' },
|
|
4364
5179
|
parts: {
|
|
4365
5180
|
root: {
|
|
5181
|
+
// The row's own scroll box (#43): a value never wraps or shrinks
|
|
5182
|
+
// (`nowrap` below), so a row too wide for its container scrolls
|
|
5183
|
+
// inside the root — daisyUI's own `.stats` behaviour, and Table's
|
|
5184
|
+
// and Pagination's answer — instead of pushing the page sideways.
|
|
5185
|
+
// Items keep their flex automatic minimum — their min-content
|
|
5186
|
+
// width — so the overflow lands here rather than in the next
|
|
5187
|
+
// item. No ring room: a stat is not focusable, and anything a
|
|
5188
|
+
// consumer puts in one sits inside the item's own padding.
|
|
4366
5189
|
base: {
|
|
4367
5190
|
display: 'flex',
|
|
5191
|
+
overflowX: 'auto',
|
|
4368
5192
|
border: 'var(--border) solid var(--color-base-200)',
|
|
4369
5193
|
borderRadius: 'var(--radius-box)',
|
|
4370
5194
|
background: 'var(--color-base-100)',
|
|
@@ -4401,6 +5225,8 @@ export const stats = {
|
|
|
4401
5225
|
value: {
|
|
4402
5226
|
base: {
|
|
4403
5227
|
gridColumn: '1',
|
|
5228
|
+
// A figure is read whole: `$12 930` never breaks at its space.
|
|
5229
|
+
whiteSpace: 'nowrap',
|
|
4404
5230
|
fontSize: 'var(--text-2xl)',
|
|
4405
5231
|
fontWeight: 'var(--weight-bold)',
|
|
4406
5232
|
fontVariantNumeric: 'tabular-nums',
|
|
@@ -4423,8 +5249,19 @@ export const stats = {
|
|
|
4423
5249
|
},
|
|
4424
5250
|
},
|
|
4425
5251
|
variants: {
|
|
4426
|
-
|
|
4427
|
-
|
|
5252
|
+
// Keyed on the ITEM, not the root (#161): the item re-carries
|
|
5253
|
+
// `color`, so a stat's own value outranks the row's, and the value
|
|
5254
|
+
// inside it inherits whichever won. A root colour still reaches
|
|
5255
|
+
// every item through the carrier's donut.
|
|
5256
|
+
//
|
|
5257
|
+
// The value is TEXT on the page, so the raw role is not always a
|
|
5258
|
+
// readable ink: measured by the text matrix once the item re-carried
|
|
5259
|
+
// the axis (#161), light warning is 1.76:1 and dark neutral 1.26:1
|
|
5260
|
+
// on base-100. `roleInk` is the shared answer for a role used as ink
|
|
5261
|
+
// (a per-role share of the role over base-content, which flips with
|
|
5262
|
+
// the scheme), the same one the field and button text use.
|
|
5263
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { item: { base: {
|
|
5264
|
+
'--stats-accent': roleInk(c),
|
|
4428
5265
|
} } }])),
|
|
4429
5266
|
size: {
|
|
4430
5267
|
xs: { value: { base: { fontSize: 'var(--text-lg)' } } },
|
|
@@ -4470,7 +5307,11 @@ export const timeline = {
|
|
|
4470
5307
|
padding: '0',
|
|
4471
5308
|
},
|
|
4472
5309
|
selectors: {
|
|
4473
|
-
|
|
5310
|
+
// A horizontal timeline is a row of steps whose width follows
|
|
5311
|
+
// the step count, not the container: at phone width it scrolls
|
|
5312
|
+
// inside its own box, the answer Table and Pagination give
|
|
5313
|
+
// (#45), rather than widening the page.
|
|
5314
|
+
'&[data-orientation="horizontal"]': { flexDirection: 'row', overflowX: 'auto' },
|
|
4474
5315
|
},
|
|
4475
5316
|
},
|
|
4476
5317
|
/**
|
|
@@ -4614,6 +5455,87 @@ export const timeline = {
|
|
|
4614
5455
|
xl: { marker: { base: { '--timeline-marker-size': 'calc(var(--size-selector) * 4)' } }, content: { base: { fontSize: 'var(--text-md)' } } },
|
|
4615
5456
|
},
|
|
4616
5457
|
},
|
|
5458
|
+
/**
|
|
5459
|
+
* Lynx has no grid, and it does not resolve `inline-size`/`block-size`:
|
|
5460
|
+
* the item fell back to the engine's default layout and the marker,
|
|
5461
|
+
* sized only logically, stretched into a pill (signalxjs/lynx#1145).
|
|
5462
|
+
*
|
|
5463
|
+
* The item is a wrapping flex row instead. Vertical: line one is the
|
|
5464
|
+
* marker beside its content (start-side content ordered before the
|
|
5465
|
+
* marker), and the connector takes line two by itself (`flex-basis:
|
|
5466
|
+
* 100%`), drawn as a left border under the marker's centre, which is the
|
|
5467
|
+
* web's row-2 segment. Horizontal: line one is the marker with the
|
|
5468
|
+
* connector running on to the item's end, and the content wraps under it
|
|
5469
|
+
* (start-side content above). Content grows from a zero basis, so long
|
|
5470
|
+
* text never wraps onto its own line.
|
|
5471
|
+
*
|
|
5472
|
+
* The web collapses the start track when no item has start-side content
|
|
5473
|
+
* (`:has()`); lynx has no such test. The vertical connector sits under a
|
|
5474
|
+
* marker at the item's start edge, which is where the marker is unless
|
|
5475
|
+
* that item has start-side content: an alternating vertical timeline is
|
|
5476
|
+
* web-only for now.
|
|
5477
|
+
*/
|
|
5478
|
+
targets: {
|
|
5479
|
+
lynx: {
|
|
5480
|
+
parts: {
|
|
5481
|
+
item: { base: { display: 'flex', flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center' } },
|
|
5482
|
+
marker: {
|
|
5483
|
+
base: {
|
|
5484
|
+
width: 'var(--timeline-marker-size)',
|
|
5485
|
+
height: 'var(--timeline-marker-size)',
|
|
5486
|
+
flexGrow: '0',
|
|
5487
|
+
flexShrink: '0',
|
|
5488
|
+
},
|
|
5489
|
+
},
|
|
5490
|
+
connector: {
|
|
5491
|
+
selectors: {
|
|
5492
|
+
'&[data-orientation="vertical"]': {
|
|
5493
|
+
flexGrow: '0',
|
|
5494
|
+
flexShrink: '1',
|
|
5495
|
+
flexBasis: '100%',
|
|
5496
|
+
height: 'var(--space-lg)',
|
|
5497
|
+
background: 'transparent',
|
|
5498
|
+
borderLeftWidth: 'var(--border)',
|
|
5499
|
+
borderLeftStyle: 'solid',
|
|
5500
|
+
borderLeftColor: 'var(--color-base-300)',
|
|
5501
|
+
marginLeft: 'calc(var(--timeline-marker-size) / 2 - var(--border) / 2)',
|
|
5502
|
+
},
|
|
5503
|
+
'&[data-orientation="horizontal"]': {
|
|
5504
|
+
order: '1',
|
|
5505
|
+
flexGrow: '1',
|
|
5506
|
+
flexShrink: '1',
|
|
5507
|
+
flexBasis: '0',
|
|
5508
|
+
height: 'var(--border)',
|
|
5509
|
+
minWidth: 'var(--space-lg)',
|
|
5510
|
+
},
|
|
5511
|
+
},
|
|
5512
|
+
},
|
|
5513
|
+
content: {
|
|
5514
|
+
selectors: {
|
|
5515
|
+
'&[data-orientation="vertical"][data-placement="start"]': {
|
|
5516
|
+
order: '-1',
|
|
5517
|
+
flexGrow: '1',
|
|
5518
|
+
flexShrink: '1',
|
|
5519
|
+
flexBasis: '0',
|
|
5520
|
+
},
|
|
5521
|
+
'&[data-orientation="vertical"][data-placement="end"]': {
|
|
5522
|
+
flexGrow: '1',
|
|
5523
|
+
flexShrink: '1',
|
|
5524
|
+
flexBasis: '0',
|
|
5525
|
+
},
|
|
5526
|
+
'&[data-orientation="horizontal"][data-placement="start"]': {
|
|
5527
|
+
order: '-1',
|
|
5528
|
+
flexBasis: '100%',
|
|
5529
|
+
},
|
|
5530
|
+
'&[data-orientation="horizontal"][data-placement="end"]': {
|
|
5531
|
+
order: '2',
|
|
5532
|
+
flexBasis: '100%',
|
|
5533
|
+
},
|
|
5534
|
+
},
|
|
5535
|
+
},
|
|
5536
|
+
},
|
|
5537
|
+
},
|
|
5538
|
+
},
|
|
4617
5539
|
};
|
|
4618
5540
|
/** daisy chat: the classic bubble, colour-refilled per role. */
|
|
4619
5541
|
export const chat = {
|
|
@@ -5164,9 +6086,27 @@ export const pagination = {
|
|
|
5164
6086
|
'--pg-accent-content': 'var(--color-primary-content)',
|
|
5165
6087
|
'--pg-size': 'calc(var(--size-field) * 10)',
|
|
5166
6088
|
'--pg-font': 'var(--text-sm)',
|
|
6089
|
+
// How far the focus ring reaches outside a control: the root's
|
|
6090
|
+
// scroll box pads by it so the ring is never clipped (#44).
|
|
6091
|
+
'--pg-ring-room': '4px',
|
|
5167
6092
|
},
|
|
5168
6093
|
parts: {
|
|
5169
|
-
root: {
|
|
6094
|
+
root: {
|
|
6095
|
+
// The row's own scroll box (#44): the window is constant-width by
|
|
6096
|
+
// design, so a narrow container scrolls it rather than clipping
|
|
6097
|
+
// it — Table's answer. The padding is room for the focus ring's
|
|
6098
|
+
// 2px + 2px offset, which the scroll box would otherwise clip at
|
|
6099
|
+
// the row's edges; the matching scroll padding keeps it clear
|
|
6100
|
+
// when focus scrolls a page into view.
|
|
6101
|
+
base: {
|
|
6102
|
+
display: 'flex',
|
|
6103
|
+
alignItems: 'center',
|
|
6104
|
+
gap: 'var(--space-2xs)',
|
|
6105
|
+
overflowX: 'auto',
|
|
6106
|
+
padding: 'var(--pg-ring-room)',
|
|
6107
|
+
scrollPaddingInline: 'var(--pg-ring-room)',
|
|
6108
|
+
},
|
|
6109
|
+
},
|
|
5170
6110
|
item: {
|
|
5171
6111
|
base: {
|
|
5172
6112
|
display: 'inline-flex',
|
|
@@ -5624,6 +6564,9 @@ export const drawer = {
|
|
|
5624
6564
|
*/
|
|
5625
6565
|
export const table = {
|
|
5626
6566
|
component: 'table',
|
|
6567
|
+
// A sortable header cell carries the sort state for `aria-sort`'s sake;
|
|
6568
|
+
// its trigger and indicator paint it.
|
|
6569
|
+
skipStates: { 'header-cell': ['ascending', 'descending', 'none'] },
|
|
5627
6570
|
// Public to a design system derived from this one (#73).
|
|
5628
6571
|
hooks: {
|
|
5629
6572
|
properties: {
|
|
@@ -5651,6 +6594,11 @@ export const table = {
|
|
|
5651
6594
|
borderRadius: 'var(--radius-box)',
|
|
5652
6595
|
background: 'var(--color-base-100)',
|
|
5653
6596
|
},
|
|
6597
|
+
// The scroll box is a keyboard stop (#270): daisy's ink ring,
|
|
6598
|
+
// drawn inside the box (negative offset) — the root is as wide
|
|
6599
|
+
// as its column, so an outward ring would be clipped by
|
|
6600
|
+
// whatever scrolls around it.
|
|
6601
|
+
states: { 'focus-visible': { outline: '2px solid var(--color-base-content)', outlineOffset: '-2px' } },
|
|
5654
6602
|
// Stacked (Table.Root stack, #55): zero lays each row out as a block
|
|
5655
6603
|
// below the table's breakpoint; the card it becomes is drawn here.
|
|
5656
6604
|
at: tableStackAt(tokens, 'root', { border: '0', borderRadius: '0', background: 'transparent' }),
|
|
@@ -5704,6 +6652,55 @@ export const table = {
|
|
|
5704
6652
|
},
|
|
5705
6653
|
at: tableStackAt(tokens, 'header-cell', { paddingInline: '0' }),
|
|
5706
6654
|
},
|
|
6655
|
+
// Sorting (#286): daisy's header label made a button — the sorted
|
|
6656
|
+
// column's label takes the full accent ink, and the mark beside it
|
|
6657
|
+
// turns with the direction.
|
|
6658
|
+
'sort-trigger': {
|
|
6659
|
+
base: {
|
|
6660
|
+
appearance: 'none',
|
|
6661
|
+
display: 'inline-flex',
|
|
6662
|
+
alignItems: 'center',
|
|
6663
|
+
margin: '0',
|
|
6664
|
+
border: '0',
|
|
6665
|
+
font: 'inherit',
|
|
6666
|
+
letterSpacing: 'inherit',
|
|
6667
|
+
textTransform: 'inherit',
|
|
6668
|
+
textAlign: 'inherit',
|
|
6669
|
+
cursor: 'pointer',
|
|
6670
|
+
gap: 'var(--space-2xs)',
|
|
6671
|
+
padding: '0',
|
|
6672
|
+
borderRadius: 'var(--radius-selector)',
|
|
6673
|
+
background: 'transparent',
|
|
6674
|
+
color: 'inherit',
|
|
6675
|
+
transition: 'color var(--duration-fast) var(--ease-standard)',
|
|
6676
|
+
},
|
|
6677
|
+
states: {
|
|
6678
|
+
ascending: { color: 'var(--table-accent)' },
|
|
6679
|
+
descending: { color: 'var(--table-accent)' },
|
|
6680
|
+
none: {},
|
|
6681
|
+
hover: { color: 'var(--table-accent)' },
|
|
6682
|
+
disabled: { cursor: 'not-allowed', opacity: 'var(--disabled-opacity)' },
|
|
6683
|
+
...focusRing,
|
|
6684
|
+
},
|
|
6685
|
+
at: { 'reduced-motion': { base: { transition: 'none' } } },
|
|
6686
|
+
},
|
|
6687
|
+
// zero's ▲, turned for descending; an unsorted column shows it only
|
|
6688
|
+
// under a hovered or keyboard-focused trigger.
|
|
6689
|
+
'sort-indicator': {
|
|
6690
|
+
base: {
|
|
6691
|
+
display: 'inline-block',
|
|
6692
|
+
fontSize: '0.75em',
|
|
6693
|
+
lineHeight: '1',
|
|
6694
|
+
transition: 'transform var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard)',
|
|
6695
|
+
},
|
|
6696
|
+
states: {
|
|
6697
|
+
ascending: {},
|
|
6698
|
+
descending: { transform: 'rotate(180deg)' },
|
|
6699
|
+
none: { opacity: '0' },
|
|
6700
|
+
},
|
|
6701
|
+
selectors: { '[data-scope="table"][data-part="sort-trigger"]:is(:hover, [data-focus-visible]) > &[data-state="none"]': { opacity: '0.6' } },
|
|
6702
|
+
at: { 'reduced-motion': { base: { transition: 'none' } } },
|
|
6703
|
+
},
|
|
5707
6704
|
cell: {
|
|
5708
6705
|
base: {
|
|
5709
6706
|
padding: 'var(--table-pad-block) var(--table-pad-inline)',
|
|
@@ -5840,7 +6837,15 @@ export const fileUpload = {
|
|
|
5840
6837
|
borderRadius: 'var(--radius-field)',
|
|
5841
6838
|
background: 'var(--color-base-100)',
|
|
5842
6839
|
},
|
|
5843
|
-
states: {
|
|
6840
|
+
states: {
|
|
6841
|
+
disabled: { opacity: 'var(--disabled-opacity)' },
|
|
6842
|
+
// A rejected file the app renders through Item: daisy's
|
|
6843
|
+
// error border over a faint error wash.
|
|
6844
|
+
invalid: {
|
|
6845
|
+
borderColor: 'var(--color-error)',
|
|
6846
|
+
background: 'color-mix(in oklab, var(--color-error) 8%, var(--color-base-100))',
|
|
6847
|
+
},
|
|
6848
|
+
},
|
|
5844
6849
|
},
|
|
5845
6850
|
'item-name': {
|
|
5846
6851
|
base: {
|
|
@@ -5876,6 +6881,26 @@ export const fileUpload = {
|
|
|
5876
6881
|
...focusRing,
|
|
5877
6882
|
},
|
|
5878
6883
|
},
|
|
6884
|
+
// daisy's btn-ghost btn-xs: the remove button's pill, at text size.
|
|
6885
|
+
'clear-trigger': {
|
|
6886
|
+
base: {
|
|
6887
|
+
appearance: 'none',
|
|
6888
|
+
border: 'none',
|
|
6889
|
+
background: 'transparent',
|
|
6890
|
+
color: 'inherit',
|
|
6891
|
+
borderRadius: '9999px',
|
|
6892
|
+
padding: 'var(--space-2xs) var(--space-sm)',
|
|
6893
|
+
fontSize: 'var(--fu-font)',
|
|
6894
|
+
fontWeight: 'var(--weight-semibold)',
|
|
6895
|
+
lineHeight: 'var(--leading-none)',
|
|
6896
|
+
cursor: 'pointer',
|
|
6897
|
+
},
|
|
6898
|
+
states: {
|
|
6899
|
+
hover: { background: 'var(--color-base-200)' },
|
|
6900
|
+
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
6901
|
+
...focusRing,
|
|
6902
|
+
},
|
|
6903
|
+
},
|
|
5879
6904
|
},
|
|
5880
6905
|
variants: {
|
|
5881
6906
|
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
@@ -6216,6 +7241,9 @@ export const diff = {
|
|
|
6216
7241
|
background: 'var(--color-base-100)',
|
|
6217
7242
|
borderRadius: 'var(--radius-box)',
|
|
6218
7243
|
},
|
|
7244
|
+
// The root holds the images, which are content: disabled is the
|
|
7245
|
+
// handle's to show (#272).
|
|
7246
|
+
states: { disabled: {} },
|
|
6219
7247
|
},
|
|
6220
7248
|
before: {
|
|
6221
7249
|
base: { gridArea: '1 / 1', minWidth: '0' },
|
|
@@ -6245,6 +7273,9 @@ export const diff = {
|
|
|
6245
7273
|
},
|
|
6246
7274
|
states: {
|
|
6247
7275
|
...focusRing,
|
|
7276
|
+
// daisy's disabled look (#272): the uniform fade, and no grab
|
|
7277
|
+
// cursor — the images are content and stay unfaded.
|
|
7278
|
+
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
6248
7279
|
pressed: { '--diff-accent': 'color-mix(in oklab, var(--color-base-content) 85%, var(--color-base-100))' },
|
|
6249
7280
|
},
|
|
6250
7281
|
selectors: {
|
|
@@ -6318,7 +7349,7 @@ export const diff = {
|
|
|
6318
7349
|
};
|
|
6319
7350
|
export const recipes = [
|
|
6320
7351
|
tabs, collapsible, switchRecipe, dialog, popover, tooltip, menu,
|
|
6321
|
-
field, checkbox, radioGroup, progress, slider, accordion, select, button, avatar, toast, combobox,
|
|
7352
|
+
field, fieldset, checkbox, checkboxGroup, radioGroup, progress, slider, accordion, select, button, avatar, toast, combobox,
|
|
6322
7353
|
toggle, toggleGroup, numberInput, ratingGroup, treeView, input, textarea,
|
|
6323
7354
|
card, alert, emptyState, badge, divider, skeleton, spinner,
|
|
6324
7355
|
kbd, status, indicator, stats, timeline, chat, radialProgress, join,
|