@sigx/zero-daisyui 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit.json +3813 -2
- package/dist/components.d.ts +28 -1
- package/dist/components.js +3 -0
- package/dist/css/components/breadcrumbs.css +30 -0
- package/dist/css/components/card.css +26 -0
- package/dist/css/components/chat-log.css +172 -0
- package/dist/css/components/divider.css +68 -3
- package/dist/css/components/hover-card.css +174 -0
- package/dist/css/components/menu.css +7 -0
- package/dist/css/components/menubar.css +75 -0
- package/dist/css/components/pagination.css +83 -0
- package/dist/css/components/steps.css +103 -0
- package/dist/css/components/timeline.css +14 -0
- package/dist/css/components/toast.css +65 -0
- package/dist/css/components/tree-view.css +118 -0
- package/dist/css/index.css +951 -22
- package/dist/lynx/components/accordion.css +3 -1
- package/dist/lynx/components/breadcrumbs.css +44 -0
- package/dist/lynx/components/button.css +71 -12
- package/dist/lynx/components/card.css +9 -0
- package/dist/lynx/components/chat-log.css +147 -0
- package/dist/lynx/components/dialog.css +9 -3
- package/dist/lynx/components/divider.css +8 -17
- package/dist/lynx/components/hover-card.css +211 -0
- package/dist/lynx/components/menu.css +7 -0
- package/dist/lynx/components/menubar.css +199 -0
- package/dist/lynx/components/pagination.css +107 -0
- package/dist/lynx/components/popover.css +7 -2
- package/dist/lynx/components/select.css +40 -3
- package/dist/lynx/components/slider.css +3 -2
- package/dist/lynx/components/steps.css +103 -0
- package/dist/lynx/components/switch.css +2 -1
- package/dist/lynx/components/tabs.css +7 -1
- package/dist/lynx/components/timeline.css +32 -0
- package/dist/lynx/components/toast.css +70 -2
- package/dist/lynx/components/tree-view.css +28 -0
- package/dist/lynx/index.css +1194 -128
- package/dist/lynx/manifest.json +85 -4
- package/dist/manifest.json +83 -2
- package/dist/recipes.d.ts +19 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +741 -98
- package/dist/recipes.js.map +1 -1
- package/dist/register.d.ts +27 -0
- package/dist/report.json +1078 -159
- package/package.json +4 -4
- package/src/recipes.ts +763 -98
package/src/recipes.ts
CHANGED
|
@@ -39,6 +39,41 @@ const focusRing: Record<string, NonNullable<PartStyles['base']>> = {
|
|
|
39
39
|
},
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* The focus ring on lynx, for `targets.lynx` (signalxjs/lynx#1163): the same
|
|
44
|
+
* 2px ring 2px clear of the part, spelled as spread box-shadows.
|
|
45
|
+
*
|
|
46
|
+
* The web ring is `outline` + `outline-offset`, and neither survives on
|
|
47
|
+
* lynx: its `outline` ignores `border-radius` (a square box around a
|
|
48
|
+
* rounded button, a rectangle around the switch pill) and it has no
|
|
49
|
+
* `outline-offset` property at all, so the ring sat flush on the fill. A
|
|
50
|
+
* zero-blur shadow's spread follows the element's radius. Shadows stack
|
|
51
|
+
* rather than mask, so the offset gap cannot be transparent: the inner
|
|
52
|
+
* shadow paints it in `gap` — the surface the part sits on, which is what
|
|
53
|
+
* the web's gap shows through to. `outline: none` retires the shared ring
|
|
54
|
+
* in the same merged declaration block.
|
|
55
|
+
*
|
|
56
|
+
* `under` appends the part's own lynx shadows (a knob's depth relief), which
|
|
57
|
+
* the focus rule would otherwise replace. `inset` draws the ring inside the
|
|
58
|
+
* part, flush with its edge, for a part whose parent clips an outside ring
|
|
59
|
+
* away (signalxjs/lynx#1164, the accordion trigger inside its card).
|
|
60
|
+
*/
|
|
61
|
+
const lynxFocusRing = (
|
|
62
|
+
ink: string,
|
|
63
|
+
{ gap = 'var(--color-base-100)', under, inset = false }: { gap?: string; under?: string; inset?: boolean } = {},
|
|
64
|
+
): CssProps => ({
|
|
65
|
+
outline: 'none',
|
|
66
|
+
boxShadow: [
|
|
67
|
+
...(inset ? [`inset 0 0 0 2px ${ink}`] : [`0 0 0 2px ${gap}`, `0 0 0 4px ${ink}`]),
|
|
68
|
+
...(under ? [under] : []),
|
|
69
|
+
].join(', '),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/** `focusRing`'s lynx spelling — the base-content ring, as a states entry. */
|
|
73
|
+
const lynxFocus = (opts?: Parameters<typeof lynxFocusRing>[1]): Record<string, CssProps> => ({
|
|
74
|
+
'focus-visible': lynxFocusRing('var(--color-base-content)', opts),
|
|
75
|
+
});
|
|
76
|
+
|
|
42
77
|
/**
|
|
43
78
|
* How far each role survives in the button's INK — the colour a transparent or
|
|
44
79
|
* tinted fill draws its label, border and focus ring with.
|
|
@@ -524,7 +559,7 @@ export const tabs: RecipeInput = {
|
|
|
524
559
|
parts: {
|
|
525
560
|
// No hover on a touch platform: the held tab takes the ink
|
|
526
561
|
// daisy's hover gives it.
|
|
527
|
-
tab: { states: { pressed: { color: 'var(--color-base-content)' } } },
|
|
562
|
+
tab: { states: { pressed: { color: 'var(--color-base-content)' }, ...lynxFocus() } },
|
|
528
563
|
// The list is the indicator's containing block.
|
|
529
564
|
list: { base: { position: 'relative' } },
|
|
530
565
|
// The underline's ink (read by the `border` flavor below) —
|
|
@@ -577,6 +612,14 @@ export const tabs: RecipeInput = {
|
|
|
577
612
|
base: { '--tab-radius-limit': 'var(--radius-field)' },
|
|
578
613
|
},
|
|
579
614
|
},
|
|
615
|
+
// The box flavor's tabs sit on the list's base-200
|
|
616
|
+
// surface, so the ring's gap is that surface. Stated on
|
|
617
|
+
// the variant (one class more than the part rule) so it
|
|
618
|
+
// also outranks the active pill's `--shadow-sm`, which
|
|
619
|
+
// is a box-shadow too.
|
|
620
|
+
box: {
|
|
621
|
+
tab: { states: lynxFocus({ gap: 'var(--color-base-200)' }) },
|
|
622
|
+
},
|
|
580
623
|
},
|
|
581
624
|
},
|
|
582
625
|
},
|
|
@@ -931,6 +974,9 @@ export const switchRecipe: RecipeInput = {
|
|
|
931
974
|
borderColor: 'var(--switch-accent)',
|
|
932
975
|
backgroundColor: 'var(--color-base-100)',
|
|
933
976
|
},
|
|
977
|
+
// The pill ring (signalxjs/lynx#1163), keeping the
|
|
978
|
+
// track's inset depth line under it.
|
|
979
|
+
'focus-visible': lynxFocusRing('var(--switch-accent)', { under: '0 1px var(--depth-shade) inset' }),
|
|
934
980
|
},
|
|
935
981
|
},
|
|
936
982
|
thumb: {
|
|
@@ -1056,6 +1102,43 @@ const lynxBtnPad = (mult: number): NonNullable<PartStyles['base']> => ({
|
|
|
1056
1102
|
paddingRight: `calc(var(--size-field) * ${mult})`,
|
|
1057
1103
|
});
|
|
1058
1104
|
|
|
1105
|
+
/**
|
|
1106
|
+
* A four-side property spelled as its physical longhands, for `targets.lynx`
|
|
1107
|
+
* sections that must not ship a `var()`-bearing shorthand.
|
|
1108
|
+
*
|
|
1109
|
+
* Lynx keeps a declaration holding `var()` under its own property id,
|
|
1110
|
+
* unexpanded, and expands it only after the cascade has merged every rule
|
|
1111
|
+
* into one insertion-ordered style map — in that map's order, writing over
|
|
1112
|
+
* the longhands already there (`CSSVariableHandler::HandleCSSVariables`). A
|
|
1113
|
+
* static shorthand, by contrast, is expanded into its longhands at parse
|
|
1114
|
+
* time. So a var-bearing `border-color` and a static `border-top-color` never
|
|
1115
|
+
* meet in the cascade at all: whichever key the map saw later wins, whatever
|
|
1116
|
+
* the specificity (signalxjs/lynx#1161, #1162). Longhands share one key per
|
|
1117
|
+
* side, and the cascade decides again.
|
|
1118
|
+
*/
|
|
1119
|
+
const lynxSides = (
|
|
1120
|
+
prop: 'border{}Width' | 'border{}Color' | 'padding{}',
|
|
1121
|
+
value: string,
|
|
1122
|
+
sides: readonly ('Top' | 'Right' | 'Bottom' | 'Left')[] = ['Top', 'Right', 'Bottom', 'Left'],
|
|
1123
|
+
): NonNullable<PartStyles['base']> =>
|
|
1124
|
+
Object.fromEntries(sides.map((side) => [prop.replace('{}', side), value]));
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* daisy's `.btn` is content-sized and never squeezed: `display: inline-flex`
|
|
1128
|
+
* with `flex-shrink: 0; flex-wrap: nowrap`, so a narrow row makes the button
|
|
1129
|
+
* overflow, never break its label. A lynx view has no inline formatting
|
|
1130
|
+
* context: it stretches to a linear or column parent, and a squeezed label
|
|
1131
|
+
* `<text>` then wraps per character ("Bt / n", signalxjs/lynx#1165).
|
|
1132
|
+
* `width: max-content` is the intrinsic keyword starlight implements (an
|
|
1133
|
+
* intrinsic width is also exempt from the cross-axis stretch), so the box
|
|
1134
|
+
* sizes to its content as the web's does; the other two are daisy's own.
|
|
1135
|
+
*/
|
|
1136
|
+
const lynxBtnFit: NonNullable<PartStyles['base']> = {
|
|
1137
|
+
width: 'max-content',
|
|
1138
|
+
flexShrink: '0',
|
|
1139
|
+
flexWrap: 'nowrap',
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1059
1142
|
/** The btn size ramp's physical inline padding, for `targets.lynx.variants`. */
|
|
1060
1143
|
const lynxBtnSizes = (part: string): Record<string, Record<string, PartStyles>> => ({
|
|
1061
1144
|
xs: { [part]: { base: lynxBtnPad(2) } },
|
|
@@ -1177,9 +1260,10 @@ export const dialog: RecipeInput = {
|
|
|
1177
1260
|
targets: {
|
|
1178
1261
|
lynx: {
|
|
1179
1262
|
parts: {
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1263
|
+
// Content-sized like daisy's btn, never broken mid-word (signalxjs/lynx#1165).
|
|
1264
|
+
trigger: { base: { ...lynxBtnPad(4), ...lynxBtnFit }, states: { pressed: lynxBtnPressed, ...lynxFocus() } },
|
|
1265
|
+
close: { base: lynxBtnPad(4), states: { pressed: lynxBtnPressed, ...lynxFocus() } },
|
|
1266
|
+
cancel: { base: lynxBtnPad(4), states: { pressed: lynxBtnPressed, ...lynxFocus() } },
|
|
1183
1267
|
footer: { base: { marginTop: 'var(--space-2xl)', flexDirection: 'row' } },
|
|
1184
1268
|
},
|
|
1185
1269
|
variants: { size: lynxBtnSizes('trigger') },
|
|
@@ -1274,8 +1358,9 @@ export const popover: RecipeInput = {
|
|
|
1274
1358
|
web: floatingArrow('popover', panelArrowPaint),
|
|
1275
1359
|
lynx: {
|
|
1276
1360
|
parts: {
|
|
1277
|
-
|
|
1278
|
-
|
|
1361
|
+
// Content-sized like daisy's btn, never broken mid-word (signalxjs/lynx#1165).
|
|
1362
|
+
trigger: { base: { ...lynxBtnPad(4), ...lynxBtnFit }, states: { pressed: lynxBtnPressed, ...lynxFocus() } },
|
|
1363
|
+
close: { base: lynxBtnPad(3), states: { pressed: lynxBtnPressed, ...lynxFocus() } },
|
|
1279
1364
|
},
|
|
1280
1365
|
variants: { size: lynxBtnSizes('trigger') },
|
|
1281
1366
|
},
|
|
@@ -1326,6 +1411,57 @@ export const tooltip: RecipeInput = {
|
|
|
1326
1411
|
},
|
|
1327
1412
|
};
|
|
1328
1413
|
|
|
1414
|
+
/**
|
|
1415
|
+
* Hover card — daisy's `link` opening a `dropdown-content` card. The trigger
|
|
1416
|
+
* is running text, not a btn: underlined in the ink it sits in (daisy's bare
|
|
1417
|
+
* `link`), and a colour is `link-<role>` in the role's readable ink
|
|
1418
|
+
* (`roleInk`). The underline thickens on hover and while the card is up,
|
|
1419
|
+
* rather than the ink dimming — the `btn-link` reasoning. The card is the
|
|
1420
|
+
* floating panel popover and menu wear.
|
|
1421
|
+
*/
|
|
1422
|
+
export const hoverCard: RecipeInput = {
|
|
1423
|
+
component: 'hover-card',
|
|
1424
|
+
parts: {
|
|
1425
|
+
trigger: {
|
|
1426
|
+
base: {
|
|
1427
|
+
color: 'inherit',
|
|
1428
|
+
textDecorationLine: 'underline',
|
|
1429
|
+
textUnderlineOffset: '2px',
|
|
1430
|
+
cursor: 'pointer',
|
|
1431
|
+
borderRadius: 'var(--radius-selector)',
|
|
1432
|
+
},
|
|
1433
|
+
states: {
|
|
1434
|
+
hover: { textDecorationThickness: '2px' },
|
|
1435
|
+
open: { textDecorationThickness: '2px' },
|
|
1436
|
+
closed: {},
|
|
1437
|
+
...focusRing,
|
|
1438
|
+
},
|
|
1439
|
+
},
|
|
1440
|
+
popup: withPresence(popupPresence('translateY(-4px)'), {
|
|
1441
|
+
base: {
|
|
1442
|
+
...floatingPanel,
|
|
1443
|
+
padding: 'var(--space-xl)',
|
|
1444
|
+
maxWidth: '20rem',
|
|
1445
|
+
fontSize: 'var(--text-sm)',
|
|
1446
|
+
},
|
|
1447
|
+
states: { open: {}, closed: {} },
|
|
1448
|
+
}),
|
|
1449
|
+
},
|
|
1450
|
+
variants: {
|
|
1451
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { trigger: { base: { color: roleInk(c) } } }])),
|
|
1452
|
+
size: {
|
|
1453
|
+
xs: { trigger: { base: { fontSize: 'var(--text-xs)' } } },
|
|
1454
|
+
sm: { trigger: { base: { fontSize: 'var(--text-sm)' } } },
|
|
1455
|
+
md: {},
|
|
1456
|
+
lg: { trigger: { base: { fontSize: 'var(--text-lg)' } } },
|
|
1457
|
+
xl: { trigger: { base: { fontSize: 'var(--text-xl)' } } },
|
|
1458
|
+
},
|
|
1459
|
+
},
|
|
1460
|
+
// Web-only: the popover top layer and the runtime's arrow geometry have
|
|
1461
|
+
// no lynx counterpart.
|
|
1462
|
+
targets: { web: floatingArrow('hover-card', panelArrowPaint) },
|
|
1463
|
+
};
|
|
1464
|
+
|
|
1329
1465
|
// daisy "menu in a dropdown" look.
|
|
1330
1466
|
export const menu: RecipeInput = {
|
|
1331
1467
|
component: 'menu',
|
|
@@ -1459,6 +1595,17 @@ export const menu: RecipeInput = {
|
|
|
1459
1595
|
opacity: '0.6',
|
|
1460
1596
|
},
|
|
1461
1597
|
},
|
|
1598
|
+
// daisy's menu hint: the group label's faded ink, pushed to the
|
|
1599
|
+
// row's reading end — faded less, since 0.6 leaves nord's paper
|
|
1600
|
+
// under the 4.5:1 a line read by eye owes.
|
|
1601
|
+
shortcut: {
|
|
1602
|
+
base: {
|
|
1603
|
+
marginInlineStart: 'auto',
|
|
1604
|
+
paddingInlineStart: 'var(--space-lg)',
|
|
1605
|
+
fontSize: 'var(--text-xs)',
|
|
1606
|
+
opacity: '0.75',
|
|
1607
|
+
},
|
|
1608
|
+
},
|
|
1462
1609
|
separator: {
|
|
1463
1610
|
base: {
|
|
1464
1611
|
height: 'var(--border)',
|
|
@@ -1474,12 +1621,55 @@ export const menu: RecipeInput = {
|
|
|
1474
1621
|
targets: {
|
|
1475
1622
|
web: floatingArrow('menu', panelArrowPaint),
|
|
1476
1623
|
lynx: {
|
|
1477
|
-
parts: {
|
|
1624
|
+
parts: {
|
|
1625
|
+
trigger: { base: lynxBtnPad(4) },
|
|
1626
|
+
// The shortcut's reading-end push, restated physically (#1084).
|
|
1627
|
+
shortcut: { base: { marginLeft: 'auto', paddingLeft: 'var(--space-lg)' } },
|
|
1628
|
+
},
|
|
1478
1629
|
variants: { size: lynxBtnSizes('trigger') },
|
|
1479
1630
|
},
|
|
1480
1631
|
},
|
|
1481
1632
|
};
|
|
1482
1633
|
|
|
1634
|
+
// The menubar row: daisy's `menu-horizontal` strip — a base-200 box the
|
|
1635
|
+
// menu triggers sit in. The triggers are ordinary `menu` triggers.
|
|
1636
|
+
export const menubar: RecipeInput = {
|
|
1637
|
+
component: 'menubar',
|
|
1638
|
+
tokens: { '--menubar-surface': 'var(--color-base-200)' },
|
|
1639
|
+
parts: {
|
|
1640
|
+
root: {
|
|
1641
|
+
base: {
|
|
1642
|
+
display: 'flex',
|
|
1643
|
+
alignItems: 'center',
|
|
1644
|
+
gap: 'var(--space-xs)',
|
|
1645
|
+
padding: 'var(--space-xs)',
|
|
1646
|
+
width: 'max-content',
|
|
1647
|
+
maxWidth: '100%',
|
|
1648
|
+
flexWrap: 'wrap',
|
|
1649
|
+
background: 'var(--menubar-surface)',
|
|
1650
|
+
borderRadius: 'var(--radius-box)',
|
|
1651
|
+
},
|
|
1652
|
+
// The triggers fade themselves — see basic.
|
|
1653
|
+
states: { disabled: {} },
|
|
1654
|
+
selectors: {
|
|
1655
|
+
'&[data-orientation="vertical"]': { flexDirection: 'column', alignItems: 'stretch' },
|
|
1656
|
+
},
|
|
1657
|
+
},
|
|
1658
|
+
},
|
|
1659
|
+
variants: {
|
|
1660
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
1661
|
+
'--menubar-surface': `color-mix(in oklch, var(--color-${c}) 12%, var(--color-base-200))`,
|
|
1662
|
+
} } }])),
|
|
1663
|
+
size: {
|
|
1664
|
+
xs: { root: { base: { gap: '0', padding: '0' } } },
|
|
1665
|
+
sm: { root: { base: { padding: 'var(--space-2xs)' } } },
|
|
1666
|
+
md: {},
|
|
1667
|
+
lg: { root: { base: { gap: 'var(--space-sm)', padding: 'var(--space-sm)' } } },
|
|
1668
|
+
xl: { root: { base: { gap: 'var(--space-md)', padding: 'var(--space-sm)' } } },
|
|
1669
|
+
},
|
|
1670
|
+
},
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1483
1673
|
export const field: RecipeInput = {
|
|
1484
1674
|
component: 'field',
|
|
1485
1675
|
// The label's accent ink — base-content by default, so the un-attributed
|
|
@@ -2498,6 +2688,12 @@ export const slider: RecipeInput = {
|
|
|
2498
2688
|
control: {
|
|
2499
2689
|
states: {
|
|
2500
2690
|
invalid: { '--slider-fill': sliderFill('var(--color-error)') },
|
|
2691
|
+
// The control's ring is the NATIVE input's on the web;
|
|
2692
|
+
// lynx always renders the composed parts, where the
|
|
2693
|
+
// thumb is what takes focus and wears the ring below
|
|
2694
|
+
// (signalxjs/lynx#1163 — both at once read as two
|
|
2695
|
+
// nested boxes).
|
|
2696
|
+
'focus-visible': { outline: 'none' },
|
|
2501
2697
|
},
|
|
2502
2698
|
selectors: {
|
|
2503
2699
|
'&[data-orientation="vertical"]': {
|
|
@@ -2595,6 +2791,13 @@ export const slider: RecipeInput = {
|
|
|
2595
2791
|
transform: 'translateY(-50%)',
|
|
2596
2792
|
marginLeft: 'calc(var(--slider-thumb-size) / -2)',
|
|
2597
2793
|
},
|
|
2794
|
+
// The round ring (signalxjs/lynx#1163), over the knob's
|
|
2795
|
+
// depth relief.
|
|
2796
|
+
states: {
|
|
2797
|
+
'focus-visible': lynxFocusRing('var(--slider-accent)', {
|
|
2798
|
+
under: '0 -1px var(--depth-shade) inset, 0 8px 0 -4px var(--depth-sheen) inset, 0 1px var(--depth-shade)',
|
|
2799
|
+
}),
|
|
2800
|
+
},
|
|
2598
2801
|
},
|
|
2599
2802
|
},
|
|
2600
2803
|
},
|
|
@@ -2611,7 +2814,20 @@ export const accordion: RecipeInput = {
|
|
|
2611
2814
|
// hover fill. The ::after chevron has no lynx counterpart (no
|
|
2612
2815
|
// pseudo-elements, and the anatomy declares no part for it) —
|
|
2613
2816
|
// an app draws its own glyph in the trigger.
|
|
2614
|
-
|
|
2817
|
+
//
|
|
2818
|
+
// The item card clips (`overflow: hidden`) and the trigger
|
|
2819
|
+
// fills it, so an outside ring is clipped away entirely
|
|
2820
|
+
// (signalxjs/lynx#1164): the ring is drawn inset, rounded to
|
|
2821
|
+
// the card's inner corners.
|
|
2822
|
+
trigger: {
|
|
2823
|
+
states: {
|
|
2824
|
+
pressed: { background: 'var(--color-base-200)' },
|
|
2825
|
+
'focus-visible': {
|
|
2826
|
+
...lynxFocusRing('var(--color-base-content)', { inset: true }),
|
|
2827
|
+
borderRadius: 'calc(var(--radius-box) - var(--border))',
|
|
2828
|
+
},
|
|
2829
|
+
},
|
|
2830
|
+
},
|
|
2615
2831
|
},
|
|
2616
2832
|
},
|
|
2617
2833
|
},
|
|
@@ -2902,22 +3118,43 @@ export const select: RecipeInput = {
|
|
|
2902
3118
|
// never wider than its container. lynx has no `clamp()`, so the
|
|
2903
3119
|
// same bounds are spelled out — and the base's 13rem floor
|
|
2904
3120
|
// comes down to daisy's 3rem, or the trigger overflowed any
|
|
2905
|
-
// cell narrower than 208px (signalxjs/lynx#1146).
|
|
2906
|
-
//
|
|
2907
|
-
|
|
3121
|
+
// cell narrower than 208px (signalxjs/lynx#1146).
|
|
3122
|
+
//
|
|
3123
|
+
// The bounds sit on the ROOT, the box daisy's `.select` is,
|
|
3124
|
+
// and the trigger fills it. The web root is `inline-flex`, so
|
|
3125
|
+
// it shrink-wraps the trigger; lynx rewrites that to `flex`,
|
|
3126
|
+
// and a root stretched by its parent while the trigger stayed
|
|
3127
|
+
// capped left the clear-trigger — positioned against the root
|
|
3128
|
+
// — floating past the trigger's end (signalxjs/lynx#1166).
|
|
3129
|
+
root: { base: { width: '20rem', maxWidth: '100%', minWidth: '3rem' } },
|
|
2908
3130
|
trigger: {
|
|
2909
|
-
base: { ...lynxBtnPad(4), width: '
|
|
3131
|
+
base: { ...lynxBtnPad(4), width: '100%', maxWidth: '100%', minWidth: '3rem' },
|
|
2910
3132
|
// The web's `:hover` border is dropped on a touch
|
|
2911
3133
|
// platform; a press gets a wash instead — a background,
|
|
2912
3134
|
// so it never fights the open/invalid border inks.
|
|
2913
|
-
states: { pressed: { background: 'var(--color-base-200)' } },
|
|
2914
|
-
},
|
|
3135
|
+
states: { pressed: { background: 'var(--color-base-200)' }, ...lynxFocus() },
|
|
3136
|
+
},
|
|
3137
|
+
// The popup renders in the overlay outlet, outside the root,
|
|
3138
|
+
// so it cannot inherit the root's `--select-accent`, and an
|
|
3139
|
+
// unresolved `var()` on lynx has no fallback: the selected
|
|
3140
|
+
// item and its tick fell back to base-content
|
|
3141
|
+
// (signalxjs/lynx#1168). The popup restates the accent; its
|
|
3142
|
+
// colour-axis twins are in `variants.color` below.
|
|
3143
|
+
popup: { base: { '--select-accent': 'var(--color-primary)' } },
|
|
2915
3144
|
// A touch list has no highlight: the pressed item takes the
|
|
2916
3145
|
// highlighted item's wash.
|
|
2917
3146
|
item: { states: { pressed: { background: 'var(--color-base-200)' } } },
|
|
2918
|
-
'clear-trigger': {
|
|
3147
|
+
'clear-trigger': {
|
|
3148
|
+
base: { top: 'var(--space-xs)', bottom: 'var(--space-xs)', right: 'calc(var(--size-field) * 4 + 1em + var(--space-xs))', minWidth: '1.5rem' },
|
|
3149
|
+
states: lynxFocus(),
|
|
3150
|
+
},
|
|
2919
3151
|
},
|
|
2920
3152
|
variants: {
|
|
3153
|
+
// The popup's accent follows the root's colour axis — the
|
|
3154
|
+
// popup carries the axis class itself (lynx stamps every part).
|
|
3155
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { popup: { base: {
|
|
3156
|
+
'--select-accent': `var(--color-${c})`,
|
|
3157
|
+
} } }])),
|
|
2921
3158
|
size: { ...lynxBtnSizes('trigger'), md: { trigger: { base: lynxBtnPad(4) } } },
|
|
2922
3159
|
},
|
|
2923
3160
|
},
|
|
@@ -2942,6 +3179,23 @@ const BTN_STEPS = { xs: 6, sm: 8, md: 10, lg: 12, xl: 14 } as const;
|
|
|
2942
3179
|
const btnHeight = (step: keyof typeof BTN_STEPS): string =>
|
|
2943
3180
|
`calc(var(--size-field) * ${BTN_STEPS[step]})`;
|
|
2944
3181
|
|
|
3182
|
+
/**
|
|
3183
|
+
* The btn padding ramp — `[size, block space, inline space]` per step. One
|
|
3184
|
+
* table for the web's `padding` shorthand and the lynx section's longhands,
|
|
3185
|
+
* so the two spellings cannot drift apart.
|
|
3186
|
+
*/
|
|
3187
|
+
const BTN_PAD = [
|
|
3188
|
+
['xs', '2xs', 'xs'],
|
|
3189
|
+
['sm', 'xs', 'sm'],
|
|
3190
|
+
['md', 'sm', 'lg'],
|
|
3191
|
+
['lg', 'md', 'xl'],
|
|
3192
|
+
['xl', 'lg', '2xl'],
|
|
3193
|
+
] as const satisfies readonly (readonly [keyof typeof BTN_STEPS, string, string])[];
|
|
3194
|
+
const btnPadding = (step: keyof typeof BTN_STEPS): string => {
|
|
3195
|
+
const [, block, inline] = BTN_PAD.find(([size]) => size === step)!;
|
|
3196
|
+
return `var(--space-${block}) var(--space-${inline})`;
|
|
3197
|
+
};
|
|
3198
|
+
|
|
2945
3199
|
/**
|
|
2946
3200
|
* daisy 5's disabled btn, for the lynx target (signalxjs/lynx#1143). The web
|
|
2947
3201
|
* fades the whole button (`opacity: var(--disabled-opacity)`, 0.3 on the
|
|
@@ -3139,11 +3393,11 @@ export const button: RecipeInput = {
|
|
|
3139
3393
|
// The `<button>` element is UA `border-box`, so the stated height is
|
|
3140
3394
|
// the rendered height, border included — as in daisy.
|
|
3141
3395
|
size: {
|
|
3142
|
-
xs: { root: { base: { height: btnHeight('xs'), padding: '
|
|
3143
|
-
sm: { root: { base: { height: btnHeight('sm'), padding: '
|
|
3144
|
-
md: { root: { base: { height: btnHeight('md'), padding: '
|
|
3145
|
-
lg: { root: { base: { height: btnHeight('lg'), padding: '
|
|
3146
|
-
xl: { root: { base: { height: btnHeight('xl'), padding: '
|
|
3396
|
+
xs: { root: { base: { height: btnHeight('xs'), padding: btnPadding('xs'), fontSize: 'var(--text-xs)' } } },
|
|
3397
|
+
sm: { root: { base: { height: btnHeight('sm'), padding: btnPadding('sm'), fontSize: 'var(--text-sm)' } } },
|
|
3398
|
+
md: { root: { base: { height: btnHeight('md'), padding: btnPadding('md'), fontSize: 'var(--text-md)' } } },
|
|
3399
|
+
lg: { root: { base: { height: btnHeight('lg'), padding: btnPadding('lg'), fontSize: 'var(--text-lg)' } } },
|
|
3400
|
+
xl: { root: { base: { height: btnHeight('xl'), padding: btnPadding('xl'), fontSize: 'var(--text-xl)' } } },
|
|
3147
3401
|
},
|
|
3148
3402
|
},
|
|
3149
3403
|
/**
|
|
@@ -3180,36 +3434,90 @@ export const button: RecipeInput = {
|
|
|
3180
3434
|
// The ring, restated: `currentColor` never resolves on lynx
|
|
3181
3435
|
// (signalxjs/lynx#1079) and the logical sizes/edge are
|
|
3182
3436
|
// unproven there, so the lynx ring is physical longhands in
|
|
3183
|
-
// the variant's own ink (the variants below pick it).
|
|
3437
|
+
// the variant's own ink (the variants below pick it). Every
|
|
3438
|
+
// var-bearing edge is a longhand (`lynxSides`): a var-bearing
|
|
3439
|
+
// `border` expanded after the transparent top and closed the
|
|
3440
|
+
// ring (signalxjs/lynx#1161). The shorthand stays, static, so
|
|
3441
|
+
// the shared `currentColor` spelling never ships.
|
|
3184
3442
|
spinner: {
|
|
3185
3443
|
base: {
|
|
3186
3444
|
width: '1em',
|
|
3187
3445
|
height: '1em',
|
|
3188
|
-
border: '
|
|
3446
|
+
border: '0 solid transparent',
|
|
3447
|
+
...lynxSides('border{}Width', 'calc(var(--border) * 2)'),
|
|
3448
|
+
...lynxSides('border{}Color', 'var(--btn-ink)', ['Right', 'Bottom', 'Left']),
|
|
3189
3449
|
borderTopColor: 'transparent',
|
|
3190
3450
|
},
|
|
3191
3451
|
},
|
|
3192
|
-
root: {
|
|
3452
|
+
root: {
|
|
3453
|
+
// daisy's content-sized, never-squeezed btn (#1165), and
|
|
3454
|
+
// the hairline as longhands: a var-bearing `border`
|
|
3455
|
+
// shorthand would expand over the variants' and the
|
|
3456
|
+
// disabled state's edge colours (see `lynxSides`).
|
|
3457
|
+
base: {
|
|
3458
|
+
...lynxBtnFit,
|
|
3459
|
+
border: '0 solid transparent',
|
|
3460
|
+
...lynxSides('border{}Width', 'var(--border)'),
|
|
3461
|
+
},
|
|
3462
|
+
// The ring in the variant's own ink (signalxjs/lynx#1163).
|
|
3463
|
+
states: { disabled: lynxBtnDisabled, 'focus-visible': lynxFocusRing('var(--btn-ink)') },
|
|
3464
|
+
},
|
|
3193
3465
|
},
|
|
3194
3466
|
variants: {
|
|
3195
3467
|
// The baked disabled paint above outranks every variant's fill
|
|
3196
3468
|
// (the theme host adds a class), but its plain declarations
|
|
3197
3469
|
// do not: outline/dash restate the border, ghost/link drop
|
|
3198
|
-
// the wash (as in daisy).
|
|
3470
|
+
// the wash (as in daisy). outline/dash draw their ink edge as
|
|
3471
|
+
// longhands, so that transparent restatement wins by
|
|
3472
|
+
// specificity instead of losing to a var-bearing
|
|
3473
|
+
// `border-color` (signalxjs/lynx#1162); the shared shorthand
|
|
3474
|
+
// is neutralised to a static value.
|
|
3199
3475
|
variant: {
|
|
3200
|
-
solid: { spinner: { base: {
|
|
3201
|
-
outline: {
|
|
3202
|
-
|
|
3476
|
+
solid: { spinner: { base: lynxSides('border{}Color', 'var(--btn-on-accent)', ['Right', 'Bottom', 'Left']) } },
|
|
3477
|
+
outline: {
|
|
3478
|
+
root: {
|
|
3479
|
+
base: { borderColor: 'transparent', ...lynxSides('border{}Color', 'var(--btn-ink)') },
|
|
3480
|
+
states: { disabled: { borderColor: 'transparent' } },
|
|
3481
|
+
},
|
|
3482
|
+
},
|
|
3483
|
+
dash: {
|
|
3484
|
+
root: {
|
|
3485
|
+
base: { borderColor: 'transparent', ...lynxSides('border{}Color', 'var(--btn-ink)') },
|
|
3486
|
+
states: { disabled: { borderColor: 'transparent' } },
|
|
3487
|
+
},
|
|
3488
|
+
},
|
|
3203
3489
|
ghost: { root: { states: { disabled: { background: 'transparent' } } } },
|
|
3204
|
-
link
|
|
3205
|
-
|
|
3490
|
+
// `link` zeroes the box-shadow in a variant rule that
|
|
3491
|
+
// lands after the ring, so it restates the ring.
|
|
3492
|
+
link: { root: { states: { disabled: { background: 'transparent' }, 'focus-visible': lynxFocusRing('var(--btn-ink)') } } },
|
|
3493
|
+
},
|
|
3494
|
+
// The size ramp's paddings as longhands, so square/circle's
|
|
3495
|
+
// zeroed sides below win by the cascade rather than by the
|
|
3496
|
+
// order lynx happens to expand a var-bearing `padding` in.
|
|
3497
|
+
size: Object.fromEntries(BTN_PAD.map(([size, block, inline]) => [size, {
|
|
3498
|
+
root: {
|
|
3499
|
+
base: {
|
|
3500
|
+
padding: '0',
|
|
3501
|
+
...lynxSides('padding{}', `var(--space-${block})`, ['Top', 'Bottom']),
|
|
3502
|
+
...lynxSides('padding{}', `var(--space-${inline})`, ['Right', 'Left']),
|
|
3503
|
+
},
|
|
3504
|
+
},
|
|
3505
|
+
}])),
|
|
3206
3506
|
},
|
|
3207
3507
|
// The icon chips' zeroed inline padding, restated physically: logical
|
|
3208
3508
|
// spellings resolve on iOS but not on Android (measured,
|
|
3209
|
-
// signalxjs/lynx#1084), so the emitter refuses them.
|
|
3509
|
+
// signalxjs/lynx#1084), so the emitter refuses them. `width: auto`
|
|
3510
|
+
// undoes the root's `max-content`, which would otherwise switch
|
|
3511
|
+
// off the `aspect-ratio` that squares the chip. `wide`/`block`
|
|
3512
|
+
// restate their width physically: `inline-size` and
|
|
3513
|
+
// `max-inline-size` are not lynx properties at all.
|
|
3210
3514
|
modifiers: {
|
|
3211
|
-
|
|
3212
|
-
|
|
3515
|
+
wide: { root: { base: { width: '100%', maxWidth: '16rem' } } },
|
|
3516
|
+
block: { root: { base: { width: '100%' } } },
|
|
3517
|
+
square: { root: { base: { width: 'auto', paddingLeft: '0', paddingRight: '0' } } },
|
|
3518
|
+
circle: { root: { base: { width: 'auto', paddingLeft: '0', paddingRight: '0' } } },
|
|
3519
|
+
// `active` zeroes the box-shadow too (the held press look).
|
|
3520
|
+
active: { root: { states: { 'focus-visible': lynxFocusRing('var(--btn-ink)') } } },
|
|
3213
3521
|
},
|
|
3214
3522
|
},
|
|
3215
3523
|
},
|
|
@@ -3291,6 +3599,24 @@ export const avatar: RecipeInput = {
|
|
|
3291
3599
|
},
|
|
3292
3600
|
};
|
|
3293
3601
|
|
|
3602
|
+
/**
|
|
3603
|
+
* A toast whose indicator is rendered (#292): the grid grows a leading column
|
|
3604
|
+
* for the mark, and the text, action and close step one column along.
|
|
3605
|
+
*/
|
|
3606
|
+
const TOAST_MARKED = '[data-scope="toast"][data-part="root"]:has(> [data-scope="toast"][data-part="indicator"]) > &';
|
|
3607
|
+
/** The indicator's marks, cut from its fill — not mirrored in RTL (a tick is not a direction). */
|
|
3608
|
+
const TOAST_CHECK = 'polygon(15.1% 41.3%, 1% 55%, 37.6% 92.8%, 99% 19.8%, 83.9% 7.2%, 36.6% 63.5%)';
|
|
3609
|
+
const TOAST_CROSS = 'polygon(20% 8%, 50% 38%, 80% 8%, 92% 20%, 62% 50%, 92% 80%, 80% 92%, 50% 62%, 20% 92%, 8% 80%, 38% 50%, 8% 20%)';
|
|
3610
|
+
|
|
3611
|
+
/**
|
|
3612
|
+
* daisy's success and error are FILLS — light enough in `light` and `nord`
|
|
3613
|
+
* that a bare mark in them reads under 2:1 on the toast's base-200. Deepened
|
|
3614
|
+
* toward base-content (which flips with the theme), the hue survives and the
|
|
3615
|
+
* mark clears the 3:1 non-text floor in every theme.
|
|
3616
|
+
*/
|
|
3617
|
+
const TOAST_SUCCESS_INK = 'color-mix(in oklab, var(--color-success) 55%, var(--color-base-content))';
|
|
3618
|
+
const TOAST_ERROR_INK = 'color-mix(in oklab, var(--color-error) 55%, var(--color-base-content))';
|
|
3619
|
+
|
|
3294
3620
|
/**
|
|
3295
3621
|
* Toast presence is runtime-managed (see the SKILL's Toast section): plain
|
|
3296
3622
|
* two-state transitions only — no `@starting-style`, no `allow-discrete`.
|
|
@@ -3301,6 +3627,7 @@ export const toast: RecipeInput = {
|
|
|
3301
3627
|
'--toast-bg': 'var(--color-base-200)',
|
|
3302
3628
|
'--toast-ink': 'var(--color-base-content)',
|
|
3303
3629
|
'--toast-from': '8px',
|
|
3630
|
+
'--toast-mark': '1rem',
|
|
3304
3631
|
},
|
|
3305
3632
|
parts: {
|
|
3306
3633
|
viewport: {
|
|
@@ -3354,6 +3681,7 @@ export const toast: RecipeInput = {
|
|
|
3354
3681
|
},
|
|
3355
3682
|
selectors: {
|
|
3356
3683
|
'&[data-placement^="top"]': { '--toast-from': '-8px' },
|
|
3684
|
+
'&:has(> [data-scope="toast"][data-part="indicator"])': { gridTemplateColumns: 'auto 1fr auto auto' },
|
|
3357
3685
|
},
|
|
3358
3686
|
states: {
|
|
3359
3687
|
open: { opacity: '1', transform: 'none' },
|
|
@@ -3363,8 +3691,39 @@ export const toast: RecipeInput = {
|
|
|
3363
3691
|
'reduced-motion': { base: { transition: 'none' }, states: { open: { transform: 'none' } } },
|
|
3364
3692
|
},
|
|
3365
3693
|
},
|
|
3694
|
+
// daisy's `loading-spinner` ring in the alert's own ink, then the
|
|
3695
|
+
// success tick or the error cross — the promise toast's status.
|
|
3696
|
+
indicator: {
|
|
3697
|
+
base: {
|
|
3698
|
+
gridColumn: '1',
|
|
3699
|
+
gridRow: '1 / span 2',
|
|
3700
|
+
inlineSize: 'var(--toast-mark)',
|
|
3701
|
+
blockSize: 'var(--toast-mark)',
|
|
3702
|
+
boxSizing: 'border-box',
|
|
3703
|
+
},
|
|
3704
|
+
states: {
|
|
3705
|
+
loading: {
|
|
3706
|
+
borderRadius: '50%',
|
|
3707
|
+
border: 'calc(var(--border) * 2) solid color-mix(in oklab, var(--toast-ink) 25%, transparent)',
|
|
3708
|
+
borderBlockStartColor: 'var(--toast-ink)',
|
|
3709
|
+
animation: 'zero-daisyui-toast-spin 0.75s linear infinite',
|
|
3710
|
+
},
|
|
3711
|
+
complete: { background: TOAST_SUCCESS_INK, clipPath: TOAST_CHECK },
|
|
3712
|
+
error: { background: TOAST_ERROR_INK, clipPath: TOAST_CROSS },
|
|
3713
|
+
},
|
|
3714
|
+
at: {
|
|
3715
|
+
'reduced-motion': { states: { loading: { animation: 'none' } } },
|
|
3716
|
+
'forced-colors': {
|
|
3717
|
+
states: {
|
|
3718
|
+
complete: { background: 'CanvasText', forcedColorAdjust: 'none' },
|
|
3719
|
+
error: { background: 'CanvasText', forcedColorAdjust: 'none' },
|
|
3720
|
+
},
|
|
3721
|
+
},
|
|
3722
|
+
},
|
|
3723
|
+
},
|
|
3366
3724
|
title: {
|
|
3367
3725
|
base: { gridColumn: '1', fontWeight: 'var(--weight-semibold)' },
|
|
3726
|
+
selectors: { [TOAST_MARKED]: { gridColumn: '2' } },
|
|
3368
3727
|
},
|
|
3369
3728
|
description: {
|
|
3370
3729
|
base: {
|
|
@@ -3372,6 +3731,7 @@ export const toast: RecipeInput = {
|
|
|
3372
3731
|
fontSize: 'var(--text-xs)',
|
|
3373
3732
|
color: 'color-mix(in oklab, var(--toast-ink) 75%, transparent)',
|
|
3374
3733
|
},
|
|
3734
|
+
selectors: { [TOAST_MARKED]: { gridColumn: '2' } },
|
|
3375
3735
|
},
|
|
3376
3736
|
action: {
|
|
3377
3737
|
base: {
|
|
@@ -3392,6 +3752,7 @@ export const toast: RecipeInput = {
|
|
|
3392
3752
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
3393
3753
|
...focusRing,
|
|
3394
3754
|
},
|
|
3755
|
+
selectors: { [TOAST_MARKED]: { gridColumn: '3' } },
|
|
3395
3756
|
},
|
|
3396
3757
|
close: {
|
|
3397
3758
|
base: {
|
|
@@ -3411,6 +3772,7 @@ export const toast: RecipeInput = {
|
|
|
3411
3772
|
disabled: { opacity: 'var(--disabled-opacity)' },
|
|
3412
3773
|
...focusRing,
|
|
3413
3774
|
},
|
|
3775
|
+
selectors: { [TOAST_MARKED]: { gridColumn: '4' } },
|
|
3414
3776
|
},
|
|
3415
3777
|
},
|
|
3416
3778
|
variants: {
|
|
@@ -3447,6 +3809,7 @@ export const toast: RecipeInput = {
|
|
|
3447
3809
|
// signalxjs/lynx#1070. No `color` default: the un-attributed toast is a
|
|
3448
3810
|
// neutral base-200 surface, outside the color vocabulary.
|
|
3449
3811
|
defaultVariants: { size: 'md' },
|
|
3812
|
+
keyframes: { 'zero-daisyui-toast-spin': 'to { transform: rotate(360deg); }' },
|
|
3450
3813
|
targets: {
|
|
3451
3814
|
lynx: {
|
|
3452
3815
|
parts: {
|
|
@@ -3500,16 +3863,25 @@ export const toast: RecipeInput = {
|
|
|
3500
3863
|
// the same ink at 75% opacity reads the same on the soft fill.
|
|
3501
3864
|
description: { base: { color: 'var(--toast-ink)', opacity: '0.75' } },
|
|
3502
3865
|
// The 25% ink border cannot bake either: it takes the full ink.
|
|
3866
|
+
// Their rings' gap is the card's own fill.
|
|
3503
3867
|
action: {
|
|
3504
3868
|
base: {
|
|
3505
3869
|
marginTop: 'var(--space-xs)',
|
|
3506
3870
|
border: 'var(--border) solid var(--toast-ink)',
|
|
3507
3871
|
},
|
|
3872
|
+
states: lynxFocus({ gap: 'var(--toast-bg)' }),
|
|
3873
|
+
},
|
|
3874
|
+
close: {
|
|
3875
|
+
base: { position: 'absolute', top: 'var(--space-xs)', right: 'var(--space-xs)' },
|
|
3876
|
+
states: lynxFocus({ gap: 'var(--toast-bg)' }),
|
|
3508
3877
|
},
|
|
3509
|
-
close: { base: { position: 'absolute', top: 'var(--space-xs)', right: 'var(--space-xs)' } },
|
|
3510
3878
|
},
|
|
3511
3879
|
},
|
|
3512
3880
|
},
|
|
3881
|
+
// The viewport's `open` (the stack expanded) is a fact this skin has no
|
|
3882
|
+
// use for: its toasts are always a plain column, so both states look the
|
|
3883
|
+
// same by design (#292).
|
|
3884
|
+
skipStates: { viewport: ['open', 'closed'] },
|
|
3513
3885
|
};
|
|
3514
3886
|
|
|
3515
3887
|
export const combobox: RecipeInput = {
|
|
@@ -4325,12 +4697,29 @@ const treeRowSelectors: NonNullable<PartStyles['selectors']> = {
|
|
|
4325
4697
|
},
|
|
4326
4698
|
};
|
|
4327
4699
|
|
|
4700
|
+
/**
|
|
4701
|
+
* `tickGlyphFallback` for the node check box, whose tick is its `::after`:
|
|
4702
|
+
* the geometry drops and the same pseudo carries the glyph, in the medium's
|
|
4703
|
+
* own ink (the #233 divergence, restated).
|
|
4704
|
+
*/
|
|
4705
|
+
const nodeTickFallback = (ink: string): PartStyles => ({
|
|
4706
|
+
selectors: {
|
|
4707
|
+
'&::after': { clipPath: 'none', backgroundColor: '#0000', rotate: '0deg', inset: '0', display: 'grid', placeItems: 'center', color: ink, fontSize: '0.85em', lineHeight: '1' },
|
|
4708
|
+
'&[data-state="checked"]::after': { content: '"✔︎"', clipPath: 'none', rotate: '0deg' },
|
|
4709
|
+
'&[data-state="indeterminate"]::after': { content: '"−"', clipPath: 'none', rotate: '0deg', translate: 'none' },
|
|
4710
|
+
},
|
|
4711
|
+
});
|
|
4712
|
+
|
|
4328
4713
|
export const treeView: RecipeInput = {
|
|
4329
4714
|
component: 'tree-view',
|
|
4330
4715
|
tokens: {
|
|
4331
4716
|
'--tree-accent': 'var(--color-primary)',
|
|
4332
4717
|
'--tree-text': 'var(--text-sm)',
|
|
4333
4718
|
'--tree-on-accent': 'var(--color-primary-content)',
|
|
4719
|
+
// The node check box's pair — the tree's own accent, so a colour
|
|
4720
|
+
// variant retints the box with the row.
|
|
4721
|
+
'--tree-check': 'var(--tree-accent)',
|
|
4722
|
+
'--tree-on-check': 'var(--tree-on-accent)',
|
|
4334
4723
|
},
|
|
4335
4724
|
parts: {
|
|
4336
4725
|
root: {
|
|
@@ -4379,6 +4768,58 @@ export const treeView: RecipeInput = {
|
|
|
4379
4768
|
'reduced-motion': { base: { transition: 'none' } },
|
|
4380
4769
|
},
|
|
4381
4770
|
},
|
|
4771
|
+
// daisy's `.checkbox` at row scale: the accent outline, the recessed
|
|
4772
|
+
// depth shade, the fill on check — and daisy's own tick, the six
|
|
4773
|
+
// clip-path points rotated 45°, drawn on the box's `::after` since
|
|
4774
|
+
// the part is one span. On a selected row (the accent fill) the pair
|
|
4775
|
+
// swaps, so the box never vanishes into the row it sits on.
|
|
4776
|
+
'node-checkbox': {
|
|
4777
|
+
base: {
|
|
4778
|
+
display: 'inline-block',
|
|
4779
|
+
position: 'relative',
|
|
4780
|
+
flexShrink: '0',
|
|
4781
|
+
boxSizing: 'border-box',
|
|
4782
|
+
width: '1.25em',
|
|
4783
|
+
height: '1.25em',
|
|
4784
|
+
border: 'var(--border) solid var(--tree-check)',
|
|
4785
|
+
borderRadius: 'var(--radius-selector)',
|
|
4786
|
+
boxShadow: '0 1px var(--depth-shade) inset',
|
|
4787
|
+
cursor: 'pointer',
|
|
4788
|
+
transition: 'background-color var(--duration-normal) var(--ease-standard)',
|
|
4789
|
+
},
|
|
4790
|
+
states: {
|
|
4791
|
+
checked: { backgroundColor: 'var(--tree-check)' },
|
|
4792
|
+
indeterminate: { backgroundColor: 'var(--tree-check)' },
|
|
4793
|
+
unchecked: {},
|
|
4794
|
+
disabled: { cursor: 'not-allowed' },
|
|
4795
|
+
},
|
|
4796
|
+
selectors: {
|
|
4797
|
+
'&::after': {
|
|
4798
|
+
content: '""',
|
|
4799
|
+
position: 'absolute',
|
|
4800
|
+
inset: '15%',
|
|
4801
|
+
opacity: '0',
|
|
4802
|
+
rotate: '45deg',
|
|
4803
|
+
backgroundColor: 'var(--tree-on-check)',
|
|
4804
|
+
clipPath: TICK_COLLAPSED,
|
|
4805
|
+
transition: 'clip-path var(--duration-slow) var(--ease-standard) var(--duration-instant), '
|
|
4806
|
+
+ 'opacity var(--duration-instant) var(--ease-standard) var(--duration-instant), '
|
|
4807
|
+
+ 'rotate var(--duration-slow) var(--ease-standard) var(--duration-instant), '
|
|
4808
|
+
+ 'translate var(--duration-slow) var(--ease-standard) var(--duration-instant)',
|
|
4809
|
+
},
|
|
4810
|
+
'&[data-state="checked"]::after': { clipPath: TICK_DRAWN, opacity: '1' },
|
|
4811
|
+
'&[data-state="indeterminate"]::after': { clipPath: DASH_DRAWN, opacity: '1', translate: '0 -35%', rotate: '0deg' },
|
|
4812
|
+
'[data-scope="tree-view"][data-selected] &': {
|
|
4813
|
+
'--tree-check': 'var(--tree-on-accent)',
|
|
4814
|
+
'--tree-on-check': 'var(--tree-accent)',
|
|
4815
|
+
},
|
|
4816
|
+
},
|
|
4817
|
+
at: {
|
|
4818
|
+
'reduced-motion': { base: { transition: 'none' }, selectors: { '&::after': { transition: 'none' } } },
|
|
4819
|
+
'forced-colors': nodeTickFallback('CanvasText'),
|
|
4820
|
+
print: nodeTickFallback('var(--print-ink)'),
|
|
4821
|
+
},
|
|
4822
|
+
},
|
|
4382
4823
|
// Indentation comes from this inline padding — depth is the DOM
|
|
4383
4824
|
// nesting, no per-level rules needed.
|
|
4384
4825
|
'branch-content': {
|
|
@@ -4663,6 +5104,36 @@ export const card: RecipeInput = {
|
|
|
4663
5104
|
overflow: 'hidden',
|
|
4664
5105
|
},
|
|
4665
5106
|
},
|
|
5107
|
+
/**
|
|
5108
|
+
* Media (#302) — daisy's `card > figure`: a centred flex box, flush
|
|
5109
|
+
* to the card's edges, the image filling it.
|
|
5110
|
+
*/
|
|
5111
|
+
media: {
|
|
5112
|
+
base: {
|
|
5113
|
+
display: 'flex',
|
|
5114
|
+
alignItems: 'center',
|
|
5115
|
+
justifyContent: 'center',
|
|
5116
|
+
margin: '0',
|
|
5117
|
+
inlineSize: '100%',
|
|
5118
|
+
objectFit: 'cover',
|
|
5119
|
+
},
|
|
5120
|
+
selectors: {
|
|
5121
|
+
// The media's own image, when the band wraps one (a div or a
|
|
5122
|
+
// figure): block-level, the band's full width, cropped rather
|
|
5123
|
+
// than stretched when the band is given a height.
|
|
5124
|
+
'& > :is(img, picture, video, svg, canvas)': {
|
|
5125
|
+
display: 'block',
|
|
5126
|
+
inlineSize: '100%',
|
|
5127
|
+
blockSize: '100%',
|
|
5128
|
+
objectFit: 'cover',
|
|
5129
|
+
},
|
|
5130
|
+
// The corners it shares with the card (#302): the root clips
|
|
5131
|
+
// too, but an asChild <img> is the band itself and rounds on
|
|
5132
|
+
// its own.
|
|
5133
|
+
'&:first-child': { borderStartStartRadius: 'var(--radius-box)', borderStartEndRadius: 'var(--radius-box)' },
|
|
5134
|
+
'&:last-child': { borderEndStartRadius: 'var(--radius-box)', borderEndEndRadius: 'var(--radius-box)' },
|
|
5135
|
+
},
|
|
5136
|
+
},
|
|
4666
5137
|
header: {
|
|
4667
5138
|
base: {
|
|
4668
5139
|
display: 'flex',
|
|
@@ -4996,16 +5467,51 @@ export const divider: RecipeInput = {
|
|
|
4996
5467
|
tokens: { '--divider-ink': 'var(--color-base-300)', '--divider-thickness': 'var(--border)' },
|
|
4997
5468
|
parts: {
|
|
4998
5469
|
root: {
|
|
4999
|
-
|
|
5470
|
+
// The rule is two flex segments (#298), drawn as borders: with no
|
|
5471
|
+
// label they meet and read as one line, and a label sits between
|
|
5472
|
+
// them. Borders rather than a background, so forced colours keep
|
|
5473
|
+
// the line instead of revaluing it to Canvas.
|
|
5474
|
+
base: {
|
|
5475
|
+
display: 'flex',
|
|
5476
|
+
alignItems: 'center',
|
|
5477
|
+
border: 'none',
|
|
5478
|
+
alignSelf: 'stretch',
|
|
5479
|
+
},
|
|
5000
5480
|
selectors: {
|
|
5001
|
-
'
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
},
|
|
5005
|
-
'&[data-orientation="
|
|
5006
|
-
|
|
5007
|
-
|
|
5481
|
+
'&::before': { content: '""', flex: '1 1 0', minInlineSize: '0' },
|
|
5482
|
+
'&::after': { content: '""', flex: '1 1 0', minInlineSize: '0' },
|
|
5483
|
+
'&[data-orientation="horizontal"]': { inlineSize: '100%' },
|
|
5484
|
+
'&[data-orientation="horizontal"]::before': { borderBlockStart: 'var(--divider-thickness) solid var(--divider-ink)' },
|
|
5485
|
+
'&[data-orientation="horizontal"]::after': { borderBlockStart: 'var(--divider-thickness) solid var(--divider-ink)' },
|
|
5486
|
+
'&[data-orientation="vertical"]': { flexDirection: 'column', minBlockSize: '1em' },
|
|
5487
|
+
'&[data-orientation="vertical"]::before': { borderInlineStart: 'var(--divider-thickness) solid var(--divider-ink)' },
|
|
5488
|
+
'&[data-orientation="vertical"]::after': { borderInlineStart: 'var(--divider-thickness) solid var(--divider-ink)' },
|
|
5489
|
+
// A placed label sits flush at its edge: the segment on
|
|
5490
|
+
// that side goes, and the other one takes the whole run.
|
|
5491
|
+
'&:has(> [data-scope="divider"][data-part="label"][data-placement="start"])::before': { display: 'none' },
|
|
5492
|
+
'&:has(> [data-scope="divider"][data-part="label"][data-placement="end"])::after': { display: 'none' },
|
|
5493
|
+
},
|
|
5494
|
+
},
|
|
5495
|
+
label: {
|
|
5496
|
+
base: {
|
|
5497
|
+
// daisy's `divider` text: the content colour, the rule
|
|
5498
|
+
// stopping a gap short of it on either side.
|
|
5499
|
+
flex: 'none',
|
|
5500
|
+
paddingInline: 'var(--space-md)',
|
|
5501
|
+
fontSize: 'var(--text-sm)',
|
|
5502
|
+
whiteSpace: 'nowrap',
|
|
5503
|
+
color: 'var(--color-base-content)',
|
|
5504
|
+
},
|
|
5505
|
+
selectors: {
|
|
5506
|
+
'[data-scope="divider"][data-part="root"][data-orientation="vertical"] > &': {
|
|
5507
|
+
paddingInline: '0',
|
|
5508
|
+
paddingBlock: 'var(--space-sm)',
|
|
5008
5509
|
},
|
|
5510
|
+
// Flush means flush: no inset on the side the rule gave up.
|
|
5511
|
+
'&[data-placement="start"]': { paddingInlineStart: '0' },
|
|
5512
|
+
'&[data-placement="end"]': { paddingInlineEnd: '0' },
|
|
5513
|
+
'[data-scope="divider"][data-part="root"][data-orientation="vertical"] > &[data-placement="start"]': { paddingBlockStart: '0' },
|
|
5514
|
+
'[data-scope="divider"][data-part="root"][data-orientation="vertical"] > &[data-placement="end"]': { paddingBlockEnd: '0' },
|
|
5009
5515
|
},
|
|
5010
5516
|
},
|
|
5011
5517
|
},
|
|
@@ -5556,6 +6062,10 @@ export const timeline: RecipeInput = {
|
|
|
5556
6062
|
},
|
|
5557
6063
|
},
|
|
5558
6064
|
},
|
|
6065
|
+
// Title and description (#302): daisy's timeline-box leads with a
|
|
6066
|
+
// bold line; the detail under it in the card description's ink.
|
|
6067
|
+
title: { base: { display: 'block', margin: '0', fontWeight: 'var(--weight-semibold)', lineHeight: 'var(--leading-tight)' } },
|
|
6068
|
+
description: { base: { display: 'block', margin: '0', marginBlockStart: 'var(--space-2xs)', color: 'color-mix(in oklab, var(--color-base-content) 70%, transparent)' } },
|
|
5559
6069
|
},
|
|
5560
6070
|
variants: {
|
|
5561
6071
|
color: Object.fromEntries(ROLES.map((c) => [c, { marker: { base: {
|
|
@@ -5762,6 +6272,96 @@ export const chat: RecipeInput = {
|
|
|
5762
6272
|
},
|
|
5763
6273
|
};
|
|
5764
6274
|
|
|
6275
|
+
/**
|
|
6276
|
+
* ChatLog — daisy's `bg-base-200 rounded-box` well that the chat bubbles
|
|
6277
|
+
* already sit in, scrolling; the jump trigger is a `btn btn-sm` pill in the
|
|
6278
|
+
* role's solid pair with daisy's `shadow-xs` and its 1px press sink, floated
|
|
6279
|
+
* over the rows by `position: sticky` at the box's foot. Its negative block
|
|
6280
|
+
* margin gives back the line it would take, so appearing moves no row.
|
|
6281
|
+
*/
|
|
6282
|
+
export const chatLog: RecipeInput = {
|
|
6283
|
+
component: 'chat-log',
|
|
6284
|
+
tokens: {
|
|
6285
|
+
'--chat-log-fill': 'var(--color-primary)',
|
|
6286
|
+
'--chat-log-on-fill': 'var(--color-primary-content)',
|
|
6287
|
+
'--chat-log-jump-size': 'calc(var(--size-field) * 8)',
|
|
6288
|
+
},
|
|
6289
|
+
parts: {
|
|
6290
|
+
root: {
|
|
6291
|
+
base: {
|
|
6292
|
+
display: 'flex',
|
|
6293
|
+
flexDirection: 'column',
|
|
6294
|
+
overflowY: 'auto',
|
|
6295
|
+
overscrollBehaviorY: 'contain',
|
|
6296
|
+
borderRadius: 'var(--radius-box)',
|
|
6297
|
+
background: 'var(--color-base-100)',
|
|
6298
|
+
color: 'var(--color-base-content)',
|
|
6299
|
+
border: 'var(--border) solid var(--color-base-300)',
|
|
6300
|
+
padding: 'var(--space-sm) var(--space-md)',
|
|
6301
|
+
},
|
|
6302
|
+
// Drawn inside the box (negative offset): an outward ring would
|
|
6303
|
+
// be clipped by whatever scrolls around the transcript.
|
|
6304
|
+
states: { 'focus-visible': { outline: '2px solid var(--color-base-content)', outlineOffset: '-2px' } },
|
|
6305
|
+
},
|
|
6306
|
+
content: {
|
|
6307
|
+
base: {
|
|
6308
|
+
// `auto` gathers a short transcript at the foot; a long one
|
|
6309
|
+
// overflows and the margin resolves to nothing.
|
|
6310
|
+
marginBlockStart: 'auto',
|
|
6311
|
+
flex: 'none',
|
|
6312
|
+
display: 'grid',
|
|
6313
|
+
rowGap: 'var(--space-xs)',
|
|
6314
|
+
},
|
|
6315
|
+
},
|
|
6316
|
+
'jump-trigger': {
|
|
6317
|
+
base: {
|
|
6318
|
+
position: 'sticky',
|
|
6319
|
+
insetBlockEnd: 'var(--space-sm)',
|
|
6320
|
+
alignSelf: 'center',
|
|
6321
|
+
flex: 'none',
|
|
6322
|
+
marginBlockStart: 'calc(var(--chat-log-jump-size) * -1)',
|
|
6323
|
+
blockSize: 'var(--chat-log-jump-size)',
|
|
6324
|
+
appearance: 'none',
|
|
6325
|
+
display: 'inline-flex',
|
|
6326
|
+
alignItems: 'center',
|
|
6327
|
+
gap: 'var(--space-2xs)',
|
|
6328
|
+
paddingInline: 'var(--space-md)',
|
|
6329
|
+
border: 'var(--border) solid transparent',
|
|
6330
|
+
borderRadius: '9999px',
|
|
6331
|
+
background: 'var(--chat-log-fill)',
|
|
6332
|
+
color: 'var(--chat-log-on-fill)',
|
|
6333
|
+
boxShadow: 'var(--shadow-xs)',
|
|
6334
|
+
fontFamily: 'inherit',
|
|
6335
|
+
fontSize: 'var(--text-xs)',
|
|
6336
|
+
fontWeight: 'var(--weight-semibold)',
|
|
6337
|
+
lineHeight: 'var(--leading-none)',
|
|
6338
|
+
cursor: 'pointer',
|
|
6339
|
+
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
6340
|
+
},
|
|
6341
|
+
states: {
|
|
6342
|
+
open: {},
|
|
6343
|
+
closed: {},
|
|
6344
|
+
hover: { background: 'color-mix(in oklab, var(--chat-log-fill) 90%, black)' },
|
|
6345
|
+
'focus-visible': { outline: '2px solid var(--color-base-content)', outlineOffset: '2px' },
|
|
6346
|
+
},
|
|
6347
|
+
selectors: { '&[data-pressed]:not([data-disabled])': { transform: 'translateY(1px)', boxShadow: 'none' } },
|
|
6348
|
+
},
|
|
6349
|
+
},
|
|
6350
|
+
variants: {
|
|
6351
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { 'jump-trigger': { base: {
|
|
6352
|
+
'--chat-log-fill': `var(--color-${c})`,
|
|
6353
|
+
'--chat-log-on-fill': `var(--color-${c}-content)`,
|
|
6354
|
+
} } }])),
|
|
6355
|
+
size: {
|
|
6356
|
+
xs: { content: { base: { rowGap: 'var(--space-2xs)' } }, 'jump-trigger': { base: { '--chat-log-jump-size': 'calc(var(--size-field) * 6)', fontSize: 'var(--text-xs)' } } },
|
|
6357
|
+
sm: { content: { base: { rowGap: 'var(--space-2xs)' } }, 'jump-trigger': { base: { '--chat-log-jump-size': 'calc(var(--size-field) * 7)', fontSize: 'var(--text-xs)' } } },
|
|
6358
|
+
md: {},
|
|
6359
|
+
lg: { content: { base: { rowGap: 'var(--space-sm)' } }, 'jump-trigger': { base: { '--chat-log-jump-size': 'calc(var(--size-field) * 9)', fontSize: 'var(--text-sm)' } } },
|
|
6360
|
+
xl: { content: { base: { rowGap: 'var(--space-md)' } }, 'jump-trigger': { base: { '--chat-log-jump-size': 'calc(var(--size-field) * 10)', fontSize: 'var(--text-sm)' } } },
|
|
6361
|
+
},
|
|
6362
|
+
},
|
|
6363
|
+
};
|
|
6364
|
+
|
|
5765
6365
|
/** daisy radial-progress: --value became --progress-percent; same ring. */
|
|
5766
6366
|
export const radialProgress: RecipeInput = {
|
|
5767
6367
|
component: 'radial-progress',
|
|
@@ -6152,6 +6752,8 @@ export const breadcrumbs: RecipeInput = {
|
|
|
6152
6752
|
alignItems: 'center',
|
|
6153
6753
|
gap: 'var(--space-xs)',
|
|
6154
6754
|
},
|
|
6755
|
+
// `closed` is presence: the runtime hides the part (#295).
|
|
6756
|
+
states: { open: {}, closed: {} },
|
|
6155
6757
|
},
|
|
6156
6758
|
link: {
|
|
6157
6759
|
base: {
|
|
@@ -6180,6 +6782,37 @@ export const breadcrumbs: RecipeInput = {
|
|
|
6180
6782
|
userSelect: 'none',
|
|
6181
6783
|
},
|
|
6182
6784
|
},
|
|
6785
|
+
ellipsis: {
|
|
6786
|
+
base: {
|
|
6787
|
+
display: 'flex',
|
|
6788
|
+
alignItems: 'center',
|
|
6789
|
+
gap: 'var(--space-xs)',
|
|
6790
|
+
},
|
|
6791
|
+
// `closed` is presence: the runtime hides the part (#295).
|
|
6792
|
+
states: { open: {}, closed: {} },
|
|
6793
|
+
},
|
|
6794
|
+
'ellipsis-trigger': {
|
|
6795
|
+
base: {
|
|
6796
|
+
appearance: 'none',
|
|
6797
|
+
background: 'transparent',
|
|
6798
|
+
border: '0',
|
|
6799
|
+
padding: '0 var(--space-2xs)',
|
|
6800
|
+
margin: '0',
|
|
6801
|
+
font: 'inherit',
|
|
6802
|
+
lineHeight: 'inherit',
|
|
6803
|
+
color: 'color-mix(in oklch, var(--color-base-content) 75%, transparent)',
|
|
6804
|
+
borderRadius: 'var(--radius-selector)',
|
|
6805
|
+
cursor: 'pointer',
|
|
6806
|
+
transition: 'color var(--duration-fast) var(--ease-standard)',
|
|
6807
|
+
},
|
|
6808
|
+
states: {
|
|
6809
|
+
hover: { color: 'var(--color-base-content)', textDecoration: 'underline' },
|
|
6810
|
+
'focus-visible': {
|
|
6811
|
+
outline: '2px solid var(--color-primary)',
|
|
6812
|
+
outlineOffset: '2px',
|
|
6813
|
+
},
|
|
6814
|
+
},
|
|
6815
|
+
},
|
|
6183
6816
|
},
|
|
6184
6817
|
variants: {
|
|
6185
6818
|
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
@@ -6195,11 +6828,47 @@ export const breadcrumbs: RecipeInput = {
|
|
|
6195
6828
|
},
|
|
6196
6829
|
};
|
|
6197
6830
|
|
|
6831
|
+
/**
|
|
6832
|
+
* The four pagination triggers — prev/next and, with `withEdges`, the
|
|
6833
|
+
* first/last jumps (#294) — are one cell: the same box, the same
|
|
6834
|
+
* glyph-sized type, and the same flip under the rtl guard, since every
|
|
6835
|
+
* glyph (`‹ › « »`) is physical ink pointing at a reading edge.
|
|
6836
|
+
*/
|
|
6837
|
+
const pageTrigger: PartStyles = {
|
|
6838
|
+
base: {
|
|
6839
|
+
display: 'inline-flex',
|
|
6840
|
+
alignItems: 'center',
|
|
6841
|
+
justifyContent: 'center',
|
|
6842
|
+
minInlineSize: 'var(--pg-size)',
|
|
6843
|
+
blockSize: 'var(--pg-size)',
|
|
6844
|
+
background: 'var(--color-base-200)',
|
|
6845
|
+
color: 'var(--color-base-content)',
|
|
6846
|
+
border: 'none',
|
|
6847
|
+
borderRadius: 'var(--radius-field)',
|
|
6848
|
+
fontSize: 'calc(var(--pg-font) * 1.2)',
|
|
6849
|
+
lineHeight: 'var(--leading-none)',
|
|
6850
|
+
appearance: 'none',
|
|
6851
|
+
// Link mode renders an <a> (#294): no UA underline.
|
|
6852
|
+
textDecoration: 'none',
|
|
6853
|
+
cursor: 'pointer',
|
|
6854
|
+
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
6855
|
+
},
|
|
6856
|
+
states: {
|
|
6857
|
+
hover: { background: 'var(--color-base-300)' },
|
|
6858
|
+
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
6859
|
+
...focusRing,
|
|
6860
|
+
},
|
|
6861
|
+
selectors: {
|
|
6862
|
+
'&[data-pressed]:not([data-disabled])': { transform: 'translateY(1px)' },
|
|
6863
|
+
[`&${rtl}`]: { scale: '-1 1' },
|
|
6864
|
+
},
|
|
6865
|
+
};
|
|
6866
|
+
|
|
6198
6867
|
/**
|
|
6199
6868
|
* Pagination — daisy's join-of-buttons made of standalone btn cells: the
|
|
6200
6869
|
* quiet base-200 fill, base-300 hover, the current page inverted into the
|
|
6201
6870
|
* accent pair (primary by default). Pressed is the daisy 1px sink. The
|
|
6202
|
-
*
|
|
6871
|
+
* `‹ › « »` glyphs flip under the shared rtl guard.
|
|
6203
6872
|
*/
|
|
6204
6873
|
export const pagination: RecipeInput = {
|
|
6205
6874
|
component: 'pagination',
|
|
@@ -6245,6 +6914,7 @@ export const pagination: RecipeInput = {
|
|
|
6245
6914
|
fontWeight: 'var(--weight-semibold)',
|
|
6246
6915
|
fontVariantNumeric: 'tabular-nums',
|
|
6247
6916
|
appearance: 'none',
|
|
6917
|
+
textDecoration: 'none',
|
|
6248
6918
|
cursor: 'pointer',
|
|
6249
6919
|
transition: 'background var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard)',
|
|
6250
6920
|
},
|
|
@@ -6272,60 +6942,10 @@ export const pagination: RecipeInput = {
|
|
|
6272
6942
|
userSelect: 'none',
|
|
6273
6943
|
},
|
|
6274
6944
|
},
|
|
6275
|
-
'
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
justifyContent: 'center',
|
|
6280
|
-
minInlineSize: 'var(--pg-size)',
|
|
6281
|
-
blockSize: 'var(--pg-size)',
|
|
6282
|
-
background: 'var(--color-base-200)',
|
|
6283
|
-
color: 'var(--color-base-content)',
|
|
6284
|
-
border: 'none',
|
|
6285
|
-
borderRadius: 'var(--radius-field)',
|
|
6286
|
-
fontSize: 'calc(var(--pg-font) * 1.2)',
|
|
6287
|
-
lineHeight: 'var(--leading-none)',
|
|
6288
|
-
appearance: 'none',
|
|
6289
|
-
cursor: 'pointer',
|
|
6290
|
-
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
6291
|
-
},
|
|
6292
|
-
states: {
|
|
6293
|
-
hover: { background: 'var(--color-base-300)' },
|
|
6294
|
-
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
6295
|
-
...focusRing,
|
|
6296
|
-
},
|
|
6297
|
-
selectors: {
|
|
6298
|
-
'&[data-pressed]:not([data-disabled])': { transform: 'translateY(1px)' },
|
|
6299
|
-
[`&${rtl}`]: { scale: '-1 1' },
|
|
6300
|
-
},
|
|
6301
|
-
},
|
|
6302
|
-
'next-trigger': {
|
|
6303
|
-
base: {
|
|
6304
|
-
display: 'inline-flex',
|
|
6305
|
-
alignItems: 'center',
|
|
6306
|
-
justifyContent: 'center',
|
|
6307
|
-
minInlineSize: 'var(--pg-size)',
|
|
6308
|
-
blockSize: 'var(--pg-size)',
|
|
6309
|
-
background: 'var(--color-base-200)',
|
|
6310
|
-
color: 'var(--color-base-content)',
|
|
6311
|
-
border: 'none',
|
|
6312
|
-
borderRadius: 'var(--radius-field)',
|
|
6313
|
-
fontSize: 'calc(var(--pg-font) * 1.2)',
|
|
6314
|
-
lineHeight: 'var(--leading-none)',
|
|
6315
|
-
appearance: 'none',
|
|
6316
|
-
cursor: 'pointer',
|
|
6317
|
-
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
6318
|
-
},
|
|
6319
|
-
states: {
|
|
6320
|
-
hover: { background: 'var(--color-base-300)' },
|
|
6321
|
-
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
6322
|
-
...focusRing,
|
|
6323
|
-
},
|
|
6324
|
-
selectors: {
|
|
6325
|
-
'&[data-pressed]:not([data-disabled])': { transform: 'translateY(1px)' },
|
|
6326
|
-
[`&${rtl}`]: { scale: '-1 1' },
|
|
6327
|
-
},
|
|
6328
|
-
},
|
|
6945
|
+
'first-trigger': pageTrigger,
|
|
6946
|
+
'prev-trigger': pageTrigger,
|
|
6947
|
+
'next-trigger': pageTrigger,
|
|
6948
|
+
'last-trigger': pageTrigger,
|
|
6329
6949
|
},
|
|
6330
6950
|
variants: {
|
|
6331
6951
|
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
@@ -6366,6 +6986,22 @@ const stepInk = (accent: string, role: string): string =>
|
|
|
6366
6986
|
`color-mix(in oklab, ${accent} `
|
|
6367
6987
|
+ `${Math.min(ROLE_INK_KEEP[role as keyof typeof roles] ?? 55, 70)}%, var(--color-base-content))`;
|
|
6368
6988
|
|
|
6989
|
+
/** A Steps root holding wizard parts (#296) — the panel or the triggers. */
|
|
6990
|
+
const STEPS_WIZARD = '&:has(> [data-scope="steps"]:is([data-part="content"], [data-part="prev-trigger"], [data-part="next-trigger"]))';
|
|
6991
|
+
|
|
6992
|
+
/** Steps' Back/Next (#296): the neutral daisy btn, with its 1px sink. */
|
|
6993
|
+
const stepsTrigger: PartStyles = {
|
|
6994
|
+
base: { ...btn, flex: '0 0 auto', alignSelf: 'flex-start' },
|
|
6995
|
+
states: {
|
|
6996
|
+
hover: { background: 'var(--color-base-300)' },
|
|
6997
|
+
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
6998
|
+
...focusRing,
|
|
6999
|
+
},
|
|
7000
|
+
selectors: {
|
|
7001
|
+
'&[data-pressed]:not([data-disabled])': { transform: 'translateY(1px)' },
|
|
7002
|
+
},
|
|
7003
|
+
};
|
|
7004
|
+
|
|
6369
7005
|
/**
|
|
6370
7006
|
* Steps — daisy's steps translated to the richer rail: bold numbered discs
|
|
6371
7007
|
* on base-200, the walked disc and line refilled with the accent pair
|
|
@@ -6392,6 +7028,10 @@ export const steps: RecipeInput = {
|
|
|
6392
7028
|
},
|
|
6393
7029
|
selectors: {
|
|
6394
7030
|
'&[data-orientation="vertical"]': { flexDirection: 'column' },
|
|
7031
|
+
// A wizard (#296): the rail keeps its own line and the panel
|
|
7032
|
+
// and triggers wrap below it. Only then — a bare rail never
|
|
7033
|
+
// wraps, so its steps stay one row at any width.
|
|
7034
|
+
[STEPS_WIZARD]: { flexWrap: 'wrap', rowGap: 'var(--space-md)' },
|
|
6395
7035
|
},
|
|
6396
7036
|
},
|
|
6397
7037
|
/**
|
|
@@ -6430,6 +7070,10 @@ export const steps: RecipeInput = {
|
|
|
6430
7070
|
active: { color: 'var(--steps-accent-ink)', fontWeight: 'var(--weight-semibold)' },
|
|
6431
7071
|
complete: { color: 'var(--color-base-content)' },
|
|
6432
7072
|
inactive: { color: 'color-mix(in oklch, var(--color-base-content) 65%, transparent)' },
|
|
7073
|
+
// daisy's `step-error`, whatever the phase: after the phases,
|
|
7074
|
+
// so it wins at equal weight. `stepInk`, not `roleInk`: the
|
|
7075
|
+
// 85% error ink measured 3.79:1 on light's page as text.
|
|
7076
|
+
invalid: { color: stepInk('var(--color-error)', 'error') },
|
|
6433
7077
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
6434
7078
|
...focusRing,
|
|
6435
7079
|
},
|
|
@@ -6461,6 +7105,7 @@ export const steps: RecipeInput = {
|
|
|
6461
7105
|
// the disc (#112).
|
|
6462
7106
|
complete: { background: 'color-mix(in oklab, var(--steps-accent) 20%, var(--color-base-100))', color: 'var(--steps-complete-ink)' },
|
|
6463
7107
|
inactive: { background: 'var(--color-base-200)', color: 'var(--color-base-content)' },
|
|
7108
|
+
invalid: { background: 'var(--color-error)', color: 'var(--color-error-content)' },
|
|
6464
7109
|
},
|
|
6465
7110
|
},
|
|
6466
7111
|
/**
|
|
@@ -6492,6 +7137,7 @@ export const steps: RecipeInput = {
|
|
|
6492
7137
|
states: {
|
|
6493
7138
|
complete: { background: 'var(--steps-accent)' },
|
|
6494
7139
|
inactive: { background: 'var(--color-base-300)' },
|
|
7140
|
+
invalid: { background: 'var(--color-error)' },
|
|
6495
7141
|
},
|
|
6496
7142
|
},
|
|
6497
7143
|
title: {
|
|
@@ -6506,6 +7152,25 @@ export const steps: RecipeInput = {
|
|
|
6506
7152
|
fontWeight: 'var(--weight-normal)',
|
|
6507
7153
|
},
|
|
6508
7154
|
},
|
|
7155
|
+
/**
|
|
7156
|
+
* The active step's panel: a full-width line under the rail (the
|
|
7157
|
+
* wizard wrap above). Inactive panels are `hidden`, so they need no
|
|
7158
|
+
* rule.
|
|
7159
|
+
*/
|
|
7160
|
+
content: {
|
|
7161
|
+
base: {
|
|
7162
|
+
flex: '1 0 100%',
|
|
7163
|
+
fontSize: 'var(--text-md)',
|
|
7164
|
+
color: 'var(--color-base-content)',
|
|
7165
|
+
},
|
|
7166
|
+
states: { active: {}, inactive: {} },
|
|
7167
|
+
},
|
|
7168
|
+
// Back/Next are daisy btns; Next takes the far end of their line.
|
|
7169
|
+
'prev-trigger': stepsTrigger,
|
|
7170
|
+
'next-trigger': {
|
|
7171
|
+
...stepsTrigger,
|
|
7172
|
+
base: { ...stepsTrigger.base, marginInlineStart: 'auto' },
|
|
7173
|
+
},
|
|
6509
7174
|
},
|
|
6510
7175
|
variants: {
|
|
6511
7176
|
// Keyed on the ITEM, not the root (#112): the item re-carries
|
|
@@ -7485,11 +8150,11 @@ export const diff: RecipeInput = {
|
|
|
7485
8150
|
};
|
|
7486
8151
|
|
|
7487
8152
|
export const recipes: RecipeInput[] = [
|
|
7488
|
-
tabs, collapsible, switchRecipe, dialog, popover, tooltip, menu,
|
|
8153
|
+
tabs, collapsible, switchRecipe, dialog, popover, tooltip, hoverCard, menu, menubar,
|
|
7489
8154
|
field, fieldset, checkbox, checkboxGroup, radioGroup, progress, slider, accordion, select, button, avatar, toast, combobox,
|
|
7490
8155
|
toggle, toggleGroup, numberInput, ratingGroup, treeView, input, textarea,
|
|
7491
8156
|
card, alert, emptyState, badge, divider, skeleton, spinner,
|
|
7492
|
-
kbd, status, indicator, stats, timeline, chat, radialProgress, join,
|
|
8157
|
+
kbd, status, indicator, stats, timeline, chat, chatLog, radialProgress, join,
|
|
7493
8158
|
navbar, navList, breadcrumbs, pagination, steps, drawer,
|
|
7494
8159
|
table,
|
|
7495
8160
|
fileUpload,
|