@sigx/zero-daisyui 0.6.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 +1 -2
- package/dist/audit.json +1805 -70
- package/dist/components.d.ts +9 -0
- package/dist/components.js +1 -0
- package/dist/css/components/fieldset.css +91 -0
- package/dist/css/components/menu.css +66 -0
- package/dist/css/components/popover.css +72 -0
- package/dist/css/components/progress.css +0 -4
- package/dist/css/components/table.css +70 -0
- package/dist/css/components/tooltip.css +65 -0
- package/dist/css/index.css +363 -4
- package/dist/lynx/components/accordion.css +4 -0
- package/dist/lynx/components/button.css +53 -1
- package/dist/lynx/components/dialog.css +19 -0
- package/dist/lynx/components/fieldset.css +219 -0
- package/dist/lynx/components/popover.css +37 -0
- package/dist/lynx/components/progress.css +0 -20
- package/dist/lynx/components/select.css +12 -1
- package/dist/lynx/components/slider.css +292 -10
- package/dist/lynx/components/switch.css +2 -0
- package/dist/lynx/components/table.css +52 -0
- package/dist/lynx/components/tabs.css +102 -93
- package/dist/lynx/components/timeline.css +58 -1
- package/dist/lynx/components/toast.css +41 -6
- package/dist/lynx/index.css +894 -134
- package/dist/lynx/manifest.json +30 -3
- package/dist/manifest.json +28 -1
- package/dist/recipes.d.ts +10 -0
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +508 -55
- package/dist/recipes.js.map +1 -1
- package/dist/register.d.ts +9 -0
- package/dist/report.json +303 -57
- package/package.json +4 -4
- package/src/recipes.ts +514 -55
package/src/recipes.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* value of that data.
|
|
6
6
|
*/
|
|
7
7
|
import type { CssProps, PartStyles, RecipeInput } from '@sigx/zero-kit';
|
|
8
|
-
import { axisRoles, tableStackAt } from '@sigx/zero-kit/define';
|
|
8
|
+
import { axisRoles, popupArrow, popupArrowHost, tableStackAt } from '@sigx/zero-kit/define';
|
|
9
9
|
import { roles, tokens } from './tokens.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -316,7 +316,9 @@ export const tabs: RecipeInput = {
|
|
|
316
316
|
},
|
|
317
317
|
// daisy draws each flavor's active mark on the tab itself (the
|
|
318
318
|
// underline, the lift, the pill) and none of them slides, so a
|
|
319
|
-
// `Tabs.Indicator` renders nothing here (#283).
|
|
319
|
+
// `Tabs.Indicator` renders nothing here (#283). On lynx, which has
|
|
320
|
+
// no pseudo-elements, the `border` flavor's underline is drawn on
|
|
321
|
+
// this part instead (see `targets.lynx`).
|
|
320
322
|
indicator: { base: { display: 'none' } },
|
|
321
323
|
panel: {
|
|
322
324
|
base: { fontSize: 'var(--text-md)' },
|
|
@@ -520,11 +522,17 @@ export const tabs: RecipeInput = {
|
|
|
520
522
|
targets: {
|
|
521
523
|
lynx: {
|
|
522
524
|
parts: {
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
+
// No hover on a touch platform: the held tab takes the ink
|
|
526
|
+
// daisy's hover gives it.
|
|
527
|
+
tab: { states: { pressed: { color: 'var(--color-base-content)' } } },
|
|
528
|
+
// The list is the indicator's containing block.
|
|
529
|
+
list: { base: { position: 'relative' } },
|
|
530
|
+
// The underline's ink (read by the `border` flavor below) —
|
|
531
|
+
// base-content by default, exactly what the shared
|
|
532
|
+
// `states.active.color` paints on the tab. One class, so any
|
|
525
533
|
// color-axis definition below (host + two classes, baked per
|
|
526
534
|
// theme) wins on specificity.
|
|
527
|
-
|
|
535
|
+
indicator: { base: { '--tab-active-ink': 'var(--color-base-content)' } },
|
|
528
536
|
},
|
|
529
537
|
variants: {
|
|
530
538
|
// `currentColor` never resolves on lynx (measured on device
|
|
@@ -535,19 +543,29 @@ export const tabs: RecipeInput = {
|
|
|
535
543
|
// once per theme — the same per-color/per-theme literal the
|
|
536
544
|
// active `color:` rules already land as.
|
|
537
545
|
color: Object.fromEntries(
|
|
538
|
-
ROLES.map((c) => [c, {
|
|
546
|
+
ROLES.map((c) => [c, { indicator: { base: { '--tab-active-ink': roleInk(c) } } }]),
|
|
539
547
|
),
|
|
540
548
|
variant: {
|
|
541
549
|
// The underline is a ::before on the web, which lynx
|
|
542
|
-
// drops
|
|
543
|
-
//
|
|
544
|
-
// the
|
|
545
|
-
//
|
|
546
|
-
//
|
|
550
|
+
// drops. It is redrawn on the `indicator` part (zero#324),
|
|
551
|
+
// which lynx-zero renders in every list and positions
|
|
552
|
+
// over the active tab from measured geometry (the web's
|
|
553
|
+
// `--tabs-indicator-*` properties are web-only). The part
|
|
554
|
+
// IS the tab's box, so daisy's bar is its bottom edge:
|
|
555
|
+
// 3px, 80% wide and centred, like `left: 10%; width: 80%`
|
|
556
|
+
// on the web. The ink rides `--tab-active-ink` — a plain
|
|
557
|
+
// var() chain, device-proven — instead of `currentColor`,
|
|
558
|
+
// which lynx never resolves.
|
|
547
559
|
border: {
|
|
548
|
-
|
|
549
|
-
base: {
|
|
550
|
-
|
|
560
|
+
indicator: {
|
|
561
|
+
base: {
|
|
562
|
+
display: 'flex',
|
|
563
|
+
boxSizing: 'border-box',
|
|
564
|
+
borderBottomWidth: '3px',
|
|
565
|
+
borderBottomStyle: 'solid',
|
|
566
|
+
borderBottomColor: 'var(--tab-active-ink)',
|
|
567
|
+
transform: 'scaleX(0.8)',
|
|
568
|
+
},
|
|
551
569
|
},
|
|
552
570
|
},
|
|
553
571
|
// `min()` does not exist on lynx (signalxjs/lynx#1066) and
|
|
@@ -900,6 +918,10 @@ export const switchRecipe: RecipeInput = {
|
|
|
900
918
|
// No currentColor on this target — the ink is named
|
|
901
919
|
// directly, and `checked` moves border + fill below.
|
|
902
920
|
border: 'var(--border) solid var(--switch-ink)',
|
|
921
|
+
// daisy's 1px inset depth line along the track's top
|
|
922
|
+
// edge — `currentColor` at `--depth` × 10% on the
|
|
923
|
+
// web, the theme's own depth shade here.
|
|
924
|
+
boxShadow: '0 1px var(--depth-shade) inset',
|
|
903
925
|
userSelect: 'none',
|
|
904
926
|
transition: 'border-color var(--duration-slow) var(--ease-standard), '
|
|
905
927
|
+ 'background-color var(--duration-normal) var(--ease-standard)',
|
|
@@ -920,6 +942,12 @@ export const switchRecipe: RecipeInput = {
|
|
|
920
942
|
height: 'calc(var(--switch-size) - (var(--border) + var(--switch-p)) * 2)',
|
|
921
943
|
borderRadius: 'var(--radius-selector)',
|
|
922
944
|
backgroundColor: 'var(--switch-ink)',
|
|
945
|
+
// daisy's knob relief (`.toggle:before`): a shade
|
|
946
|
+
// along the bottom edge, a sheen across the top, and
|
|
947
|
+
// a 1px drop — the web's `currentColor` drop spelled
|
|
948
|
+
// as the theme's depth shade. The noise layer has no
|
|
949
|
+
// lynx spelling and stays web-only.
|
|
950
|
+
boxShadow: '0 -1px var(--depth-shade) inset, 0 8px 0 -4px var(--depth-sheen) inset, 0 1px var(--depth-shade)',
|
|
923
951
|
transition: 'transform var(--duration-normal) var(--ease-standard), '
|
|
924
952
|
+ 'background-color var(--duration-instant) var(--ease-standard)',
|
|
925
953
|
},
|
|
@@ -1036,6 +1064,22 @@ const lynxBtnSizes = (part: string): Record<string, Record<string, PartStyles>>
|
|
|
1036
1064
|
xl: { [part]: { base: lynxBtnPad(6) } },
|
|
1037
1065
|
});
|
|
1038
1066
|
|
|
1067
|
+
/**
|
|
1068
|
+
* The press rendering of the overlay btns (dialog, popover), for their
|
|
1069
|
+
* `targets.lynx` sections. The web answers a pointer with `:hover`, which
|
|
1070
|
+
* the lynx emitter drops (a touch platform), and these parts' `pressed`
|
|
1071
|
+
* flag — stamped by the lynx runtime on every touch — had no rule, so a
|
|
1072
|
+
* tap showed nothing (signalxjs/lynx#1146). daisy's `:hover` wash plus the
|
|
1073
|
+
* btn's 1px sink, the same pair `button` paints for its own `pressed`. A
|
|
1074
|
+
* colour variant restates the background after the states at equal
|
|
1075
|
+
* specificity, so a coloured trigger keeps its fill and sinks.
|
|
1076
|
+
*/
|
|
1077
|
+
const lynxBtnPressed: NonNullable<PartStyles['base']> = {
|
|
1078
|
+
background: 'var(--color-base-300)',
|
|
1079
|
+
transform: 'translateY(1px)',
|
|
1080
|
+
boxShadow: 'none',
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1039
1083
|
export const dialog: RecipeInput = {
|
|
1040
1084
|
component: 'dialog',
|
|
1041
1085
|
// Public to a design system derived from this one (#73).
|
|
@@ -1127,14 +1171,16 @@ export const dialog: RecipeInput = {
|
|
|
1127
1171
|
// reach and the trigger is the whole story here.
|
|
1128
1172
|
variants: { color: btnColors(), size: btnSizes },
|
|
1129
1173
|
// The btn paddings and the footer's push-down, restated physically —
|
|
1130
|
-
// see `lynxBtnPad` for the #1084 verdict this answers
|
|
1174
|
+
// see `lynxBtnPad` for the #1084 verdict this answers — and the btns'
|
|
1175
|
+
// press rendering (`lynxBtnPressed`). The footer states its row: on lynx
|
|
1176
|
+
// a row container says so next to `display: flex`.
|
|
1131
1177
|
targets: {
|
|
1132
1178
|
lynx: {
|
|
1133
1179
|
parts: {
|
|
1134
|
-
trigger: { base: lynxBtnPad(4) },
|
|
1135
|
-
close: { base: lynxBtnPad(4) },
|
|
1136
|
-
cancel: { base: lynxBtnPad(4) },
|
|
1137
|
-
footer: { base: { marginTop: 'var(--space-2xl)' } },
|
|
1180
|
+
trigger: { base: lynxBtnPad(4), states: { pressed: lynxBtnPressed } },
|
|
1181
|
+
close: { base: lynxBtnPad(4), states: { pressed: lynxBtnPressed } },
|
|
1182
|
+
cancel: { base: lynxBtnPad(4), states: { pressed: lynxBtnPressed } },
|
|
1183
|
+
footer: { base: { marginTop: 'var(--space-2xl)', flexDirection: 'row' } },
|
|
1138
1184
|
},
|
|
1139
1185
|
variants: { size: lynxBtnSizes('trigger') },
|
|
1140
1186
|
},
|
|
@@ -1150,6 +1196,22 @@ const floatingPanel: NonNullable<PartStyles['base']> = {
|
|
|
1150
1196
|
boxShadow: 'var(--shadow-lg)',
|
|
1151
1197
|
};
|
|
1152
1198
|
|
|
1199
|
+
/**
|
|
1200
|
+
* The arrow a floating surface grows toward its anchor (#279), in the
|
|
1201
|
+
* surface's own paint: the dropdown's base-100 and border for popover and
|
|
1202
|
+
* menu, the tooltip's borderless neutral for the tooltip — daisy's
|
|
1203
|
+
* `tooltip` draws exactly that tail under its bubble. Web-only (placed by
|
|
1204
|
+
* the runtime's `--arrow-x`/`--arrow-y`), so it rides `targets.web` with
|
|
1205
|
+
* the popup's permission to let it out.
|
|
1206
|
+
*/
|
|
1207
|
+
const floatingArrow = (scope: string, paint: CssProps, size = '0.625rem'): NonNullable<NonNullable<RecipeInput['targets']>['web']> => ({
|
|
1208
|
+
parts: {
|
|
1209
|
+
popup: { selectors: popupArrowHost(scope) },
|
|
1210
|
+
arrow: popupArrow(scope, { size, paint }),
|
|
1211
|
+
},
|
|
1212
|
+
});
|
|
1213
|
+
const panelArrowPaint: CssProps = { background: 'var(--color-base-100)', border: 'var(--border) solid var(--color-base-300)' };
|
|
1214
|
+
|
|
1153
1215
|
/**
|
|
1154
1216
|
* daisy's dropdown menu for a listbox, sized by the geometry the
|
|
1155
1217
|
* anchored-position strategy publishes (#278): at least the width of the
|
|
@@ -1187,6 +1249,14 @@ export const popover: RecipeInput = {
|
|
|
1187
1249
|
title: {
|
|
1188
1250
|
base: { margin: '0 0 var(--space-md)', fontSize: 'var(--text-md)', fontWeight: 'var(--weight-bold)' },
|
|
1189
1251
|
},
|
|
1252
|
+
// The modal's muted body line, at the dropdown's tighter rhythm.
|
|
1253
|
+
description: {
|
|
1254
|
+
base: {
|
|
1255
|
+
margin: '0 0 var(--space-lg)',
|
|
1256
|
+
fontSize: 'var(--text-sm)',
|
|
1257
|
+
color: 'color-mix(in oklab, var(--color-base-content) 75%, transparent)',
|
|
1258
|
+
},
|
|
1259
|
+
},
|
|
1190
1260
|
close: {
|
|
1191
1261
|
base: { ...btn, height: 'calc(var(--size-field) * 8)', paddingInline: 'calc(var(--size-field) * 3)', fontSize: 'var(--text-xs)' },
|
|
1192
1262
|
states: {
|
|
@@ -1198,12 +1268,14 @@ export const popover: RecipeInput = {
|
|
|
1198
1268
|
},
|
|
1199
1269
|
// Trigger-carried axes — same wiring as dialog, same reason.
|
|
1200
1270
|
variants: { color: btnColors(), size: btnSizes },
|
|
1201
|
-
// The btn paddings, restated physically — see `lynxBtnPad` (#1084)
|
|
1271
|
+
// The btn paddings, restated physically — see `lynxBtnPad` (#1084) —
|
|
1272
|
+
// and the btns' press rendering (`lynxBtnPressed`).
|
|
1202
1273
|
targets: {
|
|
1274
|
+
web: floatingArrow('popover', panelArrowPaint),
|
|
1203
1275
|
lynx: {
|
|
1204
1276
|
parts: {
|
|
1205
|
-
trigger: { base: lynxBtnPad(4) },
|
|
1206
|
-
close: { base: lynxBtnPad(3) },
|
|
1277
|
+
trigger: { base: lynxBtnPad(4), states: { pressed: lynxBtnPressed } },
|
|
1278
|
+
close: { base: lynxBtnPad(3), states: { pressed: lynxBtnPressed } },
|
|
1207
1279
|
},
|
|
1208
1280
|
variants: { size: lynxBtnSizes('trigger') },
|
|
1209
1281
|
},
|
|
@@ -1246,6 +1318,7 @@ export const tooltip: RecipeInput = {
|
|
|
1246
1318
|
variants: { color: btnColors(), size: btnSizes },
|
|
1247
1319
|
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
1248
1320
|
targets: {
|
|
1321
|
+
web: floatingArrow('tooltip', { background: 'var(--color-neutral)' }, '0.5rem'),
|
|
1249
1322
|
lynx: {
|
|
1250
1323
|
parts: { trigger: { base: lynxBtnPad(4) } },
|
|
1251
1324
|
variants: { size: lynxBtnSizes('trigger') },
|
|
@@ -1399,6 +1472,7 @@ export const menu: RecipeInput = {
|
|
|
1399
1472
|
variants: { color: btnColors(), size: btnSizes },
|
|
1400
1473
|
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
1401
1474
|
targets: {
|
|
1475
|
+
web: floatingArrow('menu', panelArrowPaint),
|
|
1402
1476
|
lynx: {
|
|
1403
1477
|
parts: { trigger: { base: lynxBtnPad(4) } },
|
|
1404
1478
|
variants: { size: lynxBtnSizes('trigger') },
|
|
@@ -1456,6 +1530,61 @@ export const field: RecipeInput = {
|
|
|
1456
1530
|
},
|
|
1457
1531
|
};
|
|
1458
1532
|
|
|
1533
|
+
/**
|
|
1534
|
+
* Fieldset (#285), daisyUI 5's `.fieldset` / `.fieldset-legend`: a
|
|
1535
|
+
* one-column grid with a tight row gap and a little block padding, no frame
|
|
1536
|
+
* of its own (daisy draws one only when the app adds `border`), and a
|
|
1537
|
+
* semibold legend with block padding standing in for its margin. The UA
|
|
1538
|
+
* `min-inline-size: min-content` is undone so a wide control cannot push the
|
|
1539
|
+
* group past its column. Colour accents the legend through `roleInk`, the
|
|
1540
|
+
* Field way.
|
|
1541
|
+
*/
|
|
1542
|
+
export const fieldset: RecipeInput = {
|
|
1543
|
+
component: 'fieldset',
|
|
1544
|
+
tokens: { '--fieldset-accent': 'var(--color-base-content)' },
|
|
1545
|
+
parts: {
|
|
1546
|
+
root: {
|
|
1547
|
+
base: {
|
|
1548
|
+
display: 'grid',
|
|
1549
|
+
gridTemplateColumns: '1fr',
|
|
1550
|
+
gridAutoRows: 'max-content',
|
|
1551
|
+
gap: 'var(--space-sm)',
|
|
1552
|
+
minInlineSize: '0',
|
|
1553
|
+
margin: '0',
|
|
1554
|
+
paddingBlock: 'var(--space-xs)',
|
|
1555
|
+
paddingInline: '0',
|
|
1556
|
+
border: 'none',
|
|
1557
|
+
},
|
|
1558
|
+
},
|
|
1559
|
+
legend: {
|
|
1560
|
+
base: {
|
|
1561
|
+
paddingBlock: 'var(--space-sm)',
|
|
1562
|
+
paddingInline: '0',
|
|
1563
|
+
fontSize: 'var(--text-sm)',
|
|
1564
|
+
fontWeight: 'var(--weight-semibold)',
|
|
1565
|
+
color: 'var(--fieldset-accent)',
|
|
1566
|
+
},
|
|
1567
|
+
states: {
|
|
1568
|
+
disabled: { opacity: 'var(--disabled-opacity)' },
|
|
1569
|
+
invalid: { color: roleInk('error') },
|
|
1570
|
+
},
|
|
1571
|
+
},
|
|
1572
|
+
},
|
|
1573
|
+
variants: {
|
|
1574
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
1575
|
+
'--fieldset-accent': roleInk(c),
|
|
1576
|
+
} } }])),
|
|
1577
|
+
size: {
|
|
1578
|
+
xs: { legend: { base: { fontSize: 'var(--text-xs)' } } },
|
|
1579
|
+
sm: { legend: { base: { fontSize: 'var(--text-xs)' } } },
|
|
1580
|
+
// `md` is the un-attributed render.
|
|
1581
|
+
md: {},
|
|
1582
|
+
lg: { legend: { base: { fontSize: 'var(--text-md)' } } },
|
|
1583
|
+
xl: { legend: { base: { fontSize: 'var(--text-lg)' } } },
|
|
1584
|
+
},
|
|
1585
|
+
},
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1459
1588
|
/**
|
|
1460
1589
|
* daisy's tick, drawn by a clip-path that GROWS.
|
|
1461
1590
|
*
|
|
@@ -1962,9 +2091,13 @@ export const progress: RecipeInput = {
|
|
|
1962
2091
|
transition: 'width var(--duration-slow) var(--ease-standard)',
|
|
1963
2092
|
},
|
|
1964
2093
|
states: {
|
|
1965
|
-
// `complete`
|
|
1966
|
-
//
|
|
1967
|
-
|
|
2094
|
+
// `complete` keeps the colour axis. daisyUI 5 has no complete
|
|
2095
|
+
// tint — a full `progress-primary` stays primary — and a
|
|
2096
|
+
// `color="secondary"` bar that turns green at 100% reads as a
|
|
2097
|
+
// bug, not as meaning (signalxjs/lynx#1144). A caller who wants
|
|
2098
|
+
// "done" to look like success says so: `color="success"`.
|
|
2099
|
+
// Declared equal to `loading` in `sameAs` below.
|
|
2100
|
+
complete: {},
|
|
1968
2101
|
loading: {},
|
|
1969
2102
|
indeterminate: { width: '40%', animation: 'zero-daisy-indeterminate 1.2s ease-in-out infinite' },
|
|
1970
2103
|
},
|
|
@@ -2000,6 +2133,8 @@ export const progress: RecipeInput = {
|
|
|
2000
2133
|
// so the manifest states the default for runtimes with no `:not()`
|
|
2001
2134
|
// fallback (the lynx target; signalxjs/lynx#1070).
|
|
2002
2135
|
defaultVariants: { color: 'primary', size: 'md' },
|
|
2136
|
+
// A finished bar is a full bar of the same ink (see `complete` above).
|
|
2137
|
+
sameAs: { root: { complete: 'loading' }, range: { complete: 'loading' } },
|
|
2003
2138
|
keyframes: {
|
|
2004
2139
|
// Logical, so the sweep runs the way the bar fills — `margin-inline-start`
|
|
2005
2140
|
// is animatable and direction-aware on its own. The determinate `width`,
|
|
@@ -2021,6 +2156,10 @@ export const progress: RecipeInput = {
|
|
|
2021
2156
|
},
|
|
2022
2157
|
};
|
|
2023
2158
|
|
|
2159
|
+
/** The slider's deepened fill — the web's `--slider-fill` mix, for a given accent. */
|
|
2160
|
+
const sliderFill = (accent: string): string =>
|
|
2161
|
+
`color-mix(in oklab, ${accent} 90%, var(--color-base-content))`;
|
|
2162
|
+
|
|
2024
2163
|
export const slider: RecipeInput = {
|
|
2025
2164
|
component: 'slider',
|
|
2026
2165
|
// daisy's `.range` metrics: the thumb is `--range-thumb-size`
|
|
@@ -2331,29 +2470,94 @@ export const slider: RecipeInput = {
|
|
|
2331
2470
|
},
|
|
2332
2471
|
},
|
|
2333
2472
|
lynx: {
|
|
2473
|
+
// The web's deepened fill, theme-baked: `color-mix()` over theme
|
|
2474
|
+
// colours cannot resolve on device, but the emitter restates a
|
|
2475
|
+
// colour function per theme as literals (as `progress` does for
|
|
2476
|
+
// its accent), so each colour step gets the same 90/10 mix the
|
|
2477
|
+
// web track and knob ring wear. Bound on the root like
|
|
2478
|
+
// `--slider-accent`, rebound by `color` below and by `invalid`
|
|
2479
|
+
// on the control (which wraps the rail on lynx).
|
|
2480
|
+
tokens: { '--slider-fill': sliderFill('var(--color-primary)') },
|
|
2481
|
+
variants: {
|
|
2482
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
2483
|
+
'--slider-fill': sliderFill(`var(--color-${c})`),
|
|
2484
|
+
} } }])),
|
|
2485
|
+
},
|
|
2334
2486
|
parts: {
|
|
2487
|
+
// Vertical (lynx#1144): the lynx runtime positions the moving
|
|
2488
|
+
// parts itself — `top` percentages, the knob centred by
|
|
2489
|
+
// `left: 50%` plus the base's `translateY(-50%)` and
|
|
2490
|
+
// negative `margin-left` — so the class rules only turn the
|
|
2491
|
+
// channel upright: the control is a thumb-wide column of
|
|
2492
|
+
// daisy's rail length, the track fills it.
|
|
2493
|
+
root: {
|
|
2494
|
+
selectors: {
|
|
2495
|
+
'&[data-orientation="vertical"]': { width: 'auto', alignItems: 'center' },
|
|
2496
|
+
},
|
|
2497
|
+
},
|
|
2498
|
+
control: {
|
|
2499
|
+
states: {
|
|
2500
|
+
invalid: { '--slider-fill': sliderFill('var(--color-error)') },
|
|
2501
|
+
},
|
|
2502
|
+
selectors: {
|
|
2503
|
+
'&[data-orientation="vertical"]': {
|
|
2504
|
+
display: 'flex',
|
|
2505
|
+
flexDirection: 'row',
|
|
2506
|
+
justifyContent: 'center',
|
|
2507
|
+
width: 'var(--slider-thumb-size)',
|
|
2508
|
+
height: 'calc(var(--size-selector) * 40)',
|
|
2509
|
+
},
|
|
2510
|
+
},
|
|
2511
|
+
},
|
|
2335
2512
|
track: {
|
|
2336
2513
|
// The web track's `margin-block` breathing room,
|
|
2337
2514
|
// restated physically: logical spellings resolve on iOS
|
|
2338
2515
|
// but not on Android (measured, signalxjs/lynx#1084), so
|
|
2339
2516
|
// the emitter refuses them. Physical is the lynx
|
|
2340
2517
|
// target's norm — no RTL flow there.
|
|
2518
|
+
//
|
|
2519
|
+
// daisy's channel radius is the selector radius (the
|
|
2520
|
+
// native control's runnable-track), not a pill.
|
|
2341
2521
|
base: {
|
|
2342
2522
|
marginTop: 'calc((var(--slider-thumb-size) - var(--slider-track-size)) / 2)',
|
|
2343
2523
|
marginBottom: 'calc((var(--slider-thumb-size) - var(--slider-track-size)) / 2)',
|
|
2524
|
+
borderRadius: 'var(--radius-selector)',
|
|
2525
|
+
},
|
|
2526
|
+
selectors: {
|
|
2527
|
+
'&[data-orientation="vertical"]': {
|
|
2528
|
+
width: 'var(--slider-track-size)',
|
|
2529
|
+
height: '100%',
|
|
2530
|
+
marginTop: '0',
|
|
2531
|
+
marginBottom: '0',
|
|
2532
|
+
},
|
|
2344
2533
|
},
|
|
2345
2534
|
},
|
|
2346
2535
|
mark: {
|
|
2347
|
-
//
|
|
2348
|
-
//
|
|
2349
|
-
|
|
2536
|
+
// The web draws the tick with `::before`, which lynx has
|
|
2537
|
+
// no projection for — the mark was an empty, invisible
|
|
2538
|
+
// box. Here the mark IS the tick: a 2px rule across the
|
|
2539
|
+
// channel, centred on its runtime-set `left` percent.
|
|
2540
|
+
// (Lynx marks carry no label text.)
|
|
2541
|
+
base: {
|
|
2542
|
+
top: '0',
|
|
2543
|
+
width: '2px',
|
|
2544
|
+
height: 'var(--slider-track-size)',
|
|
2545
|
+
marginLeft: '-1px',
|
|
2546
|
+
paddingTop: '0',
|
|
2547
|
+
backgroundColor: 'var(--color-base-content)',
|
|
2548
|
+
},
|
|
2549
|
+
selectors: {
|
|
2550
|
+
'&[data-orientation="vertical"]': {
|
|
2551
|
+
left: '0',
|
|
2552
|
+
width: 'var(--slider-track-size)',
|
|
2553
|
+
height: '2px',
|
|
2554
|
+
marginLeft: '0',
|
|
2555
|
+
marginTop: '-1px',
|
|
2556
|
+
},
|
|
2557
|
+
},
|
|
2350
2558
|
},
|
|
2351
2559
|
range: {
|
|
2352
|
-
|
|
2353
|
-
// base-content (`progressFill`'s recipe); the plain
|
|
2354
|
-
// accent — a var() chain, proven working on device — is
|
|
2355
|
-
// the closest lynx-expressible equivalent.
|
|
2356
|
-
base: { background: 'var(--slider-accent)' },
|
|
2560
|
+
base: { background: 'var(--slider-fill)', borderRadius: 'var(--radius-selector)' },
|
|
2357
2561
|
},
|
|
2358
2562
|
thumb: {
|
|
2359
2563
|
// daisy's real range thumb, ported from daisyUI 5
|
|
@@ -2364,13 +2568,16 @@ export const slider: RecipeInput = {
|
|
|
2364
2568
|
// `--range-thumb` defaults to `var(--color-base-100)`,
|
|
2365
2569
|
// `--range-p` is daisy's literal `.25rem` (deliberately
|
|
2366
2570
|
// not the theme's size token), and the border ink is the
|
|
2367
|
-
//
|
|
2368
|
-
//
|
|
2369
|
-
//
|
|
2370
|
-
//
|
|
2371
|
-
//
|
|
2372
|
-
//
|
|
2373
|
-
//
|
|
2571
|
+
// deepened fill the web's native thumb ring reads. daisy
|
|
2572
|
+
// paints the progress fill itself as a 100cqw box-shadow
|
|
2573
|
+
// thrown off this thumb — a native-input trick with no
|
|
2574
|
+
// lynx equivalent; the composed `.zx-slider__range` box
|
|
2575
|
+
// above IS that fill here, so the knob only needs its own
|
|
2576
|
+
// paints. Its radius is daisy's formula minus the min()
|
|
2577
|
+
// clamp lynx cannot evaluate — exact for every shipped
|
|
2578
|
+
// theme (all set `--radius-selector` ≥ .25rem / 3) — and
|
|
2579
|
+
// it wears daisy's depth shading (`currentColor`'s 10%
|
|
2580
|
+
// outer shadow spelled as the theme's shade).
|
|
2374
2581
|
//
|
|
2375
2582
|
// The centering geometry is restated physically beside
|
|
2376
2583
|
// the paint: `inset-block-start`/`translate`/
|
|
@@ -2381,7 +2588,9 @@ export const slider: RecipeInput = {
|
|
|
2381
2588
|
// proven on both platforms.
|
|
2382
2589
|
base: {
|
|
2383
2590
|
background: 'var(--color-base-100)',
|
|
2384
|
-
border: '0.25rem solid var(--slider-
|
|
2591
|
+
border: '0.25rem solid var(--slider-fill)',
|
|
2592
|
+
borderRadius: 'calc(var(--radius-selector) + 0.25rem)',
|
|
2593
|
+
boxShadow: '0 -1px var(--depth-shade) inset, 0 8px 0 -4px var(--depth-sheen) inset, 0 1px var(--depth-shade)',
|
|
2385
2594
|
top: '50%',
|
|
2386
2595
|
transform: 'translateY(-50%)',
|
|
2387
2596
|
marginLeft: 'calc(var(--slider-thumb-size) / -2)',
|
|
@@ -2394,7 +2603,18 @@ export const slider: RecipeInput = {
|
|
|
2394
2603
|
|
|
2395
2604
|
export const accordion: RecipeInput = {
|
|
2396
2605
|
component: 'accordion',
|
|
2397
|
-
targets:
|
|
2606
|
+
targets: {
|
|
2607
|
+
...disclosureExit('accordion'),
|
|
2608
|
+
lynx: {
|
|
2609
|
+
parts: {
|
|
2610
|
+
// No hover on a touch platform: the held trigger takes daisy's
|
|
2611
|
+
// hover fill. The ::after chevron has no lynx counterpart (no
|
|
2612
|
+
// pseudo-elements, and the anatomy declares no part for it) —
|
|
2613
|
+
// an app draws its own glyph in the trigger.
|
|
2614
|
+
trigger: { states: { pressed: { background: 'var(--color-base-200)' } } },
|
|
2615
|
+
},
|
|
2616
|
+
},
|
|
2617
|
+
},
|
|
2398
2618
|
// Same accent grammar as collapsible: the open heading's ink, defaulting
|
|
2399
2619
|
// to base-content so the un-attributed render is unchanged.
|
|
2400
2620
|
tokens: { '--accordion-accent': 'var(--color-base-content)' },
|
|
@@ -2678,7 +2898,23 @@ export const select: RecipeInput = {
|
|
|
2678
2898
|
web: { parts: { popup: { base: anchoredListbox } } },
|
|
2679
2899
|
lynx: {
|
|
2680
2900
|
parts: {
|
|
2681
|
-
|
|
2901
|
+
// daisy's `.select` is `width: clamp(3rem, 20rem, 100%)`: 20rem,
|
|
2902
|
+
// never wider than its container. lynx has no `clamp()`, so the
|
|
2903
|
+
// same bounds are spelled out — and the base's 13rem floor
|
|
2904
|
+
// comes down to daisy's 3rem, or the trigger overflowed any
|
|
2905
|
+
// cell narrower than 208px (signalxjs/lynx#1146). The root
|
|
2906
|
+
// caps too, so a shrink-wrapped root cannot push it back out.
|
|
2907
|
+
root: { base: { maxWidth: '100%' } },
|
|
2908
|
+
trigger: {
|
|
2909
|
+
base: { ...lynxBtnPad(4), width: '20rem', maxWidth: '100%', minWidth: '3rem' },
|
|
2910
|
+
// The web's `:hover` border is dropped on a touch
|
|
2911
|
+
// platform; a press gets a wash instead — a background,
|
|
2912
|
+
// so it never fights the open/invalid border inks.
|
|
2913
|
+
states: { pressed: { background: 'var(--color-base-200)' } },
|
|
2914
|
+
},
|
|
2915
|
+
// A touch list has no highlight: the pressed item takes the
|
|
2916
|
+
// highlighted item's wash.
|
|
2917
|
+
item: { states: { pressed: { background: 'var(--color-base-200)' } } },
|
|
2682
2918
|
'clear-trigger': { base: { top: 'var(--space-xs)', bottom: 'var(--space-xs)', right: 'calc(var(--size-field) * 4 + 1em + var(--space-xs))', minWidth: '1.5rem' } },
|
|
2683
2919
|
},
|
|
2684
2920
|
variants: {
|
|
@@ -2706,6 +2942,23 @@ const BTN_STEPS = { xs: 6, sm: 8, md: 10, lg: 12, xl: 14 } as const;
|
|
|
2706
2942
|
const btnHeight = (step: keyof typeof BTN_STEPS): string =>
|
|
2707
2943
|
`calc(var(--size-field) * ${BTN_STEPS[step]})`;
|
|
2708
2944
|
|
|
2945
|
+
/**
|
|
2946
|
+
* daisy 5's disabled btn, for the lynx target (signalxjs/lynx#1143). The web
|
|
2947
|
+
* fades the whole button (`opacity: var(--disabled-opacity)`, 0.3 on the
|
|
2948
|
+
* dark themes), which on device left a solid button's label barely legible.
|
|
2949
|
+
* daisy's own `.btn:disabled` paints instead: a base-content wash (not on
|
|
2950
|
+
* ghost/link), a transparent border, no shadow, and a muted base-content
|
|
2951
|
+
* label. daisy mutes that label to 20%; this keeps it at 50%, still plainly
|
|
2952
|
+
* inert but readable on a phone at arm's length.
|
|
2953
|
+
*/
|
|
2954
|
+
const lynxBtnDisabled: CssProps = {
|
|
2955
|
+
opacity: '1',
|
|
2956
|
+
background: 'color-mix(in oklab, var(--color-base-content) 10%, transparent)',
|
|
2957
|
+
color: 'color-mix(in oklab, var(--color-base-content) 50%, transparent)',
|
|
2958
|
+
borderColor: 'transparent',
|
|
2959
|
+
boxShadow: 'none',
|
|
2960
|
+
};
|
|
2961
|
+
|
|
2709
2962
|
export const button: RecipeInput = {
|
|
2710
2963
|
component: 'button',
|
|
2711
2964
|
// Public to a design system derived from this one (#73).
|
|
@@ -2921,11 +3174,39 @@ export const button: RecipeInput = {
|
|
|
2921
3174
|
},
|
|
2922
3175
|
defaultVariants: { color: 'primary', variant: 'solid', size: 'md' },
|
|
2923
3176
|
keyframes: { 'zero-daisyui-btn-spin': 'to { transform: rotate(360deg) }' },
|
|
2924
|
-
// The icon chips' zeroed inline padding, restated physically: logical
|
|
2925
|
-
// spellings resolve on iOS but not on Android (measured,
|
|
2926
|
-
// signalxjs/lynx#1084), so the emitter refuses them.
|
|
2927
3177
|
targets: {
|
|
2928
3178
|
lynx: {
|
|
3179
|
+
parts: {
|
|
3180
|
+
// The ring, restated: `currentColor` never resolves on lynx
|
|
3181
|
+
// (signalxjs/lynx#1079) and the logical sizes/edge are
|
|
3182
|
+
// unproven there, so the lynx ring is physical longhands in
|
|
3183
|
+
// the variant's own ink (the variants below pick it).
|
|
3184
|
+
spinner: {
|
|
3185
|
+
base: {
|
|
3186
|
+
width: '1em',
|
|
3187
|
+
height: '1em',
|
|
3188
|
+
border: 'calc(var(--border) * 2) solid var(--btn-ink)',
|
|
3189
|
+
borderTopColor: 'transparent',
|
|
3190
|
+
},
|
|
3191
|
+
},
|
|
3192
|
+
root: { states: { disabled: lynxBtnDisabled } },
|
|
3193
|
+
},
|
|
3194
|
+
variants: {
|
|
3195
|
+
// The baked disabled paint above outranks every variant's fill
|
|
3196
|
+
// (the theme host adds a class), but its plain declarations
|
|
3197
|
+
// do not: outline/dash restate the border, ghost/link drop
|
|
3198
|
+
// the wash (as in daisy).
|
|
3199
|
+
variant: {
|
|
3200
|
+
solid: { spinner: { base: { borderColor: 'var(--btn-on-accent)', borderTopColor: 'transparent' } } },
|
|
3201
|
+
outline: { root: { states: { disabled: { borderColor: 'transparent' } } } },
|
|
3202
|
+
dash: { root: { states: { disabled: { borderColor: 'transparent' } } } },
|
|
3203
|
+
ghost: { root: { states: { disabled: { background: 'transparent' } } } },
|
|
3204
|
+
link: { root: { states: { disabled: { background: 'transparent' } } } },
|
|
3205
|
+
},
|
|
3206
|
+
},
|
|
3207
|
+
// The icon chips' zeroed inline padding, restated physically: logical
|
|
3208
|
+
// spellings resolve on iOS but not on Android (measured,
|
|
3209
|
+
// signalxjs/lynx#1084), so the emitter refuses them.
|
|
2929
3210
|
modifiers: {
|
|
2930
3211
|
square: { root: { base: { paddingLeft: '0', paddingRight: '0' } } },
|
|
2931
3212
|
circle: { root: { base: { paddingLeft: '0', paddingRight: '0' } } },
|
|
@@ -3166,21 +3447,66 @@ export const toast: RecipeInput = {
|
|
|
3166
3447
|
// signalxjs/lynx#1070. No `color` default: the un-attributed toast is a
|
|
3167
3448
|
// neutral base-200 surface, outside the color vocabulary.
|
|
3168
3449
|
defaultVariants: { size: 'md' },
|
|
3169
|
-
// The corner placements' physical anchors: logical inset spellings
|
|
3170
|
-
// resolve on iOS but not on Android (measured, signalxjs/lynx#1084), so
|
|
3171
|
-
// the emitter refuses them. Physical is the lynx target's norm — no RTL
|
|
3172
|
-
// flow there, so `start` IS left and `end` IS right.
|
|
3173
3450
|
targets: {
|
|
3174
3451
|
lynx: {
|
|
3175
3452
|
parts: {
|
|
3453
|
+
// The viewport is a full-width strip on lynx (the runtime pins
|
|
3454
|
+
// it to the outlet's top or bottom edge). What the web gets
|
|
3455
|
+
// from `:popover-open` and `min(24rem, 100vw)` is restated:
|
|
3456
|
+
// flex (a lynx view defaults to linear layout, which ignores
|
|
3457
|
+
// flex-direction and gap), and the cards aligned to the
|
|
3458
|
+
// placement's edge. The centred pair's `left: 50%` +
|
|
3459
|
+
// `translateX(-50%)` pull-back is for a shrink-wrapped popover;
|
|
3460
|
+
// on the strip it shifted everything half a width left, so it
|
|
3461
|
+
// is cancelled. The corner anchors are physical: logical
|
|
3462
|
+
// insets resolve on iOS but not on Android (measured,
|
|
3463
|
+
// signalxjs/lynx#1084), and lynx has no RTL flow, so `start`
|
|
3464
|
+
// IS left and `end` IS right.
|
|
3176
3465
|
viewport: {
|
|
3466
|
+
base: { display: 'flex', width: 'auto' },
|
|
3467
|
+
selectors: {
|
|
3468
|
+
'&[data-placement="top-start"]': { left: '0', alignItems: 'flex-start' },
|
|
3469
|
+
'&[data-placement="top"]': { left: '0', right: '0', transform: 'none', alignItems: 'center' },
|
|
3470
|
+
'&[data-placement="top-end"]': { right: '0', alignItems: 'flex-end' },
|
|
3471
|
+
'&[data-placement="bottom-start"]': { left: '0', alignItems: 'flex-start' },
|
|
3472
|
+
'&[data-placement="bottom"]': { left: '0', right: '0', transform: 'none', alignItems: 'center' },
|
|
3473
|
+
'&[data-placement="bottom-end"]': { right: '0', alignItems: 'flex-end' },
|
|
3474
|
+
},
|
|
3475
|
+
},
|
|
3476
|
+
// No grid on lynx (signalxjs/lynx#1075): the card is a flex
|
|
3477
|
+
// column (title, description, then the action), with the
|
|
3478
|
+
// close button pinned to the top-right corner and the title
|
|
3479
|
+
// kept clear of it. The web's 24rem viewport cap becomes the
|
|
3480
|
+
// card's own cap. The `^="top"` entry offset has no class
|
|
3481
|
+
// form, so each top placement restates it.
|
|
3482
|
+
root: {
|
|
3483
|
+
base: {
|
|
3484
|
+
display: 'flex',
|
|
3485
|
+
flexDirection: 'column',
|
|
3486
|
+
alignItems: 'flex-start',
|
|
3487
|
+
position: 'relative',
|
|
3488
|
+
boxSizing: 'border-box',
|
|
3489
|
+
width: '100%',
|
|
3490
|
+
maxWidth: '24rem',
|
|
3491
|
+
},
|
|
3177
3492
|
selectors: {
|
|
3178
|
-
'&[data-placement="top-start"]': {
|
|
3179
|
-
'&[data-placement="top
|
|
3180
|
-
'&[data-placement="
|
|
3181
|
-
|
|
3493
|
+
'&[data-placement="top-start"]': { '--toast-from': '-8px' },
|
|
3494
|
+
'&[data-placement="top"]': { '--toast-from': '-8px' },
|
|
3495
|
+
'&[data-placement="top-end"]': { '--toast-from': '-8px' },
|
|
3496
|
+
},
|
|
3497
|
+
},
|
|
3498
|
+
title: { base: { paddingRight: '1.75em' } },
|
|
3499
|
+
// The 75% ink mix over a recipe-local property cannot bake;
|
|
3500
|
+
// the same ink at 75% opacity reads the same on the soft fill.
|
|
3501
|
+
description: { base: { color: 'var(--toast-ink)', opacity: '0.75' } },
|
|
3502
|
+
// The 25% ink border cannot bake either: it takes the full ink.
|
|
3503
|
+
action: {
|
|
3504
|
+
base: {
|
|
3505
|
+
marginTop: 'var(--space-xs)',
|
|
3506
|
+
border: 'var(--border) solid var(--toast-ink)',
|
|
3182
3507
|
},
|
|
3183
3508
|
},
|
|
3509
|
+
close: { base: { position: 'absolute', top: 'var(--space-xs)', right: 'var(--space-xs)' } },
|
|
3184
3510
|
},
|
|
3185
3511
|
},
|
|
3186
3512
|
},
|
|
@@ -5244,6 +5570,87 @@ export const timeline: RecipeInput = {
|
|
|
5244
5570
|
xl: { marker: { base: { '--timeline-marker-size': 'calc(var(--size-selector) * 4)' } }, content: { base: { fontSize: 'var(--text-md)' } } },
|
|
5245
5571
|
},
|
|
5246
5572
|
},
|
|
5573
|
+
/**
|
|
5574
|
+
* Lynx has no grid, and it does not resolve `inline-size`/`block-size`:
|
|
5575
|
+
* the item fell back to the engine's default layout and the marker,
|
|
5576
|
+
* sized only logically, stretched into a pill (signalxjs/lynx#1145).
|
|
5577
|
+
*
|
|
5578
|
+
* The item is a wrapping flex row instead. Vertical: line one is the
|
|
5579
|
+
* marker beside its content (start-side content ordered before the
|
|
5580
|
+
* marker), and the connector takes line two by itself (`flex-basis:
|
|
5581
|
+
* 100%`), drawn as a left border under the marker's centre, which is the
|
|
5582
|
+
* web's row-2 segment. Horizontal: line one is the marker with the
|
|
5583
|
+
* connector running on to the item's end, and the content wraps under it
|
|
5584
|
+
* (start-side content above). Content grows from a zero basis, so long
|
|
5585
|
+
* text never wraps onto its own line.
|
|
5586
|
+
*
|
|
5587
|
+
* The web collapses the start track when no item has start-side content
|
|
5588
|
+
* (`:has()`); lynx has no such test. The vertical connector sits under a
|
|
5589
|
+
* marker at the item's start edge, which is where the marker is unless
|
|
5590
|
+
* that item has start-side content: an alternating vertical timeline is
|
|
5591
|
+
* web-only for now.
|
|
5592
|
+
*/
|
|
5593
|
+
targets: {
|
|
5594
|
+
lynx: {
|
|
5595
|
+
parts: {
|
|
5596
|
+
item: { base: { display: 'flex', flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center' } },
|
|
5597
|
+
marker: {
|
|
5598
|
+
base: {
|
|
5599
|
+
width: 'var(--timeline-marker-size)',
|
|
5600
|
+
height: 'var(--timeline-marker-size)',
|
|
5601
|
+
flexGrow: '0',
|
|
5602
|
+
flexShrink: '0',
|
|
5603
|
+
},
|
|
5604
|
+
},
|
|
5605
|
+
connector: {
|
|
5606
|
+
selectors: {
|
|
5607
|
+
'&[data-orientation="vertical"]': {
|
|
5608
|
+
flexGrow: '0',
|
|
5609
|
+
flexShrink: '1',
|
|
5610
|
+
flexBasis: '100%',
|
|
5611
|
+
height: 'var(--space-lg)',
|
|
5612
|
+
background: 'transparent',
|
|
5613
|
+
borderLeftWidth: 'var(--border)',
|
|
5614
|
+
borderLeftStyle: 'solid',
|
|
5615
|
+
borderLeftColor: 'var(--color-base-300)',
|
|
5616
|
+
marginLeft: 'calc(var(--timeline-marker-size) / 2 - var(--border) / 2)',
|
|
5617
|
+
},
|
|
5618
|
+
'&[data-orientation="horizontal"]': {
|
|
5619
|
+
order: '1',
|
|
5620
|
+
flexGrow: '1',
|
|
5621
|
+
flexShrink: '1',
|
|
5622
|
+
flexBasis: '0',
|
|
5623
|
+
height: 'var(--border)',
|
|
5624
|
+
minWidth: 'var(--space-lg)',
|
|
5625
|
+
},
|
|
5626
|
+
},
|
|
5627
|
+
},
|
|
5628
|
+
content: {
|
|
5629
|
+
selectors: {
|
|
5630
|
+
'&[data-orientation="vertical"][data-placement="start"]': {
|
|
5631
|
+
order: '-1',
|
|
5632
|
+
flexGrow: '1',
|
|
5633
|
+
flexShrink: '1',
|
|
5634
|
+
flexBasis: '0',
|
|
5635
|
+
},
|
|
5636
|
+
'&[data-orientation="vertical"][data-placement="end"]': {
|
|
5637
|
+
flexGrow: '1',
|
|
5638
|
+
flexShrink: '1',
|
|
5639
|
+
flexBasis: '0',
|
|
5640
|
+
},
|
|
5641
|
+
'&[data-orientation="horizontal"][data-placement="start"]': {
|
|
5642
|
+
order: '-1',
|
|
5643
|
+
flexBasis: '100%',
|
|
5644
|
+
},
|
|
5645
|
+
'&[data-orientation="horizontal"][data-placement="end"]': {
|
|
5646
|
+
order: '2',
|
|
5647
|
+
flexBasis: '100%',
|
|
5648
|
+
},
|
|
5649
|
+
},
|
|
5650
|
+
},
|
|
5651
|
+
},
|
|
5652
|
+
},
|
|
5653
|
+
},
|
|
5247
5654
|
};
|
|
5248
5655
|
|
|
5249
5656
|
/** daisy chat: the classic bubble, colour-refilled per role. */
|
|
@@ -6288,6 +6695,9 @@ export const drawer: RecipeInput = {
|
|
|
6288
6695
|
*/
|
|
6289
6696
|
export const table: RecipeInput = {
|
|
6290
6697
|
component: 'table',
|
|
6698
|
+
// A sortable header cell carries the sort state for `aria-sort`'s sake;
|
|
6699
|
+
// its trigger and indicator paint it.
|
|
6700
|
+
skipStates: { 'header-cell': ['ascending', 'descending', 'none'] },
|
|
6291
6701
|
// Public to a design system derived from this one (#73).
|
|
6292
6702
|
hooks: {
|
|
6293
6703
|
properties: {
|
|
@@ -6373,6 +6783,55 @@ export const table: RecipeInput = {
|
|
|
6373
6783
|
},
|
|
6374
6784
|
at: tableStackAt(tokens, 'header-cell', { paddingInline: '0' }),
|
|
6375
6785
|
},
|
|
6786
|
+
// Sorting (#286): daisy's header label made a button — the sorted
|
|
6787
|
+
// column's label takes the full accent ink, and the mark beside it
|
|
6788
|
+
// turns with the direction.
|
|
6789
|
+
'sort-trigger': {
|
|
6790
|
+
base: {
|
|
6791
|
+
appearance: 'none',
|
|
6792
|
+
display: 'inline-flex',
|
|
6793
|
+
alignItems: 'center',
|
|
6794
|
+
margin: '0',
|
|
6795
|
+
border: '0',
|
|
6796
|
+
font: 'inherit',
|
|
6797
|
+
letterSpacing: 'inherit',
|
|
6798
|
+
textTransform: 'inherit',
|
|
6799
|
+
textAlign: 'inherit',
|
|
6800
|
+
cursor: 'pointer',
|
|
6801
|
+
gap: 'var(--space-2xs)',
|
|
6802
|
+
padding: '0',
|
|
6803
|
+
borderRadius: 'var(--radius-selector)',
|
|
6804
|
+
background: 'transparent',
|
|
6805
|
+
color: 'inherit',
|
|
6806
|
+
transition: 'color var(--duration-fast) var(--ease-standard)',
|
|
6807
|
+
},
|
|
6808
|
+
states: {
|
|
6809
|
+
ascending: { color: 'var(--table-accent)' },
|
|
6810
|
+
descending: { color: 'var(--table-accent)' },
|
|
6811
|
+
none: {},
|
|
6812
|
+
hover: { color: 'var(--table-accent)' },
|
|
6813
|
+
disabled: { cursor: 'not-allowed', opacity: 'var(--disabled-opacity)' },
|
|
6814
|
+
...focusRing,
|
|
6815
|
+
},
|
|
6816
|
+
at: { 'reduced-motion': { base: { transition: 'none' } } },
|
|
6817
|
+
},
|
|
6818
|
+
// zero's ▲, turned for descending; an unsorted column shows it only
|
|
6819
|
+
// under a hovered or keyboard-focused trigger.
|
|
6820
|
+
'sort-indicator': {
|
|
6821
|
+
base: {
|
|
6822
|
+
display: 'inline-block',
|
|
6823
|
+
fontSize: '0.75em',
|
|
6824
|
+
lineHeight: '1',
|
|
6825
|
+
transition: 'transform var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard)',
|
|
6826
|
+
},
|
|
6827
|
+
states: {
|
|
6828
|
+
ascending: {},
|
|
6829
|
+
descending: { transform: 'rotate(180deg)' },
|
|
6830
|
+
none: { opacity: '0' },
|
|
6831
|
+
},
|
|
6832
|
+
selectors: { '[data-scope="table"][data-part="sort-trigger"]:is(:hover, [data-focus-visible]) > &[data-state="none"]': { opacity: '0.6' } },
|
|
6833
|
+
at: { 'reduced-motion': { base: { transition: 'none' } } },
|
|
6834
|
+
},
|
|
6376
6835
|
cell: {
|
|
6377
6836
|
base: {
|
|
6378
6837
|
padding: 'var(--table-pad-block) var(--table-pad-inline)',
|
|
@@ -7027,7 +7486,7 @@ export const diff: RecipeInput = {
|
|
|
7027
7486
|
|
|
7028
7487
|
export const recipes: RecipeInput[] = [
|
|
7029
7488
|
tabs, collapsible, switchRecipe, dialog, popover, tooltip, menu,
|
|
7030
|
-
field, checkbox, checkboxGroup, radioGroup, progress, slider, accordion, select, button, avatar, toast, combobox,
|
|
7489
|
+
field, fieldset, checkbox, checkboxGroup, radioGroup, progress, slider, accordion, select, button, avatar, toast, combobox,
|
|
7031
7490
|
toggle, toggleGroup, numberInput, ratingGroup, treeView, input, textarea,
|
|
7032
7491
|
card, alert, emptyState, badge, divider, skeleton, spinner,
|
|
7033
7492
|
kbd, status, indicator, stats, timeline, chat, radialProgress, join,
|