@sigx/zero-daisyui 0.6.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/README.md +1 -2
- package/dist/audit.json +5618 -72
- package/dist/components.d.ts +37 -1
- package/dist/components.js +4 -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/fieldset.css +91 -0
- package/dist/css/components/hover-card.css +174 -0
- package/dist/css/components/menu.css +73 -0
- package/dist/css/components/menubar.css +75 -0
- package/dist/css/components/pagination.css +83 -0
- package/dist/css/components/popover.css +72 -0
- package/dist/css/components/progress.css +0 -4
- package/dist/css/components/steps.css +103 -0
- package/dist/css/components/table.css +70 -0
- package/dist/css/components/timeline.css +14 -0
- package/dist/css/components/toast.css +65 -0
- package/dist/css/components/tooltip.css +65 -0
- package/dist/css/components/tree-view.css +118 -0
- package/dist/css/index.css +1330 -42
- package/dist/lynx/components/accordion.css +7 -1
- package/dist/lynx/components/breadcrumbs.css +44 -0
- package/dist/lynx/components/button.css +121 -10
- package/dist/lynx/components/card.css +9 -0
- package/dist/lynx/components/chat-log.css +147 -0
- package/dist/lynx/components/dialog.css +28 -3
- package/dist/lynx/components/divider.css +8 -17
- package/dist/lynx/components/fieldset.css +219 -0
- 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 +44 -2
- package/dist/lynx/components/progress.css +0 -20
- package/dist/lynx/components/select.css +51 -3
- package/dist/lynx/components/slider.css +295 -12
- package/dist/lynx/components/steps.css +103 -0
- package/dist/lynx/components/switch.css +4 -1
- package/dist/lynx/components/table.css +52 -0
- package/dist/lynx/components/tabs.css +109 -94
- package/dist/lynx/components/timeline.css +90 -1
- package/dist/lynx/components/toast.css +111 -8
- package/dist/lynx/components/tree-view.css +28 -0
- package/dist/lynx/index.css +2081 -255
- package/dist/lynx/manifest.json +112 -4
- package/dist/manifest.json +110 -2
- package/dist/recipes.d.ts +29 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +1225 -129
- package/dist/recipes.js.map +1 -1
- package/dist/register.d.ts +36 -0
- package/dist/report.json +1353 -188
- package/package.json +4 -4
- package/src/recipes.ts +1254 -130
package/dist/recipes.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { axisRoles, tableStackAt } from '@sigx/zero-kit/define';
|
|
1
|
+
import { axisRoles, popupArrow, popupArrowHost, tableStackAt } from '@sigx/zero-kit/define';
|
|
2
2
|
import { roles, tokens } from './tokens.js';
|
|
3
3
|
/**
|
|
4
4
|
* Every role a consumer can pass as `color`, derived from the declaration
|
|
@@ -28,6 +28,36 @@ const focusRing = {
|
|
|
28
28
|
outlineOffset: '2px',
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* The focus ring on lynx, for `targets.lynx` (signalxjs/lynx#1163): the same
|
|
33
|
+
* 2px ring 2px clear of the part, spelled as spread box-shadows.
|
|
34
|
+
*
|
|
35
|
+
* The web ring is `outline` + `outline-offset`, and neither survives on
|
|
36
|
+
* lynx: its `outline` ignores `border-radius` (a square box around a
|
|
37
|
+
* rounded button, a rectangle around the switch pill) and it has no
|
|
38
|
+
* `outline-offset` property at all, so the ring sat flush on the fill. A
|
|
39
|
+
* zero-blur shadow's spread follows the element's radius. Shadows stack
|
|
40
|
+
* rather than mask, so the offset gap cannot be transparent: the inner
|
|
41
|
+
* shadow paints it in `gap` — the surface the part sits on, which is what
|
|
42
|
+
* the web's gap shows through to. `outline: none` retires the shared ring
|
|
43
|
+
* in the same merged declaration block.
|
|
44
|
+
*
|
|
45
|
+
* `under` appends the part's own lynx shadows (a knob's depth relief), which
|
|
46
|
+
* the focus rule would otherwise replace. `inset` draws the ring inside the
|
|
47
|
+
* part, flush with its edge, for a part whose parent clips an outside ring
|
|
48
|
+
* away (signalxjs/lynx#1164, the accordion trigger inside its card).
|
|
49
|
+
*/
|
|
50
|
+
const lynxFocusRing = (ink, { gap = 'var(--color-base-100)', under, inset = false } = {}) => ({
|
|
51
|
+
outline: 'none',
|
|
52
|
+
boxShadow: [
|
|
53
|
+
...(inset ? [`inset 0 0 0 2px ${ink}`] : [`0 0 0 2px ${gap}`, `0 0 0 4px ${ink}`]),
|
|
54
|
+
...(under ? [under] : []),
|
|
55
|
+
].join(', '),
|
|
56
|
+
});
|
|
57
|
+
/** `focusRing`'s lynx spelling — the base-content ring, as a states entry. */
|
|
58
|
+
const lynxFocus = (opts) => ({
|
|
59
|
+
'focus-visible': lynxFocusRing('var(--color-base-content)', opts),
|
|
60
|
+
});
|
|
31
61
|
/**
|
|
32
62
|
* How far each role survives in the button's INK — the colour a transparent or
|
|
33
63
|
* tinted fill draws its label, border and focus ring with.
|
|
@@ -289,7 +319,9 @@ export const tabs = {
|
|
|
289
319
|
},
|
|
290
320
|
// daisy draws each flavor's active mark on the tab itself (the
|
|
291
321
|
// underline, the lift, the pill) and none of them slides, so a
|
|
292
|
-
// `Tabs.Indicator` renders nothing here (#283).
|
|
322
|
+
// `Tabs.Indicator` renders nothing here (#283). On lynx, which has
|
|
323
|
+
// no pseudo-elements, the `border` flavor's underline is drawn on
|
|
324
|
+
// this part instead (see `targets.lynx`).
|
|
293
325
|
indicator: { base: { display: 'none' } },
|
|
294
326
|
panel: {
|
|
295
327
|
base: { fontSize: 'var(--text-md)' },
|
|
@@ -491,11 +523,17 @@ export const tabs = {
|
|
|
491
523
|
targets: {
|
|
492
524
|
lynx: {
|
|
493
525
|
parts: {
|
|
494
|
-
//
|
|
495
|
-
//
|
|
526
|
+
// No hover on a touch platform: the held tab takes the ink
|
|
527
|
+
// daisy's hover gives it.
|
|
528
|
+
tab: { states: { pressed: { color: 'var(--color-base-content)' }, ...lynxFocus() } },
|
|
529
|
+
// The list is the indicator's containing block.
|
|
530
|
+
list: { base: { position: 'relative' } },
|
|
531
|
+
// The underline's ink (read by the `border` flavor below) —
|
|
532
|
+
// base-content by default, exactly what the shared
|
|
533
|
+
// `states.active.color` paints on the tab. One class, so any
|
|
496
534
|
// color-axis definition below (host + two classes, baked per
|
|
497
535
|
// theme) wins on specificity.
|
|
498
|
-
|
|
536
|
+
indicator: { base: { '--tab-active-ink': 'var(--color-base-content)' } },
|
|
499
537
|
},
|
|
500
538
|
variants: {
|
|
501
539
|
// `currentColor` never resolves on lynx (measured on device
|
|
@@ -505,18 +543,28 @@ export const tabs = {
|
|
|
505
543
|
// theme colors through color-mix(), which the emitter bakes
|
|
506
544
|
// once per theme — the same per-color/per-theme literal the
|
|
507
545
|
// active `color:` rules already land as.
|
|
508
|
-
color: Object.fromEntries(ROLES.map((c) => [c, {
|
|
546
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { indicator: { base: { '--tab-active-ink': roleInk(c) } } }])),
|
|
509
547
|
variant: {
|
|
510
548
|
// The underline is a ::before on the web, which lynx
|
|
511
|
-
// drops
|
|
512
|
-
//
|
|
513
|
-
// the
|
|
514
|
-
//
|
|
515
|
-
//
|
|
549
|
+
// drops. It is redrawn on the `indicator` part (zero#324),
|
|
550
|
+
// which lynx-zero renders in every list and positions
|
|
551
|
+
// over the active tab from measured geometry (the web's
|
|
552
|
+
// `--tabs-indicator-*` properties are web-only). The part
|
|
553
|
+
// IS the tab's box, so daisy's bar is its bottom edge:
|
|
554
|
+
// 3px, 80% wide and centred, like `left: 10%; width: 80%`
|
|
555
|
+
// on the web. The ink rides `--tab-active-ink` — a plain
|
|
556
|
+
// var() chain, device-proven — instead of `currentColor`,
|
|
557
|
+
// which lynx never resolves.
|
|
516
558
|
border: {
|
|
517
|
-
|
|
518
|
-
base: {
|
|
519
|
-
|
|
559
|
+
indicator: {
|
|
560
|
+
base: {
|
|
561
|
+
display: 'flex',
|
|
562
|
+
boxSizing: 'border-box',
|
|
563
|
+
borderBottomWidth: '3px',
|
|
564
|
+
borderBottomStyle: 'solid',
|
|
565
|
+
borderBottomColor: 'var(--tab-active-ink)',
|
|
566
|
+
transform: 'scaleX(0.8)',
|
|
567
|
+
},
|
|
520
568
|
},
|
|
521
569
|
},
|
|
522
570
|
// `min()` does not exist on lynx (signalxjs/lynx#1066) and
|
|
@@ -528,6 +576,14 @@ export const tabs = {
|
|
|
528
576
|
base: { '--tab-radius-limit': 'var(--radius-field)' },
|
|
529
577
|
},
|
|
530
578
|
},
|
|
579
|
+
// The box flavor's tabs sit on the list's base-200
|
|
580
|
+
// surface, so the ring's gap is that surface. Stated on
|
|
581
|
+
// the variant (one class more than the part rule) so it
|
|
582
|
+
// also outranks the active pill's `--shadow-sm`, which
|
|
583
|
+
// is a box-shadow too.
|
|
584
|
+
box: {
|
|
585
|
+
tab: { states: lynxFocus({ gap: 'var(--color-base-200)' }) },
|
|
586
|
+
},
|
|
531
587
|
},
|
|
532
588
|
},
|
|
533
589
|
},
|
|
@@ -864,6 +920,10 @@ export const switchRecipe = {
|
|
|
864
920
|
// No currentColor on this target — the ink is named
|
|
865
921
|
// directly, and `checked` moves border + fill below.
|
|
866
922
|
border: 'var(--border) solid var(--switch-ink)',
|
|
923
|
+
// daisy's 1px inset depth line along the track's top
|
|
924
|
+
// edge — `currentColor` at `--depth` × 10% on the
|
|
925
|
+
// web, the theme's own depth shade here.
|
|
926
|
+
boxShadow: '0 1px var(--depth-shade) inset',
|
|
867
927
|
userSelect: 'none',
|
|
868
928
|
transition: 'border-color var(--duration-slow) var(--ease-standard), '
|
|
869
929
|
+ 'background-color var(--duration-normal) var(--ease-standard)',
|
|
@@ -873,6 +933,9 @@ export const switchRecipe = {
|
|
|
873
933
|
borderColor: 'var(--switch-accent)',
|
|
874
934
|
backgroundColor: 'var(--color-base-100)',
|
|
875
935
|
},
|
|
936
|
+
// The pill ring (signalxjs/lynx#1163), keeping the
|
|
937
|
+
// track's inset depth line under it.
|
|
938
|
+
'focus-visible': lynxFocusRing('var(--switch-accent)', { under: '0 1px var(--depth-shade) inset' }),
|
|
876
939
|
},
|
|
877
940
|
},
|
|
878
941
|
thumb: {
|
|
@@ -884,6 +947,12 @@ export const switchRecipe = {
|
|
|
884
947
|
height: 'calc(var(--switch-size) - (var(--border) + var(--switch-p)) * 2)',
|
|
885
948
|
borderRadius: 'var(--radius-selector)',
|
|
886
949
|
backgroundColor: 'var(--switch-ink)',
|
|
950
|
+
// daisy's knob relief (`.toggle:before`): a shade
|
|
951
|
+
// along the bottom edge, a sheen across the top, and
|
|
952
|
+
// a 1px drop — the web's `currentColor` drop spelled
|
|
953
|
+
// as the theme's depth shade. The noise layer has no
|
|
954
|
+
// lynx spelling and stays web-only.
|
|
955
|
+
boxShadow: '0 -1px var(--depth-shade) inset, 0 8px 0 -4px var(--depth-sheen) inset, 0 1px var(--depth-shade)',
|
|
887
956
|
transition: 'transform var(--duration-normal) var(--ease-standard), '
|
|
888
957
|
+ 'background-color var(--duration-instant) var(--ease-standard)',
|
|
889
958
|
},
|
|
@@ -983,6 +1052,36 @@ const lynxBtnPad = (mult) => ({
|
|
|
983
1052
|
paddingLeft: `calc(var(--size-field) * ${mult})`,
|
|
984
1053
|
paddingRight: `calc(var(--size-field) * ${mult})`,
|
|
985
1054
|
});
|
|
1055
|
+
/**
|
|
1056
|
+
* A four-side property spelled as its physical longhands, for `targets.lynx`
|
|
1057
|
+
* sections that must not ship a `var()`-bearing shorthand.
|
|
1058
|
+
*
|
|
1059
|
+
* Lynx keeps a declaration holding `var()` under its own property id,
|
|
1060
|
+
* unexpanded, and expands it only after the cascade has merged every rule
|
|
1061
|
+
* into one insertion-ordered style map — in that map's order, writing over
|
|
1062
|
+
* the longhands already there (`CSSVariableHandler::HandleCSSVariables`). A
|
|
1063
|
+
* static shorthand, by contrast, is expanded into its longhands at parse
|
|
1064
|
+
* time. So a var-bearing `border-color` and a static `border-top-color` never
|
|
1065
|
+
* meet in the cascade at all: whichever key the map saw later wins, whatever
|
|
1066
|
+
* the specificity (signalxjs/lynx#1161, #1162). Longhands share one key per
|
|
1067
|
+
* side, and the cascade decides again.
|
|
1068
|
+
*/
|
|
1069
|
+
const lynxSides = (prop, value, sides = ['Top', 'Right', 'Bottom', 'Left']) => Object.fromEntries(sides.map((side) => [prop.replace('{}', side), value]));
|
|
1070
|
+
/**
|
|
1071
|
+
* daisy's `.btn` is content-sized and never squeezed: `display: inline-flex`
|
|
1072
|
+
* with `flex-shrink: 0; flex-wrap: nowrap`, so a narrow row makes the button
|
|
1073
|
+
* overflow, never break its label. A lynx view has no inline formatting
|
|
1074
|
+
* context: it stretches to a linear or column parent, and a squeezed label
|
|
1075
|
+
* `<text>` then wraps per character ("Bt / n", signalxjs/lynx#1165).
|
|
1076
|
+
* `width: max-content` is the intrinsic keyword starlight implements (an
|
|
1077
|
+
* intrinsic width is also exempt from the cross-axis stretch), so the box
|
|
1078
|
+
* sizes to its content as the web's does; the other two are daisy's own.
|
|
1079
|
+
*/
|
|
1080
|
+
const lynxBtnFit = {
|
|
1081
|
+
width: 'max-content',
|
|
1082
|
+
flexShrink: '0',
|
|
1083
|
+
flexWrap: 'nowrap',
|
|
1084
|
+
};
|
|
986
1085
|
/** The btn size ramp's physical inline padding, for `targets.lynx.variants`. */
|
|
987
1086
|
const lynxBtnSizes = (part) => ({
|
|
988
1087
|
xs: { [part]: { base: lynxBtnPad(2) } },
|
|
@@ -990,6 +1089,21 @@ const lynxBtnSizes = (part) => ({
|
|
|
990
1089
|
lg: { [part]: { base: lynxBtnPad(5) } },
|
|
991
1090
|
xl: { [part]: { base: lynxBtnPad(6) } },
|
|
992
1091
|
});
|
|
1092
|
+
/**
|
|
1093
|
+
* The press rendering of the overlay btns (dialog, popover), for their
|
|
1094
|
+
* `targets.lynx` sections. The web answers a pointer with `:hover`, which
|
|
1095
|
+
* the lynx emitter drops (a touch platform), and these parts' `pressed`
|
|
1096
|
+
* flag — stamped by the lynx runtime on every touch — had no rule, so a
|
|
1097
|
+
* tap showed nothing (signalxjs/lynx#1146). daisy's `:hover` wash plus the
|
|
1098
|
+
* btn's 1px sink, the same pair `button` paints for its own `pressed`. A
|
|
1099
|
+
* colour variant restates the background after the states at equal
|
|
1100
|
+
* specificity, so a coloured trigger keeps its fill and sinks.
|
|
1101
|
+
*/
|
|
1102
|
+
const lynxBtnPressed = {
|
|
1103
|
+
background: 'var(--color-base-300)',
|
|
1104
|
+
transform: 'translateY(1px)',
|
|
1105
|
+
boxShadow: 'none',
|
|
1106
|
+
};
|
|
993
1107
|
export const dialog = {
|
|
994
1108
|
component: 'dialog',
|
|
995
1109
|
// Public to a design system derived from this one (#73).
|
|
@@ -1081,14 +1195,17 @@ export const dialog = {
|
|
|
1081
1195
|
// reach and the trigger is the whole story here.
|
|
1082
1196
|
variants: { color: btnColors(), size: btnSizes },
|
|
1083
1197
|
// The btn paddings and the footer's push-down, restated physically —
|
|
1084
|
-
// see `lynxBtnPad` for the #1084 verdict this answers
|
|
1198
|
+
// see `lynxBtnPad` for the #1084 verdict this answers — and the btns'
|
|
1199
|
+
// press rendering (`lynxBtnPressed`). The footer states its row: on lynx
|
|
1200
|
+
// a row container says so next to `display: flex`.
|
|
1085
1201
|
targets: {
|
|
1086
1202
|
lynx: {
|
|
1087
1203
|
parts: {
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1204
|
+
// Content-sized like daisy's btn, never broken mid-word (signalxjs/lynx#1165).
|
|
1205
|
+
trigger: { base: { ...lynxBtnPad(4), ...lynxBtnFit }, states: { pressed: lynxBtnPressed, ...lynxFocus() } },
|
|
1206
|
+
close: { base: lynxBtnPad(4), states: { pressed: lynxBtnPressed, ...lynxFocus() } },
|
|
1207
|
+
cancel: { base: lynxBtnPad(4), states: { pressed: lynxBtnPressed, ...lynxFocus() } },
|
|
1208
|
+
footer: { base: { marginTop: 'var(--space-2xl)', flexDirection: 'row' } },
|
|
1092
1209
|
},
|
|
1093
1210
|
variants: { size: lynxBtnSizes('trigger') },
|
|
1094
1211
|
},
|
|
@@ -1102,6 +1219,21 @@ const floatingPanel = {
|
|
|
1102
1219
|
borderRadius: 'var(--radius-box)',
|
|
1103
1220
|
boxShadow: 'var(--shadow-lg)',
|
|
1104
1221
|
};
|
|
1222
|
+
/**
|
|
1223
|
+
* The arrow a floating surface grows toward its anchor (#279), in the
|
|
1224
|
+
* surface's own paint: the dropdown's base-100 and border for popover and
|
|
1225
|
+
* menu, the tooltip's borderless neutral for the tooltip — daisy's
|
|
1226
|
+
* `tooltip` draws exactly that tail under its bubble. Web-only (placed by
|
|
1227
|
+
* the runtime's `--arrow-x`/`--arrow-y`), so it rides `targets.web` with
|
|
1228
|
+
* the popup's permission to let it out.
|
|
1229
|
+
*/
|
|
1230
|
+
const floatingArrow = (scope, paint, size = '0.625rem') => ({
|
|
1231
|
+
parts: {
|
|
1232
|
+
popup: { selectors: popupArrowHost(scope) },
|
|
1233
|
+
arrow: popupArrow(scope, { size, paint }),
|
|
1234
|
+
},
|
|
1235
|
+
});
|
|
1236
|
+
const panelArrowPaint = { background: 'var(--color-base-100)', border: 'var(--border) solid var(--color-base-300)' };
|
|
1105
1237
|
/**
|
|
1106
1238
|
* daisy's dropdown menu for a listbox, sized by the geometry the
|
|
1107
1239
|
* anchored-position strategy publishes (#278): at least the width of the
|
|
@@ -1138,6 +1270,14 @@ export const popover = {
|
|
|
1138
1270
|
title: {
|
|
1139
1271
|
base: { margin: '0 0 var(--space-md)', fontSize: 'var(--text-md)', fontWeight: 'var(--weight-bold)' },
|
|
1140
1272
|
},
|
|
1273
|
+
// The modal's muted body line, at the dropdown's tighter rhythm.
|
|
1274
|
+
description: {
|
|
1275
|
+
base: {
|
|
1276
|
+
margin: '0 0 var(--space-lg)',
|
|
1277
|
+
fontSize: 'var(--text-sm)',
|
|
1278
|
+
color: 'color-mix(in oklab, var(--color-base-content) 75%, transparent)',
|
|
1279
|
+
},
|
|
1280
|
+
},
|
|
1141
1281
|
close: {
|
|
1142
1282
|
base: { ...btn, height: 'calc(var(--size-field) * 8)', paddingInline: 'calc(var(--size-field) * 3)', fontSize: 'var(--text-xs)' },
|
|
1143
1283
|
states: {
|
|
@@ -1149,12 +1289,15 @@ export const popover = {
|
|
|
1149
1289
|
},
|
|
1150
1290
|
// Trigger-carried axes — same wiring as dialog, same reason.
|
|
1151
1291
|
variants: { color: btnColors(), size: btnSizes },
|
|
1152
|
-
// The btn paddings, restated physically — see `lynxBtnPad` (#1084)
|
|
1292
|
+
// The btn paddings, restated physically — see `lynxBtnPad` (#1084) —
|
|
1293
|
+
// and the btns' press rendering (`lynxBtnPressed`).
|
|
1153
1294
|
targets: {
|
|
1295
|
+
web: floatingArrow('popover', panelArrowPaint),
|
|
1154
1296
|
lynx: {
|
|
1155
1297
|
parts: {
|
|
1156
|
-
|
|
1157
|
-
|
|
1298
|
+
// Content-sized like daisy's btn, never broken mid-word (signalxjs/lynx#1165).
|
|
1299
|
+
trigger: { base: { ...lynxBtnPad(4), ...lynxBtnFit }, states: { pressed: lynxBtnPressed, ...lynxFocus() } },
|
|
1300
|
+
close: { base: lynxBtnPad(3), states: { pressed: lynxBtnPressed, ...lynxFocus() } },
|
|
1158
1301
|
},
|
|
1159
1302
|
variants: { size: lynxBtnSizes('trigger') },
|
|
1160
1303
|
},
|
|
@@ -1196,12 +1339,63 @@ export const tooltip = {
|
|
|
1196
1339
|
variants: { color: btnColors(), size: btnSizes },
|
|
1197
1340
|
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
1198
1341
|
targets: {
|
|
1342
|
+
web: floatingArrow('tooltip', { background: 'var(--color-neutral)' }, '0.5rem'),
|
|
1199
1343
|
lynx: {
|
|
1200
1344
|
parts: { trigger: { base: lynxBtnPad(4) } },
|
|
1201
1345
|
variants: { size: lynxBtnSizes('trigger') },
|
|
1202
1346
|
},
|
|
1203
1347
|
},
|
|
1204
1348
|
};
|
|
1349
|
+
/**
|
|
1350
|
+
* Hover card — daisy's `link` opening a `dropdown-content` card. The trigger
|
|
1351
|
+
* is running text, not a btn: underlined in the ink it sits in (daisy's bare
|
|
1352
|
+
* `link`), and a colour is `link-<role>` in the role's readable ink
|
|
1353
|
+
* (`roleInk`). The underline thickens on hover and while the card is up,
|
|
1354
|
+
* rather than the ink dimming — the `btn-link` reasoning. The card is the
|
|
1355
|
+
* floating panel popover and menu wear.
|
|
1356
|
+
*/
|
|
1357
|
+
export const hoverCard = {
|
|
1358
|
+
component: 'hover-card',
|
|
1359
|
+
parts: {
|
|
1360
|
+
trigger: {
|
|
1361
|
+
base: {
|
|
1362
|
+
color: 'inherit',
|
|
1363
|
+
textDecorationLine: 'underline',
|
|
1364
|
+
textUnderlineOffset: '2px',
|
|
1365
|
+
cursor: 'pointer',
|
|
1366
|
+
borderRadius: 'var(--radius-selector)',
|
|
1367
|
+
},
|
|
1368
|
+
states: {
|
|
1369
|
+
hover: { textDecorationThickness: '2px' },
|
|
1370
|
+
open: { textDecorationThickness: '2px' },
|
|
1371
|
+
closed: {},
|
|
1372
|
+
...focusRing,
|
|
1373
|
+
},
|
|
1374
|
+
},
|
|
1375
|
+
popup: withPresence(popupPresence('translateY(-4px)'), {
|
|
1376
|
+
base: {
|
|
1377
|
+
...floatingPanel,
|
|
1378
|
+
padding: 'var(--space-xl)',
|
|
1379
|
+
maxWidth: '20rem',
|
|
1380
|
+
fontSize: 'var(--text-sm)',
|
|
1381
|
+
},
|
|
1382
|
+
states: { open: {}, closed: {} },
|
|
1383
|
+
}),
|
|
1384
|
+
},
|
|
1385
|
+
variants: {
|
|
1386
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { trigger: { base: { color: roleInk(c) } } }])),
|
|
1387
|
+
size: {
|
|
1388
|
+
xs: { trigger: { base: { fontSize: 'var(--text-xs)' } } },
|
|
1389
|
+
sm: { trigger: { base: { fontSize: 'var(--text-sm)' } } },
|
|
1390
|
+
md: {},
|
|
1391
|
+
lg: { trigger: { base: { fontSize: 'var(--text-lg)' } } },
|
|
1392
|
+
xl: { trigger: { base: { fontSize: 'var(--text-xl)' } } },
|
|
1393
|
+
},
|
|
1394
|
+
},
|
|
1395
|
+
// Web-only: the popover top layer and the runtime's arrow geometry have
|
|
1396
|
+
// no lynx counterpart.
|
|
1397
|
+
targets: { web: floatingArrow('hover-card', panelArrowPaint) },
|
|
1398
|
+
};
|
|
1205
1399
|
// daisy "menu in a dropdown" look.
|
|
1206
1400
|
export const menu = {
|
|
1207
1401
|
component: 'menu',
|
|
@@ -1335,6 +1529,17 @@ export const menu = {
|
|
|
1335
1529
|
opacity: '0.6',
|
|
1336
1530
|
},
|
|
1337
1531
|
},
|
|
1532
|
+
// daisy's menu hint: the group label's faded ink, pushed to the
|
|
1533
|
+
// row's reading end — faded less, since 0.6 leaves nord's paper
|
|
1534
|
+
// under the 4.5:1 a line read by eye owes.
|
|
1535
|
+
shortcut: {
|
|
1536
|
+
base: {
|
|
1537
|
+
marginInlineStart: 'auto',
|
|
1538
|
+
paddingInlineStart: 'var(--space-lg)',
|
|
1539
|
+
fontSize: 'var(--text-xs)',
|
|
1540
|
+
opacity: '0.75',
|
|
1541
|
+
},
|
|
1542
|
+
},
|
|
1338
1543
|
separator: {
|
|
1339
1544
|
base: {
|
|
1340
1545
|
height: 'var(--border)',
|
|
@@ -1348,12 +1553,55 @@ export const menu = {
|
|
|
1348
1553
|
variants: { color: btnColors(), size: btnSizes },
|
|
1349
1554
|
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
1350
1555
|
targets: {
|
|
1556
|
+
web: floatingArrow('menu', panelArrowPaint),
|
|
1351
1557
|
lynx: {
|
|
1352
|
-
parts: {
|
|
1558
|
+
parts: {
|
|
1559
|
+
trigger: { base: lynxBtnPad(4) },
|
|
1560
|
+
// The shortcut's reading-end push, restated physically (#1084).
|
|
1561
|
+
shortcut: { base: { marginLeft: 'auto', paddingLeft: 'var(--space-lg)' } },
|
|
1562
|
+
},
|
|
1353
1563
|
variants: { size: lynxBtnSizes('trigger') },
|
|
1354
1564
|
},
|
|
1355
1565
|
},
|
|
1356
1566
|
};
|
|
1567
|
+
// The menubar row: daisy's `menu-horizontal` strip — a base-200 box the
|
|
1568
|
+
// menu triggers sit in. The triggers are ordinary `menu` triggers.
|
|
1569
|
+
export const menubar = {
|
|
1570
|
+
component: 'menubar',
|
|
1571
|
+
tokens: { '--menubar-surface': 'var(--color-base-200)' },
|
|
1572
|
+
parts: {
|
|
1573
|
+
root: {
|
|
1574
|
+
base: {
|
|
1575
|
+
display: 'flex',
|
|
1576
|
+
alignItems: 'center',
|
|
1577
|
+
gap: 'var(--space-xs)',
|
|
1578
|
+
padding: 'var(--space-xs)',
|
|
1579
|
+
width: 'max-content',
|
|
1580
|
+
maxWidth: '100%',
|
|
1581
|
+
flexWrap: 'wrap',
|
|
1582
|
+
background: 'var(--menubar-surface)',
|
|
1583
|
+
borderRadius: 'var(--radius-box)',
|
|
1584
|
+
},
|
|
1585
|
+
// The triggers fade themselves — see basic.
|
|
1586
|
+
states: { disabled: {} },
|
|
1587
|
+
selectors: {
|
|
1588
|
+
'&[data-orientation="vertical"]': { flexDirection: 'column', alignItems: 'stretch' },
|
|
1589
|
+
},
|
|
1590
|
+
},
|
|
1591
|
+
},
|
|
1592
|
+
variants: {
|
|
1593
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
1594
|
+
'--menubar-surface': `color-mix(in oklch, var(--color-${c}) 12%, var(--color-base-200))`,
|
|
1595
|
+
} } }])),
|
|
1596
|
+
size: {
|
|
1597
|
+
xs: { root: { base: { gap: '0', padding: '0' } } },
|
|
1598
|
+
sm: { root: { base: { padding: 'var(--space-2xs)' } } },
|
|
1599
|
+
md: {},
|
|
1600
|
+
lg: { root: { base: { gap: 'var(--space-sm)', padding: 'var(--space-sm)' } } },
|
|
1601
|
+
xl: { root: { base: { gap: 'var(--space-md)', padding: 'var(--space-sm)' } } },
|
|
1602
|
+
},
|
|
1603
|
+
},
|
|
1604
|
+
};
|
|
1357
1605
|
export const field = {
|
|
1358
1606
|
component: 'field',
|
|
1359
1607
|
// The label's accent ink — base-content by default, so the un-attributed
|
|
@@ -1403,6 +1651,60 @@ export const field = {
|
|
|
1403
1651
|
},
|
|
1404
1652
|
},
|
|
1405
1653
|
};
|
|
1654
|
+
/**
|
|
1655
|
+
* Fieldset (#285), daisyUI 5's `.fieldset` / `.fieldset-legend`: a
|
|
1656
|
+
* one-column grid with a tight row gap and a little block padding, no frame
|
|
1657
|
+
* of its own (daisy draws one only when the app adds `border`), and a
|
|
1658
|
+
* semibold legend with block padding standing in for its margin. The UA
|
|
1659
|
+
* `min-inline-size: min-content` is undone so a wide control cannot push the
|
|
1660
|
+
* group past its column. Colour accents the legend through `roleInk`, the
|
|
1661
|
+
* Field way.
|
|
1662
|
+
*/
|
|
1663
|
+
export const fieldset = {
|
|
1664
|
+
component: 'fieldset',
|
|
1665
|
+
tokens: { '--fieldset-accent': 'var(--color-base-content)' },
|
|
1666
|
+
parts: {
|
|
1667
|
+
root: {
|
|
1668
|
+
base: {
|
|
1669
|
+
display: 'grid',
|
|
1670
|
+
gridTemplateColumns: '1fr',
|
|
1671
|
+
gridAutoRows: 'max-content',
|
|
1672
|
+
gap: 'var(--space-sm)',
|
|
1673
|
+
minInlineSize: '0',
|
|
1674
|
+
margin: '0',
|
|
1675
|
+
paddingBlock: 'var(--space-xs)',
|
|
1676
|
+
paddingInline: '0',
|
|
1677
|
+
border: 'none',
|
|
1678
|
+
},
|
|
1679
|
+
},
|
|
1680
|
+
legend: {
|
|
1681
|
+
base: {
|
|
1682
|
+
paddingBlock: 'var(--space-sm)',
|
|
1683
|
+
paddingInline: '0',
|
|
1684
|
+
fontSize: 'var(--text-sm)',
|
|
1685
|
+
fontWeight: 'var(--weight-semibold)',
|
|
1686
|
+
color: 'var(--fieldset-accent)',
|
|
1687
|
+
},
|
|
1688
|
+
states: {
|
|
1689
|
+
disabled: { opacity: 'var(--disabled-opacity)' },
|
|
1690
|
+
invalid: { color: roleInk('error') },
|
|
1691
|
+
},
|
|
1692
|
+
},
|
|
1693
|
+
},
|
|
1694
|
+
variants: {
|
|
1695
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
1696
|
+
'--fieldset-accent': roleInk(c),
|
|
1697
|
+
} } }])),
|
|
1698
|
+
size: {
|
|
1699
|
+
xs: { legend: { base: { fontSize: 'var(--text-xs)' } } },
|
|
1700
|
+
sm: { legend: { base: { fontSize: 'var(--text-xs)' } } },
|
|
1701
|
+
// `md` is the un-attributed render.
|
|
1702
|
+
md: {},
|
|
1703
|
+
lg: { legend: { base: { fontSize: 'var(--text-md)' } } },
|
|
1704
|
+
xl: { legend: { base: { fontSize: 'var(--text-lg)' } } },
|
|
1705
|
+
},
|
|
1706
|
+
},
|
|
1707
|
+
};
|
|
1406
1708
|
/**
|
|
1407
1709
|
* daisy's tick, drawn by a clip-path that GROWS.
|
|
1408
1710
|
*
|
|
@@ -1900,9 +2202,13 @@ export const progress = {
|
|
|
1900
2202
|
transition: 'width var(--duration-slow) var(--ease-standard)',
|
|
1901
2203
|
},
|
|
1902
2204
|
states: {
|
|
1903
|
-
// `complete`
|
|
1904
|
-
//
|
|
1905
|
-
|
|
2205
|
+
// `complete` keeps the colour axis. daisyUI 5 has no complete
|
|
2206
|
+
// tint — a full `progress-primary` stays primary — and a
|
|
2207
|
+
// `color="secondary"` bar that turns green at 100% reads as a
|
|
2208
|
+
// bug, not as meaning (signalxjs/lynx#1144). A caller who wants
|
|
2209
|
+
// "done" to look like success says so: `color="success"`.
|
|
2210
|
+
// Declared equal to `loading` in `sameAs` below.
|
|
2211
|
+
complete: {},
|
|
1906
2212
|
loading: {},
|
|
1907
2213
|
indeterminate: { width: '40%', animation: 'zero-daisy-indeterminate 1.2s ease-in-out infinite' },
|
|
1908
2214
|
},
|
|
@@ -1938,6 +2244,8 @@ export const progress = {
|
|
|
1938
2244
|
// so the manifest states the default for runtimes with no `:not()`
|
|
1939
2245
|
// fallback (the lynx target; signalxjs/lynx#1070).
|
|
1940
2246
|
defaultVariants: { color: 'primary', size: 'md' },
|
|
2247
|
+
// A finished bar is a full bar of the same ink (see `complete` above).
|
|
2248
|
+
sameAs: { root: { complete: 'loading' }, range: { complete: 'loading' } },
|
|
1941
2249
|
keyframes: {
|
|
1942
2250
|
// Logical, so the sweep runs the way the bar fills — `margin-inline-start`
|
|
1943
2251
|
// is animatable and direction-aware on its own. The determinate `width`,
|
|
@@ -1958,6 +2266,8 @@ export const progress = {
|
|
|
1958
2266
|
},
|
|
1959
2267
|
},
|
|
1960
2268
|
};
|
|
2269
|
+
/** The slider's deepened fill — the web's `--slider-fill` mix, for a given accent. */
|
|
2270
|
+
const sliderFill = (accent) => `color-mix(in oklab, ${accent} 90%, var(--color-base-content))`;
|
|
1961
2271
|
export const slider = {
|
|
1962
2272
|
component: 'slider',
|
|
1963
2273
|
// daisy's `.range` metrics: the thumb is `--range-thumb-size`
|
|
@@ -2266,29 +2576,100 @@ export const slider = {
|
|
|
2266
2576
|
},
|
|
2267
2577
|
},
|
|
2268
2578
|
lynx: {
|
|
2579
|
+
// The web's deepened fill, theme-baked: `color-mix()` over theme
|
|
2580
|
+
// colours cannot resolve on device, but the emitter restates a
|
|
2581
|
+
// colour function per theme as literals (as `progress` does for
|
|
2582
|
+
// its accent), so each colour step gets the same 90/10 mix the
|
|
2583
|
+
// web track and knob ring wear. Bound on the root like
|
|
2584
|
+
// `--slider-accent`, rebound by `color` below and by `invalid`
|
|
2585
|
+
// on the control (which wraps the rail on lynx).
|
|
2586
|
+
tokens: { '--slider-fill': sliderFill('var(--color-primary)') },
|
|
2587
|
+
variants: {
|
|
2588
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
2589
|
+
'--slider-fill': sliderFill(`var(--color-${c})`),
|
|
2590
|
+
} } }])),
|
|
2591
|
+
},
|
|
2269
2592
|
parts: {
|
|
2593
|
+
// Vertical (lynx#1144): the lynx runtime positions the moving
|
|
2594
|
+
// parts itself — `top` percentages, the knob centred by
|
|
2595
|
+
// `left: 50%` plus the base's `translateY(-50%)` and
|
|
2596
|
+
// negative `margin-left` — so the class rules only turn the
|
|
2597
|
+
// channel upright: the control is a thumb-wide column of
|
|
2598
|
+
// daisy's rail length, the track fills it.
|
|
2599
|
+
root: {
|
|
2600
|
+
selectors: {
|
|
2601
|
+
'&[data-orientation="vertical"]': { width: 'auto', alignItems: 'center' },
|
|
2602
|
+
},
|
|
2603
|
+
},
|
|
2604
|
+
control: {
|
|
2605
|
+
states: {
|
|
2606
|
+
invalid: { '--slider-fill': sliderFill('var(--color-error)') },
|
|
2607
|
+
// The control's ring is the NATIVE input's on the web;
|
|
2608
|
+
// lynx always renders the composed parts, where the
|
|
2609
|
+
// thumb is what takes focus and wears the ring below
|
|
2610
|
+
// (signalxjs/lynx#1163 — both at once read as two
|
|
2611
|
+
// nested boxes).
|
|
2612
|
+
'focus-visible': { outline: 'none' },
|
|
2613
|
+
},
|
|
2614
|
+
selectors: {
|
|
2615
|
+
'&[data-orientation="vertical"]': {
|
|
2616
|
+
display: 'flex',
|
|
2617
|
+
flexDirection: 'row',
|
|
2618
|
+
justifyContent: 'center',
|
|
2619
|
+
width: 'var(--slider-thumb-size)',
|
|
2620
|
+
height: 'calc(var(--size-selector) * 40)',
|
|
2621
|
+
},
|
|
2622
|
+
},
|
|
2623
|
+
},
|
|
2270
2624
|
track: {
|
|
2271
2625
|
// The web track's `margin-block` breathing room,
|
|
2272
2626
|
// restated physically: logical spellings resolve on iOS
|
|
2273
2627
|
// but not on Android (measured, signalxjs/lynx#1084), so
|
|
2274
2628
|
// the emitter refuses them. Physical is the lynx
|
|
2275
2629
|
// target's norm — no RTL flow there.
|
|
2630
|
+
//
|
|
2631
|
+
// daisy's channel radius is the selector radius (the
|
|
2632
|
+
// native control's runnable-track), not a pill.
|
|
2276
2633
|
base: {
|
|
2277
2634
|
marginTop: 'calc((var(--slider-thumb-size) - var(--slider-track-size)) / 2)',
|
|
2278
2635
|
marginBottom: 'calc((var(--slider-thumb-size) - var(--slider-track-size)) / 2)',
|
|
2636
|
+
borderRadius: 'var(--radius-selector)',
|
|
2637
|
+
},
|
|
2638
|
+
selectors: {
|
|
2639
|
+
'&[data-orientation="vertical"]': {
|
|
2640
|
+
width: 'var(--slider-track-size)',
|
|
2641
|
+
height: '100%',
|
|
2642
|
+
marginTop: '0',
|
|
2643
|
+
marginBottom: '0',
|
|
2644
|
+
},
|
|
2279
2645
|
},
|
|
2280
2646
|
},
|
|
2281
2647
|
mark: {
|
|
2282
|
-
//
|
|
2283
|
-
//
|
|
2284
|
-
|
|
2648
|
+
// The web draws the tick with `::before`, which lynx has
|
|
2649
|
+
// no projection for — the mark was an empty, invisible
|
|
2650
|
+
// box. Here the mark IS the tick: a 2px rule across the
|
|
2651
|
+
// channel, centred on its runtime-set `left` percent.
|
|
2652
|
+
// (Lynx marks carry no label text.)
|
|
2653
|
+
base: {
|
|
2654
|
+
top: '0',
|
|
2655
|
+
width: '2px',
|
|
2656
|
+
height: 'var(--slider-track-size)',
|
|
2657
|
+
marginLeft: '-1px',
|
|
2658
|
+
paddingTop: '0',
|
|
2659
|
+
backgroundColor: 'var(--color-base-content)',
|
|
2660
|
+
},
|
|
2661
|
+
selectors: {
|
|
2662
|
+
'&[data-orientation="vertical"]': {
|
|
2663
|
+
left: '0',
|
|
2664
|
+
width: 'var(--slider-track-size)',
|
|
2665
|
+
height: '2px',
|
|
2666
|
+
marginLeft: '0',
|
|
2667
|
+
marginTop: '-1px',
|
|
2668
|
+
},
|
|
2669
|
+
},
|
|
2285
2670
|
},
|
|
2286
2671
|
range: {
|
|
2287
|
-
|
|
2288
|
-
// base-content (`progressFill`'s recipe); the plain
|
|
2289
|
-
// accent — a var() chain, proven working on device — is
|
|
2290
|
-
// the closest lynx-expressible equivalent.
|
|
2291
|
-
base: { background: 'var(--slider-accent)' },
|
|
2672
|
+
base: { background: 'var(--slider-fill)', borderRadius: 'var(--radius-selector)' },
|
|
2292
2673
|
},
|
|
2293
2674
|
thumb: {
|
|
2294
2675
|
// daisy's real range thumb, ported from daisyUI 5
|
|
@@ -2299,13 +2680,16 @@ export const slider = {
|
|
|
2299
2680
|
// `--range-thumb` defaults to `var(--color-base-100)`,
|
|
2300
2681
|
// `--range-p` is daisy's literal `.25rem` (deliberately
|
|
2301
2682
|
// not the theme's size token), and the border ink is the
|
|
2302
|
-
//
|
|
2303
|
-
//
|
|
2304
|
-
//
|
|
2305
|
-
//
|
|
2306
|
-
//
|
|
2307
|
-
//
|
|
2308
|
-
//
|
|
2683
|
+
// deepened fill the web's native thumb ring reads. daisy
|
|
2684
|
+
// paints the progress fill itself as a 100cqw box-shadow
|
|
2685
|
+
// thrown off this thumb — a native-input trick with no
|
|
2686
|
+
// lynx equivalent; the composed `.zx-slider__range` box
|
|
2687
|
+
// above IS that fill here, so the knob only needs its own
|
|
2688
|
+
// paints. Its radius is daisy's formula minus the min()
|
|
2689
|
+
// clamp lynx cannot evaluate — exact for every shipped
|
|
2690
|
+
// theme (all set `--radius-selector` ≥ .25rem / 3) — and
|
|
2691
|
+
// it wears daisy's depth shading (`currentColor`'s 10%
|
|
2692
|
+
// outer shadow spelled as the theme's shade).
|
|
2309
2693
|
//
|
|
2310
2694
|
// The centering geometry is restated physically beside
|
|
2311
2695
|
// the paint: `inset-block-start`/`translate`/
|
|
@@ -2316,11 +2700,20 @@ export const slider = {
|
|
|
2316
2700
|
// proven on both platforms.
|
|
2317
2701
|
base: {
|
|
2318
2702
|
background: 'var(--color-base-100)',
|
|
2319
|
-
border: '0.25rem solid var(--slider-
|
|
2703
|
+
border: '0.25rem solid var(--slider-fill)',
|
|
2704
|
+
borderRadius: 'calc(var(--radius-selector) + 0.25rem)',
|
|
2705
|
+
boxShadow: '0 -1px var(--depth-shade) inset, 0 8px 0 -4px var(--depth-sheen) inset, 0 1px var(--depth-shade)',
|
|
2320
2706
|
top: '50%',
|
|
2321
2707
|
transform: 'translateY(-50%)',
|
|
2322
2708
|
marginLeft: 'calc(var(--slider-thumb-size) / -2)',
|
|
2323
2709
|
},
|
|
2710
|
+
// The round ring (signalxjs/lynx#1163), over the knob's
|
|
2711
|
+
// depth relief.
|
|
2712
|
+
states: {
|
|
2713
|
+
'focus-visible': lynxFocusRing('var(--slider-accent)', {
|
|
2714
|
+
under: '0 -1px var(--depth-shade) inset, 0 8px 0 -4px var(--depth-sheen) inset, 0 1px var(--depth-shade)',
|
|
2715
|
+
}),
|
|
2716
|
+
},
|
|
2324
2717
|
},
|
|
2325
2718
|
},
|
|
2326
2719
|
},
|
|
@@ -2328,7 +2721,31 @@ export const slider = {
|
|
|
2328
2721
|
};
|
|
2329
2722
|
export const accordion = {
|
|
2330
2723
|
component: 'accordion',
|
|
2331
|
-
targets:
|
|
2724
|
+
targets: {
|
|
2725
|
+
...disclosureExit('accordion'),
|
|
2726
|
+
lynx: {
|
|
2727
|
+
parts: {
|
|
2728
|
+
// No hover on a touch platform: the held trigger takes daisy's
|
|
2729
|
+
// hover fill. The ::after chevron has no lynx counterpart (no
|
|
2730
|
+
// pseudo-elements, and the anatomy declares no part for it) —
|
|
2731
|
+
// an app draws its own glyph in the trigger.
|
|
2732
|
+
//
|
|
2733
|
+
// The item card clips (`overflow: hidden`) and the trigger
|
|
2734
|
+
// fills it, so an outside ring is clipped away entirely
|
|
2735
|
+
// (signalxjs/lynx#1164): the ring is drawn inset, rounded to
|
|
2736
|
+
// the card's inner corners.
|
|
2737
|
+
trigger: {
|
|
2738
|
+
states: {
|
|
2739
|
+
pressed: { background: 'var(--color-base-200)' },
|
|
2740
|
+
'focus-visible': {
|
|
2741
|
+
...lynxFocusRing('var(--color-base-content)', { inset: true }),
|
|
2742
|
+
borderRadius: 'calc(var(--radius-box) - var(--border))',
|
|
2743
|
+
},
|
|
2744
|
+
},
|
|
2745
|
+
},
|
|
2746
|
+
},
|
|
2747
|
+
},
|
|
2748
|
+
},
|
|
2332
2749
|
// Same accent grammar as collapsible: the open heading's ink, defaulting
|
|
2333
2750
|
// to base-content so the un-attributed render is unchanged.
|
|
2334
2751
|
tokens: { '--accordion-accent': 'var(--color-base-content)' },
|
|
@@ -2609,10 +3026,47 @@ export const select = {
|
|
|
2609
3026
|
web: { parts: { popup: { base: anchoredListbox } } },
|
|
2610
3027
|
lynx: {
|
|
2611
3028
|
parts: {
|
|
2612
|
-
|
|
2613
|
-
|
|
3029
|
+
// daisy's `.select` is `width: clamp(3rem, 20rem, 100%)`: 20rem,
|
|
3030
|
+
// never wider than its container. lynx has no `clamp()`, so the
|
|
3031
|
+
// same bounds are spelled out — and the base's 13rem floor
|
|
3032
|
+
// comes down to daisy's 3rem, or the trigger overflowed any
|
|
3033
|
+
// cell narrower than 208px (signalxjs/lynx#1146).
|
|
3034
|
+
//
|
|
3035
|
+
// The bounds sit on the ROOT, the box daisy's `.select` is,
|
|
3036
|
+
// and the trigger fills it. The web root is `inline-flex`, so
|
|
3037
|
+
// it shrink-wraps the trigger; lynx rewrites that to `flex`,
|
|
3038
|
+
// and a root stretched by its parent while the trigger stayed
|
|
3039
|
+
// capped left the clear-trigger — positioned against the root
|
|
3040
|
+
// — floating past the trigger's end (signalxjs/lynx#1166).
|
|
3041
|
+
root: { base: { width: '20rem', maxWidth: '100%', minWidth: '3rem' } },
|
|
3042
|
+
trigger: {
|
|
3043
|
+
base: { ...lynxBtnPad(4), width: '100%', maxWidth: '100%', minWidth: '3rem' },
|
|
3044
|
+
// The web's `:hover` border is dropped on a touch
|
|
3045
|
+
// platform; a press gets a wash instead — a background,
|
|
3046
|
+
// so it never fights the open/invalid border inks.
|
|
3047
|
+
states: { pressed: { background: 'var(--color-base-200)' }, ...lynxFocus() },
|
|
3048
|
+
},
|
|
3049
|
+
// The popup renders in the overlay outlet, outside the root,
|
|
3050
|
+
// so it cannot inherit the root's `--select-accent`, and an
|
|
3051
|
+
// unresolved `var()` on lynx has no fallback: the selected
|
|
3052
|
+
// item and its tick fell back to base-content
|
|
3053
|
+
// (signalxjs/lynx#1168). The popup restates the accent; its
|
|
3054
|
+
// colour-axis twins are in `variants.color` below.
|
|
3055
|
+
popup: { base: { '--select-accent': 'var(--color-primary)' } },
|
|
3056
|
+
// A touch list has no highlight: the pressed item takes the
|
|
3057
|
+
// highlighted item's wash.
|
|
3058
|
+
item: { states: { pressed: { background: 'var(--color-base-200)' } } },
|
|
3059
|
+
'clear-trigger': {
|
|
3060
|
+
base: { top: 'var(--space-xs)', bottom: 'var(--space-xs)', right: 'calc(var(--size-field) * 4 + 1em + var(--space-xs))', minWidth: '1.5rem' },
|
|
3061
|
+
states: lynxFocus(),
|
|
3062
|
+
},
|
|
2614
3063
|
},
|
|
2615
3064
|
variants: {
|
|
3065
|
+
// The popup's accent follows the root's colour axis — the
|
|
3066
|
+
// popup carries the axis class itself (lynx stamps every part).
|
|
3067
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { popup: { base: {
|
|
3068
|
+
'--select-accent': `var(--color-${c})`,
|
|
3069
|
+
} } }])),
|
|
2616
3070
|
size: { ...lynxBtnSizes('trigger'), md: { trigger: { base: lynxBtnPad(4) } } },
|
|
2617
3071
|
},
|
|
2618
3072
|
},
|
|
@@ -2633,6 +3087,38 @@ export const select = {
|
|
|
2633
3087
|
*/
|
|
2634
3088
|
const BTN_STEPS = { xs: 6, sm: 8, md: 10, lg: 12, xl: 14 };
|
|
2635
3089
|
const btnHeight = (step) => `calc(var(--size-field) * ${BTN_STEPS[step]})`;
|
|
3090
|
+
/**
|
|
3091
|
+
* The btn padding ramp — `[size, block space, inline space]` per step. One
|
|
3092
|
+
* table for the web's `padding` shorthand and the lynx section's longhands,
|
|
3093
|
+
* so the two spellings cannot drift apart.
|
|
3094
|
+
*/
|
|
3095
|
+
const BTN_PAD = [
|
|
3096
|
+
['xs', '2xs', 'xs'],
|
|
3097
|
+
['sm', 'xs', 'sm'],
|
|
3098
|
+
['md', 'sm', 'lg'],
|
|
3099
|
+
['lg', 'md', 'xl'],
|
|
3100
|
+
['xl', 'lg', '2xl'],
|
|
3101
|
+
];
|
|
3102
|
+
const btnPadding = (step) => {
|
|
3103
|
+
const [, block, inline] = BTN_PAD.find(([size]) => size === step);
|
|
3104
|
+
return `var(--space-${block}) var(--space-${inline})`;
|
|
3105
|
+
};
|
|
3106
|
+
/**
|
|
3107
|
+
* daisy 5's disabled btn, for the lynx target (signalxjs/lynx#1143). The web
|
|
3108
|
+
* fades the whole button (`opacity: var(--disabled-opacity)`, 0.3 on the
|
|
3109
|
+
* dark themes), which on device left a solid button's label barely legible.
|
|
3110
|
+
* daisy's own `.btn:disabled` paints instead: a base-content wash (not on
|
|
3111
|
+
* ghost/link), a transparent border, no shadow, and a muted base-content
|
|
3112
|
+
* label. daisy mutes that label to 20%; this keeps it at 50%, still plainly
|
|
3113
|
+
* inert but readable on a phone at arm's length.
|
|
3114
|
+
*/
|
|
3115
|
+
const lynxBtnDisabled = {
|
|
3116
|
+
opacity: '1',
|
|
3117
|
+
background: 'color-mix(in oklab, var(--color-base-content) 10%, transparent)',
|
|
3118
|
+
color: 'color-mix(in oklab, var(--color-base-content) 50%, transparent)',
|
|
3119
|
+
borderColor: 'transparent',
|
|
3120
|
+
boxShadow: 'none',
|
|
3121
|
+
};
|
|
2636
3122
|
export const button = {
|
|
2637
3123
|
component: 'button',
|
|
2638
3124
|
// Public to a design system derived from this one (#73).
|
|
@@ -2811,11 +3297,11 @@ export const button = {
|
|
|
2811
3297
|
// The `<button>` element is UA `border-box`, so the stated height is
|
|
2812
3298
|
// the rendered height, border included — as in daisy.
|
|
2813
3299
|
size: {
|
|
2814
|
-
xs: { root: { base: { height: btnHeight('xs'), padding: '
|
|
2815
|
-
sm: { root: { base: { height: btnHeight('sm'), padding: '
|
|
2816
|
-
md: { root: { base: { height: btnHeight('md'), padding: '
|
|
2817
|
-
lg: { root: { base: { height: btnHeight('lg'), padding: '
|
|
2818
|
-
xl: { root: { base: { height: btnHeight('xl'), padding: '
|
|
3300
|
+
xs: { root: { base: { height: btnHeight('xs'), padding: btnPadding('xs'), fontSize: 'var(--text-xs)' } } },
|
|
3301
|
+
sm: { root: { base: { height: btnHeight('sm'), padding: btnPadding('sm'), fontSize: 'var(--text-sm)' } } },
|
|
3302
|
+
md: { root: { base: { height: btnHeight('md'), padding: btnPadding('md'), fontSize: 'var(--text-md)' } } },
|
|
3303
|
+
lg: { root: { base: { height: btnHeight('lg'), padding: btnPadding('lg'), fontSize: 'var(--text-lg)' } } },
|
|
3304
|
+
xl: { root: { base: { height: btnHeight('xl'), padding: btnPadding('xl'), fontSize: 'var(--text-xl)' } } },
|
|
2819
3305
|
},
|
|
2820
3306
|
},
|
|
2821
3307
|
/**
|
|
@@ -2846,14 +3332,96 @@ export const button = {
|
|
|
2846
3332
|
},
|
|
2847
3333
|
defaultVariants: { color: 'primary', variant: 'solid', size: 'md' },
|
|
2848
3334
|
keyframes: { 'zero-daisyui-btn-spin': 'to { transform: rotate(360deg) }' },
|
|
2849
|
-
// The icon chips' zeroed inline padding, restated physically: logical
|
|
2850
|
-
// spellings resolve on iOS but not on Android (measured,
|
|
2851
|
-
// signalxjs/lynx#1084), so the emitter refuses them.
|
|
2852
3335
|
targets: {
|
|
2853
3336
|
lynx: {
|
|
3337
|
+
parts: {
|
|
3338
|
+
// The ring, restated: `currentColor` never resolves on lynx
|
|
3339
|
+
// (signalxjs/lynx#1079) and the logical sizes/edge are
|
|
3340
|
+
// unproven there, so the lynx ring is physical longhands in
|
|
3341
|
+
// the variant's own ink (the variants below pick it). Every
|
|
3342
|
+
// var-bearing edge is a longhand (`lynxSides`): a var-bearing
|
|
3343
|
+
// `border` expanded after the transparent top and closed the
|
|
3344
|
+
// ring (signalxjs/lynx#1161). The shorthand stays, static, so
|
|
3345
|
+
// the shared `currentColor` spelling never ships.
|
|
3346
|
+
spinner: {
|
|
3347
|
+
base: {
|
|
3348
|
+
width: '1em',
|
|
3349
|
+
height: '1em',
|
|
3350
|
+
border: '0 solid transparent',
|
|
3351
|
+
...lynxSides('border{}Width', 'calc(var(--border) * 2)'),
|
|
3352
|
+
...lynxSides('border{}Color', 'var(--btn-ink)', ['Right', 'Bottom', 'Left']),
|
|
3353
|
+
borderTopColor: 'transparent',
|
|
3354
|
+
},
|
|
3355
|
+
},
|
|
3356
|
+
root: {
|
|
3357
|
+
// daisy's content-sized, never-squeezed btn (#1165), and
|
|
3358
|
+
// the hairline as longhands: a var-bearing `border`
|
|
3359
|
+
// shorthand would expand over the variants' and the
|
|
3360
|
+
// disabled state's edge colours (see `lynxSides`).
|
|
3361
|
+
base: {
|
|
3362
|
+
...lynxBtnFit,
|
|
3363
|
+
border: '0 solid transparent',
|
|
3364
|
+
...lynxSides('border{}Width', 'var(--border)'),
|
|
3365
|
+
},
|
|
3366
|
+
// The ring in the variant's own ink (signalxjs/lynx#1163).
|
|
3367
|
+
states: { disabled: lynxBtnDisabled, 'focus-visible': lynxFocusRing('var(--btn-ink)') },
|
|
3368
|
+
},
|
|
3369
|
+
},
|
|
3370
|
+
variants: {
|
|
3371
|
+
// The baked disabled paint above outranks every variant's fill
|
|
3372
|
+
// (the theme host adds a class), but its plain declarations
|
|
3373
|
+
// do not: outline/dash restate the border, ghost/link drop
|
|
3374
|
+
// the wash (as in daisy). outline/dash draw their ink edge as
|
|
3375
|
+
// longhands, so that transparent restatement wins by
|
|
3376
|
+
// specificity instead of losing to a var-bearing
|
|
3377
|
+
// `border-color` (signalxjs/lynx#1162); the shared shorthand
|
|
3378
|
+
// is neutralised to a static value.
|
|
3379
|
+
variant: {
|
|
3380
|
+
solid: { spinner: { base: lynxSides('border{}Color', 'var(--btn-on-accent)', ['Right', 'Bottom', 'Left']) } },
|
|
3381
|
+
outline: {
|
|
3382
|
+
root: {
|
|
3383
|
+
base: { borderColor: 'transparent', ...lynxSides('border{}Color', 'var(--btn-ink)') },
|
|
3384
|
+
states: { disabled: { borderColor: 'transparent' } },
|
|
3385
|
+
},
|
|
3386
|
+
},
|
|
3387
|
+
dash: {
|
|
3388
|
+
root: {
|
|
3389
|
+
base: { borderColor: 'transparent', ...lynxSides('border{}Color', 'var(--btn-ink)') },
|
|
3390
|
+
states: { disabled: { borderColor: 'transparent' } },
|
|
3391
|
+
},
|
|
3392
|
+
},
|
|
3393
|
+
ghost: { root: { states: { disabled: { background: 'transparent' } } } },
|
|
3394
|
+
// `link` zeroes the box-shadow in a variant rule that
|
|
3395
|
+
// lands after the ring, so it restates the ring.
|
|
3396
|
+
link: { root: { states: { disabled: { background: 'transparent' }, 'focus-visible': lynxFocusRing('var(--btn-ink)') } } },
|
|
3397
|
+
},
|
|
3398
|
+
// The size ramp's paddings as longhands, so square/circle's
|
|
3399
|
+
// zeroed sides below win by the cascade rather than by the
|
|
3400
|
+
// order lynx happens to expand a var-bearing `padding` in.
|
|
3401
|
+
size: Object.fromEntries(BTN_PAD.map(([size, block, inline]) => [size, {
|
|
3402
|
+
root: {
|
|
3403
|
+
base: {
|
|
3404
|
+
padding: '0',
|
|
3405
|
+
...lynxSides('padding{}', `var(--space-${block})`, ['Top', 'Bottom']),
|
|
3406
|
+
...lynxSides('padding{}', `var(--space-${inline})`, ['Right', 'Left']),
|
|
3407
|
+
},
|
|
3408
|
+
},
|
|
3409
|
+
}])),
|
|
3410
|
+
},
|
|
3411
|
+
// The icon chips' zeroed inline padding, restated physically: logical
|
|
3412
|
+
// spellings resolve on iOS but not on Android (measured,
|
|
3413
|
+
// signalxjs/lynx#1084), so the emitter refuses them. `width: auto`
|
|
3414
|
+
// undoes the root's `max-content`, which would otherwise switch
|
|
3415
|
+
// off the `aspect-ratio` that squares the chip. `wide`/`block`
|
|
3416
|
+
// restate their width physically: `inline-size` and
|
|
3417
|
+
// `max-inline-size` are not lynx properties at all.
|
|
2854
3418
|
modifiers: {
|
|
2855
|
-
|
|
2856
|
-
|
|
3419
|
+
wide: { root: { base: { width: '100%', maxWidth: '16rem' } } },
|
|
3420
|
+
block: { root: { base: { width: '100%' } } },
|
|
3421
|
+
square: { root: { base: { width: 'auto', paddingLeft: '0', paddingRight: '0' } } },
|
|
3422
|
+
circle: { root: { base: { width: 'auto', paddingLeft: '0', paddingRight: '0' } } },
|
|
3423
|
+
// `active` zeroes the box-shadow too (the held press look).
|
|
3424
|
+
active: { root: { states: { 'focus-visible': lynxFocusRing('var(--btn-ink)') } } },
|
|
2857
3425
|
},
|
|
2858
3426
|
},
|
|
2859
3427
|
},
|
|
@@ -2933,6 +3501,22 @@ export const avatar = {
|
|
|
2933
3501
|
},
|
|
2934
3502
|
},
|
|
2935
3503
|
};
|
|
3504
|
+
/**
|
|
3505
|
+
* A toast whose indicator is rendered (#292): the grid grows a leading column
|
|
3506
|
+
* for the mark, and the text, action and close step one column along.
|
|
3507
|
+
*/
|
|
3508
|
+
const TOAST_MARKED = '[data-scope="toast"][data-part="root"]:has(> [data-scope="toast"][data-part="indicator"]) > &';
|
|
3509
|
+
/** The indicator's marks, cut from its fill — not mirrored in RTL (a tick is not a direction). */
|
|
3510
|
+
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%)';
|
|
3511
|
+
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%)';
|
|
3512
|
+
/**
|
|
3513
|
+
* daisy's success and error are FILLS — light enough in `light` and `nord`
|
|
3514
|
+
* that a bare mark in them reads under 2:1 on the toast's base-200. Deepened
|
|
3515
|
+
* toward base-content (which flips with the theme), the hue survives and the
|
|
3516
|
+
* mark clears the 3:1 non-text floor in every theme.
|
|
3517
|
+
*/
|
|
3518
|
+
const TOAST_SUCCESS_INK = 'color-mix(in oklab, var(--color-success) 55%, var(--color-base-content))';
|
|
3519
|
+
const TOAST_ERROR_INK = 'color-mix(in oklab, var(--color-error) 55%, var(--color-base-content))';
|
|
2936
3520
|
/**
|
|
2937
3521
|
* Toast presence is runtime-managed (see the SKILL's Toast section): plain
|
|
2938
3522
|
* two-state transitions only — no `@starting-style`, no `allow-discrete`.
|
|
@@ -2943,6 +3527,7 @@ export const toast = {
|
|
|
2943
3527
|
'--toast-bg': 'var(--color-base-200)',
|
|
2944
3528
|
'--toast-ink': 'var(--color-base-content)',
|
|
2945
3529
|
'--toast-from': '8px',
|
|
3530
|
+
'--toast-mark': '1rem',
|
|
2946
3531
|
},
|
|
2947
3532
|
parts: {
|
|
2948
3533
|
viewport: {
|
|
@@ -2996,6 +3581,7 @@ export const toast = {
|
|
|
2996
3581
|
},
|
|
2997
3582
|
selectors: {
|
|
2998
3583
|
'&[data-placement^="top"]': { '--toast-from': '-8px' },
|
|
3584
|
+
'&:has(> [data-scope="toast"][data-part="indicator"])': { gridTemplateColumns: 'auto 1fr auto auto' },
|
|
2999
3585
|
},
|
|
3000
3586
|
states: {
|
|
3001
3587
|
open: { opacity: '1', transform: 'none' },
|
|
@@ -3005,8 +3591,39 @@ export const toast = {
|
|
|
3005
3591
|
'reduced-motion': { base: { transition: 'none' }, states: { open: { transform: 'none' } } },
|
|
3006
3592
|
},
|
|
3007
3593
|
},
|
|
3594
|
+
// daisy's `loading-spinner` ring in the alert's own ink, then the
|
|
3595
|
+
// success tick or the error cross — the promise toast's status.
|
|
3596
|
+
indicator: {
|
|
3597
|
+
base: {
|
|
3598
|
+
gridColumn: '1',
|
|
3599
|
+
gridRow: '1 / span 2',
|
|
3600
|
+
inlineSize: 'var(--toast-mark)',
|
|
3601
|
+
blockSize: 'var(--toast-mark)',
|
|
3602
|
+
boxSizing: 'border-box',
|
|
3603
|
+
},
|
|
3604
|
+
states: {
|
|
3605
|
+
loading: {
|
|
3606
|
+
borderRadius: '50%',
|
|
3607
|
+
border: 'calc(var(--border) * 2) solid color-mix(in oklab, var(--toast-ink) 25%, transparent)',
|
|
3608
|
+
borderBlockStartColor: 'var(--toast-ink)',
|
|
3609
|
+
animation: 'zero-daisyui-toast-spin 0.75s linear infinite',
|
|
3610
|
+
},
|
|
3611
|
+
complete: { background: TOAST_SUCCESS_INK, clipPath: TOAST_CHECK },
|
|
3612
|
+
error: { background: TOAST_ERROR_INK, clipPath: TOAST_CROSS },
|
|
3613
|
+
},
|
|
3614
|
+
at: {
|
|
3615
|
+
'reduced-motion': { states: { loading: { animation: 'none' } } },
|
|
3616
|
+
'forced-colors': {
|
|
3617
|
+
states: {
|
|
3618
|
+
complete: { background: 'CanvasText', forcedColorAdjust: 'none' },
|
|
3619
|
+
error: { background: 'CanvasText', forcedColorAdjust: 'none' },
|
|
3620
|
+
},
|
|
3621
|
+
},
|
|
3622
|
+
},
|
|
3623
|
+
},
|
|
3008
3624
|
title: {
|
|
3009
3625
|
base: { gridColumn: '1', fontWeight: 'var(--weight-semibold)' },
|
|
3626
|
+
selectors: { [TOAST_MARKED]: { gridColumn: '2' } },
|
|
3010
3627
|
},
|
|
3011
3628
|
description: {
|
|
3012
3629
|
base: {
|
|
@@ -3014,6 +3631,7 @@ export const toast = {
|
|
|
3014
3631
|
fontSize: 'var(--text-xs)',
|
|
3015
3632
|
color: 'color-mix(in oklab, var(--toast-ink) 75%, transparent)',
|
|
3016
3633
|
},
|
|
3634
|
+
selectors: { [TOAST_MARKED]: { gridColumn: '2' } },
|
|
3017
3635
|
},
|
|
3018
3636
|
action: {
|
|
3019
3637
|
base: {
|
|
@@ -3034,6 +3652,7 @@ export const toast = {
|
|
|
3034
3652
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
3035
3653
|
...focusRing,
|
|
3036
3654
|
},
|
|
3655
|
+
selectors: { [TOAST_MARKED]: { gridColumn: '3' } },
|
|
3037
3656
|
},
|
|
3038
3657
|
close: {
|
|
3039
3658
|
base: {
|
|
@@ -3053,6 +3672,7 @@ export const toast = {
|
|
|
3053
3672
|
disabled: { opacity: 'var(--disabled-opacity)' },
|
|
3054
3673
|
...focusRing,
|
|
3055
3674
|
},
|
|
3675
|
+
selectors: { [TOAST_MARKED]: { gridColumn: '4' } },
|
|
3056
3676
|
},
|
|
3057
3677
|
},
|
|
3058
3678
|
variants: {
|
|
@@ -3089,24 +3709,79 @@ export const toast = {
|
|
|
3089
3709
|
// signalxjs/lynx#1070. No `color` default: the un-attributed toast is a
|
|
3090
3710
|
// neutral base-200 surface, outside the color vocabulary.
|
|
3091
3711
|
defaultVariants: { size: 'md' },
|
|
3092
|
-
|
|
3093
|
-
// resolve on iOS but not on Android (measured, signalxjs/lynx#1084), so
|
|
3094
|
-
// the emitter refuses them. Physical is the lynx target's norm — no RTL
|
|
3095
|
-
// flow there, so `start` IS left and `end` IS right.
|
|
3712
|
+
keyframes: { 'zero-daisyui-toast-spin': 'to { transform: rotate(360deg); }' },
|
|
3096
3713
|
targets: {
|
|
3097
3714
|
lynx: {
|
|
3098
3715
|
parts: {
|
|
3716
|
+
// The viewport is a full-width strip on lynx (the runtime pins
|
|
3717
|
+
// it to the outlet's top or bottom edge). What the web gets
|
|
3718
|
+
// from `:popover-open` and `min(24rem, 100vw)` is restated:
|
|
3719
|
+
// flex (a lynx view defaults to linear layout, which ignores
|
|
3720
|
+
// flex-direction and gap), and the cards aligned to the
|
|
3721
|
+
// placement's edge. The centred pair's `left: 50%` +
|
|
3722
|
+
// `translateX(-50%)` pull-back is for a shrink-wrapped popover;
|
|
3723
|
+
// on the strip it shifted everything half a width left, so it
|
|
3724
|
+
// is cancelled. The corner anchors are physical: logical
|
|
3725
|
+
// insets resolve on iOS but not on Android (measured,
|
|
3726
|
+
// signalxjs/lynx#1084), and lynx has no RTL flow, so `start`
|
|
3727
|
+
// IS left and `end` IS right.
|
|
3099
3728
|
viewport: {
|
|
3729
|
+
base: { display: 'flex', width: 'auto' },
|
|
3100
3730
|
selectors: {
|
|
3101
|
-
'&[data-placement="top-start"]': { left: '0' },
|
|
3102
|
-
'&[data-placement="top
|
|
3103
|
-
'&[data-placement="
|
|
3104
|
-
'&[data-placement="bottom-
|
|
3731
|
+
'&[data-placement="top-start"]': { left: '0', alignItems: 'flex-start' },
|
|
3732
|
+
'&[data-placement="top"]': { left: '0', right: '0', transform: 'none', alignItems: 'center' },
|
|
3733
|
+
'&[data-placement="top-end"]': { right: '0', alignItems: 'flex-end' },
|
|
3734
|
+
'&[data-placement="bottom-start"]': { left: '0', alignItems: 'flex-start' },
|
|
3735
|
+
'&[data-placement="bottom"]': { left: '0', right: '0', transform: 'none', alignItems: 'center' },
|
|
3736
|
+
'&[data-placement="bottom-end"]': { right: '0', alignItems: 'flex-end' },
|
|
3105
3737
|
},
|
|
3106
3738
|
},
|
|
3739
|
+
// No grid on lynx (signalxjs/lynx#1075): the card is a flex
|
|
3740
|
+
// column (title, description, then the action), with the
|
|
3741
|
+
// close button pinned to the top-right corner and the title
|
|
3742
|
+
// kept clear of it. The web's 24rem viewport cap becomes the
|
|
3743
|
+
// card's own cap. The `^="top"` entry offset has no class
|
|
3744
|
+
// form, so each top placement restates it.
|
|
3745
|
+
root: {
|
|
3746
|
+
base: {
|
|
3747
|
+
display: 'flex',
|
|
3748
|
+
flexDirection: 'column',
|
|
3749
|
+
alignItems: 'flex-start',
|
|
3750
|
+
position: 'relative',
|
|
3751
|
+
boxSizing: 'border-box',
|
|
3752
|
+
width: '100%',
|
|
3753
|
+
maxWidth: '24rem',
|
|
3754
|
+
},
|
|
3755
|
+
selectors: {
|
|
3756
|
+
'&[data-placement="top-start"]': { '--toast-from': '-8px' },
|
|
3757
|
+
'&[data-placement="top"]': { '--toast-from': '-8px' },
|
|
3758
|
+
'&[data-placement="top-end"]': { '--toast-from': '-8px' },
|
|
3759
|
+
},
|
|
3760
|
+
},
|
|
3761
|
+
title: { base: { paddingRight: '1.75em' } },
|
|
3762
|
+
// The 75% ink mix over a recipe-local property cannot bake;
|
|
3763
|
+
// the same ink at 75% opacity reads the same on the soft fill.
|
|
3764
|
+
description: { base: { color: 'var(--toast-ink)', opacity: '0.75' } },
|
|
3765
|
+
// The 25% ink border cannot bake either: it takes the full ink.
|
|
3766
|
+
// Their rings' gap is the card's own fill.
|
|
3767
|
+
action: {
|
|
3768
|
+
base: {
|
|
3769
|
+
marginTop: 'var(--space-xs)',
|
|
3770
|
+
border: 'var(--border) solid var(--toast-ink)',
|
|
3771
|
+
},
|
|
3772
|
+
states: lynxFocus({ gap: 'var(--toast-bg)' }),
|
|
3773
|
+
},
|
|
3774
|
+
close: {
|
|
3775
|
+
base: { position: 'absolute', top: 'var(--space-xs)', right: 'var(--space-xs)' },
|
|
3776
|
+
states: lynxFocus({ gap: 'var(--toast-bg)' }),
|
|
3777
|
+
},
|
|
3107
3778
|
},
|
|
3108
3779
|
},
|
|
3109
3780
|
},
|
|
3781
|
+
// The viewport's `open` (the stack expanded) is a fact this skin has no
|
|
3782
|
+
// use for: its toasts are always a plain column, so both states look the
|
|
3783
|
+
// same by design (#292).
|
|
3784
|
+
skipStates: { viewport: ['open', 'closed'] },
|
|
3110
3785
|
};
|
|
3111
3786
|
export const combobox = {
|
|
3112
3787
|
component: 'combobox',
|
|
@@ -3906,12 +4581,28 @@ const treeRowSelectors = {
|
|
|
3906
4581
|
background: 'var(--color-base-300)',
|
|
3907
4582
|
},
|
|
3908
4583
|
};
|
|
4584
|
+
/**
|
|
4585
|
+
* `tickGlyphFallback` for the node check box, whose tick is its `::after`:
|
|
4586
|
+
* the geometry drops and the same pseudo carries the glyph, in the medium's
|
|
4587
|
+
* own ink (the #233 divergence, restated).
|
|
4588
|
+
*/
|
|
4589
|
+
const nodeTickFallback = (ink) => ({
|
|
4590
|
+
selectors: {
|
|
4591
|
+
'&::after': { clipPath: 'none', backgroundColor: '#0000', rotate: '0deg', inset: '0', display: 'grid', placeItems: 'center', color: ink, fontSize: '0.85em', lineHeight: '1' },
|
|
4592
|
+
'&[data-state="checked"]::after': { content: '"✔︎"', clipPath: 'none', rotate: '0deg' },
|
|
4593
|
+
'&[data-state="indeterminate"]::after': { content: '"−"', clipPath: 'none', rotate: '0deg', translate: 'none' },
|
|
4594
|
+
},
|
|
4595
|
+
});
|
|
3909
4596
|
export const treeView = {
|
|
3910
4597
|
component: 'tree-view',
|
|
3911
4598
|
tokens: {
|
|
3912
4599
|
'--tree-accent': 'var(--color-primary)',
|
|
3913
4600
|
'--tree-text': 'var(--text-sm)',
|
|
3914
4601
|
'--tree-on-accent': 'var(--color-primary-content)',
|
|
4602
|
+
// The node check box's pair — the tree's own accent, so a colour
|
|
4603
|
+
// variant retints the box with the row.
|
|
4604
|
+
'--tree-check': 'var(--tree-accent)',
|
|
4605
|
+
'--tree-on-check': 'var(--tree-on-accent)',
|
|
3915
4606
|
},
|
|
3916
4607
|
parts: {
|
|
3917
4608
|
root: {
|
|
@@ -3960,6 +4651,58 @@ export const treeView = {
|
|
|
3960
4651
|
'reduced-motion': { base: { transition: 'none' } },
|
|
3961
4652
|
},
|
|
3962
4653
|
},
|
|
4654
|
+
// daisy's `.checkbox` at row scale: the accent outline, the recessed
|
|
4655
|
+
// depth shade, the fill on check — and daisy's own tick, the six
|
|
4656
|
+
// clip-path points rotated 45°, drawn on the box's `::after` since
|
|
4657
|
+
// the part is one span. On a selected row (the accent fill) the pair
|
|
4658
|
+
// swaps, so the box never vanishes into the row it sits on.
|
|
4659
|
+
'node-checkbox': {
|
|
4660
|
+
base: {
|
|
4661
|
+
display: 'inline-block',
|
|
4662
|
+
position: 'relative',
|
|
4663
|
+
flexShrink: '0',
|
|
4664
|
+
boxSizing: 'border-box',
|
|
4665
|
+
width: '1.25em',
|
|
4666
|
+
height: '1.25em',
|
|
4667
|
+
border: 'var(--border) solid var(--tree-check)',
|
|
4668
|
+
borderRadius: 'var(--radius-selector)',
|
|
4669
|
+
boxShadow: '0 1px var(--depth-shade) inset',
|
|
4670
|
+
cursor: 'pointer',
|
|
4671
|
+
transition: 'background-color var(--duration-normal) var(--ease-standard)',
|
|
4672
|
+
},
|
|
4673
|
+
states: {
|
|
4674
|
+
checked: { backgroundColor: 'var(--tree-check)' },
|
|
4675
|
+
indeterminate: { backgroundColor: 'var(--tree-check)' },
|
|
4676
|
+
unchecked: {},
|
|
4677
|
+
disabled: { cursor: 'not-allowed' },
|
|
4678
|
+
},
|
|
4679
|
+
selectors: {
|
|
4680
|
+
'&::after': {
|
|
4681
|
+
content: '""',
|
|
4682
|
+
position: 'absolute',
|
|
4683
|
+
inset: '15%',
|
|
4684
|
+
opacity: '0',
|
|
4685
|
+
rotate: '45deg',
|
|
4686
|
+
backgroundColor: 'var(--tree-on-check)',
|
|
4687
|
+
clipPath: TICK_COLLAPSED,
|
|
4688
|
+
transition: 'clip-path var(--duration-slow) var(--ease-standard) var(--duration-instant), '
|
|
4689
|
+
+ 'opacity var(--duration-instant) var(--ease-standard) var(--duration-instant), '
|
|
4690
|
+
+ 'rotate var(--duration-slow) var(--ease-standard) var(--duration-instant), '
|
|
4691
|
+
+ 'translate var(--duration-slow) var(--ease-standard) var(--duration-instant)',
|
|
4692
|
+
},
|
|
4693
|
+
'&[data-state="checked"]::after': { clipPath: TICK_DRAWN, opacity: '1' },
|
|
4694
|
+
'&[data-state="indeterminate"]::after': { clipPath: DASH_DRAWN, opacity: '1', translate: '0 -35%', rotate: '0deg' },
|
|
4695
|
+
'[data-scope="tree-view"][data-selected] &': {
|
|
4696
|
+
'--tree-check': 'var(--tree-on-accent)',
|
|
4697
|
+
'--tree-on-check': 'var(--tree-accent)',
|
|
4698
|
+
},
|
|
4699
|
+
},
|
|
4700
|
+
at: {
|
|
4701
|
+
'reduced-motion': { base: { transition: 'none' }, selectors: { '&::after': { transition: 'none' } } },
|
|
4702
|
+
'forced-colors': nodeTickFallback('CanvasText'),
|
|
4703
|
+
print: nodeTickFallback('var(--print-ink)'),
|
|
4704
|
+
},
|
|
4705
|
+
},
|
|
3963
4706
|
// Indentation comes from this inline padding — depth is the DOM
|
|
3964
4707
|
// nesting, no per-level rules needed.
|
|
3965
4708
|
'branch-content': {
|
|
@@ -4240,6 +4983,36 @@ export const card = {
|
|
|
4240
4983
|
overflow: 'hidden',
|
|
4241
4984
|
},
|
|
4242
4985
|
},
|
|
4986
|
+
/**
|
|
4987
|
+
* Media (#302) — daisy's `card > figure`: a centred flex box, flush
|
|
4988
|
+
* to the card's edges, the image filling it.
|
|
4989
|
+
*/
|
|
4990
|
+
media: {
|
|
4991
|
+
base: {
|
|
4992
|
+
display: 'flex',
|
|
4993
|
+
alignItems: 'center',
|
|
4994
|
+
justifyContent: 'center',
|
|
4995
|
+
margin: '0',
|
|
4996
|
+
inlineSize: '100%',
|
|
4997
|
+
objectFit: 'cover',
|
|
4998
|
+
},
|
|
4999
|
+
selectors: {
|
|
5000
|
+
// The media's own image, when the band wraps one (a div or a
|
|
5001
|
+
// figure): block-level, the band's full width, cropped rather
|
|
5002
|
+
// than stretched when the band is given a height.
|
|
5003
|
+
'& > :is(img, picture, video, svg, canvas)': {
|
|
5004
|
+
display: 'block',
|
|
5005
|
+
inlineSize: '100%',
|
|
5006
|
+
blockSize: '100%',
|
|
5007
|
+
objectFit: 'cover',
|
|
5008
|
+
},
|
|
5009
|
+
// The corners it shares with the card (#302): the root clips
|
|
5010
|
+
// too, but an asChild <img> is the band itself and rounds on
|
|
5011
|
+
// its own.
|
|
5012
|
+
'&:first-child': { borderStartStartRadius: 'var(--radius-box)', borderStartEndRadius: 'var(--radius-box)' },
|
|
5013
|
+
'&:last-child': { borderEndStartRadius: 'var(--radius-box)', borderEndEndRadius: 'var(--radius-box)' },
|
|
5014
|
+
},
|
|
5015
|
+
},
|
|
4243
5016
|
header: {
|
|
4244
5017
|
base: {
|
|
4245
5018
|
display: 'flex',
|
|
@@ -4569,16 +5342,51 @@ export const divider = {
|
|
|
4569
5342
|
tokens: { '--divider-ink': 'var(--color-base-300)', '--divider-thickness': 'var(--border)' },
|
|
4570
5343
|
parts: {
|
|
4571
5344
|
root: {
|
|
4572
|
-
|
|
5345
|
+
// The rule is two flex segments (#298), drawn as borders: with no
|
|
5346
|
+
// label they meet and read as one line, and a label sits between
|
|
5347
|
+
// them. Borders rather than a background, so forced colours keep
|
|
5348
|
+
// the line instead of revaluing it to Canvas.
|
|
5349
|
+
base: {
|
|
5350
|
+
display: 'flex',
|
|
5351
|
+
alignItems: 'center',
|
|
5352
|
+
border: 'none',
|
|
5353
|
+
alignSelf: 'stretch',
|
|
5354
|
+
},
|
|
4573
5355
|
selectors: {
|
|
4574
|
-
'
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
},
|
|
4578
|
-
'&[data-orientation="
|
|
4579
|
-
|
|
4580
|
-
|
|
5356
|
+
'&::before': { content: '""', flex: '1 1 0', minInlineSize: '0' },
|
|
5357
|
+
'&::after': { content: '""', flex: '1 1 0', minInlineSize: '0' },
|
|
5358
|
+
'&[data-orientation="horizontal"]': { inlineSize: '100%' },
|
|
5359
|
+
'&[data-orientation="horizontal"]::before': { borderBlockStart: 'var(--divider-thickness) solid var(--divider-ink)' },
|
|
5360
|
+
'&[data-orientation="horizontal"]::after': { borderBlockStart: 'var(--divider-thickness) solid var(--divider-ink)' },
|
|
5361
|
+
'&[data-orientation="vertical"]': { flexDirection: 'column', minBlockSize: '1em' },
|
|
5362
|
+
'&[data-orientation="vertical"]::before': { borderInlineStart: 'var(--divider-thickness) solid var(--divider-ink)' },
|
|
5363
|
+
'&[data-orientation="vertical"]::after': { borderInlineStart: 'var(--divider-thickness) solid var(--divider-ink)' },
|
|
5364
|
+
// A placed label sits flush at its edge: the segment on
|
|
5365
|
+
// that side goes, and the other one takes the whole run.
|
|
5366
|
+
'&:has(> [data-scope="divider"][data-part="label"][data-placement="start"])::before': { display: 'none' },
|
|
5367
|
+
'&:has(> [data-scope="divider"][data-part="label"][data-placement="end"])::after': { display: 'none' },
|
|
5368
|
+
},
|
|
5369
|
+
},
|
|
5370
|
+
label: {
|
|
5371
|
+
base: {
|
|
5372
|
+
// daisy's `divider` text: the content colour, the rule
|
|
5373
|
+
// stopping a gap short of it on either side.
|
|
5374
|
+
flex: 'none',
|
|
5375
|
+
paddingInline: 'var(--space-md)',
|
|
5376
|
+
fontSize: 'var(--text-sm)',
|
|
5377
|
+
whiteSpace: 'nowrap',
|
|
5378
|
+
color: 'var(--color-base-content)',
|
|
5379
|
+
},
|
|
5380
|
+
selectors: {
|
|
5381
|
+
'[data-scope="divider"][data-part="root"][data-orientation="vertical"] > &': {
|
|
5382
|
+
paddingInline: '0',
|
|
5383
|
+
paddingBlock: 'var(--space-sm)',
|
|
4581
5384
|
},
|
|
5385
|
+
// Flush means flush: no inset on the side the rule gave up.
|
|
5386
|
+
'&[data-placement="start"]': { paddingInlineStart: '0' },
|
|
5387
|
+
'&[data-placement="end"]': { paddingInlineEnd: '0' },
|
|
5388
|
+
'[data-scope="divider"][data-part="root"][data-orientation="vertical"] > &[data-placement="start"]': { paddingBlockStart: '0' },
|
|
5389
|
+
'[data-scope="divider"][data-part="root"][data-orientation="vertical"] > &[data-placement="end"]': { paddingBlockEnd: '0' },
|
|
4582
5390
|
},
|
|
4583
5391
|
},
|
|
4584
5392
|
},
|
|
@@ -5121,6 +5929,10 @@ export const timeline = {
|
|
|
5121
5929
|
},
|
|
5122
5930
|
},
|
|
5123
5931
|
},
|
|
5932
|
+
// Title and description (#302): daisy's timeline-box leads with a
|
|
5933
|
+
// bold line; the detail under it in the card description's ink.
|
|
5934
|
+
title: { base: { display: 'block', margin: '0', fontWeight: 'var(--weight-semibold)', lineHeight: 'var(--leading-tight)' } },
|
|
5935
|
+
description: { base: { display: 'block', margin: '0', marginBlockStart: 'var(--space-2xs)', color: 'color-mix(in oklab, var(--color-base-content) 70%, transparent)' } },
|
|
5124
5936
|
},
|
|
5125
5937
|
variants: {
|
|
5126
5938
|
color: Object.fromEntries(ROLES.map((c) => [c, { marker: { base: {
|
|
@@ -5135,6 +5947,87 @@ export const timeline = {
|
|
|
5135
5947
|
xl: { marker: { base: { '--timeline-marker-size': 'calc(var(--size-selector) * 4)' } }, content: { base: { fontSize: 'var(--text-md)' } } },
|
|
5136
5948
|
},
|
|
5137
5949
|
},
|
|
5950
|
+
/**
|
|
5951
|
+
* Lynx has no grid, and it does not resolve `inline-size`/`block-size`:
|
|
5952
|
+
* the item fell back to the engine's default layout and the marker,
|
|
5953
|
+
* sized only logically, stretched into a pill (signalxjs/lynx#1145).
|
|
5954
|
+
*
|
|
5955
|
+
* The item is a wrapping flex row instead. Vertical: line one is the
|
|
5956
|
+
* marker beside its content (start-side content ordered before the
|
|
5957
|
+
* marker), and the connector takes line two by itself (`flex-basis:
|
|
5958
|
+
* 100%`), drawn as a left border under the marker's centre, which is the
|
|
5959
|
+
* web's row-2 segment. Horizontal: line one is the marker with the
|
|
5960
|
+
* connector running on to the item's end, and the content wraps under it
|
|
5961
|
+
* (start-side content above). Content grows from a zero basis, so long
|
|
5962
|
+
* text never wraps onto its own line.
|
|
5963
|
+
*
|
|
5964
|
+
* The web collapses the start track when no item has start-side content
|
|
5965
|
+
* (`:has()`); lynx has no such test. The vertical connector sits under a
|
|
5966
|
+
* marker at the item's start edge, which is where the marker is unless
|
|
5967
|
+
* that item has start-side content: an alternating vertical timeline is
|
|
5968
|
+
* web-only for now.
|
|
5969
|
+
*/
|
|
5970
|
+
targets: {
|
|
5971
|
+
lynx: {
|
|
5972
|
+
parts: {
|
|
5973
|
+
item: { base: { display: 'flex', flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center' } },
|
|
5974
|
+
marker: {
|
|
5975
|
+
base: {
|
|
5976
|
+
width: 'var(--timeline-marker-size)',
|
|
5977
|
+
height: 'var(--timeline-marker-size)',
|
|
5978
|
+
flexGrow: '0',
|
|
5979
|
+
flexShrink: '0',
|
|
5980
|
+
},
|
|
5981
|
+
},
|
|
5982
|
+
connector: {
|
|
5983
|
+
selectors: {
|
|
5984
|
+
'&[data-orientation="vertical"]': {
|
|
5985
|
+
flexGrow: '0',
|
|
5986
|
+
flexShrink: '1',
|
|
5987
|
+
flexBasis: '100%',
|
|
5988
|
+
height: 'var(--space-lg)',
|
|
5989
|
+
background: 'transparent',
|
|
5990
|
+
borderLeftWidth: 'var(--border)',
|
|
5991
|
+
borderLeftStyle: 'solid',
|
|
5992
|
+
borderLeftColor: 'var(--color-base-300)',
|
|
5993
|
+
marginLeft: 'calc(var(--timeline-marker-size) / 2 - var(--border) / 2)',
|
|
5994
|
+
},
|
|
5995
|
+
'&[data-orientation="horizontal"]': {
|
|
5996
|
+
order: '1',
|
|
5997
|
+
flexGrow: '1',
|
|
5998
|
+
flexShrink: '1',
|
|
5999
|
+
flexBasis: '0',
|
|
6000
|
+
height: 'var(--border)',
|
|
6001
|
+
minWidth: 'var(--space-lg)',
|
|
6002
|
+
},
|
|
6003
|
+
},
|
|
6004
|
+
},
|
|
6005
|
+
content: {
|
|
6006
|
+
selectors: {
|
|
6007
|
+
'&[data-orientation="vertical"][data-placement="start"]': {
|
|
6008
|
+
order: '-1',
|
|
6009
|
+
flexGrow: '1',
|
|
6010
|
+
flexShrink: '1',
|
|
6011
|
+
flexBasis: '0',
|
|
6012
|
+
},
|
|
6013
|
+
'&[data-orientation="vertical"][data-placement="end"]': {
|
|
6014
|
+
flexGrow: '1',
|
|
6015
|
+
flexShrink: '1',
|
|
6016
|
+
flexBasis: '0',
|
|
6017
|
+
},
|
|
6018
|
+
'&[data-orientation="horizontal"][data-placement="start"]': {
|
|
6019
|
+
order: '-1',
|
|
6020
|
+
flexBasis: '100%',
|
|
6021
|
+
},
|
|
6022
|
+
'&[data-orientation="horizontal"][data-placement="end"]': {
|
|
6023
|
+
order: '2',
|
|
6024
|
+
flexBasis: '100%',
|
|
6025
|
+
},
|
|
6026
|
+
},
|
|
6027
|
+
},
|
|
6028
|
+
},
|
|
6029
|
+
},
|
|
6030
|
+
},
|
|
5138
6031
|
};
|
|
5139
6032
|
/** daisy chat: the classic bubble, colour-refilled per role. */
|
|
5140
6033
|
export const chat = {
|
|
@@ -5244,6 +6137,95 @@ export const chat = {
|
|
|
5244
6137
|
},
|
|
5245
6138
|
},
|
|
5246
6139
|
};
|
|
6140
|
+
/**
|
|
6141
|
+
* ChatLog — daisy's `bg-base-200 rounded-box` well that the chat bubbles
|
|
6142
|
+
* already sit in, scrolling; the jump trigger is a `btn btn-sm` pill in the
|
|
6143
|
+
* role's solid pair with daisy's `shadow-xs` and its 1px press sink, floated
|
|
6144
|
+
* over the rows by `position: sticky` at the box's foot. Its negative block
|
|
6145
|
+
* margin gives back the line it would take, so appearing moves no row.
|
|
6146
|
+
*/
|
|
6147
|
+
export const chatLog = {
|
|
6148
|
+
component: 'chat-log',
|
|
6149
|
+
tokens: {
|
|
6150
|
+
'--chat-log-fill': 'var(--color-primary)',
|
|
6151
|
+
'--chat-log-on-fill': 'var(--color-primary-content)',
|
|
6152
|
+
'--chat-log-jump-size': 'calc(var(--size-field) * 8)',
|
|
6153
|
+
},
|
|
6154
|
+
parts: {
|
|
6155
|
+
root: {
|
|
6156
|
+
base: {
|
|
6157
|
+
display: 'flex',
|
|
6158
|
+
flexDirection: 'column',
|
|
6159
|
+
overflowY: 'auto',
|
|
6160
|
+
overscrollBehaviorY: 'contain',
|
|
6161
|
+
borderRadius: 'var(--radius-box)',
|
|
6162
|
+
background: 'var(--color-base-100)',
|
|
6163
|
+
color: 'var(--color-base-content)',
|
|
6164
|
+
border: 'var(--border) solid var(--color-base-300)',
|
|
6165
|
+
padding: 'var(--space-sm) var(--space-md)',
|
|
6166
|
+
},
|
|
6167
|
+
// Drawn inside the box (negative offset): an outward ring would
|
|
6168
|
+
// be clipped by whatever scrolls around the transcript.
|
|
6169
|
+
states: { 'focus-visible': { outline: '2px solid var(--color-base-content)', outlineOffset: '-2px' } },
|
|
6170
|
+
},
|
|
6171
|
+
content: {
|
|
6172
|
+
base: {
|
|
6173
|
+
// `auto` gathers a short transcript at the foot; a long one
|
|
6174
|
+
// overflows and the margin resolves to nothing.
|
|
6175
|
+
marginBlockStart: 'auto',
|
|
6176
|
+
flex: 'none',
|
|
6177
|
+
display: 'grid',
|
|
6178
|
+
rowGap: 'var(--space-xs)',
|
|
6179
|
+
},
|
|
6180
|
+
},
|
|
6181
|
+
'jump-trigger': {
|
|
6182
|
+
base: {
|
|
6183
|
+
position: 'sticky',
|
|
6184
|
+
insetBlockEnd: 'var(--space-sm)',
|
|
6185
|
+
alignSelf: 'center',
|
|
6186
|
+
flex: 'none',
|
|
6187
|
+
marginBlockStart: 'calc(var(--chat-log-jump-size) * -1)',
|
|
6188
|
+
blockSize: 'var(--chat-log-jump-size)',
|
|
6189
|
+
appearance: 'none',
|
|
6190
|
+
display: 'inline-flex',
|
|
6191
|
+
alignItems: 'center',
|
|
6192
|
+
gap: 'var(--space-2xs)',
|
|
6193
|
+
paddingInline: 'var(--space-md)',
|
|
6194
|
+
border: 'var(--border) solid transparent',
|
|
6195
|
+
borderRadius: '9999px',
|
|
6196
|
+
background: 'var(--chat-log-fill)',
|
|
6197
|
+
color: 'var(--chat-log-on-fill)',
|
|
6198
|
+
boxShadow: 'var(--shadow-xs)',
|
|
6199
|
+
fontFamily: 'inherit',
|
|
6200
|
+
fontSize: 'var(--text-xs)',
|
|
6201
|
+
fontWeight: 'var(--weight-semibold)',
|
|
6202
|
+
lineHeight: 'var(--leading-none)',
|
|
6203
|
+
cursor: 'pointer',
|
|
6204
|
+
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
6205
|
+
},
|
|
6206
|
+
states: {
|
|
6207
|
+
open: {},
|
|
6208
|
+
closed: {},
|
|
6209
|
+
hover: { background: 'color-mix(in oklab, var(--chat-log-fill) 90%, black)' },
|
|
6210
|
+
'focus-visible': { outline: '2px solid var(--color-base-content)', outlineOffset: '2px' },
|
|
6211
|
+
},
|
|
6212
|
+
selectors: { '&[data-pressed]:not([data-disabled])': { transform: 'translateY(1px)', boxShadow: 'none' } },
|
|
6213
|
+
},
|
|
6214
|
+
},
|
|
6215
|
+
variants: {
|
|
6216
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { 'jump-trigger': { base: {
|
|
6217
|
+
'--chat-log-fill': `var(--color-${c})`,
|
|
6218
|
+
'--chat-log-on-fill': `var(--color-${c}-content)`,
|
|
6219
|
+
} } }])),
|
|
6220
|
+
size: {
|
|
6221
|
+
xs: { content: { base: { rowGap: 'var(--space-2xs)' } }, 'jump-trigger': { base: { '--chat-log-jump-size': 'calc(var(--size-field) * 6)', fontSize: 'var(--text-xs)' } } },
|
|
6222
|
+
sm: { content: { base: { rowGap: 'var(--space-2xs)' } }, 'jump-trigger': { base: { '--chat-log-jump-size': 'calc(var(--size-field) * 7)', fontSize: 'var(--text-xs)' } } },
|
|
6223
|
+
md: {},
|
|
6224
|
+
lg: { content: { base: { rowGap: 'var(--space-sm)' } }, 'jump-trigger': { base: { '--chat-log-jump-size': 'calc(var(--size-field) * 9)', fontSize: 'var(--text-sm)' } } },
|
|
6225
|
+
xl: { content: { base: { rowGap: 'var(--space-md)' } }, 'jump-trigger': { base: { '--chat-log-jump-size': 'calc(var(--size-field) * 10)', fontSize: 'var(--text-sm)' } } },
|
|
6226
|
+
},
|
|
6227
|
+
},
|
|
6228
|
+
};
|
|
5247
6229
|
/** daisy radial-progress: --value became --progress-percent; same ring. */
|
|
5248
6230
|
export const radialProgress = {
|
|
5249
6231
|
component: 'radial-progress',
|
|
@@ -5630,6 +6612,8 @@ export const breadcrumbs = {
|
|
|
5630
6612
|
alignItems: 'center',
|
|
5631
6613
|
gap: 'var(--space-xs)',
|
|
5632
6614
|
},
|
|
6615
|
+
// `closed` is presence: the runtime hides the part (#295).
|
|
6616
|
+
states: { open: {}, closed: {} },
|
|
5633
6617
|
},
|
|
5634
6618
|
link: {
|
|
5635
6619
|
base: {
|
|
@@ -5658,6 +6642,37 @@ export const breadcrumbs = {
|
|
|
5658
6642
|
userSelect: 'none',
|
|
5659
6643
|
},
|
|
5660
6644
|
},
|
|
6645
|
+
ellipsis: {
|
|
6646
|
+
base: {
|
|
6647
|
+
display: 'flex',
|
|
6648
|
+
alignItems: 'center',
|
|
6649
|
+
gap: 'var(--space-xs)',
|
|
6650
|
+
},
|
|
6651
|
+
// `closed` is presence: the runtime hides the part (#295).
|
|
6652
|
+
states: { open: {}, closed: {} },
|
|
6653
|
+
},
|
|
6654
|
+
'ellipsis-trigger': {
|
|
6655
|
+
base: {
|
|
6656
|
+
appearance: 'none',
|
|
6657
|
+
background: 'transparent',
|
|
6658
|
+
border: '0',
|
|
6659
|
+
padding: '0 var(--space-2xs)',
|
|
6660
|
+
margin: '0',
|
|
6661
|
+
font: 'inherit',
|
|
6662
|
+
lineHeight: 'inherit',
|
|
6663
|
+
color: 'color-mix(in oklch, var(--color-base-content) 75%, transparent)',
|
|
6664
|
+
borderRadius: 'var(--radius-selector)',
|
|
6665
|
+
cursor: 'pointer',
|
|
6666
|
+
transition: 'color var(--duration-fast) var(--ease-standard)',
|
|
6667
|
+
},
|
|
6668
|
+
states: {
|
|
6669
|
+
hover: { color: 'var(--color-base-content)', textDecoration: 'underline' },
|
|
6670
|
+
'focus-visible': {
|
|
6671
|
+
outline: '2px solid var(--color-primary)',
|
|
6672
|
+
outlineOffset: '2px',
|
|
6673
|
+
},
|
|
6674
|
+
},
|
|
6675
|
+
},
|
|
5661
6676
|
},
|
|
5662
6677
|
variants: {
|
|
5663
6678
|
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
@@ -5672,11 +6687,46 @@ export const breadcrumbs = {
|
|
|
5672
6687
|
},
|
|
5673
6688
|
},
|
|
5674
6689
|
};
|
|
6690
|
+
/**
|
|
6691
|
+
* The four pagination triggers — prev/next and, with `withEdges`, the
|
|
6692
|
+
* first/last jumps (#294) — are one cell: the same box, the same
|
|
6693
|
+
* glyph-sized type, and the same flip under the rtl guard, since every
|
|
6694
|
+
* glyph (`‹ › « »`) is physical ink pointing at a reading edge.
|
|
6695
|
+
*/
|
|
6696
|
+
const pageTrigger = {
|
|
6697
|
+
base: {
|
|
6698
|
+
display: 'inline-flex',
|
|
6699
|
+
alignItems: 'center',
|
|
6700
|
+
justifyContent: 'center',
|
|
6701
|
+
minInlineSize: 'var(--pg-size)',
|
|
6702
|
+
blockSize: 'var(--pg-size)',
|
|
6703
|
+
background: 'var(--color-base-200)',
|
|
6704
|
+
color: 'var(--color-base-content)',
|
|
6705
|
+
border: 'none',
|
|
6706
|
+
borderRadius: 'var(--radius-field)',
|
|
6707
|
+
fontSize: 'calc(var(--pg-font) * 1.2)',
|
|
6708
|
+
lineHeight: 'var(--leading-none)',
|
|
6709
|
+
appearance: 'none',
|
|
6710
|
+
// Link mode renders an <a> (#294): no UA underline.
|
|
6711
|
+
textDecoration: 'none',
|
|
6712
|
+
cursor: 'pointer',
|
|
6713
|
+
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
6714
|
+
},
|
|
6715
|
+
states: {
|
|
6716
|
+
hover: { background: 'var(--color-base-300)' },
|
|
6717
|
+
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
6718
|
+
...focusRing,
|
|
6719
|
+
},
|
|
6720
|
+
selectors: {
|
|
6721
|
+
'&[data-pressed]:not([data-disabled])': { transform: 'translateY(1px)' },
|
|
6722
|
+
[`&${rtl}`]: { scale: '-1 1' },
|
|
6723
|
+
},
|
|
6724
|
+
};
|
|
5675
6725
|
/**
|
|
5676
6726
|
* Pagination — daisy's join-of-buttons made of standalone btn cells: the
|
|
5677
6727
|
* quiet base-200 fill, base-300 hover, the current page inverted into the
|
|
5678
6728
|
* accent pair (primary by default). Pressed is the daisy 1px sink. The
|
|
5679
|
-
*
|
|
6729
|
+
* `‹ › « »` glyphs flip under the shared rtl guard.
|
|
5680
6730
|
*/
|
|
5681
6731
|
export const pagination = {
|
|
5682
6732
|
component: 'pagination',
|
|
@@ -5722,6 +6772,7 @@ export const pagination = {
|
|
|
5722
6772
|
fontWeight: 'var(--weight-semibold)',
|
|
5723
6773
|
fontVariantNumeric: 'tabular-nums',
|
|
5724
6774
|
appearance: 'none',
|
|
6775
|
+
textDecoration: 'none',
|
|
5725
6776
|
cursor: 'pointer',
|
|
5726
6777
|
transition: 'background var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard)',
|
|
5727
6778
|
},
|
|
@@ -5749,60 +6800,10 @@ export const pagination = {
|
|
|
5749
6800
|
userSelect: 'none',
|
|
5750
6801
|
},
|
|
5751
6802
|
},
|
|
5752
|
-
'
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
justifyContent: 'center',
|
|
5757
|
-
minInlineSize: 'var(--pg-size)',
|
|
5758
|
-
blockSize: 'var(--pg-size)',
|
|
5759
|
-
background: 'var(--color-base-200)',
|
|
5760
|
-
color: 'var(--color-base-content)',
|
|
5761
|
-
border: 'none',
|
|
5762
|
-
borderRadius: 'var(--radius-field)',
|
|
5763
|
-
fontSize: 'calc(var(--pg-font) * 1.2)',
|
|
5764
|
-
lineHeight: 'var(--leading-none)',
|
|
5765
|
-
appearance: 'none',
|
|
5766
|
-
cursor: 'pointer',
|
|
5767
|
-
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
5768
|
-
},
|
|
5769
|
-
states: {
|
|
5770
|
-
hover: { background: 'var(--color-base-300)' },
|
|
5771
|
-
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
5772
|
-
...focusRing,
|
|
5773
|
-
},
|
|
5774
|
-
selectors: {
|
|
5775
|
-
'&[data-pressed]:not([data-disabled])': { transform: 'translateY(1px)' },
|
|
5776
|
-
[`&${rtl}`]: { scale: '-1 1' },
|
|
5777
|
-
},
|
|
5778
|
-
},
|
|
5779
|
-
'next-trigger': {
|
|
5780
|
-
base: {
|
|
5781
|
-
display: 'inline-flex',
|
|
5782
|
-
alignItems: 'center',
|
|
5783
|
-
justifyContent: 'center',
|
|
5784
|
-
minInlineSize: 'var(--pg-size)',
|
|
5785
|
-
blockSize: 'var(--pg-size)',
|
|
5786
|
-
background: 'var(--color-base-200)',
|
|
5787
|
-
color: 'var(--color-base-content)',
|
|
5788
|
-
border: 'none',
|
|
5789
|
-
borderRadius: 'var(--radius-field)',
|
|
5790
|
-
fontSize: 'calc(var(--pg-font) * 1.2)',
|
|
5791
|
-
lineHeight: 'var(--leading-none)',
|
|
5792
|
-
appearance: 'none',
|
|
5793
|
-
cursor: 'pointer',
|
|
5794
|
-
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
5795
|
-
},
|
|
5796
|
-
states: {
|
|
5797
|
-
hover: { background: 'var(--color-base-300)' },
|
|
5798
|
-
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
5799
|
-
...focusRing,
|
|
5800
|
-
},
|
|
5801
|
-
selectors: {
|
|
5802
|
-
'&[data-pressed]:not([data-disabled])': { transform: 'translateY(1px)' },
|
|
5803
|
-
[`&${rtl}`]: { scale: '-1 1' },
|
|
5804
|
-
},
|
|
5805
|
-
},
|
|
6803
|
+
'first-trigger': pageTrigger,
|
|
6804
|
+
'prev-trigger': pageTrigger,
|
|
6805
|
+
'next-trigger': pageTrigger,
|
|
6806
|
+
'last-trigger': pageTrigger,
|
|
5806
6807
|
},
|
|
5807
6808
|
variants: {
|
|
5808
6809
|
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
@@ -5840,6 +6841,20 @@ export const pagination = {
|
|
|
5840
6841
|
*/
|
|
5841
6842
|
const stepInk = (accent, role) => `color-mix(in oklab, ${accent} `
|
|
5842
6843
|
+ `${Math.min(ROLE_INK_KEEP[role] ?? 55, 70)}%, var(--color-base-content))`;
|
|
6844
|
+
/** A Steps root holding wizard parts (#296) — the panel or the triggers. */
|
|
6845
|
+
const STEPS_WIZARD = '&:has(> [data-scope="steps"]:is([data-part="content"], [data-part="prev-trigger"], [data-part="next-trigger"]))';
|
|
6846
|
+
/** Steps' Back/Next (#296): the neutral daisy btn, with its 1px sink. */
|
|
6847
|
+
const stepsTrigger = {
|
|
6848
|
+
base: { ...btn, flex: '0 0 auto', alignSelf: 'flex-start' },
|
|
6849
|
+
states: {
|
|
6850
|
+
hover: { background: 'var(--color-base-300)' },
|
|
6851
|
+
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
6852
|
+
...focusRing,
|
|
6853
|
+
},
|
|
6854
|
+
selectors: {
|
|
6855
|
+
'&[data-pressed]:not([data-disabled])': { transform: 'translateY(1px)' },
|
|
6856
|
+
},
|
|
6857
|
+
};
|
|
5843
6858
|
/**
|
|
5844
6859
|
* Steps — daisy's steps translated to the richer rail: bold numbered discs
|
|
5845
6860
|
* on base-200, the walked disc and line refilled with the accent pair
|
|
@@ -5866,6 +6881,10 @@ export const steps = {
|
|
|
5866
6881
|
},
|
|
5867
6882
|
selectors: {
|
|
5868
6883
|
'&[data-orientation="vertical"]': { flexDirection: 'column' },
|
|
6884
|
+
// A wizard (#296): the rail keeps its own line and the panel
|
|
6885
|
+
// and triggers wrap below it. Only then — a bare rail never
|
|
6886
|
+
// wraps, so its steps stay one row at any width.
|
|
6887
|
+
[STEPS_WIZARD]: { flexWrap: 'wrap', rowGap: 'var(--space-md)' },
|
|
5869
6888
|
},
|
|
5870
6889
|
},
|
|
5871
6890
|
/**
|
|
@@ -5904,6 +6923,10 @@ export const steps = {
|
|
|
5904
6923
|
active: { color: 'var(--steps-accent-ink)', fontWeight: 'var(--weight-semibold)' },
|
|
5905
6924
|
complete: { color: 'var(--color-base-content)' },
|
|
5906
6925
|
inactive: { color: 'color-mix(in oklch, var(--color-base-content) 65%, transparent)' },
|
|
6926
|
+
// daisy's `step-error`, whatever the phase: after the phases,
|
|
6927
|
+
// so it wins at equal weight. `stepInk`, not `roleInk`: the
|
|
6928
|
+
// 85% error ink measured 3.79:1 on light's page as text.
|
|
6929
|
+
invalid: { color: stepInk('var(--color-error)', 'error') },
|
|
5907
6930
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
5908
6931
|
...focusRing,
|
|
5909
6932
|
},
|
|
@@ -5935,6 +6958,7 @@ export const steps = {
|
|
|
5935
6958
|
// the disc (#112).
|
|
5936
6959
|
complete: { background: 'color-mix(in oklab, var(--steps-accent) 20%, var(--color-base-100))', color: 'var(--steps-complete-ink)' },
|
|
5937
6960
|
inactive: { background: 'var(--color-base-200)', color: 'var(--color-base-content)' },
|
|
6961
|
+
invalid: { background: 'var(--color-error)', color: 'var(--color-error-content)' },
|
|
5938
6962
|
},
|
|
5939
6963
|
},
|
|
5940
6964
|
/**
|
|
@@ -5966,6 +6990,7 @@ export const steps = {
|
|
|
5966
6990
|
states: {
|
|
5967
6991
|
complete: { background: 'var(--steps-accent)' },
|
|
5968
6992
|
inactive: { background: 'var(--color-base-300)' },
|
|
6993
|
+
invalid: { background: 'var(--color-error)' },
|
|
5969
6994
|
},
|
|
5970
6995
|
},
|
|
5971
6996
|
title: {
|
|
@@ -5980,6 +7005,25 @@ export const steps = {
|
|
|
5980
7005
|
fontWeight: 'var(--weight-normal)',
|
|
5981
7006
|
},
|
|
5982
7007
|
},
|
|
7008
|
+
/**
|
|
7009
|
+
* The active step's panel: a full-width line under the rail (the
|
|
7010
|
+
* wizard wrap above). Inactive panels are `hidden`, so they need no
|
|
7011
|
+
* rule.
|
|
7012
|
+
*/
|
|
7013
|
+
content: {
|
|
7014
|
+
base: {
|
|
7015
|
+
flex: '1 0 100%',
|
|
7016
|
+
fontSize: 'var(--text-md)',
|
|
7017
|
+
color: 'var(--color-base-content)',
|
|
7018
|
+
},
|
|
7019
|
+
states: { active: {}, inactive: {} },
|
|
7020
|
+
},
|
|
7021
|
+
// Back/Next are daisy btns; Next takes the far end of their line.
|
|
7022
|
+
'prev-trigger': stepsTrigger,
|
|
7023
|
+
'next-trigger': {
|
|
7024
|
+
...stepsTrigger,
|
|
7025
|
+
base: { ...stepsTrigger.base, marginInlineStart: 'auto' },
|
|
7026
|
+
},
|
|
5983
7027
|
},
|
|
5984
7028
|
variants: {
|
|
5985
7029
|
// Keyed on the ITEM, not the root (#112): the item re-carries
|
|
@@ -6163,6 +7207,9 @@ export const drawer = {
|
|
|
6163
7207
|
*/
|
|
6164
7208
|
export const table = {
|
|
6165
7209
|
component: 'table',
|
|
7210
|
+
// A sortable header cell carries the sort state for `aria-sort`'s sake;
|
|
7211
|
+
// its trigger and indicator paint it.
|
|
7212
|
+
skipStates: { 'header-cell': ['ascending', 'descending', 'none'] },
|
|
6166
7213
|
// Public to a design system derived from this one (#73).
|
|
6167
7214
|
hooks: {
|
|
6168
7215
|
properties: {
|
|
@@ -6248,6 +7295,55 @@ export const table = {
|
|
|
6248
7295
|
},
|
|
6249
7296
|
at: tableStackAt(tokens, 'header-cell', { paddingInline: '0' }),
|
|
6250
7297
|
},
|
|
7298
|
+
// Sorting (#286): daisy's header label made a button — the sorted
|
|
7299
|
+
// column's label takes the full accent ink, and the mark beside it
|
|
7300
|
+
// turns with the direction.
|
|
7301
|
+
'sort-trigger': {
|
|
7302
|
+
base: {
|
|
7303
|
+
appearance: 'none',
|
|
7304
|
+
display: 'inline-flex',
|
|
7305
|
+
alignItems: 'center',
|
|
7306
|
+
margin: '0',
|
|
7307
|
+
border: '0',
|
|
7308
|
+
font: 'inherit',
|
|
7309
|
+
letterSpacing: 'inherit',
|
|
7310
|
+
textTransform: 'inherit',
|
|
7311
|
+
textAlign: 'inherit',
|
|
7312
|
+
cursor: 'pointer',
|
|
7313
|
+
gap: 'var(--space-2xs)',
|
|
7314
|
+
padding: '0',
|
|
7315
|
+
borderRadius: 'var(--radius-selector)',
|
|
7316
|
+
background: 'transparent',
|
|
7317
|
+
color: 'inherit',
|
|
7318
|
+
transition: 'color var(--duration-fast) var(--ease-standard)',
|
|
7319
|
+
},
|
|
7320
|
+
states: {
|
|
7321
|
+
ascending: { color: 'var(--table-accent)' },
|
|
7322
|
+
descending: { color: 'var(--table-accent)' },
|
|
7323
|
+
none: {},
|
|
7324
|
+
hover: { color: 'var(--table-accent)' },
|
|
7325
|
+
disabled: { cursor: 'not-allowed', opacity: 'var(--disabled-opacity)' },
|
|
7326
|
+
...focusRing,
|
|
7327
|
+
},
|
|
7328
|
+
at: { 'reduced-motion': { base: { transition: 'none' } } },
|
|
7329
|
+
},
|
|
7330
|
+
// zero's ▲, turned for descending; an unsorted column shows it only
|
|
7331
|
+
// under a hovered or keyboard-focused trigger.
|
|
7332
|
+
'sort-indicator': {
|
|
7333
|
+
base: {
|
|
7334
|
+
display: 'inline-block',
|
|
7335
|
+
fontSize: '0.75em',
|
|
7336
|
+
lineHeight: '1',
|
|
7337
|
+
transition: 'transform var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard)',
|
|
7338
|
+
},
|
|
7339
|
+
states: {
|
|
7340
|
+
ascending: {},
|
|
7341
|
+
descending: { transform: 'rotate(180deg)' },
|
|
7342
|
+
none: { opacity: '0' },
|
|
7343
|
+
},
|
|
7344
|
+
selectors: { '[data-scope="table"][data-part="sort-trigger"]:is(:hover, [data-focus-visible]) > &[data-state="none"]': { opacity: '0.6' } },
|
|
7345
|
+
at: { 'reduced-motion': { base: { transition: 'none' } } },
|
|
7346
|
+
},
|
|
6251
7347
|
cell: {
|
|
6252
7348
|
base: {
|
|
6253
7349
|
padding: 'var(--table-pad-block) var(--table-pad-inline)',
|
|
@@ -6895,11 +7991,11 @@ export const diff = {
|
|
|
6895
7991
|
},
|
|
6896
7992
|
};
|
|
6897
7993
|
export const recipes = [
|
|
6898
|
-
tabs, collapsible, switchRecipe, dialog, popover, tooltip, menu,
|
|
6899
|
-
field, checkbox, checkboxGroup, radioGroup, progress, slider, accordion, select, button, avatar, toast, combobox,
|
|
7994
|
+
tabs, collapsible, switchRecipe, dialog, popover, tooltip, hoverCard, menu, menubar,
|
|
7995
|
+
field, fieldset, checkbox, checkboxGroup, radioGroup, progress, slider, accordion, select, button, avatar, toast, combobox,
|
|
6900
7996
|
toggle, toggleGroup, numberInput, ratingGroup, treeView, input, textarea,
|
|
6901
7997
|
card, alert, emptyState, badge, divider, skeleton, spinner,
|
|
6902
|
-
kbd, status, indicator, stats, timeline, chat, radialProgress, join,
|
|
7998
|
+
kbd, status, indicator, stats, timeline, chat, chatLog, radialProgress, join,
|
|
6903
7999
|
navbar, navList, breadcrumbs, pagination, steps, drawer,
|
|
6904
8000
|
table,
|
|
6905
8001
|
fileUpload,
|