@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/src/recipes.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* package: a design system is data, and "looks like daisy" is one possible
|
|
5
5
|
* value of that data.
|
|
6
6
|
*/
|
|
7
|
-
import type { CssProps, PartStyles, RecipeInput
|
|
8
|
-
import {
|
|
7
|
+
import type { CssProps, PartStyles, RecipeInput } from '@sigx/zero-kit';
|
|
8
|
+
import { axisRoles, tableStackAt } from '@sigx/zero-kit/define';
|
|
9
|
+
import { roles, tokens } from './tokens.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Every role a consumer can pass as `color`, derived from the declaration
|
|
@@ -14,9 +15,7 @@ import { roles } from './tokens.js';
|
|
|
14
15
|
* `-content` or `-soft` are fills or hairlines, not action colours; this
|
|
15
16
|
* design system declares none.
|
|
16
17
|
*/
|
|
17
|
-
const ROLES =
|
|
18
|
-
.filter(([, decl]) => decl.content !== false && decl.soft !== false)
|
|
19
|
-
.map(([name]) => name);
|
|
18
|
+
const ROLES = axisRoles(roles);
|
|
20
19
|
|
|
21
20
|
/**
|
|
22
21
|
* "…and the reading direction is right-to-left" — appended to a selector, never
|
|
@@ -537,6 +536,13 @@ const disclosureSizes: Record<string, Record<string, PartStyles>> = {
|
|
|
537
536
|
// daisy "collapse collapse-arrow" flavor.
|
|
538
537
|
export const collapsible: RecipeInput = {
|
|
539
538
|
component: 'collapsible',
|
|
539
|
+
// Public to a design system derived from this one (#73).
|
|
540
|
+
hooks: {
|
|
541
|
+
properties: {
|
|
542
|
+
'--collapsible-accent': 'The accent of the open trigger.',
|
|
543
|
+
},
|
|
544
|
+
pseudo: { trigger: ['::after'] },
|
|
545
|
+
},
|
|
540
546
|
// The accent is the open heading's INK, so the default is base-content —
|
|
541
547
|
// the un-attributed render stays exactly daisy's neutral collapse and a
|
|
542
548
|
// role only arrives through `data-color`.
|
|
@@ -611,6 +617,14 @@ export const collapsible: RecipeInput = {
|
|
|
611
617
|
// daisy "toggle" flavor.
|
|
612
618
|
export const switchRecipe: RecipeInput = {
|
|
613
619
|
component: 'switch',
|
|
620
|
+
// Public to a design system derived from this one (#73).
|
|
621
|
+
hooks: {
|
|
622
|
+
properties: {
|
|
623
|
+
'--switch-size': 'The height; width, padding and knob travel derive from it.',
|
|
624
|
+
'--switch-accent': 'The checked track and knob accent.',
|
|
625
|
+
'--switch-ink': 'The unchecked border, knob and ring ink (daisy --input-color).',
|
|
626
|
+
},
|
|
627
|
+
},
|
|
614
628
|
/**
|
|
615
629
|
* daisy drives the whole toggle off ONE size, exactly as it drives the
|
|
616
630
|
* checkbox: `--size` is the height, the padding is a fixed eighth of it,
|
|
@@ -919,8 +933,32 @@ const btnSizes: Record<string, Record<string, PartStyles>> = {
|
|
|
919
933
|
xl: { trigger: { base: { height: fieldHeight('xl'), paddingInline: 'calc(var(--size-field) * 6)', fontSize: 'var(--text-lg)' } } },
|
|
920
934
|
};
|
|
921
935
|
|
|
936
|
+
/**
|
|
937
|
+
* `btn`'s inline padding restated physically, for the `targets.lynx`
|
|
938
|
+
* sections of every btn-wearing recipe: logical spellings resolve on iOS but
|
|
939
|
+
* not on Android (measured, signalxjs/lynx#1084), so the lynx emitter
|
|
940
|
+
* refuses `padding-inline`. Physical is that target's norm — lynx has no
|
|
941
|
+
* RTL flow for the logical spelling to flip with.
|
|
942
|
+
*/
|
|
943
|
+
const lynxBtnPad = (mult: number): NonNullable<PartStyles['base']> => ({
|
|
944
|
+
paddingLeft: `calc(var(--size-field) * ${mult})`,
|
|
945
|
+
paddingRight: `calc(var(--size-field) * ${mult})`,
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
/** The btn size ramp's physical inline padding, for `targets.lynx.variants`. */
|
|
949
|
+
const lynxBtnSizes = (part: string): Record<string, Record<string, PartStyles>> => ({
|
|
950
|
+
xs: { [part]: { base: lynxBtnPad(2) } },
|
|
951
|
+
sm: { [part]: { base: lynxBtnPad(3) } },
|
|
952
|
+
lg: { [part]: { base: lynxBtnPad(5) } },
|
|
953
|
+
xl: { [part]: { base: lynxBtnPad(6) } },
|
|
954
|
+
});
|
|
955
|
+
|
|
922
956
|
export const dialog: RecipeInput = {
|
|
923
957
|
component: 'dialog',
|
|
958
|
+
// Public to a design system derived from this one (#73).
|
|
959
|
+
hooks: {
|
|
960
|
+
keyframes: ['zero-daisy-pop'],
|
|
961
|
+
},
|
|
924
962
|
keyframes: {
|
|
925
963
|
'zero-daisy-pop': 'from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); }',
|
|
926
964
|
},
|
|
@@ -937,7 +975,12 @@ export const dialog: RecipeInput = {
|
|
|
937
975
|
},
|
|
938
976
|
popup: withPresence(popupPresence('translateY(8px) scale(0.97)'), {
|
|
939
977
|
base: {
|
|
940
|
-
|
|
978
|
+
// A <dialog> keeps the UA's `content-box`, and zero ships no
|
|
979
|
+
// reset — so a width or max-width meant to leave a gutter
|
|
980
|
+
// grew by the padding, and at phone width the popup ran past
|
|
981
|
+
// both edges (#101). Its box is the border box.
|
|
982
|
+
boxSizing: 'border-box',
|
|
983
|
+
padding: 'var(--space-2xl)',
|
|
941
984
|
maxWidth: '32rem',
|
|
942
985
|
width: 'calc(100% - 2rem)',
|
|
943
986
|
background: 'var(--color-base-100)',
|
|
@@ -967,14 +1010,15 @@ export const dialog: RecipeInput = {
|
|
|
967
1010
|
},
|
|
968
1011
|
// daisy's `modal-action`: the actions sit at the trailing edge, one
|
|
969
1012
|
// step of the button gap apart, pushed down by the modal's own
|
|
970
|
-
// padding
|
|
971
|
-
//
|
|
1013
|
+
// padding — daisy writes 1.5rem for both, and both now read the top
|
|
1014
|
+
// of the ramp (`--space-2xl`, 1.25rem) so a density override moves
|
|
1015
|
+
// the popup's inset and the footer's push-down together.
|
|
972
1016
|
footer: {
|
|
973
1017
|
base: {
|
|
974
1018
|
display: 'flex',
|
|
975
1019
|
justifyContent: 'flex-end',
|
|
976
1020
|
gap: 'var(--space-md)',
|
|
977
|
-
marginBlockStart: '
|
|
1021
|
+
marginBlockStart: 'var(--space-2xl)',
|
|
978
1022
|
},
|
|
979
1023
|
},
|
|
980
1024
|
close: {
|
|
@@ -999,6 +1043,19 @@ export const dialog: RecipeInput = {
|
|
|
999
1043
|
// Trigger-carried axes — see `btnColors` for why the popup is out of
|
|
1000
1044
|
// reach and the trigger is the whole story here.
|
|
1001
1045
|
variants: { color: btnColors(), size: btnSizes },
|
|
1046
|
+
// The btn paddings and the footer's push-down, restated physically —
|
|
1047
|
+
// see `lynxBtnPad` for the #1084 verdict this answers.
|
|
1048
|
+
targets: {
|
|
1049
|
+
lynx: {
|
|
1050
|
+
parts: {
|
|
1051
|
+
trigger: { base: lynxBtnPad(4) },
|
|
1052
|
+
close: { base: lynxBtnPad(4) },
|
|
1053
|
+
cancel: { base: lynxBtnPad(4) },
|
|
1054
|
+
footer: { base: { marginTop: 'var(--space-2xl)' } },
|
|
1055
|
+
},
|
|
1056
|
+
variants: { size: lynxBtnSizes('trigger') },
|
|
1057
|
+
},
|
|
1058
|
+
},
|
|
1002
1059
|
};
|
|
1003
1060
|
|
|
1004
1061
|
// daisy "dropdown-content"/card look for floating panels.
|
|
@@ -1041,6 +1098,16 @@ export const popover: RecipeInput = {
|
|
|
1041
1098
|
},
|
|
1042
1099
|
// Trigger-carried axes — same wiring as dialog, same reason.
|
|
1043
1100
|
variants: { color: btnColors(), size: btnSizes },
|
|
1101
|
+
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
1102
|
+
targets: {
|
|
1103
|
+
lynx: {
|
|
1104
|
+
parts: {
|
|
1105
|
+
trigger: { base: lynxBtnPad(4) },
|
|
1106
|
+
close: { base: lynxBtnPad(3) },
|
|
1107
|
+
},
|
|
1108
|
+
variants: { size: lynxBtnSizes('trigger') },
|
|
1109
|
+
},
|
|
1110
|
+
},
|
|
1044
1111
|
};
|
|
1045
1112
|
|
|
1046
1113
|
export const tooltip: RecipeInput = {
|
|
@@ -1077,6 +1144,13 @@ export const tooltip: RecipeInput = {
|
|
|
1077
1144
|
// Trigger-carried axes — same wiring as dialog, same reason. The bubble
|
|
1078
1145
|
// stays daisy's neutral tooltip whatever the trigger's colour.
|
|
1079
1146
|
variants: { color: btnColors(), size: btnSizes },
|
|
1147
|
+
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
1148
|
+
targets: {
|
|
1149
|
+
lynx: {
|
|
1150
|
+
parts: { trigger: { base: lynxBtnPad(4) } },
|
|
1151
|
+
variants: { size: lynxBtnSizes('trigger') },
|
|
1152
|
+
},
|
|
1153
|
+
},
|
|
1080
1154
|
};
|
|
1081
1155
|
|
|
1082
1156
|
// daisy "menu in a dropdown" look.
|
|
@@ -1101,7 +1175,7 @@ export const menu: RecipeInput = {
|
|
|
1101
1175
|
base: {
|
|
1102
1176
|
display: 'flex',
|
|
1103
1177
|
alignItems: 'center',
|
|
1104
|
-
gap: '
|
|
1178
|
+
gap: 'var(--space-md)',
|
|
1105
1179
|
padding: 'var(--space-md) var(--space-lg)',
|
|
1106
1180
|
fontSize: 'var(--text-sm)',
|
|
1107
1181
|
fontWeight: 'var(--weight-medium)',
|
|
@@ -1121,7 +1195,7 @@ export const menu: RecipeInput = {
|
|
|
1121
1195
|
base: {
|
|
1122
1196
|
display: 'flex',
|
|
1123
1197
|
alignItems: 'center',
|
|
1124
|
-
gap: '
|
|
1198
|
+
gap: 'var(--space-md)',
|
|
1125
1199
|
padding: 'var(--space-md) var(--space-lg)',
|
|
1126
1200
|
fontSize: 'var(--text-sm)',
|
|
1127
1201
|
fontWeight: 'var(--weight-medium)',
|
|
@@ -1140,7 +1214,7 @@ export const menu: RecipeInput = {
|
|
|
1140
1214
|
base: {
|
|
1141
1215
|
display: 'flex',
|
|
1142
1216
|
alignItems: 'center',
|
|
1143
|
-
gap: '
|
|
1217
|
+
gap: 'var(--space-md)',
|
|
1144
1218
|
padding: 'var(--space-md) var(--space-lg)',
|
|
1145
1219
|
fontSize: 'var(--text-sm)',
|
|
1146
1220
|
fontWeight: 'var(--weight-medium)',
|
|
@@ -1175,7 +1249,7 @@ export const menu: RecipeInput = {
|
|
|
1175
1249
|
base: {
|
|
1176
1250
|
display: 'flex',
|
|
1177
1251
|
alignItems: 'center',
|
|
1178
|
-
gap: '
|
|
1252
|
+
gap: 'var(--space-md)',
|
|
1179
1253
|
padding: 'var(--space-md) var(--space-lg)',
|
|
1180
1254
|
fontSize: 'var(--text-sm)',
|
|
1181
1255
|
fontWeight: 'var(--weight-medium)',
|
|
@@ -1223,6 +1297,13 @@ export const menu: RecipeInput = {
|
|
|
1223
1297
|
// Trigger-carried axes — same wiring as dialog, same reason. The dropdown
|
|
1224
1298
|
// and its items are top-layer siblings the donut cannot reach.
|
|
1225
1299
|
variants: { color: btnColors(), size: btnSizes },
|
|
1300
|
+
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
1301
|
+
targets: {
|
|
1302
|
+
lynx: {
|
|
1303
|
+
parts: { trigger: { base: lynxBtnPad(4) } },
|
|
1304
|
+
variants: { size: lynxBtnSizes('trigger') },
|
|
1305
|
+
},
|
|
1306
|
+
},
|
|
1226
1307
|
};
|
|
1227
1308
|
|
|
1228
1309
|
export const field: RecipeInput = {
|
|
@@ -1349,7 +1430,7 @@ export const checkbox: RecipeInput = {
|
|
|
1349
1430
|
},
|
|
1350
1431
|
parts: {
|
|
1351
1432
|
root: {
|
|
1352
|
-
base: { display: 'inline-flex', alignItems: 'center', gap: '
|
|
1433
|
+
base: { display: 'inline-flex', alignItems: 'center', gap: 'var(--space-md)', cursor: 'pointer' },
|
|
1353
1434
|
states: {
|
|
1354
1435
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
1355
1436
|
checked: {}, unchecked: {}, indeterminate: {},
|
|
@@ -1510,7 +1591,23 @@ export const checkbox: RecipeInput = {
|
|
|
1510
1591
|
// The tick's fill: the control's `color:` names
|
|
1511
1592
|
// `--checkbox-on-accent`, and `currentColor` was that
|
|
1512
1593
|
// ink by inheritance — spend it directly.
|
|
1513
|
-
|
|
1594
|
+
//
|
|
1595
|
+
// The tick's motion, restated as `transform`: the
|
|
1596
|
+
// standalone `rotate`/`translate` properties resolve on
|
|
1597
|
+
// iOS but not on Android (measured, signalxjs/lynx#1084),
|
|
1598
|
+
// so the emitter refuses them. `transform` functions are
|
|
1599
|
+
// proven on both platforms — and the transition follows
|
|
1600
|
+
// the property that now moves.
|
|
1601
|
+
base: {
|
|
1602
|
+
backgroundColor: 'var(--checkbox-on-accent)',
|
|
1603
|
+
transform: 'rotate(45deg)',
|
|
1604
|
+
transition: 'clip-path var(--duration-slow) var(--ease-standard) var(--duration-instant), '
|
|
1605
|
+
+ 'opacity var(--duration-instant) var(--ease-standard) var(--duration-instant), '
|
|
1606
|
+
+ 'transform var(--duration-slow) var(--ease-standard) var(--duration-instant)',
|
|
1607
|
+
},
|
|
1608
|
+
// The order matches the standalone properties' fixed
|
|
1609
|
+
// apply order (translate, then rotate).
|
|
1610
|
+
states: { indeterminate: { transform: 'translateY(-35%) rotate(0deg)' } },
|
|
1514
1611
|
},
|
|
1515
1612
|
},
|
|
1516
1613
|
},
|
|
@@ -1532,7 +1629,7 @@ export const radioGroup: RecipeInput = {
|
|
|
1532
1629
|
},
|
|
1533
1630
|
parts: {
|
|
1534
1631
|
root: {
|
|
1535
|
-
base: { display: 'flex', flexDirection: 'column', gap: '
|
|
1632
|
+
base: { display: 'flex', flexDirection: 'column', gap: 'var(--space-md)' },
|
|
1536
1633
|
states: { invalid: {}, required: {} },
|
|
1537
1634
|
selectors: {
|
|
1538
1635
|
// `invalid` is a fact about the GROUP — `item-control` carries
|
|
@@ -1546,7 +1643,7 @@ export const radioGroup: RecipeInput = {
|
|
|
1546
1643
|
states: { disabled: { opacity: 'var(--disabled-opacity)' } },
|
|
1547
1644
|
},
|
|
1548
1645
|
item: {
|
|
1549
|
-
base: { display: 'inline-flex', alignItems: 'center', gap: '
|
|
1646
|
+
base: { display: 'inline-flex', alignItems: 'center', gap: 'var(--space-md)', cursor: 'pointer' },
|
|
1550
1647
|
states: {
|
|
1551
1648
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
1552
1649
|
checked: {}, unchecked: {},
|
|
@@ -1762,6 +1859,18 @@ export const progress: RecipeInput = {
|
|
|
1762
1859
|
// other way.
|
|
1763
1860
|
'zero-daisy-indeterminate': 'from { margin-inline-start: -40%; } to { margin-inline-start: 100%; }',
|
|
1764
1861
|
},
|
|
1862
|
+
// The same sweep with a physical margin: logical spellings resolve on
|
|
1863
|
+
// iOS but not on Android (measured, signalxjs/lynx#1084), so the emitter
|
|
1864
|
+
// refuses the shared keyframes. Physical is the lynx target's norm — no
|
|
1865
|
+
// RTL flow there for the sweep to mirror with. Keyframes merge per name,
|
|
1866
|
+
// so this REPLACES the shared animation on lynx.
|
|
1867
|
+
targets: {
|
|
1868
|
+
lynx: {
|
|
1869
|
+
keyframes: {
|
|
1870
|
+
'zero-daisy-indeterminate': 'from { margin-left: -40%; } to { margin-left: 100%; }',
|
|
1871
|
+
},
|
|
1872
|
+
},
|
|
1873
|
+
},
|
|
1765
1874
|
};
|
|
1766
1875
|
|
|
1767
1876
|
export const slider: RecipeInput = {
|
|
@@ -1887,6 +1996,22 @@ export const slider: RecipeInput = {
|
|
|
1887
1996
|
targets: {
|
|
1888
1997
|
lynx: {
|
|
1889
1998
|
parts: {
|
|
1999
|
+
track: {
|
|
2000
|
+
// The web track's `margin-block` breathing room,
|
|
2001
|
+
// restated physically: logical spellings resolve on iOS
|
|
2002
|
+
// but not on Android (measured, signalxjs/lynx#1084), so
|
|
2003
|
+
// the emitter refuses them. Physical is the lynx
|
|
2004
|
+
// target's norm — no RTL flow there.
|
|
2005
|
+
base: {
|
|
2006
|
+
marginTop: 'calc(var(--size-selector) * 1.5)',
|
|
2007
|
+
marginBottom: 'calc(var(--size-selector) * 1.5)',
|
|
2008
|
+
},
|
|
2009
|
+
},
|
|
2010
|
+
mark: {
|
|
2011
|
+
// Same #1084 verdict: the tick labels hang below the
|
|
2012
|
+
// track off a physical padding.
|
|
2013
|
+
base: { paddingTop: 'calc(var(--size-selector) * 2 + var(--space-2xs))' },
|
|
2014
|
+
},
|
|
1890
2015
|
range: {
|
|
1891
2016
|
// The web fill deepens the accent 90/10 toward
|
|
1892
2017
|
// base-content (`progressFill`'s recipe); the plain
|
|
@@ -1910,9 +2035,20 @@ export const slider: RecipeInput = {
|
|
|
1910
2035
|
// thumb — a native-input trick with no lynx equivalent;
|
|
1911
2036
|
// the composed `.zx-slider__range` box above IS that
|
|
1912
2037
|
// fill here, so the knob only needs its own two paints.
|
|
2038
|
+
//
|
|
2039
|
+
// The centering geometry is restated physically beside
|
|
2040
|
+
// the paint: `inset-block-start`/`translate`/
|
|
2041
|
+
// `margin-inline-start` resolve on iOS but not on
|
|
2042
|
+
// Android (measured, signalxjs/lynx#1084 — the knob sat
|
|
2043
|
+
// off-center there), so the emitter refuses them.
|
|
2044
|
+
// `top` + `transform: translateY()` + `margin-left` are
|
|
2045
|
+
// proven on both platforms.
|
|
1913
2046
|
base: {
|
|
1914
2047
|
background: 'var(--color-base-100)',
|
|
1915
2048
|
border: '0.25rem solid var(--slider-accent)',
|
|
2049
|
+
top: '50%',
|
|
2050
|
+
transform: 'translateY(-50%)',
|
|
2051
|
+
marginLeft: 'calc(var(--size-selector) * -2.5)',
|
|
1916
2052
|
},
|
|
1917
2053
|
},
|
|
1918
2054
|
},
|
|
@@ -2097,7 +2233,7 @@ export const select: RecipeInput = {
|
|
|
2097
2233
|
display: 'flex',
|
|
2098
2234
|
alignItems: 'center',
|
|
2099
2235
|
justifyContent: 'space-between',
|
|
2100
|
-
gap: '
|
|
2236
|
+
gap: 'var(--space-md)',
|
|
2101
2237
|
padding: 'var(--space-md) var(--space-lg)',
|
|
2102
2238
|
fontSize: 'var(--text-sm)',
|
|
2103
2239
|
fontWeight: 'var(--weight-medium)',
|
|
@@ -2133,6 +2269,17 @@ export const select: RecipeInput = {
|
|
|
2133
2269
|
// Base equals the default (`tokens:` accent, `fieldControl`'s md height),
|
|
2134
2270
|
// so the twins restate it; declared for the manifest (signalxjs/lynx#1070).
|
|
2135
2271
|
defaultVariants: { color: 'primary', size: 'md' },
|
|
2272
|
+
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
2273
|
+
// The trigger ramp restates `md` too (unlike `btnSizes`), so its lynx
|
|
2274
|
+
// counterpart does as well.
|
|
2275
|
+
targets: {
|
|
2276
|
+
lynx: {
|
|
2277
|
+
parts: { trigger: { base: lynxBtnPad(4) } },
|
|
2278
|
+
variants: {
|
|
2279
|
+
size: { ...lynxBtnSizes('trigger'), md: { trigger: { base: lynxBtnPad(4) } } },
|
|
2280
|
+
},
|
|
2281
|
+
},
|
|
2282
|
+
},
|
|
2136
2283
|
};
|
|
2137
2284
|
|
|
2138
2285
|
|
|
@@ -2155,6 +2302,15 @@ const btnHeight = (step: keyof typeof BTN_STEPS): string =>
|
|
|
2155
2302
|
|
|
2156
2303
|
export const button: RecipeInput = {
|
|
2157
2304
|
component: 'button',
|
|
2305
|
+
// Public to a design system derived from this one (#73).
|
|
2306
|
+
hooks: {
|
|
2307
|
+
properties: {
|
|
2308
|
+
'--btn-accent': 'The fill of the solid variant.',
|
|
2309
|
+
'--btn-on-accent': 'The ink on --btn-accent.',
|
|
2310
|
+
'--btn-soft': 'The soft fill (soft variant, unfilled hover).',
|
|
2311
|
+
'--btn-ink': 'The ink of the unfilled variants.',
|
|
2312
|
+
},
|
|
2313
|
+
},
|
|
2158
2314
|
// The two axes meet here instead of multiplying. `color` sets the accent
|
|
2159
2315
|
// pair; `variant` decides how the accent is used. 8 + 4 + 5 rules rather
|
|
2160
2316
|
// than 8 × 4.
|
|
@@ -2165,9 +2321,34 @@ export const button: RecipeInput = {
|
|
|
2165
2321
|
'--btn-ink': roleInk('primary'),
|
|
2166
2322
|
},
|
|
2167
2323
|
parts: {
|
|
2324
|
+
// The loading spinner (#50): a real part zero renders before the
|
|
2325
|
+
// label while `loading`, drawn as a ring in `currentColor` with one
|
|
2326
|
+
// transparent quadrant — it takes whatever ink the variant chose.
|
|
2327
|
+
// A literal duration and an explicit reduced-motion `none`: the kit
|
|
2328
|
+
// collapses `--duration-*` under reduced motion, and an infinite
|
|
2329
|
+
// loop at ~0s strobes rather than stops.
|
|
2330
|
+
// daisy marks a loading button with a `loading loading-spinner` span;
|
|
2331
|
+
// this is that span, zero's own now (it was a `loading` modifier's
|
|
2332
|
+
// `::before` until #50 made loading a Button state).
|
|
2333
|
+
spinner: {
|
|
2334
|
+
base: {
|
|
2335
|
+
boxSizing: 'border-box',
|
|
2336
|
+
inlineSize: '1em',
|
|
2337
|
+
blockSize: '1em',
|
|
2338
|
+
flex: 'none',
|
|
2339
|
+
borderRadius: '9999px',
|
|
2340
|
+
border: 'calc(var(--border) * 2) solid currentColor',
|
|
2341
|
+
borderBlockStartColor: 'transparent',
|
|
2342
|
+
animation: 'zero-daisyui-btn-spin 0.7s linear infinite',
|
|
2343
|
+
},
|
|
2344
|
+
at: { 'reduced-motion': { base: { animation: 'none' } } },
|
|
2345
|
+
},
|
|
2168
2346
|
root: {
|
|
2169
2347
|
base: {
|
|
2170
2348
|
appearance: 'none',
|
|
2349
|
+
// An asChild `<a>` gets no UA underline (see the README's
|
|
2350
|
+
// link-button note for the unlayered `a { color }` case).
|
|
2351
|
+
textDecoration: 'none',
|
|
2171
2352
|
display: 'inline-flex',
|
|
2172
2353
|
alignItems: 'center',
|
|
2173
2354
|
justifyContent: 'center',
|
|
@@ -2183,6 +2364,9 @@ export const button: RecipeInput = {
|
|
|
2183
2364
|
+ 'border-color var(--duration-fast) var(--ease-standard)',
|
|
2184
2365
|
},
|
|
2185
2366
|
states: {
|
|
2367
|
+
// Work in flight (`loading`, #50): still focusable, still legible —
|
|
2368
|
+
// the label is what the reader is waiting on — so no fade here.
|
|
2369
|
+
loading: { cursor: 'progress' },
|
|
2186
2370
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
2187
2371
|
// The ring is drawn on the page, offset clear of the button —
|
|
2188
2372
|
// ink, not fill, and the same 3:1 floor. On `--btn-accent` it
|
|
@@ -2328,40 +2512,20 @@ export const button: RecipeInput = {
|
|
|
2328
2512
|
active: {
|
|
2329
2513
|
root: { base: { transform: 'translateY(1px)', boxShadow: 'none', filter: 'brightness(0.92)' } },
|
|
2330
2514
|
},
|
|
2331
|
-
/**
|
|
2332
|
-
* daisy's loading spinner, drawn by the recipe (#332): daisy marks
|
|
2333
|
-
* the button with a `loading loading-spinner` span; zero changes no
|
|
2334
|
-
* DOM, so the ring is a `::before` in `currentColor` with one
|
|
2335
|
-
* transparent quadrant — it inherits whatever ink the variant chose,
|
|
2336
|
-
* and the root's existing `gap` spaces it from the label. Literal
|
|
2337
|
-
* duration + explicit reduced-motion `none`, like the spinner
|
|
2338
|
-
* recipe: the kit collapses `--duration-*` under reduced motion, and
|
|
2339
|
-
* an infinite loop at ~0s strobes rather than stops.
|
|
2340
|
-
*/
|
|
2341
|
-
loading: {
|
|
2342
|
-
root: {
|
|
2343
|
-
base: { cursor: 'progress' },
|
|
2344
|
-
selectors: {
|
|
2345
|
-
'&::before': {
|
|
2346
|
-
content: '""',
|
|
2347
|
-
boxSizing: 'border-box',
|
|
2348
|
-
inlineSize: '1em',
|
|
2349
|
-
blockSize: '1em',
|
|
2350
|
-
flex: 'none',
|
|
2351
|
-
borderRadius: '9999px',
|
|
2352
|
-
border: 'calc(var(--border) * 2) solid currentColor',
|
|
2353
|
-
borderBlockStartColor: 'transparent',
|
|
2354
|
-
animation: 'zero-daisyui-btn-spin 0.7s linear infinite',
|
|
2355
|
-
},
|
|
2356
|
-
},
|
|
2357
|
-
at: {
|
|
2358
|
-
'reduced-motion': { selectors: { '&::before': { animation: 'none' } } },
|
|
2359
|
-
},
|
|
2360
|
-
},
|
|
2361
|
-
},
|
|
2362
2515
|
},
|
|
2363
2516
|
defaultVariants: { color: 'primary', variant: 'solid', size: 'md' },
|
|
2364
2517
|
keyframes: { 'zero-daisyui-btn-spin': 'to { transform: rotate(360deg) }' },
|
|
2518
|
+
// The icon chips' zeroed inline padding, restated physically: logical
|
|
2519
|
+
// spellings resolve on iOS but not on Android (measured,
|
|
2520
|
+
// signalxjs/lynx#1084), so the emitter refuses them.
|
|
2521
|
+
targets: {
|
|
2522
|
+
lynx: {
|
|
2523
|
+
modifiers: {
|
|
2524
|
+
square: { root: { base: { paddingLeft: '0', paddingRight: '0' } } },
|
|
2525
|
+
circle: { root: { base: { paddingLeft: '0', paddingRight: '0' } } },
|
|
2526
|
+
},
|
|
2527
|
+
},
|
|
2528
|
+
},
|
|
2365
2529
|
};
|
|
2366
2530
|
|
|
2367
2531
|
export const avatar: RecipeInput = {
|
|
@@ -2415,6 +2579,13 @@ export const avatar: RecipeInput = {
|
|
|
2415
2579
|
},
|
|
2416
2580
|
},
|
|
2417
2581
|
variants: {
|
|
2582
|
+
// The shape axis (zero#129) — one declaration on the root, whose
|
|
2583
|
+
// `overflow: hidden` clips the image and the fallback alike. `rounded` is daisy's selector radius; the default is the circle.
|
|
2584
|
+
shape: {
|
|
2585
|
+
circle: { root: { base: { borderRadius: '9999px' } } },
|
|
2586
|
+
square: { root: { base: { borderRadius: '0' } } },
|
|
2587
|
+
rounded: { root: { base: { borderRadius: 'var(--radius-selector)' } } },
|
|
2588
|
+
},
|
|
2418
2589
|
// daisy colours the RING, not the fallback — that is what its avatar
|
|
2419
2590
|
// does, and the fallback stays neutral so initials read the same
|
|
2420
2591
|
// whichever role is chosen.
|
|
@@ -2589,6 +2760,24 @@ export const toast: RecipeInput = {
|
|
|
2589
2760
|
// signalxjs/lynx#1070. No `color` default: the un-attributed toast is a
|
|
2590
2761
|
// neutral base-200 surface, outside the color vocabulary.
|
|
2591
2762
|
defaultVariants: { size: 'md' },
|
|
2763
|
+
// The corner placements' physical anchors: logical inset spellings
|
|
2764
|
+
// resolve on iOS but not on Android (measured, signalxjs/lynx#1084), so
|
|
2765
|
+
// the emitter refuses them. Physical is the lynx target's norm — no RTL
|
|
2766
|
+
// flow there, so `start` IS left and `end` IS right.
|
|
2767
|
+
targets: {
|
|
2768
|
+
lynx: {
|
|
2769
|
+
parts: {
|
|
2770
|
+
viewport: {
|
|
2771
|
+
selectors: {
|
|
2772
|
+
'&[data-placement="top-start"]': { left: '0' },
|
|
2773
|
+
'&[data-placement="top-end"]': { right: '0' },
|
|
2774
|
+
'&[data-placement="bottom-start"]': { left: '0' },
|
|
2775
|
+
'&[data-placement="bottom-end"]': { right: '0' },
|
|
2776
|
+
},
|
|
2777
|
+
},
|
|
2778
|
+
},
|
|
2779
|
+
},
|
|
2780
|
+
},
|
|
2592
2781
|
};
|
|
2593
2782
|
|
|
2594
2783
|
export const combobox: RecipeInput = {
|
|
@@ -2608,6 +2797,7 @@ export const combobox: RecipeInput = {
|
|
|
2608
2797
|
base: {
|
|
2609
2798
|
display: 'inline-flex',
|
|
2610
2799
|
alignItems: 'stretch',
|
|
2800
|
+
flexWrap: 'wrap',
|
|
2611
2801
|
minWidth: '13rem',
|
|
2612
2802
|
...fieldControl,
|
|
2613
2803
|
transition: 'border-color var(--duration-fast) var(--ease-standard)',
|
|
@@ -2653,6 +2843,52 @@ export const combobox: RecipeInput = {
|
|
|
2653
2843
|
'&::placeholder': { color: 'color-mix(in oklab, var(--color-base-content) 60%, transparent)' },
|
|
2654
2844
|
},
|
|
2655
2845
|
},
|
|
2846
|
+
// A chosen value under `multiple` (#39): a chip in the control, before
|
|
2847
|
+
// the input. Symmetric metrics only — the physical-direction lint and
|
|
2848
|
+
// the lynx emitter both refuse a one-sided inset.
|
|
2849
|
+
// daisy's badge: a base-200 pill, centred in the stretched row.
|
|
2850
|
+
tag: {
|
|
2851
|
+
base: {
|
|
2852
|
+
display: 'inline-flex',
|
|
2853
|
+
alignItems: 'center',
|
|
2854
|
+
alignSelf: 'center',
|
|
2855
|
+
gap: 'var(--space-2xs)',
|
|
2856
|
+
margin: 'var(--space-2xs)',
|
|
2857
|
+
padding: 'var(--space-2xs) var(--space-sm)',
|
|
2858
|
+
background: 'var(--color-base-200)',
|
|
2859
|
+
color: 'var(--color-base-content)',
|
|
2860
|
+
borderRadius: 'var(--radius-selector)',
|
|
2861
|
+
fontSize: 'var(--text-xs)',
|
|
2862
|
+
fontWeight: 'var(--weight-medium)',
|
|
2863
|
+
lineHeight: 'var(--leading-none)',
|
|
2864
|
+
},
|
|
2865
|
+
states: { disabled: {} },
|
|
2866
|
+
},
|
|
2867
|
+
'tag-label': { base: { whiteSpace: 'nowrap' } },
|
|
2868
|
+
'tag-remove': {
|
|
2869
|
+
base: {
|
|
2870
|
+
appearance: 'none',
|
|
2871
|
+
border: 'none',
|
|
2872
|
+
background: 'transparent',
|
|
2873
|
+
color: 'inherit',
|
|
2874
|
+
font: 'inherit',
|
|
2875
|
+
borderRadius: 'var(--radius-selector)',
|
|
2876
|
+
padding: '0 var(--space-2xs)',
|
|
2877
|
+
lineHeight: 'var(--leading-none)',
|
|
2878
|
+
cursor: 'pointer',
|
|
2879
|
+
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
2880
|
+
},
|
|
2881
|
+
states: {
|
|
2882
|
+
hover: { background: 'color-mix(in oklab, var(--color-base-content) 10%, transparent)' },
|
|
2883
|
+
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
2884
|
+
...focusRing,
|
|
2885
|
+
},
|
|
2886
|
+
selectors: {
|
|
2887
|
+
'&[data-pressed]:not([data-disabled])': {
|
|
2888
|
+
background: 'color-mix(in oklab, var(--color-base-content) 18%, transparent)',
|
|
2889
|
+
},
|
|
2890
|
+
},
|
|
2891
|
+
},
|
|
2656
2892
|
trigger: {
|
|
2657
2893
|
base: {
|
|
2658
2894
|
appearance: 'none',
|
|
@@ -2689,7 +2925,7 @@ export const combobox: RecipeInput = {
|
|
|
2689
2925
|
display: 'flex',
|
|
2690
2926
|
alignItems: 'center',
|
|
2691
2927
|
justifyContent: 'space-between',
|
|
2692
|
-
gap: '
|
|
2928
|
+
gap: 'var(--space-md)',
|
|
2693
2929
|
padding: 'var(--space-md) var(--space-lg)',
|
|
2694
2930
|
fontSize: 'var(--text-sm)',
|
|
2695
2931
|
fontWeight: 'var(--weight-medium)',
|
|
@@ -2721,15 +2957,15 @@ export const combobox: RecipeInput = {
|
|
|
2721
2957
|
'--combobox-accent': `var(--color-${c})`,
|
|
2722
2958
|
} } }])),
|
|
2723
2959
|
// The control carries the height — the shared field ramp, the same one
|
|
2724
|
-
// `select`'s trigger steps on
|
|
2725
|
-
// type. `md` restates exactly the resting values so the union stays
|
|
2960
|
+
// `select`'s trigger steps on, as a floor so wrapped tags can grow it
|
|
2961
|
+
// — and the input carries the inset and the type. `md` restates exactly the resting values so the union stays
|
|
2726
2962
|
// complete.
|
|
2727
2963
|
size: {
|
|
2728
|
-
xs: { control: { base: {
|
|
2729
|
-
sm: { control: { base: {
|
|
2730
|
-
md: { control: { base: {
|
|
2731
|
-
lg: { control: { base: {
|
|
2732
|
-
xl: { control: { base: {
|
|
2964
|
+
xs: { control: { base: { minHeight: fieldHeight('xs') } }, input: { base: { padding: 'var(--space-2xs) var(--space-md)', fontSize: 'var(--text-xs)' } } },
|
|
2965
|
+
sm: { control: { base: { minHeight: fieldHeight('sm') } }, input: { base: { padding: 'var(--space-xs) var(--space-md)', fontSize: 'var(--text-sm)' } } },
|
|
2966
|
+
md: { control: { base: { minHeight: fieldHeight('md') } }, input: { base: { padding: 'var(--space-sm) var(--space-lg)', fontSize: 'var(--text-sm)' } } },
|
|
2967
|
+
lg: { control: { base: { minHeight: fieldHeight('lg') } }, input: { base: { padding: 'var(--space-md) var(--space-xl)', fontSize: 'var(--text-md)' } } },
|
|
2968
|
+
xl: { control: { base: { minHeight: fieldHeight('xl') } }, input: { base: { padding: 'var(--space-lg) var(--space-2xl)', fontSize: 'var(--text-lg)' } } },
|
|
2733
2969
|
},
|
|
2734
2970
|
},
|
|
2735
2971
|
// The visible ring lives on `control`; input and trigger delegate.
|
|
@@ -2829,6 +3065,13 @@ export const toggle: RecipeInput = {
|
|
|
2829
3065
|
// the on item filled with the accent.
|
|
2830
3066
|
export const toggleGroup: RecipeInput = {
|
|
2831
3067
|
component: 'toggle-group',
|
|
3068
|
+
// Public to a design system derived from this one (#73).
|
|
3069
|
+
hooks: {
|
|
3070
|
+
properties: {
|
|
3071
|
+
'--toggle-group-accent': 'The fill of an on item.',
|
|
3072
|
+
'--toggle-group-on-accent': 'The ink on --toggle-group-accent.',
|
|
3073
|
+
},
|
|
3074
|
+
},
|
|
2832
3075
|
tokens: {
|
|
2833
3076
|
'--toggle-group-accent': 'var(--color-primary)',
|
|
2834
3077
|
'--toggle-group-on-accent': 'var(--color-primary-content)',
|
|
@@ -3071,6 +3314,17 @@ export const numberInput: RecipeInput = {
|
|
|
3071
3314
|
xl: { control: { base: { height: fieldHeight('xl') } }, input: { base: { fontSize: 'var(--text-lg)', padding: 'var(--space-lg) var(--space-xl)' } } },
|
|
3072
3315
|
},
|
|
3073
3316
|
},
|
|
3317
|
+
// The steppers' inline padding, restated physically: logical spellings
|
|
3318
|
+
// resolve on iOS but not on Android (measured, signalxjs/lynx#1084), so
|
|
3319
|
+
// the emitter refuses them.
|
|
3320
|
+
targets: {
|
|
3321
|
+
lynx: {
|
|
3322
|
+
parts: {
|
|
3323
|
+
'increment-trigger': { base: { paddingLeft: 'var(--space-lg)', paddingRight: 'var(--space-lg)' } },
|
|
3324
|
+
'decrement-trigger': { base: { paddingLeft: 'var(--space-lg)', paddingRight: 'var(--space-lg)' } },
|
|
3325
|
+
},
|
|
3326
|
+
},
|
|
3327
|
+
},
|
|
3074
3328
|
};
|
|
3075
3329
|
|
|
3076
3330
|
/**
|
|
@@ -3254,7 +3508,7 @@ export const ratingGroup: RecipeInput = {
|
|
|
3254
3508
|
const treeRow: NonNullable<PartStyles['base']> = {
|
|
3255
3509
|
display: 'flex',
|
|
3256
3510
|
alignItems: 'center',
|
|
3257
|
-
gap: '
|
|
3511
|
+
gap: 'var(--space-md)',
|
|
3258
3512
|
padding: 'var(--space-sm) var(--space-md)',
|
|
3259
3513
|
fontSize: 'var(--tree-text)',
|
|
3260
3514
|
fontWeight: 'var(--weight-medium)',
|
|
@@ -3368,6 +3622,16 @@ export const treeView: RecipeInput = {
|
|
|
3368
3622
|
xl: { root: { base: { '--tree-text': 'var(--text-lg)' } } },
|
|
3369
3623
|
},
|
|
3370
3624
|
},
|
|
3625
|
+
// The indent, restated physically: logical spellings resolve on iOS but
|
|
3626
|
+
// not on Android (measured, signalxjs/lynx#1084), so the emitter refuses
|
|
3627
|
+
// them. Physical is the lynx target's norm — no RTL flow there.
|
|
3628
|
+
targets: {
|
|
3629
|
+
lynx: {
|
|
3630
|
+
parts: {
|
|
3631
|
+
'branch-content': { base: { paddingLeft: 'var(--space-lg)' } },
|
|
3632
|
+
},
|
|
3633
|
+
},
|
|
3634
|
+
},
|
|
3371
3635
|
};
|
|
3372
3636
|
|
|
3373
3637
|
/**
|
|
@@ -3523,75 +3787,6 @@ export const textarea: RecipeInput = {
|
|
|
3523
3787
|
},
|
|
3524
3788
|
};
|
|
3525
3789
|
|
|
3526
|
-
/**
|
|
3527
|
-
* NativeSelect (#333) — daisy "select" flavor on the platform's own picker:
|
|
3528
|
-
* the `fieldControl` box (same metrics as every field, so mixed rows line
|
|
3529
|
-
* up), `appearance: none`, and the recipe-drawn chevron at the inline end.
|
|
3530
|
-
* No open state — the platform owns the popup — so colour surfaces on the
|
|
3531
|
-
* focus ring, exactly like input's.
|
|
3532
|
-
*/
|
|
3533
|
-
export const nativeSelect: RecipeInput = {
|
|
3534
|
-
component: 'native-select',
|
|
3535
|
-
tokens: { '--native-select-accent': 'var(--color-base-content)' },
|
|
3536
|
-
parts: {
|
|
3537
|
-
root: {
|
|
3538
|
-
base: { position: 'relative', display: 'inline-flex', alignItems: 'center' },
|
|
3539
|
-
states: { disabled: {}, invalid: {}, required: {}, placeholder: {} },
|
|
3540
|
-
},
|
|
3541
|
-
control: {
|
|
3542
|
-
base: {
|
|
3543
|
-
appearance: 'none',
|
|
3544
|
-
width: '100%',
|
|
3545
|
-
minWidth: '12rem',
|
|
3546
|
-
...fieldControl,
|
|
3547
|
-
color: 'var(--color-base-content)',
|
|
3548
|
-
font: 'inherit',
|
|
3549
|
-
fontSize: 'var(--text-sm)',
|
|
3550
|
-
padding: 'var(--space-sm) var(--space-md)',
|
|
3551
|
-
// Room for the chevron the platform no longer draws.
|
|
3552
|
-
paddingInlineEnd: 'calc(var(--space-md) + 1.25em)',
|
|
3553
|
-
cursor: 'pointer',
|
|
3554
|
-
transition: 'border-color var(--duration-fast) var(--ease-standard)',
|
|
3555
|
-
},
|
|
3556
|
-
states: {
|
|
3557
|
-
hover: { borderColor: 'var(--color-base-content)' },
|
|
3558
|
-
invalid: { borderColor: 'var(--color-error)' },
|
|
3559
|
-
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
3560
|
-
required: {},
|
|
3561
|
-
// Same muting as the fields' placeholders (#264).
|
|
3562
|
-
placeholder: { color: 'color-mix(in oklab, var(--color-base-content) 60%, transparent)' },
|
|
3563
|
-
'focus-visible': { ...focusRing['focus-visible'], outline: '2px solid var(--native-select-accent)' },
|
|
3564
|
-
},
|
|
3565
|
-
},
|
|
3566
|
-
indicator: {
|
|
3567
|
-
base: {
|
|
3568
|
-
position: 'absolute',
|
|
3569
|
-
insetInlineEnd: 'var(--space-md)',
|
|
3570
|
-
pointerEvents: 'none',
|
|
3571
|
-
opacity: '0.6',
|
|
3572
|
-
fontSize: 'var(--text-sm)',
|
|
3573
|
-
},
|
|
3574
|
-
},
|
|
3575
|
-
},
|
|
3576
|
-
// The visible ring lives on the <select> itself; the wrapper delegates.
|
|
3577
|
-
skipStates: { root: ['focus-visible'] },
|
|
3578
|
-
variants: {
|
|
3579
|
-
// The ring carries the role — daisy's field chrome is neutral, so
|
|
3580
|
-
// focus is the only place colour surfaces (input's exact move).
|
|
3581
|
-
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
3582
|
-
'--native-select-accent': `var(--color-${c})`,
|
|
3583
|
-
} } }])),
|
|
3584
|
-
// The shared field-height ramp, on the control itself.
|
|
3585
|
-
size: {
|
|
3586
|
-
xs: { control: { base: { height: fieldHeight('xs'), fontSize: 'var(--text-xs)', padding: 'var(--space-2xs) var(--space-xs)', paddingInlineEnd: 'calc(var(--space-xs) + 1.25em)' } } },
|
|
3587
|
-
sm: { control: { base: { height: fieldHeight('sm'), fontSize: 'var(--text-xs)', padding: 'var(--space-xs) var(--space-sm)', paddingInlineEnd: 'calc(var(--space-sm) + 1.25em)' } } },
|
|
3588
|
-
md: {},
|
|
3589
|
-
lg: { control: { base: { height: fieldHeight('lg'), fontSize: 'var(--text-md)', padding: 'var(--space-md) var(--space-lg)', paddingInlineEnd: 'calc(var(--space-lg) + 1.25em)' } } },
|
|
3590
|
-
xl: { control: { base: { height: fieldHeight('xl'), fontSize: 'var(--text-lg)', padding: 'var(--space-lg) var(--space-xl)', paddingInlineEnd: 'calc(var(--space-xl) + 1.25em)' } } },
|
|
3591
|
-
},
|
|
3592
|
-
},
|
|
3593
|
-
};
|
|
3594
|
-
|
|
3595
3790
|
// ── Content tier (#311) ───────────────────────────────────────────────────
|
|
3596
3791
|
/**
|
|
3597
3792
|
* daisy "card" flavor: `bg-base-100` on a `rounded-box` with a real shadow —
|
|
@@ -3600,6 +3795,12 @@ export const nativeSelect: RecipeInput = {
|
|
|
3600
3795
|
*/
|
|
3601
3796
|
export const card: RecipeInput = {
|
|
3602
3797
|
component: 'card',
|
|
3798
|
+
// Public to a design system derived from this one (#73).
|
|
3799
|
+
hooks: {
|
|
3800
|
+
properties: {
|
|
3801
|
+
'--card-pad': 'The card padding.',
|
|
3802
|
+
},
|
|
3803
|
+
},
|
|
3603
3804
|
tokens: { '--card-pad': 'var(--space-xl)' },
|
|
3604
3805
|
parts: {
|
|
3605
3806
|
root: {
|
|
@@ -3756,6 +3957,88 @@ export const alert: RecipeInput = {
|
|
|
3756
3957
|
},
|
|
3757
3958
|
};
|
|
3758
3959
|
|
|
3960
|
+
/**
|
|
3961
|
+
* EmptyState (zero#131) — daisy has no empty-state component; this is the
|
|
3962
|
+
* `hero`-content idiom at card scale: a centred column on the base-200
|
|
3963
|
+
* surface with the box radius, the icon in the role's ink and the surface
|
|
3964
|
+
* in its soft tint when a tone is given.
|
|
3965
|
+
*/
|
|
3966
|
+
export const emptyState: RecipeInput = {
|
|
3967
|
+
component: 'empty-state',
|
|
3968
|
+
hooks: {
|
|
3969
|
+
properties: {
|
|
3970
|
+
'--empty-accent': 'The icon ink.',
|
|
3971
|
+
'--empty-tint': 'The surface fill.',
|
|
3972
|
+
},
|
|
3973
|
+
},
|
|
3974
|
+
tokens: {
|
|
3975
|
+
'--empty-accent': 'color-mix(in oklab, var(--color-base-content) 60%, transparent)',
|
|
3976
|
+
'--empty-tint': 'var(--color-base-200)',
|
|
3977
|
+
},
|
|
3978
|
+
parts: {
|
|
3979
|
+
root: {
|
|
3980
|
+
base: {
|
|
3981
|
+
display: 'flex',
|
|
3982
|
+
flexDirection: 'column',
|
|
3983
|
+
alignItems: 'center',
|
|
3984
|
+
textAlign: 'center',
|
|
3985
|
+
gap: 'var(--space-sm)',
|
|
3986
|
+
padding: 'var(--space-2xl) var(--space-xl)',
|
|
3987
|
+
background: 'var(--empty-tint)',
|
|
3988
|
+
color: 'var(--color-base-content)',
|
|
3989
|
+
borderRadius: 'var(--radius-box)',
|
|
3990
|
+
},
|
|
3991
|
+
},
|
|
3992
|
+
icon: {
|
|
3993
|
+
base: {
|
|
3994
|
+
display: 'inline-flex',
|
|
3995
|
+
color: 'var(--empty-accent)',
|
|
3996
|
+
fontSize: 'var(--text-2xl)',
|
|
3997
|
+
lineHeight: 'var(--leading-none)',
|
|
3998
|
+
marginBlockEnd: 'var(--space-xs)',
|
|
3999
|
+
},
|
|
4000
|
+
},
|
|
4001
|
+
title: {
|
|
4002
|
+
base: {
|
|
4003
|
+
margin: '0',
|
|
4004
|
+
fontSize: 'var(--text-lg)',
|
|
4005
|
+
fontWeight: 'var(--weight-bold)',
|
|
4006
|
+
lineHeight: 'var(--leading-tight)',
|
|
4007
|
+
},
|
|
4008
|
+
},
|
|
4009
|
+
description: {
|
|
4010
|
+
base: {
|
|
4011
|
+
maxInlineSize: '36ch',
|
|
4012
|
+
fontSize: 'var(--text-sm)',
|
|
4013
|
+
lineHeight: 'var(--leading-normal)',
|
|
4014
|
+
color: 'color-mix(in oklab, var(--color-base-content) 70%, transparent)',
|
|
4015
|
+
},
|
|
4016
|
+
},
|
|
4017
|
+
actions: {
|
|
4018
|
+
base: {
|
|
4019
|
+
display: 'flex',
|
|
4020
|
+
flexWrap: 'wrap',
|
|
4021
|
+
justifyContent: 'center',
|
|
4022
|
+
gap: 'var(--space-sm)',
|
|
4023
|
+
marginBlockStart: 'var(--space-sm)',
|
|
4024
|
+
},
|
|
4025
|
+
},
|
|
4026
|
+
},
|
|
4027
|
+
variants: {
|
|
4028
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
4029
|
+
'--empty-accent': roleInk(c),
|
|
4030
|
+
'--empty-tint': `var(--color-${c}-soft)`,
|
|
4031
|
+
} } }])),
|
|
4032
|
+
size: {
|
|
4033
|
+
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)' } } },
|
|
4034
|
+
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)' } } },
|
|
4035
|
+
md: {},
|
|
4036
|
+
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)' } } },
|
|
4037
|
+
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)' } } },
|
|
4038
|
+
},
|
|
4039
|
+
},
|
|
4040
|
+
};
|
|
4041
|
+
|
|
3759
4042
|
/**
|
|
3760
4043
|
* daisy "badge" flavor: a fully-rounded pill with a hairline, filled by the
|
|
3761
4044
|
* role. No `variant` — the repo-wide decision (#175) holds everywhere except
|
|
@@ -3764,7 +4047,18 @@ export const alert: RecipeInput = {
|
|
|
3764
4047
|
*/
|
|
3765
4048
|
export const badge: RecipeInput = {
|
|
3766
4049
|
component: 'badge',
|
|
4050
|
+
// Public to a design system derived from this one (#73).
|
|
4051
|
+
hooks: {
|
|
4052
|
+
properties: {
|
|
4053
|
+
'--badge-dot': 'The status dot\'s fill (zero#130); the pill\'s ink at rest.',
|
|
4054
|
+
'--badge-dot-ring': 'The ring around a coloured status dot.',
|
|
4055
|
+
'--badge-fill': 'The badge fill.',
|
|
4056
|
+
'--badge-ink': 'The badge ink.',
|
|
4057
|
+
},
|
|
4058
|
+
},
|
|
3767
4059
|
tokens: {
|
|
4060
|
+
'--badge-dot': 'currentColor',
|
|
4061
|
+
'--badge-dot-ring': 'currentColor',
|
|
3768
4062
|
'--badge-fill': 'var(--color-base-200)',
|
|
3769
4063
|
'--badge-ink': 'var(--color-base-content)',
|
|
3770
4064
|
},
|
|
@@ -3780,7 +4074,7 @@ export const badge: RecipeInput = {
|
|
|
3780
4074
|
// daisy badges are pills; a large radius rather than 9999px so
|
|
3781
4075
|
// a square-cornered theme can still say so through the token.
|
|
3782
4076
|
borderRadius: 'var(--radius-selector)',
|
|
3783
|
-
padding: '
|
|
4077
|
+
padding: 'var(--space-2xs) var(--space-lg)',
|
|
3784
4078
|
fontSize: 'var(--text-xs)',
|
|
3785
4079
|
fontWeight: 'var(--weight-semibold)',
|
|
3786
4080
|
lineHeight: 'var(--leading-normal)',
|
|
@@ -3788,12 +4082,53 @@ export const badge: RecipeInput = {
|
|
|
3788
4082
|
textDecoration: 'none',
|
|
3789
4083
|
},
|
|
3790
4084
|
},
|
|
4085
|
+
// The status dot (zero#130). On an uncoloured pill it is the pill's
|
|
4086
|
+
// INK — a dot in `currentColor` is legible on whatever fill the pill
|
|
4087
|
+
// has. Coloured — by its own `data-color`, or by the pill's, since the
|
|
4088
|
+
// nearest carrier wins (#94) — it is the role's fill inside a ring in
|
|
4089
|
+
// the role's `-content` ink, the timeline marker's answer: a dot that
|
|
4090
|
+
// follows a solid pill of the same role still has an edge. `running` is a
|
|
4091
|
+
// static halo the pulse breathes; under reduced motion the halo
|
|
4092
|
+
// stays and the breathing stops, so the state never vanishes.
|
|
4093
|
+
dot: {
|
|
4094
|
+
base: {
|
|
4095
|
+
display: 'inline-block',
|
|
4096
|
+
flex: 'none',
|
|
4097
|
+
inlineSize: '0.5em',
|
|
4098
|
+
blockSize: '0.5em',
|
|
4099
|
+
boxSizing: 'border-box',
|
|
4100
|
+
borderRadius: '50%',
|
|
4101
|
+
background: 'var(--badge-dot)',
|
|
4102
|
+
border: '0.1em solid var(--badge-dot-ring)',
|
|
4103
|
+
},
|
|
4104
|
+
states: {
|
|
4105
|
+
running: {
|
|
4106
|
+
boxShadow: '0 0 0 0.2em color-mix(in oklch, var(--badge-dot) 35%, transparent)',
|
|
4107
|
+
animation: 'zero-daisy-badge-pulse 1.6s ease-out infinite',
|
|
4108
|
+
},
|
|
4109
|
+
},
|
|
4110
|
+
at: {
|
|
4111
|
+
'reduced-motion': { states: { running: { animation: 'none' } } },
|
|
4112
|
+
// Forced colours strip the fill and keep the border, so the
|
|
4113
|
+
// border draws the whole dot there.
|
|
4114
|
+
'forced-colors': { base: { borderWidth: '0.25em' } },
|
|
4115
|
+
},
|
|
4116
|
+
},
|
|
3791
4117
|
},
|
|
3792
4118
|
variants: {
|
|
3793
|
-
color: Object.fromEntries(ROLES.map((c) => [c, {
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
4119
|
+
color: Object.fromEntries(ROLES.map((c) => [c, {
|
|
4120
|
+
root: { base: {
|
|
4121
|
+
'--badge-fill': `var(--color-${c})`,
|
|
4122
|
+
'--badge-ink': `var(--color-${c}-content)`,
|
|
4123
|
+
} },
|
|
4124
|
+
// The dot's colour (zero#130): keyed under `variants.color` so the
|
|
4125
|
+
// compiler anchors it on the pill's `data-color` AND on the dot's
|
|
4126
|
+
// own re-carried one — the nearest carrier wins (#94).
|
|
4127
|
+
dot: { base: {
|
|
4128
|
+
'--badge-dot': `var(--color-${c})`,
|
|
4129
|
+
'--badge-dot-ring': `var(--color-${c}-content)`,
|
|
4130
|
+
} },
|
|
4131
|
+
}])),
|
|
3797
4132
|
size: {
|
|
3798
4133
|
xs: { root: { base: { fontSize: 'var(--text-xs)', padding: '0 var(--space-sm)' } } },
|
|
3799
4134
|
sm: { root: { base: { fontSize: 'var(--text-xs)', padding: '0 var(--space-md)' } } },
|
|
@@ -3801,6 +4136,8 @@ export const badge: RecipeInput = {
|
|
|
3801
4136
|
lg: { root: { base: { fontSize: 'var(--text-sm)', padding: 'var(--space-2xs) var(--space-lg)' } } },
|
|
3802
4137
|
xl: { root: { base: { fontSize: 'var(--text-md)', padding: 'var(--space-xs) var(--space-xl)' } } },
|
|
3803
4138
|
},
|
|
4139
|
+
}, keyframes: {
|
|
4140
|
+
'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; }',
|
|
3804
4141
|
},
|
|
3805
4142
|
};
|
|
3806
4143
|
|
|
@@ -3846,6 +4183,12 @@ export const divider: RecipeInput = {
|
|
|
3846
4183
|
*/
|
|
3847
4184
|
export const skeleton: RecipeInput = {
|
|
3848
4185
|
component: 'skeleton',
|
|
4186
|
+
// Public to a design system derived from this one (#73).
|
|
4187
|
+
hooks: {
|
|
4188
|
+
properties: {
|
|
4189
|
+
'--skeleton-fill': 'The placeholder fill.',
|
|
4190
|
+
},
|
|
4191
|
+
},
|
|
3849
4192
|
tokens: { '--skeleton-fill': 'var(--color-base-300)' },
|
|
3850
4193
|
parts: {
|
|
3851
4194
|
root: {
|
|
@@ -3935,7 +4278,11 @@ export const kbd: RecipeInput = {
|
|
|
3935
4278
|
alignItems: 'center',
|
|
3936
4279
|
justifyContent: 'center',
|
|
3937
4280
|
minInlineSize: '1.75em',
|
|
3938
|
-
|
|
4281
|
+
// daisy's .0625rem/.4375rem, restated as the ramp steps they
|
|
4282
|
+
// already are once the cap's own border is absorbed: the OUTER
|
|
4283
|
+
// box lands on 2xs/md, so the cap rides the ramp under a density
|
|
4284
|
+
// override while keeping daisy's painted size exactly at 1px.
|
|
4285
|
+
padding: 'calc(var(--space-2xs) - var(--border)) calc(var(--space-md) - var(--border))',
|
|
3939
4286
|
background: 'var(--kbd-fill)',
|
|
3940
4287
|
color: 'var(--kbd-ink)',
|
|
3941
4288
|
border: 'var(--border) solid var(--color-base-300)',
|
|
@@ -4063,6 +4410,30 @@ export const indicator: RecipeInput = {
|
|
|
4063
4410
|
xl: { item: { base: { fontSize: 'var(--text-lg)' } } },
|
|
4064
4411
|
},
|
|
4065
4412
|
},
|
|
4413
|
+
// The corner slots, restated physically: logical inset spellings and the
|
|
4414
|
+
// standalone `translate` property resolve on iOS but not on Android
|
|
4415
|
+
// (measured, signalxjs/lynx#1084), so the emitter refuses them. Physical
|
|
4416
|
+
// anchors plus `transform: translate()` are proven on both platforms,
|
|
4417
|
+
// and physical is the lynx target's norm — no RTL flow there, so the
|
|
4418
|
+
// hand-flipped RTL rules above have nothing to engage with either.
|
|
4419
|
+
targets: {
|
|
4420
|
+
lynx: {
|
|
4421
|
+
parts: {
|
|
4422
|
+
item: {
|
|
4423
|
+
selectors: {
|
|
4424
|
+
'&[data-placement="top-start"]': { top: '0', left: '0', transform: 'translate(-50%, -50%)' },
|
|
4425
|
+
'&[data-placement="top"]': { top: '0', left: '50%', transform: 'translate(-50%, -50%)' },
|
|
4426
|
+
'&[data-placement="top-end"]': { top: '0', right: '0', transform: 'translate(50%, -50%)' },
|
|
4427
|
+
'&[data-placement="start"]': { top: '50%', left: '0', transform: 'translate(-50%, -50%)' },
|
|
4428
|
+
'&[data-placement="end"]': { top: '50%', right: '0', transform: 'translate(50%, -50%)' },
|
|
4429
|
+
'&[data-placement="bottom-start"]': { bottom: '0', left: '0', transform: 'translate(-50%, 50%)' },
|
|
4430
|
+
'&[data-placement="bottom"]': { bottom: '0', left: '50%', transform: 'translate(-50%, 50%)' },
|
|
4431
|
+
'&[data-placement="bottom-end"]': { bottom: '0', right: '0', transform: 'translate(50%, 50%)' },
|
|
4432
|
+
},
|
|
4433
|
+
},
|
|
4434
|
+
},
|
|
4435
|
+
},
|
|
4436
|
+
},
|
|
4066
4437
|
};
|
|
4067
4438
|
|
|
4068
4439
|
/** daisy stats: the rounded base-100 panel with rule-thin item joins. */
|
|
@@ -4144,10 +4515,32 @@ export const stats: RecipeInput = {
|
|
|
4144
4515
|
},
|
|
4145
4516
|
};
|
|
4146
4517
|
|
|
4518
|
+
/**
|
|
4519
|
+
* A timeline none of whose content sits on the start side: the root carries
|
|
4520
|
+
* no start-placed content among its own items. Child combinators all the way
|
|
4521
|
+
* down, so a timeline nested inside another's content answers only for
|
|
4522
|
+
* itself.
|
|
4523
|
+
*/
|
|
4524
|
+
const TIMELINE_NO_START = '[data-scope="timeline"][data-part="root"]'
|
|
4525
|
+
+ ':not(:has(> [data-scope="timeline"][data-part="item"] > [data-scope="timeline"][data-part="content"][data-placement="start"])) > &';
|
|
4526
|
+
|
|
4147
4527
|
/** daisy timeline: the boxed content beside a dotted axis. */
|
|
4148
4528
|
export const timeline: RecipeInput = {
|
|
4149
4529
|
component: 'timeline',
|
|
4150
|
-
|
|
4530
|
+
// Public to a design system derived from this one (#73).
|
|
4531
|
+
hooks: {
|
|
4532
|
+
properties: {
|
|
4533
|
+
'--timeline-accent': 'The marker and connector accent.',
|
|
4534
|
+
'--timeline-ring': 'The ring around a coloured marker — the role\'s -content ink (#94).',
|
|
4535
|
+
'--timeline-marker-size': 'The marker diameter.',
|
|
4536
|
+
},
|
|
4537
|
+
},
|
|
4538
|
+
tokens: {
|
|
4539
|
+
'--timeline-accent': 'var(--color-base-content)',
|
|
4540
|
+
// The coloured dot's ring (#94); uncoloured, it IS the dot.
|
|
4541
|
+
'--timeline-ring': 'var(--timeline-accent)',
|
|
4542
|
+
'--timeline-marker-size': 'calc(var(--size-selector) * 3)',
|
|
4543
|
+
},
|
|
4151
4544
|
parts: {
|
|
4152
4545
|
root: {
|
|
4153
4546
|
base: {
|
|
@@ -4183,6 +4576,11 @@ export const timeline: RecipeInput = {
|
|
|
4183
4576
|
gridTemplateColumns: 'auto 1fr',
|
|
4184
4577
|
flex: '1 1 0%',
|
|
4185
4578
|
},
|
|
4579
|
+
// No content on the start side anywhere in this timeline →
|
|
4580
|
+
// the start track collapses instead of holding half the item
|
|
4581
|
+
// empty (#57). Automatic, so there is no prop to forget.
|
|
4582
|
+
[`${TIMELINE_NO_START}[data-orientation="vertical"]`]: { gridTemplateColumns: '0 auto 1fr' },
|
|
4583
|
+
[`${TIMELINE_NO_START}[data-orientation="horizontal"]`]: { gridTemplateRows: '0 auto 1fr' },
|
|
4186
4584
|
},
|
|
4187
4585
|
},
|
|
4188
4586
|
marker: {
|
|
@@ -4192,12 +4590,24 @@ export const timeline: RecipeInput = {
|
|
|
4192
4590
|
boxSizing: 'border-box',
|
|
4193
4591
|
borderRadius: '9999px',
|
|
4194
4592
|
background: 'var(--timeline-accent)',
|
|
4195
|
-
|
|
4593
|
+
// A coloured dot is the role fill inside a RING in the role's
|
|
4594
|
+
// `-content` ink (#94): a light role on a light page, or a dark
|
|
4595
|
+
// one on a dark page, has no edge of its own, and a per-entry
|
|
4596
|
+
// tone is exactly the mark a reader has to find. One half of
|
|
4597
|
+
// the role's own pair clears 3:1 against the page (the contrast
|
|
4598
|
+
// audit measures every colour). With no colour the ring is the
|
|
4599
|
+
// fill — the same solid dot as before.
|
|
4600
|
+
border: 'calc(var(--timeline-marker-size) / 6) solid var(--timeline-ring)',
|
|
4196
4601
|
display: 'flex',
|
|
4197
4602
|
alignItems: 'center',
|
|
4198
4603
|
justifyContent: 'center',
|
|
4199
4604
|
fontSize: '0',
|
|
4200
4605
|
},
|
|
4606
|
+
at: {
|
|
4607
|
+
// Forced colours strip the fill and keep the border, so the
|
|
4608
|
+
// border draws the whole dot there, as it always did.
|
|
4609
|
+
'forced-colors': { base: { borderWidth: 'calc(var(--timeline-marker-size) / 2)' } },
|
|
4610
|
+
},
|
|
4201
4611
|
selectors: {
|
|
4202
4612
|
// The axis cell, both orientations. `place-self` centres the
|
|
4203
4613
|
// dot on the line in the cross axis.
|
|
@@ -4275,6 +4685,7 @@ export const timeline: RecipeInput = {
|
|
|
4275
4685
|
variants: {
|
|
4276
4686
|
color: Object.fromEntries(ROLES.map((c) => [c, { marker: { base: {
|
|
4277
4687
|
'--timeline-accent': `var(--color-${c})`,
|
|
4688
|
+
'--timeline-ring': `var(--color-${c}-content)`,
|
|
4278
4689
|
} } }])),
|
|
4279
4690
|
size: {
|
|
4280
4691
|
xs: { marker: { base: { '--timeline-marker-size': 'calc(var(--size-selector) * 2)' } }, content: { base: { fontSize: 'var(--text-xs)' } } },
|
|
@@ -4383,6 +4794,16 @@ export const chat: RecipeInput = {
|
|
|
4383
4794
|
xl: { bubble: { base: { fontSize: 'var(--text-lg)', padding: 'var(--space-md) var(--space-xl)' } } },
|
|
4384
4795
|
},
|
|
4385
4796
|
},
|
|
4797
|
+
// The row's vertical padding, restated physically: logical spellings
|
|
4798
|
+
// resolve on iOS but not on Android (measured, signalxjs/lynx#1084), so
|
|
4799
|
+
// the emitter refuses them.
|
|
4800
|
+
targets: {
|
|
4801
|
+
lynx: {
|
|
4802
|
+
parts: {
|
|
4803
|
+
root: { base: { paddingTop: 'var(--space-2xs)', paddingBottom: 'var(--space-2xs)' } },
|
|
4804
|
+
},
|
|
4805
|
+
},
|
|
4806
|
+
},
|
|
4386
4807
|
};
|
|
4387
4808
|
|
|
4388
4809
|
/** daisy radial-progress: --value became --progress-percent; same ring. */
|
|
@@ -4636,6 +5057,112 @@ export const navbar: RecipeInput = {
|
|
|
4636
5057
|
},
|
|
4637
5058
|
};
|
|
4638
5059
|
|
|
5060
|
+
/**
|
|
5061
|
+
* NavList (zero#132) — daisy's `menu` idiom for a sidebar: rounded rows, a
|
|
5062
|
+
* base-200 wash on hover, and the current page as the role's soft fill in
|
|
5063
|
+
* the role's ink. `meta` is where daisy's menu puts its badge.
|
|
5064
|
+
*/
|
|
5065
|
+
export const navList: RecipeInput = {
|
|
5066
|
+
component: 'nav-list',
|
|
5067
|
+
hooks: {
|
|
5068
|
+
properties: {
|
|
5069
|
+
'--nav-accent': 'The current link\'s ink.',
|
|
5070
|
+
'--nav-tint': 'The current link\'s fill.',
|
|
5071
|
+
'--nav-ink': 'The resting link ink.',
|
|
5072
|
+
},
|
|
5073
|
+
},
|
|
5074
|
+
tokens: {
|
|
5075
|
+
'--nav-accent': 'var(--color-base-content)',
|
|
5076
|
+
'--nav-tint': 'var(--color-base-300)',
|
|
5077
|
+
'--nav-ink': 'var(--color-base-content)',
|
|
5078
|
+
},
|
|
5079
|
+
parts: {
|
|
5080
|
+
root: {
|
|
5081
|
+
base: {
|
|
5082
|
+
display: 'flex',
|
|
5083
|
+
flexDirection: 'column',
|
|
5084
|
+
gap: 'var(--space-lg)',
|
|
5085
|
+
fontSize: 'var(--text-sm)',
|
|
5086
|
+
color: 'var(--color-base-content)',
|
|
5087
|
+
},
|
|
5088
|
+
},
|
|
5089
|
+
group: {
|
|
5090
|
+
base: { display: 'flex', flexDirection: 'column', gap: 'var(--space-xs)' },
|
|
5091
|
+
},
|
|
5092
|
+
heading: {
|
|
5093
|
+
base: {
|
|
5094
|
+
padding: '0 var(--space-lg)',
|
|
5095
|
+
fontSize: 'var(--text-xs)',
|
|
5096
|
+
fontWeight: 'var(--weight-bold)',
|
|
5097
|
+
opacity: '0.6',
|
|
5098
|
+
},
|
|
5099
|
+
},
|
|
5100
|
+
list: {
|
|
5101
|
+
base: {
|
|
5102
|
+
display: 'flex',
|
|
5103
|
+
flexDirection: 'column',
|
|
5104
|
+
gap: 'var(--space-2xs)',
|
|
5105
|
+
listStyle: 'none',
|
|
5106
|
+
margin: '0',
|
|
5107
|
+
padding: '0',
|
|
5108
|
+
},
|
|
5109
|
+
},
|
|
5110
|
+
item: { base: { display: 'block' } },
|
|
5111
|
+
link: {
|
|
5112
|
+
base: {
|
|
5113
|
+
display: 'flex',
|
|
5114
|
+
alignItems: 'center',
|
|
5115
|
+
gap: 'var(--space-sm)',
|
|
5116
|
+
textDecoration: 'none',
|
|
5117
|
+
color: 'var(--nav-ink)',
|
|
5118
|
+
padding: 'var(--space-sm) var(--space-lg)',
|
|
5119
|
+
borderRadius: 'var(--radius-field)',
|
|
5120
|
+
fontWeight: 'var(--weight-medium)',
|
|
5121
|
+
lineHeight: 'var(--leading-tight)',
|
|
5122
|
+
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
5123
|
+
},
|
|
5124
|
+
states: {
|
|
5125
|
+
hover: { background: 'var(--color-base-200)' },
|
|
5126
|
+
active: { background: 'var(--nav-tint)', color: 'var(--nav-accent)' },
|
|
5127
|
+
inactive: {},
|
|
5128
|
+
...focusRing,
|
|
5129
|
+
},
|
|
5130
|
+
},
|
|
5131
|
+
icon: {
|
|
5132
|
+
base: {
|
|
5133
|
+
display: 'inline-flex',
|
|
5134
|
+
inlineSize: '1.25em',
|
|
5135
|
+
justifyContent: 'center',
|
|
5136
|
+
flex: 'none',
|
|
5137
|
+
lineHeight: 'var(--leading-none)',
|
|
5138
|
+
},
|
|
5139
|
+
},
|
|
5140
|
+
meta: {
|
|
5141
|
+
base: {
|
|
5142
|
+
marginInlineStart: 'auto',
|
|
5143
|
+
display: 'inline-flex',
|
|
5144
|
+
alignItems: 'center',
|
|
5145
|
+
fontSize: 'var(--text-xs)',
|
|
5146
|
+
fontVariantNumeric: 'tabular-nums',
|
|
5147
|
+
opacity: '0.7',
|
|
5148
|
+
},
|
|
5149
|
+
},
|
|
5150
|
+
},
|
|
5151
|
+
variants: {
|
|
5152
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
5153
|
+
'--nav-accent': roleInk(c),
|
|
5154
|
+
'--nav-tint': `var(--color-${c}-soft)`,
|
|
5155
|
+
} } }])),
|
|
5156
|
+
size: {
|
|
5157
|
+
xs: { root: { base: { fontSize: 'var(--text-xs)' } }, link: { base: { padding: 'var(--space-2xs) var(--space-md)' } } },
|
|
5158
|
+
sm: { root: { base: { fontSize: 'var(--text-xs)' } }, link: { base: { padding: 'var(--space-xs) var(--space-md)' } } },
|
|
5159
|
+
md: {},
|
|
5160
|
+
lg: { root: { base: { fontSize: 'var(--text-md)' } }, link: { base: { padding: 'var(--space-md) var(--space-lg)' } } },
|
|
5161
|
+
xl: { root: { base: { fontSize: 'var(--text-lg)' } }, link: { base: { padding: 'var(--space-lg) var(--space-xl)' } } },
|
|
5162
|
+
},
|
|
5163
|
+
},
|
|
5164
|
+
};
|
|
5165
|
+
|
|
4639
5166
|
/**
|
|
4640
5167
|
* Breadcrumbs — daisy's breadcrumbs: a compact text-sm trail, links
|
|
4641
5168
|
* underlining on hover, the current crumb plain full ink. Colour rebinds
|
|
@@ -4839,8 +5366,32 @@ export const pagination: RecipeInput = {
|
|
|
4839
5366
|
xl: { root: { base: { '--pg-size': 'calc(var(--size-field) * 14)', '--pg-font': 'var(--text-lg)' } } },
|
|
4840
5367
|
},
|
|
4841
5368
|
},
|
|
5369
|
+
// The cell's inline padding, restated physically: logical spellings
|
|
5370
|
+
// resolve on iOS but not on Android (measured, signalxjs/lynx#1084), so
|
|
5371
|
+
// the emitter refuses them.
|
|
5372
|
+
targets: {
|
|
5373
|
+
lynx: {
|
|
5374
|
+
parts: {
|
|
5375
|
+
item: { base: { paddingLeft: 'var(--space-2xs)', paddingRight: 'var(--space-2xs)' } },
|
|
5376
|
+
},
|
|
5377
|
+
},
|
|
5378
|
+
},
|
|
4842
5379
|
};
|
|
4843
5380
|
|
|
5381
|
+
/**
|
|
5382
|
+
* The complete step's digit: the role hue deepened toward base-content, on
|
|
5383
|
+
* the disc's 20% tint of that role — `roleInk`'s per-role keep, capped at
|
|
5384
|
+
* 70%. The cap is the primary story: 95% on the tint measured 2.96:1 under
|
|
5385
|
+
* nord's muted primary (the static contrast matrix, #403). The per-role part
|
|
5386
|
+
* arrived with per-step colour (#112), when the matrix first measured every
|
|
5387
|
+
* role here: a flat 70% put nord's muted accent/success/warning at 2.2–2.6:1
|
|
5388
|
+
* and the dark themes' neutral (darker than the page) at 1.7–2.0:1, and
|
|
5389
|
+
* `ROLE_INK_KEEP` already knew how much hue each of those can keep.
|
|
5390
|
+
*/
|
|
5391
|
+
const stepInk = (accent: string, role: string): string =>
|
|
5392
|
+
`color-mix(in oklab, ${accent} `
|
|
5393
|
+
+ `${Math.min(ROLE_INK_KEEP[role as keyof typeof roles] ?? 55, 70)}%, var(--color-base-content))`;
|
|
5394
|
+
|
|
4844
5395
|
/**
|
|
4845
5396
|
* Steps — daisy's steps translated to the richer rail: bold numbered discs
|
|
4846
5397
|
* on base-200, the walked disc and line refilled with the accent pair
|
|
@@ -4853,6 +5404,9 @@ export const steps: RecipeInput = {
|
|
|
4853
5404
|
'--steps-accent': 'var(--color-primary)',
|
|
4854
5405
|
'--steps-accent-content': 'var(--color-primary-content)',
|
|
4855
5406
|
'--steps-accent-ink': roleInk('primary'),
|
|
5407
|
+
// Read through the accent, so it resolves on the element that sets
|
|
5408
|
+
// it; a colour on the item restates it for that role.
|
|
5409
|
+
'--steps-complete-ink': stepInk('var(--steps-accent)', 'primary'),
|
|
4856
5410
|
'--steps-ind': 'calc(var(--size-selector) * 7)',
|
|
4857
5411
|
'--steps-font': 'var(--text-sm)',
|
|
4858
5412
|
},
|
|
@@ -4924,7 +5478,14 @@ export const steps: RecipeInput = {
|
|
|
4924
5478
|
},
|
|
4925
5479
|
states: {
|
|
4926
5480
|
active: { background: 'var(--steps-accent)', color: 'var(--steps-accent-content)' },
|
|
4927
|
-
|
|
5481
|
+
// Ink deepened toward base-content, per role (`stepInk`). The
|
|
5482
|
+
// tint mixes in OKLAB: daisy's light base-100 is
|
|
5483
|
+
// `oklch(100% 0 0)`, whose 0 hue the browser interpolates as
|
|
5484
|
+
// written, so an OKLCH mix swung every role's tint toward red
|
|
5485
|
+
// (warning's yellow came out pink) — which the browser parity
|
|
5486
|
+
// check caught once per-step colour put all eight roles on
|
|
5487
|
+
// the disc (#112).
|
|
5488
|
+
complete: { background: 'color-mix(in oklab, var(--steps-accent) 20%, var(--color-base-100))', color: 'var(--steps-complete-ink)' },
|
|
4928
5489
|
inactive: { background: 'var(--color-base-200)', color: 'var(--color-base-content)' },
|
|
4929
5490
|
},
|
|
4930
5491
|
},
|
|
@@ -4973,10 +5534,15 @@ export const steps: RecipeInput = {
|
|
|
4973
5534
|
},
|
|
4974
5535
|
},
|
|
4975
5536
|
variants: {
|
|
4976
|
-
|
|
5537
|
+
// Keyed on the ITEM, not the root (#112): the item re-carries
|
|
5538
|
+
// `color`, so a step's own value outranks the rail's, and the
|
|
5539
|
+
// disc, bridge and title inside it inherit whichever won. A root
|
|
5540
|
+
// colour still reaches every item through the carrier's donut.
|
|
5541
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { item: { base: {
|
|
4977
5542
|
'--steps-accent': `var(--color-${c})`,
|
|
4978
5543
|
'--steps-accent-content': `var(--color-${c}-content)`,
|
|
4979
5544
|
'--steps-accent-ink': roleInk(c),
|
|
5545
|
+
'--steps-complete-ink': stepInk(`var(--color-${c})`, c),
|
|
4980
5546
|
} } }])),
|
|
4981
5547
|
size: {
|
|
4982
5548
|
xs: { root: { base: { '--steps-ind': 'calc(var(--size-selector) * 5)', '--steps-font': 'var(--text-xs)' } } },
|
|
@@ -4986,12 +5552,76 @@ export const steps: RecipeInput = {
|
|
|
4986
5552
|
xl: { root: { base: { '--steps-ind': 'calc(var(--size-selector) * 9)', '--steps-font': 'var(--text-md)' } } },
|
|
4987
5553
|
},
|
|
4988
5554
|
},
|
|
5555
|
+
// The bridge's anchors and the vertical item's tail padding, restated
|
|
5556
|
+
// physically: logical spellings resolve on iOS but not on Android
|
|
5557
|
+
// (measured, signalxjs/lynx#1084), so the emitter refuses them. Physical
|
|
5558
|
+
// is the lynx target's norm — no RTL flow there to mirror with.
|
|
5559
|
+
targets: {
|
|
5560
|
+
lynx: {
|
|
5561
|
+
parts: {
|
|
5562
|
+
item: {
|
|
5563
|
+
selectors: {
|
|
5564
|
+
'&[data-orientation="vertical"]': { paddingBottom: 'var(--space-lg)' },
|
|
5565
|
+
},
|
|
5566
|
+
},
|
|
5567
|
+
separator: {
|
|
5568
|
+
selectors: {
|
|
5569
|
+
'&[data-orientation="horizontal"]': {
|
|
5570
|
+
top: 'calc(var(--space-xs) + var(--steps-ind) / 2)',
|
|
5571
|
+
left: '50%',
|
|
5572
|
+
},
|
|
5573
|
+
'&[data-orientation="vertical"]': {
|
|
5574
|
+
left: 'calc(var(--space-xs) + (var(--steps-ind) - var(--border)) / 2)',
|
|
5575
|
+
top: 'calc(var(--space-xs) + var(--steps-ind))',
|
|
5576
|
+
bottom: 'calc(var(--space-xs) * -1)',
|
|
5577
|
+
},
|
|
5578
|
+
},
|
|
5579
|
+
},
|
|
5580
|
+
},
|
|
5581
|
+
},
|
|
5582
|
+
},
|
|
5583
|
+
};
|
|
5584
|
+
|
|
5585
|
+
/**
|
|
5586
|
+
* The modal sheet's slide (#83): in from its edge, back out to it.
|
|
5587
|
+
*
|
|
5588
|
+
* `translate` is physical, so the travel is a custom property that flips with
|
|
5589
|
+
* the placement AND the direction — off the reading start is leftward in LTR
|
|
5590
|
+
* and rightward in RTL (the `rtl` hedge, as everywhere here). Keyed on
|
|
5591
|
+
* `data-l-dock="sheet"`, the regime, which holds through the exit where
|
|
5592
|
+
* `:modal` does not, so the slide-out leaves from the sheet's own box.
|
|
5593
|
+
* Opacity stays at 1: the travel is the transition. Each direction rides the
|
|
5594
|
+
* transition declared at its destination; reduced motion drops both.
|
|
5595
|
+
*/
|
|
5596
|
+
const sheetSlide = (enter: string, exit: string): PartStyles => {
|
|
5597
|
+
const sheet = '&[data-l-dock="sheet"]';
|
|
5598
|
+
const open = `${sheet}[data-state="open"]`;
|
|
5599
|
+
const travel = (tempo: string): string => {
|
|
5600
|
+
const duration = tempo.split(' ')[0];
|
|
5601
|
+
return `translate ${tempo}, display ${duration} allow-discrete, overlay ${duration} allow-discrete`;
|
|
5602
|
+
};
|
|
5603
|
+
return {
|
|
5604
|
+
base: { '--drawer-travel': '-100%' },
|
|
5605
|
+
selectors: {
|
|
5606
|
+
'&[data-placement="end"]': { '--drawer-travel': '100%' },
|
|
5607
|
+
[`&[data-placement="start"]${rtl}`]: { '--drawer-travel': '100%' },
|
|
5608
|
+
[`&[data-placement="end"]${rtl}`]: { '--drawer-travel': '-100%' },
|
|
5609
|
+
[sheet]: { opacity: '1', translate: 'var(--drawer-travel) 0', transition: travel(exit) },
|
|
5610
|
+
[open]: { translate: 'none', transition: travel(enter) },
|
|
5611
|
+
},
|
|
5612
|
+
at: {
|
|
5613
|
+
'starting-style': { selectors: { [open]: { opacity: '1', translate: 'var(--drawer-travel) 0' } } },
|
|
5614
|
+
'reduced-motion': { selectors: { [sheet]: { transition: 'none' }, [open]: { transition: 'none' } } },
|
|
5615
|
+
},
|
|
5616
|
+
};
|
|
4989
5617
|
};
|
|
4990
5618
|
|
|
4991
5619
|
/**
|
|
4992
5620
|
* Drawer — daisy's drawer-side as a native <dialog> edge sheet: base-100
|
|
4993
|
-
* paper, no border (daisy separates the drawer with the scrim),
|
|
4994
|
-
*
|
|
5621
|
+
* paper, no border (daisy separates the drawer with the scrim), sliding in
|
|
5622
|
+
* from its edge the way daisy's drawer-side does (`translate` over 0.3s,
|
|
5623
|
+
* #83). Base render is the inline mode; `data-l-dock="sheet"` is the
|
|
5624
|
+
* top-layer edge sheet.
|
|
4995
5625
|
*/
|
|
4996
5626
|
export const drawer: RecipeInput = {
|
|
4997
5627
|
component: 'drawer',
|
|
@@ -5006,7 +5636,10 @@ export const drawer: RecipeInput = {
|
|
|
5006
5636
|
...focusRing,
|
|
5007
5637
|
},
|
|
5008
5638
|
},
|
|
5009
|
-
panel: withPresence(popupPresence('none'),
|
|
5639
|
+
panel: withPresence(withPresence(popupPresence('none'), sheetSlide(
|
|
5640
|
+
'var(--duration-slow) var(--ease-standard)',
|
|
5641
|
+
'var(--duration-slow) var(--ease-standard)',
|
|
5642
|
+
)), {
|
|
5010
5643
|
base: {
|
|
5011
5644
|
padding: 'var(--space-lg)',
|
|
5012
5645
|
background: 'var(--color-base-100)',
|
|
@@ -5014,30 +5647,39 @@ export const drawer: RecipeInput = {
|
|
|
5014
5647
|
border: 'none',
|
|
5015
5648
|
borderRadius: 'var(--radius-box)',
|
|
5016
5649
|
boxShadow: 'var(--shadow-xl)',
|
|
5017
|
-
|
|
5650
|
+
// The width is capped by `--l-measure` (Drawer.Panel's
|
|
5651
|
+
// `measure`, via the layout step table); this is the default
|
|
5652
|
+
// an unset `measure` leaves. A cap, not a width, because the
|
|
5653
|
+
// table spells `full` as `none` — valid only for a max. In
|
|
5654
|
+
// flow and as a sheet alike, the panel fills what it is given
|
|
5655
|
+
// up to the measure, as its border box, so `full` never
|
|
5656
|
+
// overflows by its own padding.
|
|
5657
|
+
boxSizing: 'border-box',
|
|
5658
|
+
'--l-measure': 'min(20rem, 85vw)',
|
|
5659
|
+
inlineSize: '100%',
|
|
5660
|
+
maxInlineSize: 'var(--l-measure)',
|
|
5018
5661
|
},
|
|
5019
5662
|
states: { open: {}, closed: {} },
|
|
5020
5663
|
selectors: {
|
|
5021
5664
|
/**
|
|
5022
|
-
* The
|
|
5023
|
-
*
|
|
5024
|
-
*
|
|
5025
|
-
* the
|
|
5026
|
-
* RTL mirrors
|
|
5665
|
+
* The top-layer edge sheet, keyed on the regime
|
|
5666
|
+
* (`data-l-dock="sheet"`) rather than `:modal`, which stops
|
|
5667
|
+
* matching the moment `close()` runs — so the sheet keeps its
|
|
5668
|
+
* box through the exit (#83). The base styles above are the
|
|
5669
|
+
* INLINE render. Logical insets pin the edge, so RTL mirrors
|
|
5670
|
+
* free.
|
|
5027
5671
|
*/
|
|
5028
|
-
'
|
|
5672
|
+
'&[data-l-dock="sheet"]': {
|
|
5029
5673
|
position: 'fixed',
|
|
5030
5674
|
insetBlockStart: '0',
|
|
5031
5675
|
insetBlockEnd: '0',
|
|
5032
5676
|
blockSize: '100dvh',
|
|
5033
5677
|
maxBlockSize: '100dvh',
|
|
5034
|
-
inlineSize: 'min(20rem, 85vw)',
|
|
5035
|
-
maxInlineSize: 'none',
|
|
5036
5678
|
margin: '0',
|
|
5037
5679
|
borderRadius: '0',
|
|
5038
5680
|
},
|
|
5039
|
-
'&[data-placement="start"]
|
|
5040
|
-
'&[data-placement="end"]
|
|
5681
|
+
'&[data-placement="start"][data-l-dock="sheet"]': { insetInlineStart: '0', insetInlineEnd: 'auto' },
|
|
5682
|
+
'&[data-placement="end"][data-l-dock="sheet"]': { insetInlineStart: 'auto', insetInlineEnd: '0' },
|
|
5041
5683
|
},
|
|
5042
5684
|
}),
|
|
5043
5685
|
backdrop: {
|
|
@@ -5059,6 +5701,16 @@ export const drawer: RecipeInput = {
|
|
|
5059
5701
|
// Trigger-carried axes — see `btnColors` for why the panel is out of
|
|
5060
5702
|
// reach and the trigger is the whole story here.
|
|
5061
5703
|
variants: { color: btnColors(), size: btnSizes },
|
|
5704
|
+
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
5705
|
+
targets: {
|
|
5706
|
+
lynx: {
|
|
5707
|
+
parts: {
|
|
5708
|
+
trigger: { base: lynxBtnPad(4) },
|
|
5709
|
+
close: { base: lynxBtnPad(4) },
|
|
5710
|
+
},
|
|
5711
|
+
variants: { size: lynxBtnSizes('trigger') },
|
|
5712
|
+
},
|
|
5713
|
+
},
|
|
5062
5714
|
};
|
|
5063
5715
|
|
|
5064
5716
|
/**
|
|
@@ -5069,7 +5721,20 @@ export const drawer: RecipeInput = {
|
|
|
5069
5721
|
*/
|
|
5070
5722
|
export const table: RecipeInput = {
|
|
5071
5723
|
component: 'table',
|
|
5724
|
+
// Public to a design system derived from this one (#73).
|
|
5725
|
+
hooks: {
|
|
5726
|
+
properties: {
|
|
5727
|
+
'--table-cell-align': 'Cell text alignment; zero writes it inline from the column spec.',
|
|
5728
|
+
'--table-accent': 'The header ink accent.',
|
|
5729
|
+
'--table-pad-block': 'Cell block padding.',
|
|
5730
|
+
'--table-pad-inline': 'Cell inline padding.',
|
|
5731
|
+
'--table-font': 'The table font size.',
|
|
5732
|
+
},
|
|
5733
|
+
},
|
|
5072
5734
|
tokens: {
|
|
5735
|
+
// The column spec's alignment (#55): a cell that names an aligned
|
|
5736
|
+
// column overrides it inline; everything else reads `start`.
|
|
5737
|
+
'--table-cell-align': 'start',
|
|
5073
5738
|
'--table-accent': 'var(--color-base-content)',
|
|
5074
5739
|
'--table-pad-block': 'var(--space-sm)',
|
|
5075
5740
|
'--table-pad-inline': 'var(--space-md)',
|
|
@@ -5083,6 +5748,9 @@ export const table: RecipeInput = {
|
|
|
5083
5748
|
borderRadius: 'var(--radius-box)',
|
|
5084
5749
|
background: 'var(--color-base-100)',
|
|
5085
5750
|
},
|
|
5751
|
+
// Stacked (Table.Root stack, #55): zero lays each row out as a block
|
|
5752
|
+
// below the table's breakpoint; the card it becomes is drawn here.
|
|
5753
|
+
at: tableStackAt(tokens, 'root', { border: '0', borderRadius: '0', background: 'transparent' }),
|
|
5086
5754
|
},
|
|
5087
5755
|
table: {
|
|
5088
5756
|
base: {
|
|
@@ -5100,34 +5768,52 @@ export const table: RecipeInput = {
|
|
|
5100
5768
|
fontSize: 'var(--text-xs)',
|
|
5101
5769
|
color: 'color-mix(in oklab, var(--color-base-content) 60%, transparent)',
|
|
5102
5770
|
},
|
|
5771
|
+
at: tableStackAt(tokens, 'caption', { paddingInline: '0' }),
|
|
5103
5772
|
},
|
|
5104
5773
|
head: {},
|
|
5105
|
-
body: {},
|
|
5774
|
+
body: { at: tableStackAt(tokens, 'body', { rowGap: 'var(--space-md)' }) },
|
|
5106
5775
|
foot: {
|
|
5107
5776
|
base: {
|
|
5108
5777
|
fontSize: 'var(--text-xs)',
|
|
5109
5778
|
color: 'color-mix(in oklab, var(--color-base-content) 60%, transparent)',
|
|
5110
5779
|
},
|
|
5780
|
+
at: tableStackAt(tokens, 'foot', { rowGap: 'var(--space-md)', marginBlockStart: 'var(--space-md)' }),
|
|
5111
5781
|
},
|
|
5112
5782
|
row: {
|
|
5113
5783
|
base: { borderBlockEnd: 'var(--border) solid var(--color-base-200)' },
|
|
5114
5784
|
states: {
|
|
5115
5785
|
selected: { background: 'var(--color-base-300)' },
|
|
5116
5786
|
},
|
|
5787
|
+
at: tableStackAt(tokens, 'row', {
|
|
5788
|
+
border: 'var(--border) solid var(--color-base-200)',
|
|
5789
|
+
borderRadius: 'var(--radius-box)',
|
|
5790
|
+
background: 'var(--color-base-100)',
|
|
5791
|
+
padding: 'var(--table-pad-block) var(--table-pad-inline)',
|
|
5792
|
+
}),
|
|
5117
5793
|
},
|
|
5118
5794
|
'header-cell': {
|
|
5119
5795
|
base: {
|
|
5120
5796
|
padding: 'var(--table-pad-block) var(--table-pad-inline)',
|
|
5121
|
-
textAlign: '
|
|
5797
|
+
textAlign: 'var(--table-cell-align)',
|
|
5122
5798
|
fontWeight: 'var(--weight-semibold)',
|
|
5123
5799
|
fontSize: 'var(--text-xs)',
|
|
5124
5800
|
color: 'color-mix(in oklab, var(--table-accent) 60%, transparent)',
|
|
5125
5801
|
},
|
|
5802
|
+
at: tableStackAt(tokens, 'header-cell', { paddingInline: '0' }),
|
|
5126
5803
|
},
|
|
5127
5804
|
cell: {
|
|
5128
5805
|
base: {
|
|
5129
5806
|
padding: 'var(--table-pad-block) var(--table-pad-inline)',
|
|
5130
|
-
textAlign: '
|
|
5807
|
+
textAlign: 'var(--table-cell-align)',
|
|
5808
|
+
},
|
|
5809
|
+
at: tableStackAt(tokens, 'cell', { paddingInline: '0', paddingBlock: 'var(--space-xs)' }),
|
|
5810
|
+
},
|
|
5811
|
+
// A stacked cell's column label, in the header's muted semibold.
|
|
5812
|
+
'cell-label': {
|
|
5813
|
+
base: {
|
|
5814
|
+
fontWeight: 'var(--weight-semibold)',
|
|
5815
|
+
fontSize: 'var(--text-xs)',
|
|
5816
|
+
color: 'color-mix(in oklab, var(--table-accent) 60%, transparent)',
|
|
5131
5817
|
},
|
|
5132
5818
|
},
|
|
5133
5819
|
},
|
|
@@ -5449,6 +6135,22 @@ export const carousel: RecipeInput = {
|
|
|
5449
6135
|
xl: { root: { base: { '--carousel-dot': '0.875rem', '--carousel-nav': '3rem' } } },
|
|
5450
6136
|
},
|
|
5451
6137
|
},
|
|
6138
|
+
// The nav buttons' anchors, restated physically: logical inset
|
|
6139
|
+
// spellings resolve on iOS but not on Android (measured,
|
|
6140
|
+
// signalxjs/lynx#1084), so the emitter refuses them. Physical is the
|
|
6141
|
+
// lynx target's norm — no RTL flow there, so `start` IS left.
|
|
6142
|
+
targets: {
|
|
6143
|
+
lynx: {
|
|
6144
|
+
parts: {
|
|
6145
|
+
'prev-trigger': {
|
|
6146
|
+
base: { top: 'calc(50% - var(--carousel-nav) / 2)', left: 'var(--space-sm)' },
|
|
6147
|
+
},
|
|
6148
|
+
'next-trigger': {
|
|
6149
|
+
base: { top: 'calc(50% - var(--carousel-nav) / 2)', right: 'var(--space-sm)' },
|
|
6150
|
+
},
|
|
6151
|
+
},
|
|
6152
|
+
},
|
|
6153
|
+
},
|
|
5452
6154
|
};
|
|
5453
6155
|
|
|
5454
6156
|
/**
|
|
@@ -5588,6 +6290,12 @@ export const countdown: RecipeInput = {
|
|
|
5588
6290
|
xl: { root: { base: { '--countdown-font': 'var(--text-3xl)' } } },
|
|
5589
6291
|
},
|
|
5590
6292
|
},
|
|
6293
|
+
// One line inside a sentence (#57): the surrounding text's size and
|
|
6294
|
+
// weight instead of the display step. What stays is what makes
|
|
6295
|
+
// it a countdown — tabular digits, its ink, the per-tick entry.
|
|
6296
|
+
modifiers: {
|
|
6297
|
+
inline: { root: { base: { fontSize: 'inherit', fontWeight: 'inherit' } } },
|
|
6298
|
+
},
|
|
5591
6299
|
keyframes: { 'zero-daisyui-countdown-in': 'from { transform: translateY(0.6em); opacity: 0; }' },
|
|
5592
6300
|
};
|
|
5593
6301
|
|
|
@@ -5693,16 +6401,31 @@ export const diff: RecipeInput = {
|
|
|
5693
6401
|
},
|
|
5694
6402
|
},
|
|
5695
6403
|
},
|
|
6404
|
+
// The reveal pane's pin and the handle's centering margin, restated
|
|
6405
|
+
// physically: logical spellings resolve on iOS but not on Android
|
|
6406
|
+
// (measured, signalxjs/lynx#1084), so the emitter refuses them.
|
|
6407
|
+
// Physical is the lynx target's norm — no RTL flow there to mirror
|
|
6408
|
+
// with.
|
|
6409
|
+
lynx: {
|
|
6410
|
+
parts: {
|
|
6411
|
+
after: {
|
|
6412
|
+
base: { top: '0', bottom: '0', left: '0' },
|
|
6413
|
+
},
|
|
6414
|
+
handle: {
|
|
6415
|
+
base: { top: '0', bottom: '0', marginLeft: 'calc(var(--diff-hit) / -2)' },
|
|
6416
|
+
},
|
|
6417
|
+
},
|
|
6418
|
+
},
|
|
5696
6419
|
},
|
|
5697
6420
|
};
|
|
5698
6421
|
|
|
5699
6422
|
export const recipes: RecipeInput[] = [
|
|
5700
6423
|
tabs, collapsible, switchRecipe, dialog, popover, tooltip, menu,
|
|
5701
6424
|
field, checkbox, radioGroup, progress, slider, accordion, select, button, avatar, toast, combobox,
|
|
5702
|
-
toggle, toggleGroup, numberInput, ratingGroup, treeView, input, textarea,
|
|
5703
|
-
card, alert, badge, divider, skeleton, spinner,
|
|
6425
|
+
toggle, toggleGroup, numberInput, ratingGroup, treeView, input, textarea,
|
|
6426
|
+
card, alert, emptyState, badge, divider, skeleton, spinner,
|
|
5704
6427
|
kbd, status, indicator, stats, timeline, chat, radialProgress, join,
|
|
5705
|
-
navbar, breadcrumbs, pagination, steps, drawer,
|
|
6428
|
+
navbar, navList, breadcrumbs, pagination, steps, drawer,
|
|
5706
6429
|
table,
|
|
5707
6430
|
fileUpload,
|
|
5708
6431
|
carousel,
|