@sigx/zero-daisyui 0.2.0-beta.5 → 0.3.0-beta.1
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 +78 -5
- package/dist/audit.json +271915 -0
- package/dist/components.d.ts +75 -18
- package/dist/components.js +15 -3
- package/dist/css/breakpoints.css +7 -0
- package/dist/css/components/avatar.css +12 -0
- package/dist/css/components/badge.css +130 -1
- package/dist/css/components/box.css +58 -0
- package/dist/css/components/button.css +17 -17
- package/dist/css/components/center.css +17 -0
- package/dist/css/components/checkbox.css +1 -1
- package/dist/css/components/combobox.css +57 -6
- package/dist/css/components/container.css +16 -0
- package/dist/css/components/countdown.css +5 -0
- package/dist/css/components/dialog.css +3 -2
- package/dist/css/components/drawer.css +102 -6
- package/dist/css/components/empty-state.css +164 -0
- package/dist/css/components/grid.css +32 -0
- package/dist/css/components/kbd.css +1 -1
- package/dist/css/components/menu.css +4 -4
- package/dist/css/components/nav-list.css +165 -0
- package/dist/css/components/radio-group.css +2 -2
- package/dist/css/components/select.css +1 -1
- package/dist/css/components/spacer.css +13 -0
- package/dist/css/components/stack.css +39 -0
- package/dist/css/components/steps.css +109 -28
- package/dist/css/components/table.css +276 -2
- package/dist/css/components/timeline.css +64 -1
- package/dist/css/components/tree-view.css +2 -2
- package/dist/css/index.css +2220 -279
- package/dist/css/index.d.ts +2 -0
- package/dist/css/tokens.css +9 -0
- package/dist/design-system.d.ts.map +1 -1
- package/dist/design-system.js +15 -8
- package/dist/design-system.js.map +1 -1
- package/dist/lynx/components/avatar.css +12 -0
- package/dist/lynx/components/badge.css +56 -1
- package/dist/lynx/components/box.css +54 -0
- package/dist/lynx/components/breadcrumbs.css +10 -10
- package/dist/lynx/components/button.css +19 -6
- package/dist/lynx/components/card.css +5 -5
- package/dist/lynx/components/carousel.css +4 -4
- package/dist/lynx/components/center.css +13 -0
- package/dist/lynx/components/chat.css +12 -11
- package/dist/lynx/components/checkbox.css +4 -5
- package/dist/lynx/components/combobox.css +49 -6
- package/dist/lynx/components/container.css +11 -0
- package/dist/lynx/components/countdown.css +5 -0
- package/dist/lynx/components/dialog.css +22 -14
- package/dist/lynx/components/diff.css +6 -4
- package/dist/lynx/components/drawer.css +30 -6
- package/dist/lynx/components/empty-state.css +342 -0
- package/dist/lynx/components/file-upload.css +10 -10
- package/dist/lynx/components/grid.css +28 -0
- package/dist/lynx/components/indicator.css +24 -24
- package/dist/lynx/components/kbd.css +1 -1
- package/dist/lynx/components/menu.css +14 -9
- package/dist/lynx/components/nav-list.css +302 -0
- package/dist/lynx/components/number-input.css +4 -2
- package/dist/lynx/components/pagination.css +7 -6
- package/dist/lynx/components/popover.css +12 -6
- package/dist/lynx/components/progress.css +1 -1
- package/dist/lynx/components/radial-progress.css +5 -5
- package/dist/lynx/components/radio-group.css +2 -2
- package/dist/lynx/components/rating-group.css +5 -5
- package/dist/lynx/components/select.css +18 -12
- package/dist/lynx/components/slider.css +6 -5
- package/dist/lynx/components/spacer.css +11 -0
- package/dist/lynx/components/stack.css +35 -0
- package/dist/lynx/components/stats.css +10 -10
- package/dist/lynx/components/steps.css +117 -77
- package/dist/lynx/components/switch.css +5 -5
- package/dist/lynx/components/table.css +18 -12
- package/dist/lynx/components/tabs.css +5 -5
- package/dist/lynx/components/timeline.css +14 -5
- package/dist/lynx/components/toast.css +4 -4
- package/dist/lynx/components/tooltip.css +10 -5
- package/dist/lynx/components/tree-view.css +3 -3
- package/dist/lynx/index.css +1402 -450
- package/dist/lynx/manifest.json +354 -37
- package/dist/lynx/tokens.css +36 -0
- package/dist/manifest.json +351 -34
- package/dist/recipes.d.ts +17 -10
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +866 -153
- package/dist/recipes.js.map +1 -1
- package/dist/register.d.ts +89 -14
- package/dist/report.json +2815 -408
- package/dist/tokens.d.ts +22 -11
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +57 -7
- package/dist/tokens.js.map +1 -1
- package/package.json +28 -10
- package/src/design-system.ts +15 -8
- package/src/recipes.ts +880 -157
- package/src/tokens.ts +54 -7
- package/dist/css/components/native-select.css +0 -128
- package/dist/lynx/components/native-select.css +0 -130
package/dist/recipes.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { axisRoles, tableStackAt } from '@sigx/zero-kit/define';
|
|
2
|
+
import { roles, tokens } from './tokens.js';
|
|
2
3
|
/**
|
|
3
4
|
* Every role a consumer can pass as `color`, derived from the declaration
|
|
4
5
|
* rather than retyped — a role declared in `tokens.ts` but missing here would
|
|
@@ -6,9 +7,7 @@ import { roles } from './tokens.js';
|
|
|
6
7
|
* `-content` or `-soft` are fills or hairlines, not action colours; this
|
|
7
8
|
* design system declares none.
|
|
8
9
|
*/
|
|
9
|
-
const ROLES =
|
|
10
|
-
.filter(([, decl]) => decl.content !== false && decl.soft !== false)
|
|
11
|
-
.map(([name]) => name);
|
|
10
|
+
const ROLES = axisRoles(roles);
|
|
12
11
|
/**
|
|
13
12
|
* "…and the reading direction is right-to-left" — appended to a selector, never
|
|
14
13
|
* written alone.
|
|
@@ -506,6 +505,13 @@ const disclosureSizes = {
|
|
|
506
505
|
// daisy "collapse collapse-arrow" flavor.
|
|
507
506
|
export const collapsible = {
|
|
508
507
|
component: 'collapsible',
|
|
508
|
+
// Public to a design system derived from this one (#73).
|
|
509
|
+
hooks: {
|
|
510
|
+
properties: {
|
|
511
|
+
'--collapsible-accent': 'The accent of the open trigger.',
|
|
512
|
+
},
|
|
513
|
+
pseudo: { trigger: ['::after'] },
|
|
514
|
+
},
|
|
509
515
|
// The accent is the open heading's INK, so the default is base-content —
|
|
510
516
|
// the un-attributed render stays exactly daisy's neutral collapse and a
|
|
511
517
|
// role only arrives through `data-color`.
|
|
@@ -579,6 +585,14 @@ export const collapsible = {
|
|
|
579
585
|
// daisy "toggle" flavor.
|
|
580
586
|
export const switchRecipe = {
|
|
581
587
|
component: 'switch',
|
|
588
|
+
// Public to a design system derived from this one (#73).
|
|
589
|
+
hooks: {
|
|
590
|
+
properties: {
|
|
591
|
+
'--switch-size': 'The height; width, padding and knob travel derive from it.',
|
|
592
|
+
'--switch-accent': 'The checked track and knob accent.',
|
|
593
|
+
'--switch-ink': 'The unchecked border, knob and ring ink (daisy --input-color).',
|
|
594
|
+
},
|
|
595
|
+
},
|
|
582
596
|
/**
|
|
583
597
|
* daisy drives the whole toggle off ONE size, exactly as it drives the
|
|
584
598
|
* checkbox: `--size` is the height, the padding is a fixed eighth of it,
|
|
@@ -877,8 +891,30 @@ const btnSizes = {
|
|
|
877
891
|
lg: { trigger: { base: { height: fieldHeight('lg'), paddingInline: 'calc(var(--size-field) * 5)', fontSize: 'var(--text-md)' } } },
|
|
878
892
|
xl: { trigger: { base: { height: fieldHeight('xl'), paddingInline: 'calc(var(--size-field) * 6)', fontSize: 'var(--text-lg)' } } },
|
|
879
893
|
};
|
|
894
|
+
/**
|
|
895
|
+
* `btn`'s inline padding restated physically, for the `targets.lynx`
|
|
896
|
+
* sections of every btn-wearing recipe: logical spellings resolve on iOS but
|
|
897
|
+
* not on Android (measured, signalxjs/lynx#1084), so the lynx emitter
|
|
898
|
+
* refuses `padding-inline`. Physical is that target's norm — lynx has no
|
|
899
|
+
* RTL flow for the logical spelling to flip with.
|
|
900
|
+
*/
|
|
901
|
+
const lynxBtnPad = (mult) => ({
|
|
902
|
+
paddingLeft: `calc(var(--size-field) * ${mult})`,
|
|
903
|
+
paddingRight: `calc(var(--size-field) * ${mult})`,
|
|
904
|
+
});
|
|
905
|
+
/** The btn size ramp's physical inline padding, for `targets.lynx.variants`. */
|
|
906
|
+
const lynxBtnSizes = (part) => ({
|
|
907
|
+
xs: { [part]: { base: lynxBtnPad(2) } },
|
|
908
|
+
sm: { [part]: { base: lynxBtnPad(3) } },
|
|
909
|
+
lg: { [part]: { base: lynxBtnPad(5) } },
|
|
910
|
+
xl: { [part]: { base: lynxBtnPad(6) } },
|
|
911
|
+
});
|
|
880
912
|
export const dialog = {
|
|
881
913
|
component: 'dialog',
|
|
914
|
+
// Public to a design system derived from this one (#73).
|
|
915
|
+
hooks: {
|
|
916
|
+
keyframes: ['zero-daisy-pop'],
|
|
917
|
+
},
|
|
882
918
|
keyframes: {
|
|
883
919
|
'zero-daisy-pop': 'from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); }',
|
|
884
920
|
},
|
|
@@ -895,7 +931,12 @@ export const dialog = {
|
|
|
895
931
|
},
|
|
896
932
|
popup: withPresence(popupPresence('translateY(8px) scale(0.97)'), {
|
|
897
933
|
base: {
|
|
898
|
-
|
|
934
|
+
// A <dialog> keeps the UA's `content-box`, and zero ships no
|
|
935
|
+
// reset — so a width or max-width meant to leave a gutter
|
|
936
|
+
// grew by the padding, and at phone width the popup ran past
|
|
937
|
+
// both edges (#101). Its box is the border box.
|
|
938
|
+
boxSizing: 'border-box',
|
|
939
|
+
padding: 'var(--space-2xl)',
|
|
899
940
|
maxWidth: '32rem',
|
|
900
941
|
width: 'calc(100% - 2rem)',
|
|
901
942
|
background: 'var(--color-base-100)',
|
|
@@ -925,14 +966,15 @@ export const dialog = {
|
|
|
925
966
|
},
|
|
926
967
|
// daisy's `modal-action`: the actions sit at the trailing edge, one
|
|
927
968
|
// step of the button gap apart, pushed down by the modal's own
|
|
928
|
-
// padding
|
|
929
|
-
//
|
|
969
|
+
// padding — daisy writes 1.5rem for both, and both now read the top
|
|
970
|
+
// of the ramp (`--space-2xl`, 1.25rem) so a density override moves
|
|
971
|
+
// the popup's inset and the footer's push-down together.
|
|
930
972
|
footer: {
|
|
931
973
|
base: {
|
|
932
974
|
display: 'flex',
|
|
933
975
|
justifyContent: 'flex-end',
|
|
934
976
|
gap: 'var(--space-md)',
|
|
935
|
-
marginBlockStart: '
|
|
977
|
+
marginBlockStart: 'var(--space-2xl)',
|
|
936
978
|
},
|
|
937
979
|
},
|
|
938
980
|
close: {
|
|
@@ -957,6 +999,19 @@ export const dialog = {
|
|
|
957
999
|
// Trigger-carried axes — see `btnColors` for why the popup is out of
|
|
958
1000
|
// reach and the trigger is the whole story here.
|
|
959
1001
|
variants: { color: btnColors(), size: btnSizes },
|
|
1002
|
+
// The btn paddings and the footer's push-down, restated physically —
|
|
1003
|
+
// see `lynxBtnPad` for the #1084 verdict this answers.
|
|
1004
|
+
targets: {
|
|
1005
|
+
lynx: {
|
|
1006
|
+
parts: {
|
|
1007
|
+
trigger: { base: lynxBtnPad(4) },
|
|
1008
|
+
close: { base: lynxBtnPad(4) },
|
|
1009
|
+
cancel: { base: lynxBtnPad(4) },
|
|
1010
|
+
footer: { base: { marginTop: 'var(--space-2xl)' } },
|
|
1011
|
+
},
|
|
1012
|
+
variants: { size: lynxBtnSizes('trigger') },
|
|
1013
|
+
},
|
|
1014
|
+
},
|
|
960
1015
|
};
|
|
961
1016
|
// daisy "dropdown-content"/card look for floating panels.
|
|
962
1017
|
const floatingPanel = {
|
|
@@ -997,6 +1052,16 @@ export const popover = {
|
|
|
997
1052
|
},
|
|
998
1053
|
// Trigger-carried axes — same wiring as dialog, same reason.
|
|
999
1054
|
variants: { color: btnColors(), size: btnSizes },
|
|
1055
|
+
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
1056
|
+
targets: {
|
|
1057
|
+
lynx: {
|
|
1058
|
+
parts: {
|
|
1059
|
+
trigger: { base: lynxBtnPad(4) },
|
|
1060
|
+
close: { base: lynxBtnPad(3) },
|
|
1061
|
+
},
|
|
1062
|
+
variants: { size: lynxBtnSizes('trigger') },
|
|
1063
|
+
},
|
|
1064
|
+
},
|
|
1000
1065
|
};
|
|
1001
1066
|
export const tooltip = {
|
|
1002
1067
|
component: 'tooltip',
|
|
@@ -1032,6 +1097,13 @@ export const tooltip = {
|
|
|
1032
1097
|
// Trigger-carried axes — same wiring as dialog, same reason. The bubble
|
|
1033
1098
|
// stays daisy's neutral tooltip whatever the trigger's colour.
|
|
1034
1099
|
variants: { color: btnColors(), size: btnSizes },
|
|
1100
|
+
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
1101
|
+
targets: {
|
|
1102
|
+
lynx: {
|
|
1103
|
+
parts: { trigger: { base: lynxBtnPad(4) } },
|
|
1104
|
+
variants: { size: lynxBtnSizes('trigger') },
|
|
1105
|
+
},
|
|
1106
|
+
},
|
|
1035
1107
|
};
|
|
1036
1108
|
// daisy "menu in a dropdown" look.
|
|
1037
1109
|
export const menu = {
|
|
@@ -1055,7 +1127,7 @@ export const menu = {
|
|
|
1055
1127
|
base: {
|
|
1056
1128
|
display: 'flex',
|
|
1057
1129
|
alignItems: 'center',
|
|
1058
|
-
gap: '
|
|
1130
|
+
gap: 'var(--space-md)',
|
|
1059
1131
|
padding: 'var(--space-md) var(--space-lg)',
|
|
1060
1132
|
fontSize: 'var(--text-sm)',
|
|
1061
1133
|
fontWeight: 'var(--weight-medium)',
|
|
@@ -1075,7 +1147,7 @@ export const menu = {
|
|
|
1075
1147
|
base: {
|
|
1076
1148
|
display: 'flex',
|
|
1077
1149
|
alignItems: 'center',
|
|
1078
|
-
gap: '
|
|
1150
|
+
gap: 'var(--space-md)',
|
|
1079
1151
|
padding: 'var(--space-md) var(--space-lg)',
|
|
1080
1152
|
fontSize: 'var(--text-sm)',
|
|
1081
1153
|
fontWeight: 'var(--weight-medium)',
|
|
@@ -1094,7 +1166,7 @@ export const menu = {
|
|
|
1094
1166
|
base: {
|
|
1095
1167
|
display: 'flex',
|
|
1096
1168
|
alignItems: 'center',
|
|
1097
|
-
gap: '
|
|
1169
|
+
gap: 'var(--space-md)',
|
|
1098
1170
|
padding: 'var(--space-md) var(--space-lg)',
|
|
1099
1171
|
fontSize: 'var(--text-sm)',
|
|
1100
1172
|
fontWeight: 'var(--weight-medium)',
|
|
@@ -1129,7 +1201,7 @@ export const menu = {
|
|
|
1129
1201
|
base: {
|
|
1130
1202
|
display: 'flex',
|
|
1131
1203
|
alignItems: 'center',
|
|
1132
|
-
gap: '
|
|
1204
|
+
gap: 'var(--space-md)',
|
|
1133
1205
|
padding: 'var(--space-md) var(--space-lg)',
|
|
1134
1206
|
fontSize: 'var(--text-sm)',
|
|
1135
1207
|
fontWeight: 'var(--weight-medium)',
|
|
@@ -1177,6 +1249,13 @@ export const menu = {
|
|
|
1177
1249
|
// Trigger-carried axes — same wiring as dialog, same reason. The dropdown
|
|
1178
1250
|
// and its items are top-layer siblings the donut cannot reach.
|
|
1179
1251
|
variants: { color: btnColors(), size: btnSizes },
|
|
1252
|
+
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
1253
|
+
targets: {
|
|
1254
|
+
lynx: {
|
|
1255
|
+
parts: { trigger: { base: lynxBtnPad(4) } },
|
|
1256
|
+
variants: { size: lynxBtnSizes('trigger') },
|
|
1257
|
+
},
|
|
1258
|
+
},
|
|
1180
1259
|
};
|
|
1181
1260
|
export const field = {
|
|
1182
1261
|
component: 'field',
|
|
@@ -1299,7 +1378,7 @@ export const checkbox = {
|
|
|
1299
1378
|
},
|
|
1300
1379
|
parts: {
|
|
1301
1380
|
root: {
|
|
1302
|
-
base: { display: 'inline-flex', alignItems: 'center', gap: '
|
|
1381
|
+
base: { display: 'inline-flex', alignItems: 'center', gap: 'var(--space-md)', cursor: 'pointer' },
|
|
1303
1382
|
states: {
|
|
1304
1383
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
1305
1384
|
checked: {}, unchecked: {}, indeterminate: {},
|
|
@@ -1460,7 +1539,23 @@ export const checkbox = {
|
|
|
1460
1539
|
// The tick's fill: the control's `color:` names
|
|
1461
1540
|
// `--checkbox-on-accent`, and `currentColor` was that
|
|
1462
1541
|
// ink by inheritance — spend it directly.
|
|
1463
|
-
|
|
1542
|
+
//
|
|
1543
|
+
// The tick's motion, restated as `transform`: the
|
|
1544
|
+
// standalone `rotate`/`translate` properties resolve on
|
|
1545
|
+
// iOS but not on Android (measured, signalxjs/lynx#1084),
|
|
1546
|
+
// so the emitter refuses them. `transform` functions are
|
|
1547
|
+
// proven on both platforms — and the transition follows
|
|
1548
|
+
// the property that now moves.
|
|
1549
|
+
base: {
|
|
1550
|
+
backgroundColor: 'var(--checkbox-on-accent)',
|
|
1551
|
+
transform: 'rotate(45deg)',
|
|
1552
|
+
transition: 'clip-path var(--duration-slow) var(--ease-standard) var(--duration-instant), '
|
|
1553
|
+
+ 'opacity var(--duration-instant) var(--ease-standard) var(--duration-instant), '
|
|
1554
|
+
+ 'transform var(--duration-slow) var(--ease-standard) var(--duration-instant)',
|
|
1555
|
+
},
|
|
1556
|
+
// The order matches the standalone properties' fixed
|
|
1557
|
+
// apply order (translate, then rotate).
|
|
1558
|
+
states: { indeterminate: { transform: 'translateY(-35%) rotate(0deg)' } },
|
|
1464
1559
|
},
|
|
1465
1560
|
},
|
|
1466
1561
|
},
|
|
@@ -1480,7 +1575,7 @@ export const radioGroup = {
|
|
|
1480
1575
|
},
|
|
1481
1576
|
parts: {
|
|
1482
1577
|
root: {
|
|
1483
|
-
base: { display: 'flex', flexDirection: 'column', gap: '
|
|
1578
|
+
base: { display: 'flex', flexDirection: 'column', gap: 'var(--space-md)' },
|
|
1484
1579
|
states: { invalid: {}, required: {} },
|
|
1485
1580
|
selectors: {
|
|
1486
1581
|
// `invalid` is a fact about the GROUP — `item-control` carries
|
|
@@ -1494,7 +1589,7 @@ export const radioGroup = {
|
|
|
1494
1589
|
states: { disabled: { opacity: 'var(--disabled-opacity)' } },
|
|
1495
1590
|
},
|
|
1496
1591
|
item: {
|
|
1497
|
-
base: { display: 'inline-flex', alignItems: 'center', gap: '
|
|
1592
|
+
base: { display: 'inline-flex', alignItems: 'center', gap: 'var(--space-md)', cursor: 'pointer' },
|
|
1498
1593
|
states: {
|
|
1499
1594
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
1500
1595
|
checked: {}, unchecked: {},
|
|
@@ -1706,6 +1801,18 @@ export const progress = {
|
|
|
1706
1801
|
// other way.
|
|
1707
1802
|
'zero-daisy-indeterminate': 'from { margin-inline-start: -40%; } to { margin-inline-start: 100%; }',
|
|
1708
1803
|
},
|
|
1804
|
+
// The same sweep with a physical margin: logical spellings resolve on
|
|
1805
|
+
// iOS but not on Android (measured, signalxjs/lynx#1084), so the emitter
|
|
1806
|
+
// refuses the shared keyframes. Physical is the lynx target's norm — no
|
|
1807
|
+
// RTL flow there for the sweep to mirror with. Keyframes merge per name,
|
|
1808
|
+
// so this REPLACES the shared animation on lynx.
|
|
1809
|
+
targets: {
|
|
1810
|
+
lynx: {
|
|
1811
|
+
keyframes: {
|
|
1812
|
+
'zero-daisy-indeterminate': 'from { margin-left: -40%; } to { margin-left: 100%; }',
|
|
1813
|
+
},
|
|
1814
|
+
},
|
|
1815
|
+
},
|
|
1709
1816
|
};
|
|
1710
1817
|
export const slider = {
|
|
1711
1818
|
component: 'slider',
|
|
@@ -1830,6 +1937,22 @@ export const slider = {
|
|
|
1830
1937
|
targets: {
|
|
1831
1938
|
lynx: {
|
|
1832
1939
|
parts: {
|
|
1940
|
+
track: {
|
|
1941
|
+
// The web track's `margin-block` breathing room,
|
|
1942
|
+
// restated physically: logical spellings resolve on iOS
|
|
1943
|
+
// but not on Android (measured, signalxjs/lynx#1084), so
|
|
1944
|
+
// the emitter refuses them. Physical is the lynx
|
|
1945
|
+
// target's norm — no RTL flow there.
|
|
1946
|
+
base: {
|
|
1947
|
+
marginTop: 'calc(var(--size-selector) * 1.5)',
|
|
1948
|
+
marginBottom: 'calc(var(--size-selector) * 1.5)',
|
|
1949
|
+
},
|
|
1950
|
+
},
|
|
1951
|
+
mark: {
|
|
1952
|
+
// Same #1084 verdict: the tick labels hang below the
|
|
1953
|
+
// track off a physical padding.
|
|
1954
|
+
base: { paddingTop: 'calc(var(--size-selector) * 2 + var(--space-2xs))' },
|
|
1955
|
+
},
|
|
1833
1956
|
range: {
|
|
1834
1957
|
// The web fill deepens the accent 90/10 toward
|
|
1835
1958
|
// base-content (`progressFill`'s recipe); the plain
|
|
@@ -1853,9 +1976,20 @@ export const slider = {
|
|
|
1853
1976
|
// thumb — a native-input trick with no lynx equivalent;
|
|
1854
1977
|
// the composed `.zx-slider__range` box above IS that
|
|
1855
1978
|
// fill here, so the knob only needs its own two paints.
|
|
1979
|
+
//
|
|
1980
|
+
// The centering geometry is restated physically beside
|
|
1981
|
+
// the paint: `inset-block-start`/`translate`/
|
|
1982
|
+
// `margin-inline-start` resolve on iOS but not on
|
|
1983
|
+
// Android (measured, signalxjs/lynx#1084 — the knob sat
|
|
1984
|
+
// off-center there), so the emitter refuses them.
|
|
1985
|
+
// `top` + `transform: translateY()` + `margin-left` are
|
|
1986
|
+
// proven on both platforms.
|
|
1856
1987
|
base: {
|
|
1857
1988
|
background: 'var(--color-base-100)',
|
|
1858
1989
|
border: '0.25rem solid var(--slider-accent)',
|
|
1990
|
+
top: '50%',
|
|
1991
|
+
transform: 'translateY(-50%)',
|
|
1992
|
+
marginLeft: 'calc(var(--size-selector) * -2.5)',
|
|
1859
1993
|
},
|
|
1860
1994
|
},
|
|
1861
1995
|
},
|
|
@@ -2037,7 +2171,7 @@ export const select = {
|
|
|
2037
2171
|
display: 'flex',
|
|
2038
2172
|
alignItems: 'center',
|
|
2039
2173
|
justifyContent: 'space-between',
|
|
2040
|
-
gap: '
|
|
2174
|
+
gap: 'var(--space-md)',
|
|
2041
2175
|
padding: 'var(--space-md) var(--space-lg)',
|
|
2042
2176
|
fontSize: 'var(--text-sm)',
|
|
2043
2177
|
fontWeight: 'var(--weight-medium)',
|
|
@@ -2073,6 +2207,17 @@ export const select = {
|
|
|
2073
2207
|
// Base equals the default (`tokens:` accent, `fieldControl`'s md height),
|
|
2074
2208
|
// so the twins restate it; declared for the manifest (signalxjs/lynx#1070).
|
|
2075
2209
|
defaultVariants: { color: 'primary', size: 'md' },
|
|
2210
|
+
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
2211
|
+
// The trigger ramp restates `md` too (unlike `btnSizes`), so its lynx
|
|
2212
|
+
// counterpart does as well.
|
|
2213
|
+
targets: {
|
|
2214
|
+
lynx: {
|
|
2215
|
+
parts: { trigger: { base: lynxBtnPad(4) } },
|
|
2216
|
+
variants: {
|
|
2217
|
+
size: { ...lynxBtnSizes('trigger'), md: { trigger: { base: lynxBtnPad(4) } } },
|
|
2218
|
+
},
|
|
2219
|
+
},
|
|
2220
|
+
},
|
|
2076
2221
|
};
|
|
2077
2222
|
/**
|
|
2078
2223
|
* daisy's BUTTON ramp — `.btn`'s fixed height per size step, in `--size-field`
|
|
@@ -2091,6 +2236,15 @@ const BTN_STEPS = { xs: 6, sm: 8, md: 10, lg: 12, xl: 14 };
|
|
|
2091
2236
|
const btnHeight = (step) => `calc(var(--size-field) * ${BTN_STEPS[step]})`;
|
|
2092
2237
|
export const button = {
|
|
2093
2238
|
component: 'button',
|
|
2239
|
+
// Public to a design system derived from this one (#73).
|
|
2240
|
+
hooks: {
|
|
2241
|
+
properties: {
|
|
2242
|
+
'--btn-accent': 'The fill of the solid variant.',
|
|
2243
|
+
'--btn-on-accent': 'The ink on --btn-accent.',
|
|
2244
|
+
'--btn-soft': 'The soft fill (soft variant, unfilled hover).',
|
|
2245
|
+
'--btn-ink': 'The ink of the unfilled variants.',
|
|
2246
|
+
},
|
|
2247
|
+
},
|
|
2094
2248
|
// The two axes meet here instead of multiplying. `color` sets the accent
|
|
2095
2249
|
// pair; `variant` decides how the accent is used. 8 + 4 + 5 rules rather
|
|
2096
2250
|
// than 8 × 4.
|
|
@@ -2101,9 +2255,34 @@ export const button = {
|
|
|
2101
2255
|
'--btn-ink': roleInk('primary'),
|
|
2102
2256
|
},
|
|
2103
2257
|
parts: {
|
|
2258
|
+
// The loading spinner (#50): a real part zero renders before the
|
|
2259
|
+
// label while `loading`, drawn as a ring in `currentColor` with one
|
|
2260
|
+
// transparent quadrant — it takes whatever ink the variant chose.
|
|
2261
|
+
// A literal duration and an explicit reduced-motion `none`: the kit
|
|
2262
|
+
// collapses `--duration-*` under reduced motion, and an infinite
|
|
2263
|
+
// loop at ~0s strobes rather than stops.
|
|
2264
|
+
// daisy marks a loading button with a `loading loading-spinner` span;
|
|
2265
|
+
// this is that span, zero's own now (it was a `loading` modifier's
|
|
2266
|
+
// `::before` until #50 made loading a Button state).
|
|
2267
|
+
spinner: {
|
|
2268
|
+
base: {
|
|
2269
|
+
boxSizing: 'border-box',
|
|
2270
|
+
inlineSize: '1em',
|
|
2271
|
+
blockSize: '1em',
|
|
2272
|
+
flex: 'none',
|
|
2273
|
+
borderRadius: '9999px',
|
|
2274
|
+
border: 'calc(var(--border) * 2) solid currentColor',
|
|
2275
|
+
borderBlockStartColor: 'transparent',
|
|
2276
|
+
animation: 'zero-daisyui-btn-spin 0.7s linear infinite',
|
|
2277
|
+
},
|
|
2278
|
+
at: { 'reduced-motion': { base: { animation: 'none' } } },
|
|
2279
|
+
},
|
|
2104
2280
|
root: {
|
|
2105
2281
|
base: {
|
|
2106
2282
|
appearance: 'none',
|
|
2283
|
+
// An asChild `<a>` gets no UA underline (see the README's
|
|
2284
|
+
// link-button note for the unlayered `a { color }` case).
|
|
2285
|
+
textDecoration: 'none',
|
|
2107
2286
|
display: 'inline-flex',
|
|
2108
2287
|
alignItems: 'center',
|
|
2109
2288
|
justifyContent: 'center',
|
|
@@ -2119,6 +2298,9 @@ export const button = {
|
|
|
2119
2298
|
+ 'border-color var(--duration-fast) var(--ease-standard)',
|
|
2120
2299
|
},
|
|
2121
2300
|
states: {
|
|
2301
|
+
// Work in flight (`loading`, #50): still focusable, still legible —
|
|
2302
|
+
// the label is what the reader is waiting on — so no fade here.
|
|
2303
|
+
loading: { cursor: 'progress' },
|
|
2122
2304
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
2123
2305
|
// The ring is drawn on the page, offset clear of the button —
|
|
2124
2306
|
// ink, not fill, and the same 3:1 floor. On `--btn-accent` it
|
|
@@ -2262,40 +2444,20 @@ export const button = {
|
|
|
2262
2444
|
active: {
|
|
2263
2445
|
root: { base: { transform: 'translateY(1px)', boxShadow: 'none', filter: 'brightness(0.92)' } },
|
|
2264
2446
|
},
|
|
2265
|
-
/**
|
|
2266
|
-
* daisy's loading spinner, drawn by the recipe (#332): daisy marks
|
|
2267
|
-
* the button with a `loading loading-spinner` span; zero changes no
|
|
2268
|
-
* DOM, so the ring is a `::before` in `currentColor` with one
|
|
2269
|
-
* transparent quadrant — it inherits whatever ink the variant chose,
|
|
2270
|
-
* and the root's existing `gap` spaces it from the label. Literal
|
|
2271
|
-
* duration + explicit reduced-motion `none`, like the spinner
|
|
2272
|
-
* recipe: the kit collapses `--duration-*` under reduced motion, and
|
|
2273
|
-
* an infinite loop at ~0s strobes rather than stops.
|
|
2274
|
-
*/
|
|
2275
|
-
loading: {
|
|
2276
|
-
root: {
|
|
2277
|
-
base: { cursor: 'progress' },
|
|
2278
|
-
selectors: {
|
|
2279
|
-
'&::before': {
|
|
2280
|
-
content: '""',
|
|
2281
|
-
boxSizing: 'border-box',
|
|
2282
|
-
inlineSize: '1em',
|
|
2283
|
-
blockSize: '1em',
|
|
2284
|
-
flex: 'none',
|
|
2285
|
-
borderRadius: '9999px',
|
|
2286
|
-
border: 'calc(var(--border) * 2) solid currentColor',
|
|
2287
|
-
borderBlockStartColor: 'transparent',
|
|
2288
|
-
animation: 'zero-daisyui-btn-spin 0.7s linear infinite',
|
|
2289
|
-
},
|
|
2290
|
-
},
|
|
2291
|
-
at: {
|
|
2292
|
-
'reduced-motion': { selectors: { '&::before': { animation: 'none' } } },
|
|
2293
|
-
},
|
|
2294
|
-
},
|
|
2295
|
-
},
|
|
2296
2447
|
},
|
|
2297
2448
|
defaultVariants: { color: 'primary', variant: 'solid', size: 'md' },
|
|
2298
2449
|
keyframes: { 'zero-daisyui-btn-spin': 'to { transform: rotate(360deg) }' },
|
|
2450
|
+
// The icon chips' zeroed inline padding, restated physically: logical
|
|
2451
|
+
// spellings resolve on iOS but not on Android (measured,
|
|
2452
|
+
// signalxjs/lynx#1084), so the emitter refuses them.
|
|
2453
|
+
targets: {
|
|
2454
|
+
lynx: {
|
|
2455
|
+
modifiers: {
|
|
2456
|
+
square: { root: { base: { paddingLeft: '0', paddingRight: '0' } } },
|
|
2457
|
+
circle: { root: { base: { paddingLeft: '0', paddingRight: '0' } } },
|
|
2458
|
+
},
|
|
2459
|
+
},
|
|
2460
|
+
},
|
|
2299
2461
|
};
|
|
2300
2462
|
export const avatar = {
|
|
2301
2463
|
component: 'avatar',
|
|
@@ -2348,6 +2510,13 @@ export const avatar = {
|
|
|
2348
2510
|
},
|
|
2349
2511
|
},
|
|
2350
2512
|
variants: {
|
|
2513
|
+
// The shape axis (zero#129) — one declaration on the root, whose
|
|
2514
|
+
// `overflow: hidden` clips the image and the fallback alike. `rounded` is daisy's selector radius; the default is the circle.
|
|
2515
|
+
shape: {
|
|
2516
|
+
circle: { root: { base: { borderRadius: '9999px' } } },
|
|
2517
|
+
square: { root: { base: { borderRadius: '0' } } },
|
|
2518
|
+
rounded: { root: { base: { borderRadius: 'var(--radius-selector)' } } },
|
|
2519
|
+
},
|
|
2351
2520
|
// daisy colours the RING, not the fallback — that is what its avatar
|
|
2352
2521
|
// does, and the fallback stays neutral so initials read the same
|
|
2353
2522
|
// whichever role is chosen.
|
|
@@ -2521,6 +2690,24 @@ export const toast = {
|
|
|
2521
2690
|
// signalxjs/lynx#1070. No `color` default: the un-attributed toast is a
|
|
2522
2691
|
// neutral base-200 surface, outside the color vocabulary.
|
|
2523
2692
|
defaultVariants: { size: 'md' },
|
|
2693
|
+
// The corner placements' physical anchors: logical inset spellings
|
|
2694
|
+
// resolve on iOS but not on Android (measured, signalxjs/lynx#1084), so
|
|
2695
|
+
// the emitter refuses them. Physical is the lynx target's norm — no RTL
|
|
2696
|
+
// flow there, so `start` IS left and `end` IS right.
|
|
2697
|
+
targets: {
|
|
2698
|
+
lynx: {
|
|
2699
|
+
parts: {
|
|
2700
|
+
viewport: {
|
|
2701
|
+
selectors: {
|
|
2702
|
+
'&[data-placement="top-start"]': { left: '0' },
|
|
2703
|
+
'&[data-placement="top-end"]': { right: '0' },
|
|
2704
|
+
'&[data-placement="bottom-start"]': { left: '0' },
|
|
2705
|
+
'&[data-placement="bottom-end"]': { right: '0' },
|
|
2706
|
+
},
|
|
2707
|
+
},
|
|
2708
|
+
},
|
|
2709
|
+
},
|
|
2710
|
+
},
|
|
2524
2711
|
};
|
|
2525
2712
|
export const combobox = {
|
|
2526
2713
|
component: 'combobox',
|
|
@@ -2539,6 +2726,7 @@ export const combobox = {
|
|
|
2539
2726
|
base: {
|
|
2540
2727
|
display: 'inline-flex',
|
|
2541
2728
|
alignItems: 'stretch',
|
|
2729
|
+
flexWrap: 'wrap',
|
|
2542
2730
|
minWidth: '13rem',
|
|
2543
2731
|
...fieldControl,
|
|
2544
2732
|
transition: 'border-color var(--duration-fast) var(--ease-standard)',
|
|
@@ -2584,6 +2772,52 @@ export const combobox = {
|
|
|
2584
2772
|
'&::placeholder': { color: 'color-mix(in oklab, var(--color-base-content) 60%, transparent)' },
|
|
2585
2773
|
},
|
|
2586
2774
|
},
|
|
2775
|
+
// A chosen value under `multiple` (#39): a chip in the control, before
|
|
2776
|
+
// the input. Symmetric metrics only — the physical-direction lint and
|
|
2777
|
+
// the lynx emitter both refuse a one-sided inset.
|
|
2778
|
+
// daisy's badge: a base-200 pill, centred in the stretched row.
|
|
2779
|
+
tag: {
|
|
2780
|
+
base: {
|
|
2781
|
+
display: 'inline-flex',
|
|
2782
|
+
alignItems: 'center',
|
|
2783
|
+
alignSelf: 'center',
|
|
2784
|
+
gap: 'var(--space-2xs)',
|
|
2785
|
+
margin: 'var(--space-2xs)',
|
|
2786
|
+
padding: 'var(--space-2xs) var(--space-sm)',
|
|
2787
|
+
background: 'var(--color-base-200)',
|
|
2788
|
+
color: 'var(--color-base-content)',
|
|
2789
|
+
borderRadius: 'var(--radius-selector)',
|
|
2790
|
+
fontSize: 'var(--text-xs)',
|
|
2791
|
+
fontWeight: 'var(--weight-medium)',
|
|
2792
|
+
lineHeight: 'var(--leading-none)',
|
|
2793
|
+
},
|
|
2794
|
+
states: { disabled: {} },
|
|
2795
|
+
},
|
|
2796
|
+
'tag-label': { base: { whiteSpace: 'nowrap' } },
|
|
2797
|
+
'tag-remove': {
|
|
2798
|
+
base: {
|
|
2799
|
+
appearance: 'none',
|
|
2800
|
+
border: 'none',
|
|
2801
|
+
background: 'transparent',
|
|
2802
|
+
color: 'inherit',
|
|
2803
|
+
font: 'inherit',
|
|
2804
|
+
borderRadius: 'var(--radius-selector)',
|
|
2805
|
+
padding: '0 var(--space-2xs)',
|
|
2806
|
+
lineHeight: 'var(--leading-none)',
|
|
2807
|
+
cursor: 'pointer',
|
|
2808
|
+
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
2809
|
+
},
|
|
2810
|
+
states: {
|
|
2811
|
+
hover: { background: 'color-mix(in oklab, var(--color-base-content) 10%, transparent)' },
|
|
2812
|
+
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
2813
|
+
...focusRing,
|
|
2814
|
+
},
|
|
2815
|
+
selectors: {
|
|
2816
|
+
'&[data-pressed]:not([data-disabled])': {
|
|
2817
|
+
background: 'color-mix(in oklab, var(--color-base-content) 18%, transparent)',
|
|
2818
|
+
},
|
|
2819
|
+
},
|
|
2820
|
+
},
|
|
2587
2821
|
trigger: {
|
|
2588
2822
|
base: {
|
|
2589
2823
|
appearance: 'none',
|
|
@@ -2620,7 +2854,7 @@ export const combobox = {
|
|
|
2620
2854
|
display: 'flex',
|
|
2621
2855
|
alignItems: 'center',
|
|
2622
2856
|
justifyContent: 'space-between',
|
|
2623
|
-
gap: '
|
|
2857
|
+
gap: 'var(--space-md)',
|
|
2624
2858
|
padding: 'var(--space-md) var(--space-lg)',
|
|
2625
2859
|
fontSize: 'var(--text-sm)',
|
|
2626
2860
|
fontWeight: 'var(--weight-medium)',
|
|
@@ -2652,15 +2886,15 @@ export const combobox = {
|
|
|
2652
2886
|
'--combobox-accent': `var(--color-${c})`,
|
|
2653
2887
|
} } }])),
|
|
2654
2888
|
// The control carries the height — the shared field ramp, the same one
|
|
2655
|
-
// `select`'s trigger steps on
|
|
2656
|
-
// type. `md` restates exactly the resting values so the union stays
|
|
2889
|
+
// `select`'s trigger steps on, as a floor so wrapped tags can grow it
|
|
2890
|
+
// — and the input carries the inset and the type. `md` restates exactly the resting values so the union stays
|
|
2657
2891
|
// complete.
|
|
2658
2892
|
size: {
|
|
2659
|
-
xs: { control: { base: {
|
|
2660
|
-
sm: { control: { base: {
|
|
2661
|
-
md: { control: { base: {
|
|
2662
|
-
lg: { control: { base: {
|
|
2663
|
-
xl: { control: { base: {
|
|
2893
|
+
xs: { control: { base: { minHeight: fieldHeight('xs') } }, input: { base: { padding: 'var(--space-2xs) var(--space-md)', fontSize: 'var(--text-xs)' } } },
|
|
2894
|
+
sm: { control: { base: { minHeight: fieldHeight('sm') } }, input: { base: { padding: 'var(--space-xs) var(--space-md)', fontSize: 'var(--text-sm)' } } },
|
|
2895
|
+
md: { control: { base: { minHeight: fieldHeight('md') } }, input: { base: { padding: 'var(--space-sm) var(--space-lg)', fontSize: 'var(--text-sm)' } } },
|
|
2896
|
+
lg: { control: { base: { minHeight: fieldHeight('lg') } }, input: { base: { padding: 'var(--space-md) var(--space-xl)', fontSize: 'var(--text-md)' } } },
|
|
2897
|
+
xl: { control: { base: { minHeight: fieldHeight('xl') } }, input: { base: { padding: 'var(--space-lg) var(--space-2xl)', fontSize: 'var(--text-lg)' } } },
|
|
2664
2898
|
},
|
|
2665
2899
|
},
|
|
2666
2900
|
// The visible ring lives on `control`; input and trigger delegate.
|
|
@@ -2756,6 +2990,13 @@ export const toggle = {
|
|
|
2756
2990
|
// the on item filled with the accent.
|
|
2757
2991
|
export const toggleGroup = {
|
|
2758
2992
|
component: 'toggle-group',
|
|
2993
|
+
// Public to a design system derived from this one (#73).
|
|
2994
|
+
hooks: {
|
|
2995
|
+
properties: {
|
|
2996
|
+
'--toggle-group-accent': 'The fill of an on item.',
|
|
2997
|
+
'--toggle-group-on-accent': 'The ink on --toggle-group-accent.',
|
|
2998
|
+
},
|
|
2999
|
+
},
|
|
2759
3000
|
tokens: {
|
|
2760
3001
|
'--toggle-group-accent': 'var(--color-primary)',
|
|
2761
3002
|
'--toggle-group-on-accent': 'var(--color-primary-content)',
|
|
@@ -2995,6 +3236,17 @@ export const numberInput = {
|
|
|
2995
3236
|
xl: { control: { base: { height: fieldHeight('xl') } }, input: { base: { fontSize: 'var(--text-lg)', padding: 'var(--space-lg) var(--space-xl)' } } },
|
|
2996
3237
|
},
|
|
2997
3238
|
},
|
|
3239
|
+
// The steppers' inline padding, restated physically: logical spellings
|
|
3240
|
+
// resolve on iOS but not on Android (measured, signalxjs/lynx#1084), so
|
|
3241
|
+
// the emitter refuses them.
|
|
3242
|
+
targets: {
|
|
3243
|
+
lynx: {
|
|
3244
|
+
parts: {
|
|
3245
|
+
'increment-trigger': { base: { paddingLeft: 'var(--space-lg)', paddingRight: 'var(--space-lg)' } },
|
|
3246
|
+
'decrement-trigger': { base: { paddingLeft: 'var(--space-lg)', paddingRight: 'var(--space-lg)' } },
|
|
3247
|
+
},
|
|
3248
|
+
},
|
|
3249
|
+
},
|
|
2998
3250
|
};
|
|
2999
3251
|
/**
|
|
3000
3252
|
* daisy "rating" flavor: a row of orange filled symbols. The default content
|
|
@@ -3173,7 +3425,7 @@ export const ratingGroup = {
|
|
|
3173
3425
|
const treeRow = {
|
|
3174
3426
|
display: 'flex',
|
|
3175
3427
|
alignItems: 'center',
|
|
3176
|
-
gap: '
|
|
3428
|
+
gap: 'var(--space-md)',
|
|
3177
3429
|
padding: 'var(--space-sm) var(--space-md)',
|
|
3178
3430
|
fontSize: 'var(--tree-text)',
|
|
3179
3431
|
fontWeight: 'var(--weight-medium)',
|
|
@@ -3284,6 +3536,16 @@ export const treeView = {
|
|
|
3284
3536
|
xl: { root: { base: { '--tree-text': 'var(--text-lg)' } } },
|
|
3285
3537
|
},
|
|
3286
3538
|
},
|
|
3539
|
+
// The indent, restated physically: logical spellings resolve on iOS but
|
|
3540
|
+
// not on Android (measured, signalxjs/lynx#1084), so the emitter refuses
|
|
3541
|
+
// them. Physical is the lynx target's norm — no RTL flow there.
|
|
3542
|
+
targets: {
|
|
3543
|
+
lynx: {
|
|
3544
|
+
parts: {
|
|
3545
|
+
'branch-content': { base: { paddingLeft: 'var(--space-lg)' } },
|
|
3546
|
+
},
|
|
3547
|
+
},
|
|
3548
|
+
},
|
|
3287
3549
|
};
|
|
3288
3550
|
/**
|
|
3289
3551
|
* daisy "input" flavor: the shared field box with nothing in it but the text.
|
|
@@ -3436,74 +3698,6 @@ export const textarea = {
|
|
|
3436
3698
|
},
|
|
3437
3699
|
},
|
|
3438
3700
|
};
|
|
3439
|
-
/**
|
|
3440
|
-
* NativeSelect (#333) — daisy "select" flavor on the platform's own picker:
|
|
3441
|
-
* the `fieldControl` box (same metrics as every field, so mixed rows line
|
|
3442
|
-
* up), `appearance: none`, and the recipe-drawn chevron at the inline end.
|
|
3443
|
-
* No open state — the platform owns the popup — so colour surfaces on the
|
|
3444
|
-
* focus ring, exactly like input's.
|
|
3445
|
-
*/
|
|
3446
|
-
export const nativeSelect = {
|
|
3447
|
-
component: 'native-select',
|
|
3448
|
-
tokens: { '--native-select-accent': 'var(--color-base-content)' },
|
|
3449
|
-
parts: {
|
|
3450
|
-
root: {
|
|
3451
|
-
base: { position: 'relative', display: 'inline-flex', alignItems: 'center' },
|
|
3452
|
-
states: { disabled: {}, invalid: {}, required: {}, placeholder: {} },
|
|
3453
|
-
},
|
|
3454
|
-
control: {
|
|
3455
|
-
base: {
|
|
3456
|
-
appearance: 'none',
|
|
3457
|
-
width: '100%',
|
|
3458
|
-
minWidth: '12rem',
|
|
3459
|
-
...fieldControl,
|
|
3460
|
-
color: 'var(--color-base-content)',
|
|
3461
|
-
font: 'inherit',
|
|
3462
|
-
fontSize: 'var(--text-sm)',
|
|
3463
|
-
padding: 'var(--space-sm) var(--space-md)',
|
|
3464
|
-
// Room for the chevron the platform no longer draws.
|
|
3465
|
-
paddingInlineEnd: 'calc(var(--space-md) + 1.25em)',
|
|
3466
|
-
cursor: 'pointer',
|
|
3467
|
-
transition: 'border-color var(--duration-fast) var(--ease-standard)',
|
|
3468
|
-
},
|
|
3469
|
-
states: {
|
|
3470
|
-
hover: { borderColor: 'var(--color-base-content)' },
|
|
3471
|
-
invalid: { borderColor: 'var(--color-error)' },
|
|
3472
|
-
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
3473
|
-
required: {},
|
|
3474
|
-
// Same muting as the fields' placeholders (#264).
|
|
3475
|
-
placeholder: { color: 'color-mix(in oklab, var(--color-base-content) 60%, transparent)' },
|
|
3476
|
-
'focus-visible': { ...focusRing['focus-visible'], outline: '2px solid var(--native-select-accent)' },
|
|
3477
|
-
},
|
|
3478
|
-
},
|
|
3479
|
-
indicator: {
|
|
3480
|
-
base: {
|
|
3481
|
-
position: 'absolute',
|
|
3482
|
-
insetInlineEnd: 'var(--space-md)',
|
|
3483
|
-
pointerEvents: 'none',
|
|
3484
|
-
opacity: '0.6',
|
|
3485
|
-
fontSize: 'var(--text-sm)',
|
|
3486
|
-
},
|
|
3487
|
-
},
|
|
3488
|
-
},
|
|
3489
|
-
// The visible ring lives on the <select> itself; the wrapper delegates.
|
|
3490
|
-
skipStates: { root: ['focus-visible'] },
|
|
3491
|
-
variants: {
|
|
3492
|
-
// The ring carries the role — daisy's field chrome is neutral, so
|
|
3493
|
-
// focus is the only place colour surfaces (input's exact move).
|
|
3494
|
-
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
3495
|
-
'--native-select-accent': `var(--color-${c})`,
|
|
3496
|
-
} } }])),
|
|
3497
|
-
// The shared field-height ramp, on the control itself.
|
|
3498
|
-
size: {
|
|
3499
|
-
xs: { control: { base: { height: fieldHeight('xs'), fontSize: 'var(--text-xs)', padding: 'var(--space-2xs) var(--space-xs)', paddingInlineEnd: 'calc(var(--space-xs) + 1.25em)' } } },
|
|
3500
|
-
sm: { control: { base: { height: fieldHeight('sm'), fontSize: 'var(--text-xs)', padding: 'var(--space-xs) var(--space-sm)', paddingInlineEnd: 'calc(var(--space-sm) + 1.25em)' } } },
|
|
3501
|
-
md: {},
|
|
3502
|
-
lg: { control: { base: { height: fieldHeight('lg'), fontSize: 'var(--text-md)', padding: 'var(--space-md) var(--space-lg)', paddingInlineEnd: 'calc(var(--space-lg) + 1.25em)' } } },
|
|
3503
|
-
xl: { control: { base: { height: fieldHeight('xl'), fontSize: 'var(--text-lg)', padding: 'var(--space-lg) var(--space-xl)', paddingInlineEnd: 'calc(var(--space-xl) + 1.25em)' } } },
|
|
3504
|
-
},
|
|
3505
|
-
},
|
|
3506
|
-
};
|
|
3507
3701
|
// ── Content tier (#311) ───────────────────────────────────────────────────
|
|
3508
3702
|
/**
|
|
3509
3703
|
* daisy "card" flavor: `bg-base-100` on a `rounded-box` with a real shadow —
|
|
@@ -3512,6 +3706,12 @@ export const nativeSelect = {
|
|
|
3512
3706
|
*/
|
|
3513
3707
|
export const card = {
|
|
3514
3708
|
component: 'card',
|
|
3709
|
+
// Public to a design system derived from this one (#73).
|
|
3710
|
+
hooks: {
|
|
3711
|
+
properties: {
|
|
3712
|
+
'--card-pad': 'The card padding.',
|
|
3713
|
+
},
|
|
3714
|
+
},
|
|
3515
3715
|
tokens: { '--card-pad': 'var(--space-xl)' },
|
|
3516
3716
|
parts: {
|
|
3517
3717
|
root: {
|
|
@@ -3666,6 +3866,87 @@ export const alert = {
|
|
|
3666
3866
|
},
|
|
3667
3867
|
},
|
|
3668
3868
|
};
|
|
3869
|
+
/**
|
|
3870
|
+
* EmptyState (zero#131) — daisy has no empty-state component; this is the
|
|
3871
|
+
* `hero`-content idiom at card scale: a centred column on the base-200
|
|
3872
|
+
* surface with the box radius, the icon in the role's ink and the surface
|
|
3873
|
+
* in its soft tint when a tone is given.
|
|
3874
|
+
*/
|
|
3875
|
+
export const emptyState = {
|
|
3876
|
+
component: 'empty-state',
|
|
3877
|
+
hooks: {
|
|
3878
|
+
properties: {
|
|
3879
|
+
'--empty-accent': 'The icon ink.',
|
|
3880
|
+
'--empty-tint': 'The surface fill.',
|
|
3881
|
+
},
|
|
3882
|
+
},
|
|
3883
|
+
tokens: {
|
|
3884
|
+
'--empty-accent': 'color-mix(in oklab, var(--color-base-content) 60%, transparent)',
|
|
3885
|
+
'--empty-tint': 'var(--color-base-200)',
|
|
3886
|
+
},
|
|
3887
|
+
parts: {
|
|
3888
|
+
root: {
|
|
3889
|
+
base: {
|
|
3890
|
+
display: 'flex',
|
|
3891
|
+
flexDirection: 'column',
|
|
3892
|
+
alignItems: 'center',
|
|
3893
|
+
textAlign: 'center',
|
|
3894
|
+
gap: 'var(--space-sm)',
|
|
3895
|
+
padding: 'var(--space-2xl) var(--space-xl)',
|
|
3896
|
+
background: 'var(--empty-tint)',
|
|
3897
|
+
color: 'var(--color-base-content)',
|
|
3898
|
+
borderRadius: 'var(--radius-box)',
|
|
3899
|
+
},
|
|
3900
|
+
},
|
|
3901
|
+
icon: {
|
|
3902
|
+
base: {
|
|
3903
|
+
display: 'inline-flex',
|
|
3904
|
+
color: 'var(--empty-accent)',
|
|
3905
|
+
fontSize: 'var(--text-2xl)',
|
|
3906
|
+
lineHeight: 'var(--leading-none)',
|
|
3907
|
+
marginBlockEnd: 'var(--space-xs)',
|
|
3908
|
+
},
|
|
3909
|
+
},
|
|
3910
|
+
title: {
|
|
3911
|
+
base: {
|
|
3912
|
+
margin: '0',
|
|
3913
|
+
fontSize: 'var(--text-lg)',
|
|
3914
|
+
fontWeight: 'var(--weight-bold)',
|
|
3915
|
+
lineHeight: 'var(--leading-tight)',
|
|
3916
|
+
},
|
|
3917
|
+
},
|
|
3918
|
+
description: {
|
|
3919
|
+
base: {
|
|
3920
|
+
maxInlineSize: '36ch',
|
|
3921
|
+
fontSize: 'var(--text-sm)',
|
|
3922
|
+
lineHeight: 'var(--leading-normal)',
|
|
3923
|
+
color: 'color-mix(in oklab, var(--color-base-content) 70%, transparent)',
|
|
3924
|
+
},
|
|
3925
|
+
},
|
|
3926
|
+
actions: {
|
|
3927
|
+
base: {
|
|
3928
|
+
display: 'flex',
|
|
3929
|
+
flexWrap: 'wrap',
|
|
3930
|
+
justifyContent: 'center',
|
|
3931
|
+
gap: 'var(--space-sm)',
|
|
3932
|
+
marginBlockStart: 'var(--space-sm)',
|
|
3933
|
+
},
|
|
3934
|
+
},
|
|
3935
|
+
},
|
|
3936
|
+
variants: {
|
|
3937
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
3938
|
+
'--empty-accent': roleInk(c),
|
|
3939
|
+
'--empty-tint': `var(--color-${c}-soft)`,
|
|
3940
|
+
} } }])),
|
|
3941
|
+
size: {
|
|
3942
|
+
xs: { root: { base: { padding: 'var(--space-md)', gap: 'var(--space-2xs)' } }, icon: { base: { fontSize: 'var(--text-xl)' } }, title: { base: { fontSize: 'var(--text-sm)' } }, description: { base: { fontSize: 'var(--text-xs)' } } },
|
|
3943
|
+
sm: { root: { base: { padding: 'var(--space-lg)', gap: 'var(--space-xs)' } }, icon: { base: { fontSize: 'var(--text-2xl)' } }, title: { base: { fontSize: 'var(--text-md)' } }, description: { base: { fontSize: 'var(--text-xs)' } } },
|
|
3944
|
+
md: {},
|
|
3945
|
+
lg: { root: { base: { padding: 'calc(var(--space-2xl) * 1.5) var(--space-2xl)', gap: 'var(--space-md)' } }, icon: { base: { fontSize: 'var(--text-2xl)' } }, title: { base: { fontSize: 'var(--text-xl)' } }, description: { base: { fontSize: 'var(--text-md)' } } },
|
|
3946
|
+
xl: { root: { base: { padding: 'calc(var(--space-2xl) * 2) var(--space-2xl)', gap: 'var(--space-lg)' } }, icon: { base: { fontSize: 'var(--text-2xl)' } }, title: { base: { fontSize: 'var(--text-2xl)' } }, description: { base: { fontSize: 'var(--text-md)' } } },
|
|
3947
|
+
},
|
|
3948
|
+
},
|
|
3949
|
+
};
|
|
3669
3950
|
/**
|
|
3670
3951
|
* daisy "badge" flavor: a fully-rounded pill with a hairline, filled by the
|
|
3671
3952
|
* role. No `variant` — the repo-wide decision (#175) holds everywhere except
|
|
@@ -3674,7 +3955,18 @@ export const alert = {
|
|
|
3674
3955
|
*/
|
|
3675
3956
|
export const badge = {
|
|
3676
3957
|
component: 'badge',
|
|
3958
|
+
// Public to a design system derived from this one (#73).
|
|
3959
|
+
hooks: {
|
|
3960
|
+
properties: {
|
|
3961
|
+
'--badge-dot': 'The status dot\'s fill (zero#130); the pill\'s ink at rest.',
|
|
3962
|
+
'--badge-dot-ring': 'The ring around a coloured status dot.',
|
|
3963
|
+
'--badge-fill': 'The badge fill.',
|
|
3964
|
+
'--badge-ink': 'The badge ink.',
|
|
3965
|
+
},
|
|
3966
|
+
},
|
|
3677
3967
|
tokens: {
|
|
3968
|
+
'--badge-dot': 'currentColor',
|
|
3969
|
+
'--badge-dot-ring': 'currentColor',
|
|
3678
3970
|
'--badge-fill': 'var(--color-base-200)',
|
|
3679
3971
|
'--badge-ink': 'var(--color-base-content)',
|
|
3680
3972
|
},
|
|
@@ -3690,7 +3982,7 @@ export const badge = {
|
|
|
3690
3982
|
// daisy badges are pills; a large radius rather than 9999px so
|
|
3691
3983
|
// a square-cornered theme can still say so through the token.
|
|
3692
3984
|
borderRadius: 'var(--radius-selector)',
|
|
3693
|
-
padding: '
|
|
3985
|
+
padding: 'var(--space-2xs) var(--space-lg)',
|
|
3694
3986
|
fontSize: 'var(--text-xs)',
|
|
3695
3987
|
fontWeight: 'var(--weight-semibold)',
|
|
3696
3988
|
lineHeight: 'var(--leading-normal)',
|
|
@@ -3698,12 +3990,53 @@ export const badge = {
|
|
|
3698
3990
|
textDecoration: 'none',
|
|
3699
3991
|
},
|
|
3700
3992
|
},
|
|
3993
|
+
// The status dot (zero#130). On an uncoloured pill it is the pill's
|
|
3994
|
+
// INK — a dot in `currentColor` is legible on whatever fill the pill
|
|
3995
|
+
// has. Coloured — by its own `data-color`, or by the pill's, since the
|
|
3996
|
+
// nearest carrier wins (#94) — it is the role's fill inside a ring in
|
|
3997
|
+
// the role's `-content` ink, the timeline marker's answer: a dot that
|
|
3998
|
+
// follows a solid pill of the same role still has an edge. `running` is a
|
|
3999
|
+
// static halo the pulse breathes; under reduced motion the halo
|
|
4000
|
+
// stays and the breathing stops, so the state never vanishes.
|
|
4001
|
+
dot: {
|
|
4002
|
+
base: {
|
|
4003
|
+
display: 'inline-block',
|
|
4004
|
+
flex: 'none',
|
|
4005
|
+
inlineSize: '0.5em',
|
|
4006
|
+
blockSize: '0.5em',
|
|
4007
|
+
boxSizing: 'border-box',
|
|
4008
|
+
borderRadius: '50%',
|
|
4009
|
+
background: 'var(--badge-dot)',
|
|
4010
|
+
border: '0.1em solid var(--badge-dot-ring)',
|
|
4011
|
+
},
|
|
4012
|
+
states: {
|
|
4013
|
+
running: {
|
|
4014
|
+
boxShadow: '0 0 0 0.2em color-mix(in oklch, var(--badge-dot) 35%, transparent)',
|
|
4015
|
+
animation: 'zero-daisy-badge-pulse 1.6s ease-out infinite',
|
|
4016
|
+
},
|
|
4017
|
+
},
|
|
4018
|
+
at: {
|
|
4019
|
+
'reduced-motion': { states: { running: { animation: 'none' } } },
|
|
4020
|
+
// Forced colours strip the fill and keep the border, so the
|
|
4021
|
+
// border draws the whole dot there.
|
|
4022
|
+
'forced-colors': { base: { borderWidth: '0.25em' } },
|
|
4023
|
+
},
|
|
4024
|
+
},
|
|
3701
4025
|
},
|
|
3702
4026
|
variants: {
|
|
3703
|
-
color: Object.fromEntries(ROLES.map((c) => [c, {
|
|
4027
|
+
color: Object.fromEntries(ROLES.map((c) => [c, {
|
|
4028
|
+
root: { base: {
|
|
3704
4029
|
'--badge-fill': `var(--color-${c})`,
|
|
3705
4030
|
'--badge-ink': `var(--color-${c}-content)`,
|
|
3706
|
-
} }
|
|
4031
|
+
} },
|
|
4032
|
+
// The dot's colour (zero#130): keyed under `variants.color` so the
|
|
4033
|
+
// compiler anchors it on the pill's `data-color` AND on the dot's
|
|
4034
|
+
// own re-carried one — the nearest carrier wins (#94).
|
|
4035
|
+
dot: { base: {
|
|
4036
|
+
'--badge-dot': `var(--color-${c})`,
|
|
4037
|
+
'--badge-dot-ring': `var(--color-${c}-content)`,
|
|
4038
|
+
} },
|
|
4039
|
+
}])),
|
|
3707
4040
|
size: {
|
|
3708
4041
|
xs: { root: { base: { fontSize: 'var(--text-xs)', padding: '0 var(--space-sm)' } } },
|
|
3709
4042
|
sm: { root: { base: { fontSize: 'var(--text-xs)', padding: '0 var(--space-md)' } } },
|
|
@@ -3711,6 +4044,8 @@ export const badge = {
|
|
|
3711
4044
|
lg: { root: { base: { fontSize: 'var(--text-sm)', padding: 'var(--space-2xs) var(--space-lg)' } } },
|
|
3712
4045
|
xl: { root: { base: { fontSize: 'var(--text-md)', padding: 'var(--space-xs) var(--space-xl)' } } },
|
|
3713
4046
|
},
|
|
4047
|
+
}, keyframes: {
|
|
4048
|
+
'zero-daisy-badge-pulse': 'from { box-shadow: 0 0 0 0 color-mix(in oklch, var(--badge-dot) 45%, transparent); } to { box-shadow: 0 0 0 0.5em transparent; }',
|
|
3714
4049
|
},
|
|
3715
4050
|
};
|
|
3716
4051
|
/** daisy "divider": the base-300 rule, thickening with the ramp. */
|
|
@@ -3754,6 +4089,12 @@ export const divider = {
|
|
|
3754
4089
|
*/
|
|
3755
4090
|
export const skeleton = {
|
|
3756
4091
|
component: 'skeleton',
|
|
4092
|
+
// Public to a design system derived from this one (#73).
|
|
4093
|
+
hooks: {
|
|
4094
|
+
properties: {
|
|
4095
|
+
'--skeleton-fill': 'The placeholder fill.',
|
|
4096
|
+
},
|
|
4097
|
+
},
|
|
3757
4098
|
tokens: { '--skeleton-fill': 'var(--color-base-300)' },
|
|
3758
4099
|
parts: {
|
|
3759
4100
|
root: {
|
|
@@ -3841,7 +4182,11 @@ export const kbd = {
|
|
|
3841
4182
|
alignItems: 'center',
|
|
3842
4183
|
justifyContent: 'center',
|
|
3843
4184
|
minInlineSize: '1.75em',
|
|
3844
|
-
|
|
4185
|
+
// daisy's .0625rem/.4375rem, restated as the ramp steps they
|
|
4186
|
+
// already are once the cap's own border is absorbed: the OUTER
|
|
4187
|
+
// box lands on 2xs/md, so the cap rides the ramp under a density
|
|
4188
|
+
// override while keeping daisy's painted size exactly at 1px.
|
|
4189
|
+
padding: 'calc(var(--space-2xs) - var(--border)) calc(var(--space-md) - var(--border))',
|
|
3845
4190
|
background: 'var(--kbd-fill)',
|
|
3846
4191
|
color: 'var(--kbd-ink)',
|
|
3847
4192
|
border: 'var(--border) solid var(--color-base-300)',
|
|
@@ -3967,6 +4312,30 @@ export const indicator = {
|
|
|
3967
4312
|
xl: { item: { base: { fontSize: 'var(--text-lg)' } } },
|
|
3968
4313
|
},
|
|
3969
4314
|
},
|
|
4315
|
+
// The corner slots, restated physically: logical inset spellings and the
|
|
4316
|
+
// standalone `translate` property resolve on iOS but not on Android
|
|
4317
|
+
// (measured, signalxjs/lynx#1084), so the emitter refuses them. Physical
|
|
4318
|
+
// anchors plus `transform: translate()` are proven on both platforms,
|
|
4319
|
+
// and physical is the lynx target's norm — no RTL flow there, so the
|
|
4320
|
+
// hand-flipped RTL rules above have nothing to engage with either.
|
|
4321
|
+
targets: {
|
|
4322
|
+
lynx: {
|
|
4323
|
+
parts: {
|
|
4324
|
+
item: {
|
|
4325
|
+
selectors: {
|
|
4326
|
+
'&[data-placement="top-start"]': { top: '0', left: '0', transform: 'translate(-50%, -50%)' },
|
|
4327
|
+
'&[data-placement="top"]': { top: '0', left: '50%', transform: 'translate(-50%, -50%)' },
|
|
4328
|
+
'&[data-placement="top-end"]': { top: '0', right: '0', transform: 'translate(50%, -50%)' },
|
|
4329
|
+
'&[data-placement="start"]': { top: '50%', left: '0', transform: 'translate(-50%, -50%)' },
|
|
4330
|
+
'&[data-placement="end"]': { top: '50%', right: '0', transform: 'translate(50%, -50%)' },
|
|
4331
|
+
'&[data-placement="bottom-start"]': { bottom: '0', left: '0', transform: 'translate(-50%, 50%)' },
|
|
4332
|
+
'&[data-placement="bottom"]': { bottom: '0', left: '50%', transform: 'translate(-50%, 50%)' },
|
|
4333
|
+
'&[data-placement="bottom-end"]': { bottom: '0', right: '0', transform: 'translate(50%, 50%)' },
|
|
4334
|
+
},
|
|
4335
|
+
},
|
|
4336
|
+
},
|
|
4337
|
+
},
|
|
4338
|
+
},
|
|
3970
4339
|
};
|
|
3971
4340
|
/** daisy stats: the rounded base-100 panel with rule-thin item joins. */
|
|
3972
4341
|
export const stats = {
|
|
@@ -4046,10 +4415,31 @@ export const stats = {
|
|
|
4046
4415
|
},
|
|
4047
4416
|
},
|
|
4048
4417
|
};
|
|
4418
|
+
/**
|
|
4419
|
+
* A timeline none of whose content sits on the start side: the root carries
|
|
4420
|
+
* no start-placed content among its own items. Child combinators all the way
|
|
4421
|
+
* down, so a timeline nested inside another's content answers only for
|
|
4422
|
+
* itself.
|
|
4423
|
+
*/
|
|
4424
|
+
const TIMELINE_NO_START = '[data-scope="timeline"][data-part="root"]'
|
|
4425
|
+
+ ':not(:has(> [data-scope="timeline"][data-part="item"] > [data-scope="timeline"][data-part="content"][data-placement="start"])) > &';
|
|
4049
4426
|
/** daisy timeline: the boxed content beside a dotted axis. */
|
|
4050
4427
|
export const timeline = {
|
|
4051
4428
|
component: 'timeline',
|
|
4052
|
-
|
|
4429
|
+
// Public to a design system derived from this one (#73).
|
|
4430
|
+
hooks: {
|
|
4431
|
+
properties: {
|
|
4432
|
+
'--timeline-accent': 'The marker and connector accent.',
|
|
4433
|
+
'--timeline-ring': 'The ring around a coloured marker — the role\'s -content ink (#94).',
|
|
4434
|
+
'--timeline-marker-size': 'The marker diameter.',
|
|
4435
|
+
},
|
|
4436
|
+
},
|
|
4437
|
+
tokens: {
|
|
4438
|
+
'--timeline-accent': 'var(--color-base-content)',
|
|
4439
|
+
// The coloured dot's ring (#94); uncoloured, it IS the dot.
|
|
4440
|
+
'--timeline-ring': 'var(--timeline-accent)',
|
|
4441
|
+
'--timeline-marker-size': 'calc(var(--size-selector) * 3)',
|
|
4442
|
+
},
|
|
4053
4443
|
parts: {
|
|
4054
4444
|
root: {
|
|
4055
4445
|
base: {
|
|
@@ -4085,6 +4475,11 @@ export const timeline = {
|
|
|
4085
4475
|
gridTemplateColumns: 'auto 1fr',
|
|
4086
4476
|
flex: '1 1 0%',
|
|
4087
4477
|
},
|
|
4478
|
+
// No content on the start side anywhere in this timeline →
|
|
4479
|
+
// the start track collapses instead of holding half the item
|
|
4480
|
+
// empty (#57). Automatic, so there is no prop to forget.
|
|
4481
|
+
[`${TIMELINE_NO_START}[data-orientation="vertical"]`]: { gridTemplateColumns: '0 auto 1fr' },
|
|
4482
|
+
[`${TIMELINE_NO_START}[data-orientation="horizontal"]`]: { gridTemplateRows: '0 auto 1fr' },
|
|
4088
4483
|
},
|
|
4089
4484
|
},
|
|
4090
4485
|
marker: {
|
|
@@ -4094,12 +4489,24 @@ export const timeline = {
|
|
|
4094
4489
|
boxSizing: 'border-box',
|
|
4095
4490
|
borderRadius: '9999px',
|
|
4096
4491
|
background: 'var(--timeline-accent)',
|
|
4097
|
-
|
|
4492
|
+
// A coloured dot is the role fill inside a RING in the role's
|
|
4493
|
+
// `-content` ink (#94): a light role on a light page, or a dark
|
|
4494
|
+
// one on a dark page, has no edge of its own, and a per-entry
|
|
4495
|
+
// tone is exactly the mark a reader has to find. One half of
|
|
4496
|
+
// the role's own pair clears 3:1 against the page (the contrast
|
|
4497
|
+
// audit measures every colour). With no colour the ring is the
|
|
4498
|
+
// fill — the same solid dot as before.
|
|
4499
|
+
border: 'calc(var(--timeline-marker-size) / 6) solid var(--timeline-ring)',
|
|
4098
4500
|
display: 'flex',
|
|
4099
4501
|
alignItems: 'center',
|
|
4100
4502
|
justifyContent: 'center',
|
|
4101
4503
|
fontSize: '0',
|
|
4102
4504
|
},
|
|
4505
|
+
at: {
|
|
4506
|
+
// Forced colours strip the fill and keep the border, so the
|
|
4507
|
+
// border draws the whole dot there, as it always did.
|
|
4508
|
+
'forced-colors': { base: { borderWidth: 'calc(var(--timeline-marker-size) / 2)' } },
|
|
4509
|
+
},
|
|
4103
4510
|
selectors: {
|
|
4104
4511
|
// The axis cell, both orientations. `place-self` centres the
|
|
4105
4512
|
// dot on the line in the cross axis.
|
|
@@ -4177,6 +4584,7 @@ export const timeline = {
|
|
|
4177
4584
|
variants: {
|
|
4178
4585
|
color: Object.fromEntries(ROLES.map((c) => [c, { marker: { base: {
|
|
4179
4586
|
'--timeline-accent': `var(--color-${c})`,
|
|
4587
|
+
'--timeline-ring': `var(--color-${c}-content)`,
|
|
4180
4588
|
} } }])),
|
|
4181
4589
|
size: {
|
|
4182
4590
|
xs: { marker: { base: { '--timeline-marker-size': 'calc(var(--size-selector) * 2)' } }, content: { base: { fontSize: 'var(--text-xs)' } } },
|
|
@@ -4284,6 +4692,16 @@ export const chat = {
|
|
|
4284
4692
|
xl: { bubble: { base: { fontSize: 'var(--text-lg)', padding: 'var(--space-md) var(--space-xl)' } } },
|
|
4285
4693
|
},
|
|
4286
4694
|
},
|
|
4695
|
+
// The row's vertical padding, restated physically: logical spellings
|
|
4696
|
+
// resolve on iOS but not on Android (measured, signalxjs/lynx#1084), so
|
|
4697
|
+
// the emitter refuses them.
|
|
4698
|
+
targets: {
|
|
4699
|
+
lynx: {
|
|
4700
|
+
parts: {
|
|
4701
|
+
root: { base: { paddingTop: 'var(--space-2xs)', paddingBottom: 'var(--space-2xs)' } },
|
|
4702
|
+
},
|
|
4703
|
+
},
|
|
4704
|
+
},
|
|
4287
4705
|
};
|
|
4288
4706
|
/** daisy radial-progress: --value became --progress-percent; same ring. */
|
|
4289
4707
|
export const radialProgress = {
|
|
@@ -4533,6 +4951,111 @@ export const navbar = {
|
|
|
4533
4951
|
},
|
|
4534
4952
|
},
|
|
4535
4953
|
};
|
|
4954
|
+
/**
|
|
4955
|
+
* NavList (zero#132) — daisy's `menu` idiom for a sidebar: rounded rows, a
|
|
4956
|
+
* base-200 wash on hover, and the current page as the role's soft fill in
|
|
4957
|
+
* the role's ink. `meta` is where daisy's menu puts its badge.
|
|
4958
|
+
*/
|
|
4959
|
+
export const navList = {
|
|
4960
|
+
component: 'nav-list',
|
|
4961
|
+
hooks: {
|
|
4962
|
+
properties: {
|
|
4963
|
+
'--nav-accent': 'The current link\'s ink.',
|
|
4964
|
+
'--nav-tint': 'The current link\'s fill.',
|
|
4965
|
+
'--nav-ink': 'The resting link ink.',
|
|
4966
|
+
},
|
|
4967
|
+
},
|
|
4968
|
+
tokens: {
|
|
4969
|
+
'--nav-accent': 'var(--color-base-content)',
|
|
4970
|
+
'--nav-tint': 'var(--color-base-300)',
|
|
4971
|
+
'--nav-ink': 'var(--color-base-content)',
|
|
4972
|
+
},
|
|
4973
|
+
parts: {
|
|
4974
|
+
root: {
|
|
4975
|
+
base: {
|
|
4976
|
+
display: 'flex',
|
|
4977
|
+
flexDirection: 'column',
|
|
4978
|
+
gap: 'var(--space-lg)',
|
|
4979
|
+
fontSize: 'var(--text-sm)',
|
|
4980
|
+
color: 'var(--color-base-content)',
|
|
4981
|
+
},
|
|
4982
|
+
},
|
|
4983
|
+
group: {
|
|
4984
|
+
base: { display: 'flex', flexDirection: 'column', gap: 'var(--space-xs)' },
|
|
4985
|
+
},
|
|
4986
|
+
heading: {
|
|
4987
|
+
base: {
|
|
4988
|
+
padding: '0 var(--space-lg)',
|
|
4989
|
+
fontSize: 'var(--text-xs)',
|
|
4990
|
+
fontWeight: 'var(--weight-bold)',
|
|
4991
|
+
opacity: '0.6',
|
|
4992
|
+
},
|
|
4993
|
+
},
|
|
4994
|
+
list: {
|
|
4995
|
+
base: {
|
|
4996
|
+
display: 'flex',
|
|
4997
|
+
flexDirection: 'column',
|
|
4998
|
+
gap: 'var(--space-2xs)',
|
|
4999
|
+
listStyle: 'none',
|
|
5000
|
+
margin: '0',
|
|
5001
|
+
padding: '0',
|
|
5002
|
+
},
|
|
5003
|
+
},
|
|
5004
|
+
item: { base: { display: 'block' } },
|
|
5005
|
+
link: {
|
|
5006
|
+
base: {
|
|
5007
|
+
display: 'flex',
|
|
5008
|
+
alignItems: 'center',
|
|
5009
|
+
gap: 'var(--space-sm)',
|
|
5010
|
+
textDecoration: 'none',
|
|
5011
|
+
color: 'var(--nav-ink)',
|
|
5012
|
+
padding: 'var(--space-sm) var(--space-lg)',
|
|
5013
|
+
borderRadius: 'var(--radius-field)',
|
|
5014
|
+
fontWeight: 'var(--weight-medium)',
|
|
5015
|
+
lineHeight: 'var(--leading-tight)',
|
|
5016
|
+
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
5017
|
+
},
|
|
5018
|
+
states: {
|
|
5019
|
+
hover: { background: 'var(--color-base-200)' },
|
|
5020
|
+
active: { background: 'var(--nav-tint)', color: 'var(--nav-accent)' },
|
|
5021
|
+
inactive: {},
|
|
5022
|
+
...focusRing,
|
|
5023
|
+
},
|
|
5024
|
+
},
|
|
5025
|
+
icon: {
|
|
5026
|
+
base: {
|
|
5027
|
+
display: 'inline-flex',
|
|
5028
|
+
inlineSize: '1.25em',
|
|
5029
|
+
justifyContent: 'center',
|
|
5030
|
+
flex: 'none',
|
|
5031
|
+
lineHeight: 'var(--leading-none)',
|
|
5032
|
+
},
|
|
5033
|
+
},
|
|
5034
|
+
meta: {
|
|
5035
|
+
base: {
|
|
5036
|
+
marginInlineStart: 'auto',
|
|
5037
|
+
display: 'inline-flex',
|
|
5038
|
+
alignItems: 'center',
|
|
5039
|
+
fontSize: 'var(--text-xs)',
|
|
5040
|
+
fontVariantNumeric: 'tabular-nums',
|
|
5041
|
+
opacity: '0.7',
|
|
5042
|
+
},
|
|
5043
|
+
},
|
|
5044
|
+
},
|
|
5045
|
+
variants: {
|
|
5046
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
5047
|
+
'--nav-accent': roleInk(c),
|
|
5048
|
+
'--nav-tint': `var(--color-${c}-soft)`,
|
|
5049
|
+
} } }])),
|
|
5050
|
+
size: {
|
|
5051
|
+
xs: { root: { base: { fontSize: 'var(--text-xs)' } }, link: { base: { padding: 'var(--space-2xs) var(--space-md)' } } },
|
|
5052
|
+
sm: { root: { base: { fontSize: 'var(--text-xs)' } }, link: { base: { padding: 'var(--space-xs) var(--space-md)' } } },
|
|
5053
|
+
md: {},
|
|
5054
|
+
lg: { root: { base: { fontSize: 'var(--text-md)' } }, link: { base: { padding: 'var(--space-md) var(--space-lg)' } } },
|
|
5055
|
+
xl: { root: { base: { fontSize: 'var(--text-lg)' } }, link: { base: { padding: 'var(--space-lg) var(--space-xl)' } } },
|
|
5056
|
+
},
|
|
5057
|
+
},
|
|
5058
|
+
};
|
|
4536
5059
|
/**
|
|
4537
5060
|
* Breadcrumbs — daisy's breadcrumbs: a compact text-sm trail, links
|
|
4538
5061
|
* underlining on hover, the current crumb plain full ink. Colour rebinds
|
|
@@ -4735,7 +5258,29 @@ export const pagination = {
|
|
|
4735
5258
|
xl: { root: { base: { '--pg-size': 'calc(var(--size-field) * 14)', '--pg-font': 'var(--text-lg)' } } },
|
|
4736
5259
|
},
|
|
4737
5260
|
},
|
|
5261
|
+
// The cell's inline padding, restated physically: logical spellings
|
|
5262
|
+
// resolve on iOS but not on Android (measured, signalxjs/lynx#1084), so
|
|
5263
|
+
// the emitter refuses them.
|
|
5264
|
+
targets: {
|
|
5265
|
+
lynx: {
|
|
5266
|
+
parts: {
|
|
5267
|
+
item: { base: { paddingLeft: 'var(--space-2xs)', paddingRight: 'var(--space-2xs)' } },
|
|
5268
|
+
},
|
|
5269
|
+
},
|
|
5270
|
+
},
|
|
4738
5271
|
};
|
|
5272
|
+
/**
|
|
5273
|
+
* The complete step's digit: the role hue deepened toward base-content, on
|
|
5274
|
+
* the disc's 20% tint of that role — `roleInk`'s per-role keep, capped at
|
|
5275
|
+
* 70%. The cap is the primary story: 95% on the tint measured 2.96:1 under
|
|
5276
|
+
* nord's muted primary (the static contrast matrix, #403). The per-role part
|
|
5277
|
+
* arrived with per-step colour (#112), when the matrix first measured every
|
|
5278
|
+
* role here: a flat 70% put nord's muted accent/success/warning at 2.2–2.6:1
|
|
5279
|
+
* and the dark themes' neutral (darker than the page) at 1.7–2.0:1, and
|
|
5280
|
+
* `ROLE_INK_KEEP` already knew how much hue each of those can keep.
|
|
5281
|
+
*/
|
|
5282
|
+
const stepInk = (accent, role) => `color-mix(in oklab, ${accent} `
|
|
5283
|
+
+ `${Math.min(ROLE_INK_KEEP[role] ?? 55, 70)}%, var(--color-base-content))`;
|
|
4739
5284
|
/**
|
|
4740
5285
|
* Steps — daisy's steps translated to the richer rail: bold numbered discs
|
|
4741
5286
|
* on base-200, the walked disc and line refilled with the accent pair
|
|
@@ -4748,6 +5293,9 @@ export const steps = {
|
|
|
4748
5293
|
'--steps-accent': 'var(--color-primary)',
|
|
4749
5294
|
'--steps-accent-content': 'var(--color-primary-content)',
|
|
4750
5295
|
'--steps-accent-ink': roleInk('primary'),
|
|
5296
|
+
// Read through the accent, so it resolves on the element that sets
|
|
5297
|
+
// it; a colour on the item restates it for that role.
|
|
5298
|
+
'--steps-complete-ink': stepInk('var(--steps-accent)', 'primary'),
|
|
4751
5299
|
'--steps-ind': 'calc(var(--size-selector) * 7)',
|
|
4752
5300
|
'--steps-font': 'var(--text-sm)',
|
|
4753
5301
|
},
|
|
@@ -4819,7 +5367,14 @@ export const steps = {
|
|
|
4819
5367
|
},
|
|
4820
5368
|
states: {
|
|
4821
5369
|
active: { background: 'var(--steps-accent)', color: 'var(--steps-accent-content)' },
|
|
4822
|
-
|
|
5370
|
+
// Ink deepened toward base-content, per role (`stepInk`). The
|
|
5371
|
+
// tint mixes in OKLAB: daisy's light base-100 is
|
|
5372
|
+
// `oklch(100% 0 0)`, whose 0 hue the browser interpolates as
|
|
5373
|
+
// written, so an OKLCH mix swung every role's tint toward red
|
|
5374
|
+
// (warning's yellow came out pink) — which the browser parity
|
|
5375
|
+
// check caught once per-step colour put all eight roles on
|
|
5376
|
+
// the disc (#112).
|
|
5377
|
+
complete: { background: 'color-mix(in oklab, var(--steps-accent) 20%, var(--color-base-100))', color: 'var(--steps-complete-ink)' },
|
|
4823
5378
|
inactive: { background: 'var(--color-base-200)', color: 'var(--color-base-content)' },
|
|
4824
5379
|
},
|
|
4825
5380
|
},
|
|
@@ -4868,10 +5423,15 @@ export const steps = {
|
|
|
4868
5423
|
},
|
|
4869
5424
|
},
|
|
4870
5425
|
variants: {
|
|
4871
|
-
|
|
5426
|
+
// Keyed on the ITEM, not the root (#112): the item re-carries
|
|
5427
|
+
// `color`, so a step's own value outranks the rail's, and the
|
|
5428
|
+
// disc, bridge and title inside it inherit whichever won. A root
|
|
5429
|
+
// colour still reaches every item through the carrier's donut.
|
|
5430
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { item: { base: {
|
|
4872
5431
|
'--steps-accent': `var(--color-${c})`,
|
|
4873
5432
|
'--steps-accent-content': `var(--color-${c}-content)`,
|
|
4874
5433
|
'--steps-accent-ink': roleInk(c),
|
|
5434
|
+
'--steps-complete-ink': stepInk(`var(--color-${c})`, c),
|
|
4875
5435
|
} } }])),
|
|
4876
5436
|
size: {
|
|
4877
5437
|
xs: { root: { base: { '--steps-ind': 'calc(var(--size-selector) * 5)', '--steps-font': 'var(--text-xs)' } } },
|
|
@@ -4881,11 +5441,74 @@ export const steps = {
|
|
|
4881
5441
|
xl: { root: { base: { '--steps-ind': 'calc(var(--size-selector) * 9)', '--steps-font': 'var(--text-md)' } } },
|
|
4882
5442
|
},
|
|
4883
5443
|
},
|
|
5444
|
+
// The bridge's anchors and the vertical item's tail padding, restated
|
|
5445
|
+
// physically: logical spellings resolve on iOS but not on Android
|
|
5446
|
+
// (measured, signalxjs/lynx#1084), so the emitter refuses them. Physical
|
|
5447
|
+
// is the lynx target's norm — no RTL flow there to mirror with.
|
|
5448
|
+
targets: {
|
|
5449
|
+
lynx: {
|
|
5450
|
+
parts: {
|
|
5451
|
+
item: {
|
|
5452
|
+
selectors: {
|
|
5453
|
+
'&[data-orientation="vertical"]': { paddingBottom: 'var(--space-lg)' },
|
|
5454
|
+
},
|
|
5455
|
+
},
|
|
5456
|
+
separator: {
|
|
5457
|
+
selectors: {
|
|
5458
|
+
'&[data-orientation="horizontal"]': {
|
|
5459
|
+
top: 'calc(var(--space-xs) + var(--steps-ind) / 2)',
|
|
5460
|
+
left: '50%',
|
|
5461
|
+
},
|
|
5462
|
+
'&[data-orientation="vertical"]': {
|
|
5463
|
+
left: 'calc(var(--space-xs) + (var(--steps-ind) - var(--border)) / 2)',
|
|
5464
|
+
top: 'calc(var(--space-xs) + var(--steps-ind))',
|
|
5465
|
+
bottom: 'calc(var(--space-xs) * -1)',
|
|
5466
|
+
},
|
|
5467
|
+
},
|
|
5468
|
+
},
|
|
5469
|
+
},
|
|
5470
|
+
},
|
|
5471
|
+
},
|
|
5472
|
+
};
|
|
5473
|
+
/**
|
|
5474
|
+
* The modal sheet's slide (#83): in from its edge, back out to it.
|
|
5475
|
+
*
|
|
5476
|
+
* `translate` is physical, so the travel is a custom property that flips with
|
|
5477
|
+
* the placement AND the direction — off the reading start is leftward in LTR
|
|
5478
|
+
* and rightward in RTL (the `rtl` hedge, as everywhere here). Keyed on
|
|
5479
|
+
* `data-l-dock="sheet"`, the regime, which holds through the exit where
|
|
5480
|
+
* `:modal` does not, so the slide-out leaves from the sheet's own box.
|
|
5481
|
+
* Opacity stays at 1: the travel is the transition. Each direction rides the
|
|
5482
|
+
* transition declared at its destination; reduced motion drops both.
|
|
5483
|
+
*/
|
|
5484
|
+
const sheetSlide = (enter, exit) => {
|
|
5485
|
+
const sheet = '&[data-l-dock="sheet"]';
|
|
5486
|
+
const open = `${sheet}[data-state="open"]`;
|
|
5487
|
+
const travel = (tempo) => {
|
|
5488
|
+
const duration = tempo.split(' ')[0];
|
|
5489
|
+
return `translate ${tempo}, display ${duration} allow-discrete, overlay ${duration} allow-discrete`;
|
|
5490
|
+
};
|
|
5491
|
+
return {
|
|
5492
|
+
base: { '--drawer-travel': '-100%' },
|
|
5493
|
+
selectors: {
|
|
5494
|
+
'&[data-placement="end"]': { '--drawer-travel': '100%' },
|
|
5495
|
+
[`&[data-placement="start"]${rtl}`]: { '--drawer-travel': '100%' },
|
|
5496
|
+
[`&[data-placement="end"]${rtl}`]: { '--drawer-travel': '-100%' },
|
|
5497
|
+
[sheet]: { opacity: '1', translate: 'var(--drawer-travel) 0', transition: travel(exit) },
|
|
5498
|
+
[open]: { translate: 'none', transition: travel(enter) },
|
|
5499
|
+
},
|
|
5500
|
+
at: {
|
|
5501
|
+
'starting-style': { selectors: { [open]: { opacity: '1', translate: 'var(--drawer-travel) 0' } } },
|
|
5502
|
+
'reduced-motion': { selectors: { [sheet]: { transition: 'none' }, [open]: { transition: 'none' } } },
|
|
5503
|
+
},
|
|
5504
|
+
};
|
|
4884
5505
|
};
|
|
4885
5506
|
/**
|
|
4886
5507
|
* Drawer — daisy's drawer-side as a native <dialog> edge sheet: base-100
|
|
4887
|
-
* paper, no border (daisy separates the drawer with the scrim),
|
|
4888
|
-
*
|
|
5508
|
+
* paper, no border (daisy separates the drawer with the scrim), sliding in
|
|
5509
|
+
* from its edge the way daisy's drawer-side does (`translate` over 0.3s,
|
|
5510
|
+
* #83). Base render is the inline mode; `data-l-dock="sheet"` is the
|
|
5511
|
+
* top-layer edge sheet.
|
|
4889
5512
|
*/
|
|
4890
5513
|
export const drawer = {
|
|
4891
5514
|
component: 'drawer',
|
|
@@ -4900,7 +5523,7 @@ export const drawer = {
|
|
|
4900
5523
|
...focusRing,
|
|
4901
5524
|
},
|
|
4902
5525
|
},
|
|
4903
|
-
panel: withPresence(popupPresence('none'), {
|
|
5526
|
+
panel: withPresence(withPresence(popupPresence('none'), sheetSlide('var(--duration-slow) var(--ease-standard)', 'var(--duration-slow) var(--ease-standard)')), {
|
|
4904
5527
|
base: {
|
|
4905
5528
|
padding: 'var(--space-lg)',
|
|
4906
5529
|
background: 'var(--color-base-100)',
|
|
@@ -4908,30 +5531,39 @@ export const drawer = {
|
|
|
4908
5531
|
border: 'none',
|
|
4909
5532
|
borderRadius: 'var(--radius-box)',
|
|
4910
5533
|
boxShadow: 'var(--shadow-xl)',
|
|
4911
|
-
|
|
5534
|
+
// The width is capped by `--l-measure` (Drawer.Panel's
|
|
5535
|
+
// `measure`, via the layout step table); this is the default
|
|
5536
|
+
// an unset `measure` leaves. A cap, not a width, because the
|
|
5537
|
+
// table spells `full` as `none` — valid only for a max. In
|
|
5538
|
+
// flow and as a sheet alike, the panel fills what it is given
|
|
5539
|
+
// up to the measure, as its border box, so `full` never
|
|
5540
|
+
// overflows by its own padding.
|
|
5541
|
+
boxSizing: 'border-box',
|
|
5542
|
+
'--l-measure': 'min(20rem, 85vw)',
|
|
5543
|
+
inlineSize: '100%',
|
|
5544
|
+
maxInlineSize: 'var(--l-measure)',
|
|
4912
5545
|
},
|
|
4913
5546
|
states: { open: {}, closed: {} },
|
|
4914
5547
|
selectors: {
|
|
4915
5548
|
/**
|
|
4916
|
-
* The
|
|
4917
|
-
*
|
|
4918
|
-
*
|
|
4919
|
-
* the
|
|
4920
|
-
* RTL mirrors
|
|
5549
|
+
* The top-layer edge sheet, keyed on the regime
|
|
5550
|
+
* (`data-l-dock="sheet"`) rather than `:modal`, which stops
|
|
5551
|
+
* matching the moment `close()` runs — so the sheet keeps its
|
|
5552
|
+
* box through the exit (#83). The base styles above are the
|
|
5553
|
+
* INLINE render. Logical insets pin the edge, so RTL mirrors
|
|
5554
|
+
* free.
|
|
4921
5555
|
*/
|
|
4922
|
-
'
|
|
5556
|
+
'&[data-l-dock="sheet"]': {
|
|
4923
5557
|
position: 'fixed',
|
|
4924
5558
|
insetBlockStart: '0',
|
|
4925
5559
|
insetBlockEnd: '0',
|
|
4926
5560
|
blockSize: '100dvh',
|
|
4927
5561
|
maxBlockSize: '100dvh',
|
|
4928
|
-
inlineSize: 'min(20rem, 85vw)',
|
|
4929
|
-
maxInlineSize: 'none',
|
|
4930
5562
|
margin: '0',
|
|
4931
5563
|
borderRadius: '0',
|
|
4932
5564
|
},
|
|
4933
|
-
'&[data-placement="start"]
|
|
4934
|
-
'&[data-placement="end"]
|
|
5565
|
+
'&[data-placement="start"][data-l-dock="sheet"]': { insetInlineStart: '0', insetInlineEnd: 'auto' },
|
|
5566
|
+
'&[data-placement="end"][data-l-dock="sheet"]': { insetInlineStart: 'auto', insetInlineEnd: '0' },
|
|
4935
5567
|
},
|
|
4936
5568
|
}),
|
|
4937
5569
|
backdrop: {
|
|
@@ -4953,6 +5585,16 @@ export const drawer = {
|
|
|
4953
5585
|
// Trigger-carried axes — see `btnColors` for why the panel is out of
|
|
4954
5586
|
// reach and the trigger is the whole story here.
|
|
4955
5587
|
variants: { color: btnColors(), size: btnSizes },
|
|
5588
|
+
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
5589
|
+
targets: {
|
|
5590
|
+
lynx: {
|
|
5591
|
+
parts: {
|
|
5592
|
+
trigger: { base: lynxBtnPad(4) },
|
|
5593
|
+
close: { base: lynxBtnPad(4) },
|
|
5594
|
+
},
|
|
5595
|
+
variants: { size: lynxBtnSizes('trigger') },
|
|
5596
|
+
},
|
|
5597
|
+
},
|
|
4956
5598
|
};
|
|
4957
5599
|
/**
|
|
4958
5600
|
* Table — daisy's `table`: quiet base-200 row rules in a rounded box,
|
|
@@ -4962,7 +5604,20 @@ export const drawer = {
|
|
|
4962
5604
|
*/
|
|
4963
5605
|
export const table = {
|
|
4964
5606
|
component: 'table',
|
|
5607
|
+
// Public to a design system derived from this one (#73).
|
|
5608
|
+
hooks: {
|
|
5609
|
+
properties: {
|
|
5610
|
+
'--table-cell-align': 'Cell text alignment; zero writes it inline from the column spec.',
|
|
5611
|
+
'--table-accent': 'The header ink accent.',
|
|
5612
|
+
'--table-pad-block': 'Cell block padding.',
|
|
5613
|
+
'--table-pad-inline': 'Cell inline padding.',
|
|
5614
|
+
'--table-font': 'The table font size.',
|
|
5615
|
+
},
|
|
5616
|
+
},
|
|
4965
5617
|
tokens: {
|
|
5618
|
+
// The column spec's alignment (#55): a cell that names an aligned
|
|
5619
|
+
// column overrides it inline; everything else reads `start`.
|
|
5620
|
+
'--table-cell-align': 'start',
|
|
4966
5621
|
'--table-accent': 'var(--color-base-content)',
|
|
4967
5622
|
'--table-pad-block': 'var(--space-sm)',
|
|
4968
5623
|
'--table-pad-inline': 'var(--space-md)',
|
|
@@ -4976,6 +5631,9 @@ export const table = {
|
|
|
4976
5631
|
borderRadius: 'var(--radius-box)',
|
|
4977
5632
|
background: 'var(--color-base-100)',
|
|
4978
5633
|
},
|
|
5634
|
+
// Stacked (Table.Root stack, #55): zero lays each row out as a block
|
|
5635
|
+
// below the table's breakpoint; the card it becomes is drawn here.
|
|
5636
|
+
at: tableStackAt(tokens, 'root', { border: '0', borderRadius: '0', background: 'transparent' }),
|
|
4979
5637
|
},
|
|
4980
5638
|
table: {
|
|
4981
5639
|
base: {
|
|
@@ -4993,34 +5651,52 @@ export const table = {
|
|
|
4993
5651
|
fontSize: 'var(--text-xs)',
|
|
4994
5652
|
color: 'color-mix(in oklab, var(--color-base-content) 60%, transparent)',
|
|
4995
5653
|
},
|
|
5654
|
+
at: tableStackAt(tokens, 'caption', { paddingInline: '0' }),
|
|
4996
5655
|
},
|
|
4997
5656
|
head: {},
|
|
4998
|
-
body: {},
|
|
5657
|
+
body: { at: tableStackAt(tokens, 'body', { rowGap: 'var(--space-md)' }) },
|
|
4999
5658
|
foot: {
|
|
5000
5659
|
base: {
|
|
5001
5660
|
fontSize: 'var(--text-xs)',
|
|
5002
5661
|
color: 'color-mix(in oklab, var(--color-base-content) 60%, transparent)',
|
|
5003
5662
|
},
|
|
5663
|
+
at: tableStackAt(tokens, 'foot', { rowGap: 'var(--space-md)', marginBlockStart: 'var(--space-md)' }),
|
|
5004
5664
|
},
|
|
5005
5665
|
row: {
|
|
5006
5666
|
base: { borderBlockEnd: 'var(--border) solid var(--color-base-200)' },
|
|
5007
5667
|
states: {
|
|
5008
5668
|
selected: { background: 'var(--color-base-300)' },
|
|
5009
5669
|
},
|
|
5670
|
+
at: tableStackAt(tokens, 'row', {
|
|
5671
|
+
border: 'var(--border) solid var(--color-base-200)',
|
|
5672
|
+
borderRadius: 'var(--radius-box)',
|
|
5673
|
+
background: 'var(--color-base-100)',
|
|
5674
|
+
padding: 'var(--table-pad-block) var(--table-pad-inline)',
|
|
5675
|
+
}),
|
|
5010
5676
|
},
|
|
5011
5677
|
'header-cell': {
|
|
5012
5678
|
base: {
|
|
5013
5679
|
padding: 'var(--table-pad-block) var(--table-pad-inline)',
|
|
5014
|
-
textAlign: '
|
|
5680
|
+
textAlign: 'var(--table-cell-align)',
|
|
5015
5681
|
fontWeight: 'var(--weight-semibold)',
|
|
5016
5682
|
fontSize: 'var(--text-xs)',
|
|
5017
5683
|
color: 'color-mix(in oklab, var(--table-accent) 60%, transparent)',
|
|
5018
5684
|
},
|
|
5685
|
+
at: tableStackAt(tokens, 'header-cell', { paddingInline: '0' }),
|
|
5019
5686
|
},
|
|
5020
5687
|
cell: {
|
|
5021
5688
|
base: {
|
|
5022
5689
|
padding: 'var(--table-pad-block) var(--table-pad-inline)',
|
|
5023
|
-
textAlign: '
|
|
5690
|
+
textAlign: 'var(--table-cell-align)',
|
|
5691
|
+
},
|
|
5692
|
+
at: tableStackAt(tokens, 'cell', { paddingInline: '0', paddingBlock: 'var(--space-xs)' }),
|
|
5693
|
+
},
|
|
5694
|
+
// A stacked cell's column label, in the header's muted semibold.
|
|
5695
|
+
'cell-label': {
|
|
5696
|
+
base: {
|
|
5697
|
+
fontWeight: 'var(--weight-semibold)',
|
|
5698
|
+
fontSize: 'var(--text-xs)',
|
|
5699
|
+
color: 'color-mix(in oklab, var(--table-accent) 60%, transparent)',
|
|
5024
5700
|
},
|
|
5025
5701
|
},
|
|
5026
5702
|
},
|
|
@@ -5340,6 +6016,22 @@ export const carousel = {
|
|
|
5340
6016
|
xl: { root: { base: { '--carousel-dot': '0.875rem', '--carousel-nav': '3rem' } } },
|
|
5341
6017
|
},
|
|
5342
6018
|
},
|
|
6019
|
+
// The nav buttons' anchors, restated physically: logical inset
|
|
6020
|
+
// spellings resolve on iOS but not on Android (measured,
|
|
6021
|
+
// signalxjs/lynx#1084), so the emitter refuses them. Physical is the
|
|
6022
|
+
// lynx target's norm — no RTL flow there, so `start` IS left.
|
|
6023
|
+
targets: {
|
|
6024
|
+
lynx: {
|
|
6025
|
+
parts: {
|
|
6026
|
+
'prev-trigger': {
|
|
6027
|
+
base: { top: 'calc(50% - var(--carousel-nav) / 2)', left: 'var(--space-sm)' },
|
|
6028
|
+
},
|
|
6029
|
+
'next-trigger': {
|
|
6030
|
+
base: { top: 'calc(50% - var(--carousel-nav) / 2)', right: 'var(--space-sm)' },
|
|
6031
|
+
},
|
|
6032
|
+
},
|
|
6033
|
+
},
|
|
6034
|
+
},
|
|
5343
6035
|
};
|
|
5344
6036
|
/**
|
|
5345
6037
|
* Swap — daisy's signature swap-rotate as the ONE look: the leaving face
|
|
@@ -5477,6 +6169,12 @@ export const countdown = {
|
|
|
5477
6169
|
xl: { root: { base: { '--countdown-font': 'var(--text-3xl)' } } },
|
|
5478
6170
|
},
|
|
5479
6171
|
},
|
|
6172
|
+
// One line inside a sentence (#57): the surrounding text's size and
|
|
6173
|
+
// weight instead of the display step. What stays is what makes
|
|
6174
|
+
// it a countdown — tabular digits, its ink, the per-tick entry.
|
|
6175
|
+
modifiers: {
|
|
6176
|
+
inline: { root: { base: { fontSize: 'inherit', fontWeight: 'inherit' } } },
|
|
6177
|
+
},
|
|
5480
6178
|
keyframes: { 'zero-daisyui-countdown-in': 'from { transform: translateY(0.6em); opacity: 0; }' },
|
|
5481
6179
|
};
|
|
5482
6180
|
/**
|
|
@@ -5581,15 +6279,30 @@ export const diff = {
|
|
|
5581
6279
|
},
|
|
5582
6280
|
},
|
|
5583
6281
|
},
|
|
6282
|
+
// The reveal pane's pin and the handle's centering margin, restated
|
|
6283
|
+
// physically: logical spellings resolve on iOS but not on Android
|
|
6284
|
+
// (measured, signalxjs/lynx#1084), so the emitter refuses them.
|
|
6285
|
+
// Physical is the lynx target's norm — no RTL flow there to mirror
|
|
6286
|
+
// with.
|
|
6287
|
+
lynx: {
|
|
6288
|
+
parts: {
|
|
6289
|
+
after: {
|
|
6290
|
+
base: { top: '0', bottom: '0', left: '0' },
|
|
6291
|
+
},
|
|
6292
|
+
handle: {
|
|
6293
|
+
base: { top: '0', bottom: '0', marginLeft: 'calc(var(--diff-hit) / -2)' },
|
|
6294
|
+
},
|
|
6295
|
+
},
|
|
6296
|
+
},
|
|
5584
6297
|
},
|
|
5585
6298
|
};
|
|
5586
6299
|
export const recipes = [
|
|
5587
6300
|
tabs, collapsible, switchRecipe, dialog, popover, tooltip, menu,
|
|
5588
6301
|
field, checkbox, radioGroup, progress, slider, accordion, select, button, avatar, toast, combobox,
|
|
5589
|
-
toggle, toggleGroup, numberInput, ratingGroup, treeView, input, textarea,
|
|
5590
|
-
card, alert, badge, divider, skeleton, spinner,
|
|
6302
|
+
toggle, toggleGroup, numberInput, ratingGroup, treeView, input, textarea,
|
|
6303
|
+
card, alert, emptyState, badge, divider, skeleton, spinner,
|
|
5591
6304
|
kbd, status, indicator, stats, timeline, chat, radialProgress, join,
|
|
5592
|
-
navbar, breadcrumbs, pagination, steps, drawer,
|
|
6305
|
+
navbar, navList, breadcrumbs, pagination, steps, drawer,
|
|
5593
6306
|
table,
|
|
5594
6307
|
fileUpload,
|
|
5595
6308
|
carousel,
|