@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/src/recipes.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* value of that data.
|
|
6
6
|
*/
|
|
7
7
|
import type { CssProps, PartStyles, RecipeInput } from '@sigx/zero-kit';
|
|
8
|
-
import { axisRoles, tableStackAt } from '@sigx/zero-kit/define';
|
|
8
|
+
import { axisRoles, popupArrow, popupArrowHost, tableStackAt } from '@sigx/zero-kit/define';
|
|
9
9
|
import { roles, tokens } from './tokens.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -39,6 +39,41 @@ const focusRing: Record<string, NonNullable<PartStyles['base']>> = {
|
|
|
39
39
|
},
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* The focus ring on lynx, for `targets.lynx` (signalxjs/lynx#1163): the same
|
|
44
|
+
* 2px ring 2px clear of the part, spelled as spread box-shadows.
|
|
45
|
+
*
|
|
46
|
+
* The web ring is `outline` + `outline-offset`, and neither survives on
|
|
47
|
+
* lynx: its `outline` ignores `border-radius` (a square box around a
|
|
48
|
+
* rounded button, a rectangle around the switch pill) and it has no
|
|
49
|
+
* `outline-offset` property at all, so the ring sat flush on the fill. A
|
|
50
|
+
* zero-blur shadow's spread follows the element's radius. Shadows stack
|
|
51
|
+
* rather than mask, so the offset gap cannot be transparent: the inner
|
|
52
|
+
* shadow paints it in `gap` — the surface the part sits on, which is what
|
|
53
|
+
* the web's gap shows through to. `outline: none` retires the shared ring
|
|
54
|
+
* in the same merged declaration block.
|
|
55
|
+
*
|
|
56
|
+
* `under` appends the part's own lynx shadows (a knob's depth relief), which
|
|
57
|
+
* the focus rule would otherwise replace. `inset` draws the ring inside the
|
|
58
|
+
* part, flush with its edge, for a part whose parent clips an outside ring
|
|
59
|
+
* away (signalxjs/lynx#1164, the accordion trigger inside its card).
|
|
60
|
+
*/
|
|
61
|
+
const lynxFocusRing = (
|
|
62
|
+
ink: string,
|
|
63
|
+
{ gap = 'var(--color-base-100)', under, inset = false }: { gap?: string; under?: string; inset?: boolean } = {},
|
|
64
|
+
): CssProps => ({
|
|
65
|
+
outline: 'none',
|
|
66
|
+
boxShadow: [
|
|
67
|
+
...(inset ? [`inset 0 0 0 2px ${ink}`] : [`0 0 0 2px ${gap}`, `0 0 0 4px ${ink}`]),
|
|
68
|
+
...(under ? [under] : []),
|
|
69
|
+
].join(', '),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/** `focusRing`'s lynx spelling — the base-content ring, as a states entry. */
|
|
73
|
+
const lynxFocus = (opts?: Parameters<typeof lynxFocusRing>[1]): Record<string, CssProps> => ({
|
|
74
|
+
'focus-visible': lynxFocusRing('var(--color-base-content)', opts),
|
|
75
|
+
});
|
|
76
|
+
|
|
42
77
|
/**
|
|
43
78
|
* How far each role survives in the button's INK — the colour a transparent or
|
|
44
79
|
* tinted fill draws its label, border and focus ring with.
|
|
@@ -316,7 +351,9 @@ export const tabs: RecipeInput = {
|
|
|
316
351
|
},
|
|
317
352
|
// daisy draws each flavor's active mark on the tab itself (the
|
|
318
353
|
// underline, the lift, the pill) and none of them slides, so a
|
|
319
|
-
// `Tabs.Indicator` renders nothing here (#283).
|
|
354
|
+
// `Tabs.Indicator` renders nothing here (#283). On lynx, which has
|
|
355
|
+
// no pseudo-elements, the `border` flavor's underline is drawn on
|
|
356
|
+
// this part instead (see `targets.lynx`).
|
|
320
357
|
indicator: { base: { display: 'none' } },
|
|
321
358
|
panel: {
|
|
322
359
|
base: { fontSize: 'var(--text-md)' },
|
|
@@ -520,11 +557,17 @@ export const tabs: RecipeInput = {
|
|
|
520
557
|
targets: {
|
|
521
558
|
lynx: {
|
|
522
559
|
parts: {
|
|
523
|
-
//
|
|
524
|
-
//
|
|
560
|
+
// No hover on a touch platform: the held tab takes the ink
|
|
561
|
+
// daisy's hover gives it.
|
|
562
|
+
tab: { states: { pressed: { color: 'var(--color-base-content)' }, ...lynxFocus() } },
|
|
563
|
+
// The list is the indicator's containing block.
|
|
564
|
+
list: { base: { position: 'relative' } },
|
|
565
|
+
// The underline's ink (read by the `border` flavor below) —
|
|
566
|
+
// base-content by default, exactly what the shared
|
|
567
|
+
// `states.active.color` paints on the tab. One class, so any
|
|
525
568
|
// color-axis definition below (host + two classes, baked per
|
|
526
569
|
// theme) wins on specificity.
|
|
527
|
-
|
|
570
|
+
indicator: { base: { '--tab-active-ink': 'var(--color-base-content)' } },
|
|
528
571
|
},
|
|
529
572
|
variants: {
|
|
530
573
|
// `currentColor` never resolves on lynx (measured on device
|
|
@@ -535,19 +578,29 @@ export const tabs: RecipeInput = {
|
|
|
535
578
|
// once per theme — the same per-color/per-theme literal the
|
|
536
579
|
// active `color:` rules already land as.
|
|
537
580
|
color: Object.fromEntries(
|
|
538
|
-
ROLES.map((c) => [c, {
|
|
581
|
+
ROLES.map((c) => [c, { indicator: { base: { '--tab-active-ink': roleInk(c) } } }]),
|
|
539
582
|
),
|
|
540
583
|
variant: {
|
|
541
584
|
// The underline is a ::before on the web, which lynx
|
|
542
|
-
// drops
|
|
543
|
-
//
|
|
544
|
-
// the
|
|
545
|
-
//
|
|
546
|
-
//
|
|
585
|
+
// drops. It is redrawn on the `indicator` part (zero#324),
|
|
586
|
+
// which lynx-zero renders in every list and positions
|
|
587
|
+
// over the active tab from measured geometry (the web's
|
|
588
|
+
// `--tabs-indicator-*` properties are web-only). The part
|
|
589
|
+
// IS the tab's box, so daisy's bar is its bottom edge:
|
|
590
|
+
// 3px, 80% wide and centred, like `left: 10%; width: 80%`
|
|
591
|
+
// on the web. The ink rides `--tab-active-ink` — a plain
|
|
592
|
+
// var() chain, device-proven — instead of `currentColor`,
|
|
593
|
+
// which lynx never resolves.
|
|
547
594
|
border: {
|
|
548
|
-
|
|
549
|
-
base: {
|
|
550
|
-
|
|
595
|
+
indicator: {
|
|
596
|
+
base: {
|
|
597
|
+
display: 'flex',
|
|
598
|
+
boxSizing: 'border-box',
|
|
599
|
+
borderBottomWidth: '3px',
|
|
600
|
+
borderBottomStyle: 'solid',
|
|
601
|
+
borderBottomColor: 'var(--tab-active-ink)',
|
|
602
|
+
transform: 'scaleX(0.8)',
|
|
603
|
+
},
|
|
551
604
|
},
|
|
552
605
|
},
|
|
553
606
|
// `min()` does not exist on lynx (signalxjs/lynx#1066) and
|
|
@@ -559,6 +612,14 @@ export const tabs: RecipeInput = {
|
|
|
559
612
|
base: { '--tab-radius-limit': 'var(--radius-field)' },
|
|
560
613
|
},
|
|
561
614
|
},
|
|
615
|
+
// The box flavor's tabs sit on the list's base-200
|
|
616
|
+
// surface, so the ring's gap is that surface. Stated on
|
|
617
|
+
// the variant (one class more than the part rule) so it
|
|
618
|
+
// also outranks the active pill's `--shadow-sm`, which
|
|
619
|
+
// is a box-shadow too.
|
|
620
|
+
box: {
|
|
621
|
+
tab: { states: lynxFocus({ gap: 'var(--color-base-200)' }) },
|
|
622
|
+
},
|
|
562
623
|
},
|
|
563
624
|
},
|
|
564
625
|
},
|
|
@@ -900,6 +961,10 @@ export const switchRecipe: RecipeInput = {
|
|
|
900
961
|
// No currentColor on this target — the ink is named
|
|
901
962
|
// directly, and `checked` moves border + fill below.
|
|
902
963
|
border: 'var(--border) solid var(--switch-ink)',
|
|
964
|
+
// daisy's 1px inset depth line along the track's top
|
|
965
|
+
// edge — `currentColor` at `--depth` × 10% on the
|
|
966
|
+
// web, the theme's own depth shade here.
|
|
967
|
+
boxShadow: '0 1px var(--depth-shade) inset',
|
|
903
968
|
userSelect: 'none',
|
|
904
969
|
transition: 'border-color var(--duration-slow) var(--ease-standard), '
|
|
905
970
|
+ 'background-color var(--duration-normal) var(--ease-standard)',
|
|
@@ -909,6 +974,9 @@ export const switchRecipe: RecipeInput = {
|
|
|
909
974
|
borderColor: 'var(--switch-accent)',
|
|
910
975
|
backgroundColor: 'var(--color-base-100)',
|
|
911
976
|
},
|
|
977
|
+
// The pill ring (signalxjs/lynx#1163), keeping the
|
|
978
|
+
// track's inset depth line under it.
|
|
979
|
+
'focus-visible': lynxFocusRing('var(--switch-accent)', { under: '0 1px var(--depth-shade) inset' }),
|
|
912
980
|
},
|
|
913
981
|
},
|
|
914
982
|
thumb: {
|
|
@@ -920,6 +988,12 @@ export const switchRecipe: RecipeInput = {
|
|
|
920
988
|
height: 'calc(var(--switch-size) - (var(--border) + var(--switch-p)) * 2)',
|
|
921
989
|
borderRadius: 'var(--radius-selector)',
|
|
922
990
|
backgroundColor: 'var(--switch-ink)',
|
|
991
|
+
// daisy's knob relief (`.toggle:before`): a shade
|
|
992
|
+
// along the bottom edge, a sheen across the top, and
|
|
993
|
+
// a 1px drop — the web's `currentColor` drop spelled
|
|
994
|
+
// as the theme's depth shade. The noise layer has no
|
|
995
|
+
// lynx spelling and stays web-only.
|
|
996
|
+
boxShadow: '0 -1px var(--depth-shade) inset, 0 8px 0 -4px var(--depth-sheen) inset, 0 1px var(--depth-shade)',
|
|
923
997
|
transition: 'transform var(--duration-normal) var(--ease-standard), '
|
|
924
998
|
+ 'background-color var(--duration-instant) var(--ease-standard)',
|
|
925
999
|
},
|
|
@@ -1028,6 +1102,43 @@ const lynxBtnPad = (mult: number): NonNullable<PartStyles['base']> => ({
|
|
|
1028
1102
|
paddingRight: `calc(var(--size-field) * ${mult})`,
|
|
1029
1103
|
});
|
|
1030
1104
|
|
|
1105
|
+
/**
|
|
1106
|
+
* A four-side property spelled as its physical longhands, for `targets.lynx`
|
|
1107
|
+
* sections that must not ship a `var()`-bearing shorthand.
|
|
1108
|
+
*
|
|
1109
|
+
* Lynx keeps a declaration holding `var()` under its own property id,
|
|
1110
|
+
* unexpanded, and expands it only after the cascade has merged every rule
|
|
1111
|
+
* into one insertion-ordered style map — in that map's order, writing over
|
|
1112
|
+
* the longhands already there (`CSSVariableHandler::HandleCSSVariables`). A
|
|
1113
|
+
* static shorthand, by contrast, is expanded into its longhands at parse
|
|
1114
|
+
* time. So a var-bearing `border-color` and a static `border-top-color` never
|
|
1115
|
+
* meet in the cascade at all: whichever key the map saw later wins, whatever
|
|
1116
|
+
* the specificity (signalxjs/lynx#1161, #1162). Longhands share one key per
|
|
1117
|
+
* side, and the cascade decides again.
|
|
1118
|
+
*/
|
|
1119
|
+
const lynxSides = (
|
|
1120
|
+
prop: 'border{}Width' | 'border{}Color' | 'padding{}',
|
|
1121
|
+
value: string,
|
|
1122
|
+
sides: readonly ('Top' | 'Right' | 'Bottom' | 'Left')[] = ['Top', 'Right', 'Bottom', 'Left'],
|
|
1123
|
+
): NonNullable<PartStyles['base']> =>
|
|
1124
|
+
Object.fromEntries(sides.map((side) => [prop.replace('{}', side), value]));
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* daisy's `.btn` is content-sized and never squeezed: `display: inline-flex`
|
|
1128
|
+
* with `flex-shrink: 0; flex-wrap: nowrap`, so a narrow row makes the button
|
|
1129
|
+
* overflow, never break its label. A lynx view has no inline formatting
|
|
1130
|
+
* context: it stretches to a linear or column parent, and a squeezed label
|
|
1131
|
+
* `<text>` then wraps per character ("Bt / n", signalxjs/lynx#1165).
|
|
1132
|
+
* `width: max-content` is the intrinsic keyword starlight implements (an
|
|
1133
|
+
* intrinsic width is also exempt from the cross-axis stretch), so the box
|
|
1134
|
+
* sizes to its content as the web's does; the other two are daisy's own.
|
|
1135
|
+
*/
|
|
1136
|
+
const lynxBtnFit: NonNullable<PartStyles['base']> = {
|
|
1137
|
+
width: 'max-content',
|
|
1138
|
+
flexShrink: '0',
|
|
1139
|
+
flexWrap: 'nowrap',
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1031
1142
|
/** The btn size ramp's physical inline padding, for `targets.lynx.variants`. */
|
|
1032
1143
|
const lynxBtnSizes = (part: string): Record<string, Record<string, PartStyles>> => ({
|
|
1033
1144
|
xs: { [part]: { base: lynxBtnPad(2) } },
|
|
@@ -1036,6 +1147,22 @@ const lynxBtnSizes = (part: string): Record<string, Record<string, PartStyles>>
|
|
|
1036
1147
|
xl: { [part]: { base: lynxBtnPad(6) } },
|
|
1037
1148
|
});
|
|
1038
1149
|
|
|
1150
|
+
/**
|
|
1151
|
+
* The press rendering of the overlay btns (dialog, popover), for their
|
|
1152
|
+
* `targets.lynx` sections. The web answers a pointer with `:hover`, which
|
|
1153
|
+
* the lynx emitter drops (a touch platform), and these parts' `pressed`
|
|
1154
|
+
* flag — stamped by the lynx runtime on every touch — had no rule, so a
|
|
1155
|
+
* tap showed nothing (signalxjs/lynx#1146). daisy's `:hover` wash plus the
|
|
1156
|
+
* btn's 1px sink, the same pair `button` paints for its own `pressed`. A
|
|
1157
|
+
* colour variant restates the background after the states at equal
|
|
1158
|
+
* specificity, so a coloured trigger keeps its fill and sinks.
|
|
1159
|
+
*/
|
|
1160
|
+
const lynxBtnPressed: NonNullable<PartStyles['base']> = {
|
|
1161
|
+
background: 'var(--color-base-300)',
|
|
1162
|
+
transform: 'translateY(1px)',
|
|
1163
|
+
boxShadow: 'none',
|
|
1164
|
+
};
|
|
1165
|
+
|
|
1039
1166
|
export const dialog: RecipeInput = {
|
|
1040
1167
|
component: 'dialog',
|
|
1041
1168
|
// Public to a design system derived from this one (#73).
|
|
@@ -1127,14 +1254,17 @@ export const dialog: RecipeInput = {
|
|
|
1127
1254
|
// reach and the trigger is the whole story here.
|
|
1128
1255
|
variants: { color: btnColors(), size: btnSizes },
|
|
1129
1256
|
// The btn paddings and the footer's push-down, restated physically —
|
|
1130
|
-
// see `lynxBtnPad` for the #1084 verdict this answers
|
|
1257
|
+
// see `lynxBtnPad` for the #1084 verdict this answers — and the btns'
|
|
1258
|
+
// press rendering (`lynxBtnPressed`). The footer states its row: on lynx
|
|
1259
|
+
// a row container says so next to `display: flex`.
|
|
1131
1260
|
targets: {
|
|
1132
1261
|
lynx: {
|
|
1133
1262
|
parts: {
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1263
|
+
// Content-sized like daisy's btn, never broken mid-word (signalxjs/lynx#1165).
|
|
1264
|
+
trigger: { base: { ...lynxBtnPad(4), ...lynxBtnFit }, states: { pressed: lynxBtnPressed, ...lynxFocus() } },
|
|
1265
|
+
close: { base: lynxBtnPad(4), states: { pressed: lynxBtnPressed, ...lynxFocus() } },
|
|
1266
|
+
cancel: { base: lynxBtnPad(4), states: { pressed: lynxBtnPressed, ...lynxFocus() } },
|
|
1267
|
+
footer: { base: { marginTop: 'var(--space-2xl)', flexDirection: 'row' } },
|
|
1138
1268
|
},
|
|
1139
1269
|
variants: { size: lynxBtnSizes('trigger') },
|
|
1140
1270
|
},
|
|
@@ -1150,6 +1280,22 @@ const floatingPanel: NonNullable<PartStyles['base']> = {
|
|
|
1150
1280
|
boxShadow: 'var(--shadow-lg)',
|
|
1151
1281
|
};
|
|
1152
1282
|
|
|
1283
|
+
/**
|
|
1284
|
+
* The arrow a floating surface grows toward its anchor (#279), in the
|
|
1285
|
+
* surface's own paint: the dropdown's base-100 and border for popover and
|
|
1286
|
+
* menu, the tooltip's borderless neutral for the tooltip — daisy's
|
|
1287
|
+
* `tooltip` draws exactly that tail under its bubble. Web-only (placed by
|
|
1288
|
+
* the runtime's `--arrow-x`/`--arrow-y`), so it rides `targets.web` with
|
|
1289
|
+
* the popup's permission to let it out.
|
|
1290
|
+
*/
|
|
1291
|
+
const floatingArrow = (scope: string, paint: CssProps, size = '0.625rem'): NonNullable<NonNullable<RecipeInput['targets']>['web']> => ({
|
|
1292
|
+
parts: {
|
|
1293
|
+
popup: { selectors: popupArrowHost(scope) },
|
|
1294
|
+
arrow: popupArrow(scope, { size, paint }),
|
|
1295
|
+
},
|
|
1296
|
+
});
|
|
1297
|
+
const panelArrowPaint: CssProps = { background: 'var(--color-base-100)', border: 'var(--border) solid var(--color-base-300)' };
|
|
1298
|
+
|
|
1153
1299
|
/**
|
|
1154
1300
|
* daisy's dropdown menu for a listbox, sized by the geometry the
|
|
1155
1301
|
* anchored-position strategy publishes (#278): at least the width of the
|
|
@@ -1187,6 +1333,14 @@ export const popover: RecipeInput = {
|
|
|
1187
1333
|
title: {
|
|
1188
1334
|
base: { margin: '0 0 var(--space-md)', fontSize: 'var(--text-md)', fontWeight: 'var(--weight-bold)' },
|
|
1189
1335
|
},
|
|
1336
|
+
// The modal's muted body line, at the dropdown's tighter rhythm.
|
|
1337
|
+
description: {
|
|
1338
|
+
base: {
|
|
1339
|
+
margin: '0 0 var(--space-lg)',
|
|
1340
|
+
fontSize: 'var(--text-sm)',
|
|
1341
|
+
color: 'color-mix(in oklab, var(--color-base-content) 75%, transparent)',
|
|
1342
|
+
},
|
|
1343
|
+
},
|
|
1190
1344
|
close: {
|
|
1191
1345
|
base: { ...btn, height: 'calc(var(--size-field) * 8)', paddingInline: 'calc(var(--size-field) * 3)', fontSize: 'var(--text-xs)' },
|
|
1192
1346
|
states: {
|
|
@@ -1198,12 +1352,15 @@ export const popover: RecipeInput = {
|
|
|
1198
1352
|
},
|
|
1199
1353
|
// Trigger-carried axes — same wiring as dialog, same reason.
|
|
1200
1354
|
variants: { color: btnColors(), size: btnSizes },
|
|
1201
|
-
// The btn paddings, restated physically — see `lynxBtnPad` (#1084)
|
|
1355
|
+
// The btn paddings, restated physically — see `lynxBtnPad` (#1084) —
|
|
1356
|
+
// and the btns' press rendering (`lynxBtnPressed`).
|
|
1202
1357
|
targets: {
|
|
1358
|
+
web: floatingArrow('popover', panelArrowPaint),
|
|
1203
1359
|
lynx: {
|
|
1204
1360
|
parts: {
|
|
1205
|
-
|
|
1206
|
-
|
|
1361
|
+
// Content-sized like daisy's btn, never broken mid-word (signalxjs/lynx#1165).
|
|
1362
|
+
trigger: { base: { ...lynxBtnPad(4), ...lynxBtnFit }, states: { pressed: lynxBtnPressed, ...lynxFocus() } },
|
|
1363
|
+
close: { base: lynxBtnPad(3), states: { pressed: lynxBtnPressed, ...lynxFocus() } },
|
|
1207
1364
|
},
|
|
1208
1365
|
variants: { size: lynxBtnSizes('trigger') },
|
|
1209
1366
|
},
|
|
@@ -1246,6 +1403,7 @@ export const tooltip: RecipeInput = {
|
|
|
1246
1403
|
variants: { color: btnColors(), size: btnSizes },
|
|
1247
1404
|
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
1248
1405
|
targets: {
|
|
1406
|
+
web: floatingArrow('tooltip', { background: 'var(--color-neutral)' }, '0.5rem'),
|
|
1249
1407
|
lynx: {
|
|
1250
1408
|
parts: { trigger: { base: lynxBtnPad(4) } },
|
|
1251
1409
|
variants: { size: lynxBtnSizes('trigger') },
|
|
@@ -1253,6 +1411,57 @@ export const tooltip: RecipeInput = {
|
|
|
1253
1411
|
},
|
|
1254
1412
|
};
|
|
1255
1413
|
|
|
1414
|
+
/**
|
|
1415
|
+
* Hover card — daisy's `link` opening a `dropdown-content` card. The trigger
|
|
1416
|
+
* is running text, not a btn: underlined in the ink it sits in (daisy's bare
|
|
1417
|
+
* `link`), and a colour is `link-<role>` in the role's readable ink
|
|
1418
|
+
* (`roleInk`). The underline thickens on hover and while the card is up,
|
|
1419
|
+
* rather than the ink dimming — the `btn-link` reasoning. The card is the
|
|
1420
|
+
* floating panel popover and menu wear.
|
|
1421
|
+
*/
|
|
1422
|
+
export const hoverCard: RecipeInput = {
|
|
1423
|
+
component: 'hover-card',
|
|
1424
|
+
parts: {
|
|
1425
|
+
trigger: {
|
|
1426
|
+
base: {
|
|
1427
|
+
color: 'inherit',
|
|
1428
|
+
textDecorationLine: 'underline',
|
|
1429
|
+
textUnderlineOffset: '2px',
|
|
1430
|
+
cursor: 'pointer',
|
|
1431
|
+
borderRadius: 'var(--radius-selector)',
|
|
1432
|
+
},
|
|
1433
|
+
states: {
|
|
1434
|
+
hover: { textDecorationThickness: '2px' },
|
|
1435
|
+
open: { textDecorationThickness: '2px' },
|
|
1436
|
+
closed: {},
|
|
1437
|
+
...focusRing,
|
|
1438
|
+
},
|
|
1439
|
+
},
|
|
1440
|
+
popup: withPresence(popupPresence('translateY(-4px)'), {
|
|
1441
|
+
base: {
|
|
1442
|
+
...floatingPanel,
|
|
1443
|
+
padding: 'var(--space-xl)',
|
|
1444
|
+
maxWidth: '20rem',
|
|
1445
|
+
fontSize: 'var(--text-sm)',
|
|
1446
|
+
},
|
|
1447
|
+
states: { open: {}, closed: {} },
|
|
1448
|
+
}),
|
|
1449
|
+
},
|
|
1450
|
+
variants: {
|
|
1451
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { trigger: { base: { color: roleInk(c) } } }])),
|
|
1452
|
+
size: {
|
|
1453
|
+
xs: { trigger: { base: { fontSize: 'var(--text-xs)' } } },
|
|
1454
|
+
sm: { trigger: { base: { fontSize: 'var(--text-sm)' } } },
|
|
1455
|
+
md: {},
|
|
1456
|
+
lg: { trigger: { base: { fontSize: 'var(--text-lg)' } } },
|
|
1457
|
+
xl: { trigger: { base: { fontSize: 'var(--text-xl)' } } },
|
|
1458
|
+
},
|
|
1459
|
+
},
|
|
1460
|
+
// Web-only: the popover top layer and the runtime's arrow geometry have
|
|
1461
|
+
// no lynx counterpart.
|
|
1462
|
+
targets: { web: floatingArrow('hover-card', panelArrowPaint) },
|
|
1463
|
+
};
|
|
1464
|
+
|
|
1256
1465
|
// daisy "menu in a dropdown" look.
|
|
1257
1466
|
export const menu: RecipeInput = {
|
|
1258
1467
|
component: 'menu',
|
|
@@ -1386,6 +1595,17 @@ export const menu: RecipeInput = {
|
|
|
1386
1595
|
opacity: '0.6',
|
|
1387
1596
|
},
|
|
1388
1597
|
},
|
|
1598
|
+
// daisy's menu hint: the group label's faded ink, pushed to the
|
|
1599
|
+
// row's reading end — faded less, since 0.6 leaves nord's paper
|
|
1600
|
+
// under the 4.5:1 a line read by eye owes.
|
|
1601
|
+
shortcut: {
|
|
1602
|
+
base: {
|
|
1603
|
+
marginInlineStart: 'auto',
|
|
1604
|
+
paddingInlineStart: 'var(--space-lg)',
|
|
1605
|
+
fontSize: 'var(--text-xs)',
|
|
1606
|
+
opacity: '0.75',
|
|
1607
|
+
},
|
|
1608
|
+
},
|
|
1389
1609
|
separator: {
|
|
1390
1610
|
base: {
|
|
1391
1611
|
height: 'var(--border)',
|
|
@@ -1399,13 +1619,57 @@ export const menu: RecipeInput = {
|
|
|
1399
1619
|
variants: { color: btnColors(), size: btnSizes },
|
|
1400
1620
|
// The btn paddings, restated physically — see `lynxBtnPad` (#1084).
|
|
1401
1621
|
targets: {
|
|
1622
|
+
web: floatingArrow('menu', panelArrowPaint),
|
|
1402
1623
|
lynx: {
|
|
1403
|
-
parts: {
|
|
1624
|
+
parts: {
|
|
1625
|
+
trigger: { base: lynxBtnPad(4) },
|
|
1626
|
+
// The shortcut's reading-end push, restated physically (#1084).
|
|
1627
|
+
shortcut: { base: { marginLeft: 'auto', paddingLeft: 'var(--space-lg)' } },
|
|
1628
|
+
},
|
|
1404
1629
|
variants: { size: lynxBtnSizes('trigger') },
|
|
1405
1630
|
},
|
|
1406
1631
|
},
|
|
1407
1632
|
};
|
|
1408
1633
|
|
|
1634
|
+
// The menubar row: daisy's `menu-horizontal` strip — a base-200 box the
|
|
1635
|
+
// menu triggers sit in. The triggers are ordinary `menu` triggers.
|
|
1636
|
+
export const menubar: RecipeInput = {
|
|
1637
|
+
component: 'menubar',
|
|
1638
|
+
tokens: { '--menubar-surface': 'var(--color-base-200)' },
|
|
1639
|
+
parts: {
|
|
1640
|
+
root: {
|
|
1641
|
+
base: {
|
|
1642
|
+
display: 'flex',
|
|
1643
|
+
alignItems: 'center',
|
|
1644
|
+
gap: 'var(--space-xs)',
|
|
1645
|
+
padding: 'var(--space-xs)',
|
|
1646
|
+
width: 'max-content',
|
|
1647
|
+
maxWidth: '100%',
|
|
1648
|
+
flexWrap: 'wrap',
|
|
1649
|
+
background: 'var(--menubar-surface)',
|
|
1650
|
+
borderRadius: 'var(--radius-box)',
|
|
1651
|
+
},
|
|
1652
|
+
// The triggers fade themselves — see basic.
|
|
1653
|
+
states: { disabled: {} },
|
|
1654
|
+
selectors: {
|
|
1655
|
+
'&[data-orientation="vertical"]': { flexDirection: 'column', alignItems: 'stretch' },
|
|
1656
|
+
},
|
|
1657
|
+
},
|
|
1658
|
+
},
|
|
1659
|
+
variants: {
|
|
1660
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
1661
|
+
'--menubar-surface': `color-mix(in oklch, var(--color-${c}) 12%, var(--color-base-200))`,
|
|
1662
|
+
} } }])),
|
|
1663
|
+
size: {
|
|
1664
|
+
xs: { root: { base: { gap: '0', padding: '0' } } },
|
|
1665
|
+
sm: { root: { base: { padding: 'var(--space-2xs)' } } },
|
|
1666
|
+
md: {},
|
|
1667
|
+
lg: { root: { base: { gap: 'var(--space-sm)', padding: 'var(--space-sm)' } } },
|
|
1668
|
+
xl: { root: { base: { gap: 'var(--space-md)', padding: 'var(--space-sm)' } } },
|
|
1669
|
+
},
|
|
1670
|
+
},
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1409
1673
|
export const field: RecipeInput = {
|
|
1410
1674
|
component: 'field',
|
|
1411
1675
|
// The label's accent ink — base-content by default, so the un-attributed
|
|
@@ -1456,6 +1720,61 @@ export const field: RecipeInput = {
|
|
|
1456
1720
|
},
|
|
1457
1721
|
};
|
|
1458
1722
|
|
|
1723
|
+
/**
|
|
1724
|
+
* Fieldset (#285), daisyUI 5's `.fieldset` / `.fieldset-legend`: a
|
|
1725
|
+
* one-column grid with a tight row gap and a little block padding, no frame
|
|
1726
|
+
* of its own (daisy draws one only when the app adds `border`), and a
|
|
1727
|
+
* semibold legend with block padding standing in for its margin. The UA
|
|
1728
|
+
* `min-inline-size: min-content` is undone so a wide control cannot push the
|
|
1729
|
+
* group past its column. Colour accents the legend through `roleInk`, the
|
|
1730
|
+
* Field way.
|
|
1731
|
+
*/
|
|
1732
|
+
export const fieldset: RecipeInput = {
|
|
1733
|
+
component: 'fieldset',
|
|
1734
|
+
tokens: { '--fieldset-accent': 'var(--color-base-content)' },
|
|
1735
|
+
parts: {
|
|
1736
|
+
root: {
|
|
1737
|
+
base: {
|
|
1738
|
+
display: 'grid',
|
|
1739
|
+
gridTemplateColumns: '1fr',
|
|
1740
|
+
gridAutoRows: 'max-content',
|
|
1741
|
+
gap: 'var(--space-sm)',
|
|
1742
|
+
minInlineSize: '0',
|
|
1743
|
+
margin: '0',
|
|
1744
|
+
paddingBlock: 'var(--space-xs)',
|
|
1745
|
+
paddingInline: '0',
|
|
1746
|
+
border: 'none',
|
|
1747
|
+
},
|
|
1748
|
+
},
|
|
1749
|
+
legend: {
|
|
1750
|
+
base: {
|
|
1751
|
+
paddingBlock: 'var(--space-sm)',
|
|
1752
|
+
paddingInline: '0',
|
|
1753
|
+
fontSize: 'var(--text-sm)',
|
|
1754
|
+
fontWeight: 'var(--weight-semibold)',
|
|
1755
|
+
color: 'var(--fieldset-accent)',
|
|
1756
|
+
},
|
|
1757
|
+
states: {
|
|
1758
|
+
disabled: { opacity: 'var(--disabled-opacity)' },
|
|
1759
|
+
invalid: { color: roleInk('error') },
|
|
1760
|
+
},
|
|
1761
|
+
},
|
|
1762
|
+
},
|
|
1763
|
+
variants: {
|
|
1764
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
1765
|
+
'--fieldset-accent': roleInk(c),
|
|
1766
|
+
} } }])),
|
|
1767
|
+
size: {
|
|
1768
|
+
xs: { legend: { base: { fontSize: 'var(--text-xs)' } } },
|
|
1769
|
+
sm: { legend: { base: { fontSize: 'var(--text-xs)' } } },
|
|
1770
|
+
// `md` is the un-attributed render.
|
|
1771
|
+
md: {},
|
|
1772
|
+
lg: { legend: { base: { fontSize: 'var(--text-md)' } } },
|
|
1773
|
+
xl: { legend: { base: { fontSize: 'var(--text-lg)' } } },
|
|
1774
|
+
},
|
|
1775
|
+
},
|
|
1776
|
+
};
|
|
1777
|
+
|
|
1459
1778
|
/**
|
|
1460
1779
|
* daisy's tick, drawn by a clip-path that GROWS.
|
|
1461
1780
|
*
|
|
@@ -1962,9 +2281,13 @@ export const progress: RecipeInput = {
|
|
|
1962
2281
|
transition: 'width var(--duration-slow) var(--ease-standard)',
|
|
1963
2282
|
},
|
|
1964
2283
|
states: {
|
|
1965
|
-
// `complete`
|
|
1966
|
-
//
|
|
1967
|
-
|
|
2284
|
+
// `complete` keeps the colour axis. daisyUI 5 has no complete
|
|
2285
|
+
// tint — a full `progress-primary` stays primary — and a
|
|
2286
|
+
// `color="secondary"` bar that turns green at 100% reads as a
|
|
2287
|
+
// bug, not as meaning (signalxjs/lynx#1144). A caller who wants
|
|
2288
|
+
// "done" to look like success says so: `color="success"`.
|
|
2289
|
+
// Declared equal to `loading` in `sameAs` below.
|
|
2290
|
+
complete: {},
|
|
1968
2291
|
loading: {},
|
|
1969
2292
|
indeterminate: { width: '40%', animation: 'zero-daisy-indeterminate 1.2s ease-in-out infinite' },
|
|
1970
2293
|
},
|
|
@@ -2000,6 +2323,8 @@ export const progress: RecipeInput = {
|
|
|
2000
2323
|
// so the manifest states the default for runtimes with no `:not()`
|
|
2001
2324
|
// fallback (the lynx target; signalxjs/lynx#1070).
|
|
2002
2325
|
defaultVariants: { color: 'primary', size: 'md' },
|
|
2326
|
+
// A finished bar is a full bar of the same ink (see `complete` above).
|
|
2327
|
+
sameAs: { root: { complete: 'loading' }, range: { complete: 'loading' } },
|
|
2003
2328
|
keyframes: {
|
|
2004
2329
|
// Logical, so the sweep runs the way the bar fills — `margin-inline-start`
|
|
2005
2330
|
// is animatable and direction-aware on its own. The determinate `width`,
|
|
@@ -2021,6 +2346,10 @@ export const progress: RecipeInput = {
|
|
|
2021
2346
|
},
|
|
2022
2347
|
};
|
|
2023
2348
|
|
|
2349
|
+
/** The slider's deepened fill — the web's `--slider-fill` mix, for a given accent. */
|
|
2350
|
+
const sliderFill = (accent: string): string =>
|
|
2351
|
+
`color-mix(in oklab, ${accent} 90%, var(--color-base-content))`;
|
|
2352
|
+
|
|
2024
2353
|
export const slider: RecipeInput = {
|
|
2025
2354
|
component: 'slider',
|
|
2026
2355
|
// daisy's `.range` metrics: the thumb is `--range-thumb-size`
|
|
@@ -2331,29 +2660,100 @@ export const slider: RecipeInput = {
|
|
|
2331
2660
|
},
|
|
2332
2661
|
},
|
|
2333
2662
|
lynx: {
|
|
2663
|
+
// The web's deepened fill, theme-baked: `color-mix()` over theme
|
|
2664
|
+
// colours cannot resolve on device, but the emitter restates a
|
|
2665
|
+
// colour function per theme as literals (as `progress` does for
|
|
2666
|
+
// its accent), so each colour step gets the same 90/10 mix the
|
|
2667
|
+
// web track and knob ring wear. Bound on the root like
|
|
2668
|
+
// `--slider-accent`, rebound by `color` below and by `invalid`
|
|
2669
|
+
// on the control (which wraps the rail on lynx).
|
|
2670
|
+
tokens: { '--slider-fill': sliderFill('var(--color-primary)') },
|
|
2671
|
+
variants: {
|
|
2672
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
2673
|
+
'--slider-fill': sliderFill(`var(--color-${c})`),
|
|
2674
|
+
} } }])),
|
|
2675
|
+
},
|
|
2334
2676
|
parts: {
|
|
2677
|
+
// Vertical (lynx#1144): the lynx runtime positions the moving
|
|
2678
|
+
// parts itself — `top` percentages, the knob centred by
|
|
2679
|
+
// `left: 50%` plus the base's `translateY(-50%)` and
|
|
2680
|
+
// negative `margin-left` — so the class rules only turn the
|
|
2681
|
+
// channel upright: the control is a thumb-wide column of
|
|
2682
|
+
// daisy's rail length, the track fills it.
|
|
2683
|
+
root: {
|
|
2684
|
+
selectors: {
|
|
2685
|
+
'&[data-orientation="vertical"]': { width: 'auto', alignItems: 'center' },
|
|
2686
|
+
},
|
|
2687
|
+
},
|
|
2688
|
+
control: {
|
|
2689
|
+
states: {
|
|
2690
|
+
invalid: { '--slider-fill': sliderFill('var(--color-error)') },
|
|
2691
|
+
// The control's ring is the NATIVE input's on the web;
|
|
2692
|
+
// lynx always renders the composed parts, where the
|
|
2693
|
+
// thumb is what takes focus and wears the ring below
|
|
2694
|
+
// (signalxjs/lynx#1163 — both at once read as two
|
|
2695
|
+
// nested boxes).
|
|
2696
|
+
'focus-visible': { outline: 'none' },
|
|
2697
|
+
},
|
|
2698
|
+
selectors: {
|
|
2699
|
+
'&[data-orientation="vertical"]': {
|
|
2700
|
+
display: 'flex',
|
|
2701
|
+
flexDirection: 'row',
|
|
2702
|
+
justifyContent: 'center',
|
|
2703
|
+
width: 'var(--slider-thumb-size)',
|
|
2704
|
+
height: 'calc(var(--size-selector) * 40)',
|
|
2705
|
+
},
|
|
2706
|
+
},
|
|
2707
|
+
},
|
|
2335
2708
|
track: {
|
|
2336
2709
|
// The web track's `margin-block` breathing room,
|
|
2337
2710
|
// restated physically: logical spellings resolve on iOS
|
|
2338
2711
|
// but not on Android (measured, signalxjs/lynx#1084), so
|
|
2339
2712
|
// the emitter refuses them. Physical is the lynx
|
|
2340
2713
|
// target's norm — no RTL flow there.
|
|
2714
|
+
//
|
|
2715
|
+
// daisy's channel radius is the selector radius (the
|
|
2716
|
+
// native control's runnable-track), not a pill.
|
|
2341
2717
|
base: {
|
|
2342
2718
|
marginTop: 'calc((var(--slider-thumb-size) - var(--slider-track-size)) / 2)',
|
|
2343
2719
|
marginBottom: 'calc((var(--slider-thumb-size) - var(--slider-track-size)) / 2)',
|
|
2720
|
+
borderRadius: 'var(--radius-selector)',
|
|
2721
|
+
},
|
|
2722
|
+
selectors: {
|
|
2723
|
+
'&[data-orientation="vertical"]': {
|
|
2724
|
+
width: 'var(--slider-track-size)',
|
|
2725
|
+
height: '100%',
|
|
2726
|
+
marginTop: '0',
|
|
2727
|
+
marginBottom: '0',
|
|
2728
|
+
},
|
|
2344
2729
|
},
|
|
2345
2730
|
},
|
|
2346
2731
|
mark: {
|
|
2347
|
-
//
|
|
2348
|
-
//
|
|
2349
|
-
|
|
2732
|
+
// The web draws the tick with `::before`, which lynx has
|
|
2733
|
+
// no projection for — the mark was an empty, invisible
|
|
2734
|
+
// box. Here the mark IS the tick: a 2px rule across the
|
|
2735
|
+
// channel, centred on its runtime-set `left` percent.
|
|
2736
|
+
// (Lynx marks carry no label text.)
|
|
2737
|
+
base: {
|
|
2738
|
+
top: '0',
|
|
2739
|
+
width: '2px',
|
|
2740
|
+
height: 'var(--slider-track-size)',
|
|
2741
|
+
marginLeft: '-1px',
|
|
2742
|
+
paddingTop: '0',
|
|
2743
|
+
backgroundColor: 'var(--color-base-content)',
|
|
2744
|
+
},
|
|
2745
|
+
selectors: {
|
|
2746
|
+
'&[data-orientation="vertical"]': {
|
|
2747
|
+
left: '0',
|
|
2748
|
+
width: 'var(--slider-track-size)',
|
|
2749
|
+
height: '2px',
|
|
2750
|
+
marginLeft: '0',
|
|
2751
|
+
marginTop: '-1px',
|
|
2752
|
+
},
|
|
2753
|
+
},
|
|
2350
2754
|
},
|
|
2351
2755
|
range: {
|
|
2352
|
-
|
|
2353
|
-
// base-content (`progressFill`'s recipe); the plain
|
|
2354
|
-
// accent — a var() chain, proven working on device — is
|
|
2355
|
-
// the closest lynx-expressible equivalent.
|
|
2356
|
-
base: { background: 'var(--slider-accent)' },
|
|
2756
|
+
base: { background: 'var(--slider-fill)', borderRadius: 'var(--radius-selector)' },
|
|
2357
2757
|
},
|
|
2358
2758
|
thumb: {
|
|
2359
2759
|
// daisy's real range thumb, ported from daisyUI 5
|
|
@@ -2364,13 +2764,16 @@ export const slider: RecipeInput = {
|
|
|
2364
2764
|
// `--range-thumb` defaults to `var(--color-base-100)`,
|
|
2365
2765
|
// `--range-p` is daisy's literal `.25rem` (deliberately
|
|
2366
2766
|
// not the theme's size token), and the border ink is the
|
|
2367
|
-
//
|
|
2368
|
-
//
|
|
2369
|
-
//
|
|
2370
|
-
//
|
|
2371
|
-
//
|
|
2372
|
-
//
|
|
2373
|
-
//
|
|
2767
|
+
// deepened fill the web's native thumb ring reads. daisy
|
|
2768
|
+
// paints the progress fill itself as a 100cqw box-shadow
|
|
2769
|
+
// thrown off this thumb — a native-input trick with no
|
|
2770
|
+
// lynx equivalent; the composed `.zx-slider__range` box
|
|
2771
|
+
// above IS that fill here, so the knob only needs its own
|
|
2772
|
+
// paints. Its radius is daisy's formula minus the min()
|
|
2773
|
+
// clamp lynx cannot evaluate — exact for every shipped
|
|
2774
|
+
// theme (all set `--radius-selector` ≥ .25rem / 3) — and
|
|
2775
|
+
// it wears daisy's depth shading (`currentColor`'s 10%
|
|
2776
|
+
// outer shadow spelled as the theme's shade).
|
|
2374
2777
|
//
|
|
2375
2778
|
// The centering geometry is restated physically beside
|
|
2376
2779
|
// the paint: `inset-block-start`/`translate`/
|
|
@@ -2381,11 +2784,20 @@ export const slider: RecipeInput = {
|
|
|
2381
2784
|
// proven on both platforms.
|
|
2382
2785
|
base: {
|
|
2383
2786
|
background: 'var(--color-base-100)',
|
|
2384
|
-
border: '0.25rem solid var(--slider-
|
|
2787
|
+
border: '0.25rem solid var(--slider-fill)',
|
|
2788
|
+
borderRadius: 'calc(var(--radius-selector) + 0.25rem)',
|
|
2789
|
+
boxShadow: '0 -1px var(--depth-shade) inset, 0 8px 0 -4px var(--depth-sheen) inset, 0 1px var(--depth-shade)',
|
|
2385
2790
|
top: '50%',
|
|
2386
2791
|
transform: 'translateY(-50%)',
|
|
2387
2792
|
marginLeft: 'calc(var(--slider-thumb-size) / -2)',
|
|
2388
2793
|
},
|
|
2794
|
+
// The round ring (signalxjs/lynx#1163), over the knob's
|
|
2795
|
+
// depth relief.
|
|
2796
|
+
states: {
|
|
2797
|
+
'focus-visible': lynxFocusRing('var(--slider-accent)', {
|
|
2798
|
+
under: '0 -1px var(--depth-shade) inset, 0 8px 0 -4px var(--depth-sheen) inset, 0 1px var(--depth-shade)',
|
|
2799
|
+
}),
|
|
2800
|
+
},
|
|
2389
2801
|
},
|
|
2390
2802
|
},
|
|
2391
2803
|
},
|
|
@@ -2394,7 +2806,31 @@ export const slider: RecipeInput = {
|
|
|
2394
2806
|
|
|
2395
2807
|
export const accordion: RecipeInput = {
|
|
2396
2808
|
component: 'accordion',
|
|
2397
|
-
targets:
|
|
2809
|
+
targets: {
|
|
2810
|
+
...disclosureExit('accordion'),
|
|
2811
|
+
lynx: {
|
|
2812
|
+
parts: {
|
|
2813
|
+
// No hover on a touch platform: the held trigger takes daisy's
|
|
2814
|
+
// hover fill. The ::after chevron has no lynx counterpart (no
|
|
2815
|
+
// pseudo-elements, and the anatomy declares no part for it) —
|
|
2816
|
+
// an app draws its own glyph in the trigger.
|
|
2817
|
+
//
|
|
2818
|
+
// The item card clips (`overflow: hidden`) and the trigger
|
|
2819
|
+
// fills it, so an outside ring is clipped away entirely
|
|
2820
|
+
// (signalxjs/lynx#1164): the ring is drawn inset, rounded to
|
|
2821
|
+
// the card's inner corners.
|
|
2822
|
+
trigger: {
|
|
2823
|
+
states: {
|
|
2824
|
+
pressed: { background: 'var(--color-base-200)' },
|
|
2825
|
+
'focus-visible': {
|
|
2826
|
+
...lynxFocusRing('var(--color-base-content)', { inset: true }),
|
|
2827
|
+
borderRadius: 'calc(var(--radius-box) - var(--border))',
|
|
2828
|
+
},
|
|
2829
|
+
},
|
|
2830
|
+
},
|
|
2831
|
+
},
|
|
2832
|
+
},
|
|
2833
|
+
},
|
|
2398
2834
|
// Same accent grammar as collapsible: the open heading's ink, defaulting
|
|
2399
2835
|
// to base-content so the un-attributed render is unchanged.
|
|
2400
2836
|
tokens: { '--accordion-accent': 'var(--color-base-content)' },
|
|
@@ -2678,10 +3114,47 @@ export const select: RecipeInput = {
|
|
|
2678
3114
|
web: { parts: { popup: { base: anchoredListbox } } },
|
|
2679
3115
|
lynx: {
|
|
2680
3116
|
parts: {
|
|
2681
|
-
|
|
2682
|
-
|
|
3117
|
+
// daisy's `.select` is `width: clamp(3rem, 20rem, 100%)`: 20rem,
|
|
3118
|
+
// never wider than its container. lynx has no `clamp()`, so the
|
|
3119
|
+
// same bounds are spelled out — and the base's 13rem floor
|
|
3120
|
+
// comes down to daisy's 3rem, or the trigger overflowed any
|
|
3121
|
+
// cell narrower than 208px (signalxjs/lynx#1146).
|
|
3122
|
+
//
|
|
3123
|
+
// The bounds sit on the ROOT, the box daisy's `.select` is,
|
|
3124
|
+
// and the trigger fills it. The web root is `inline-flex`, so
|
|
3125
|
+
// it shrink-wraps the trigger; lynx rewrites that to `flex`,
|
|
3126
|
+
// and a root stretched by its parent while the trigger stayed
|
|
3127
|
+
// capped left the clear-trigger — positioned against the root
|
|
3128
|
+
// — floating past the trigger's end (signalxjs/lynx#1166).
|
|
3129
|
+
root: { base: { width: '20rem', maxWidth: '100%', minWidth: '3rem' } },
|
|
3130
|
+
trigger: {
|
|
3131
|
+
base: { ...lynxBtnPad(4), width: '100%', maxWidth: '100%', minWidth: '3rem' },
|
|
3132
|
+
// The web's `:hover` border is dropped on a touch
|
|
3133
|
+
// platform; a press gets a wash instead — a background,
|
|
3134
|
+
// so it never fights the open/invalid border inks.
|
|
3135
|
+
states: { pressed: { background: 'var(--color-base-200)' }, ...lynxFocus() },
|
|
3136
|
+
},
|
|
3137
|
+
// The popup renders in the overlay outlet, outside the root,
|
|
3138
|
+
// so it cannot inherit the root's `--select-accent`, and an
|
|
3139
|
+
// unresolved `var()` on lynx has no fallback: the selected
|
|
3140
|
+
// item and its tick fell back to base-content
|
|
3141
|
+
// (signalxjs/lynx#1168). The popup restates the accent; its
|
|
3142
|
+
// colour-axis twins are in `variants.color` below.
|
|
3143
|
+
popup: { base: { '--select-accent': 'var(--color-primary)' } },
|
|
3144
|
+
// A touch list has no highlight: the pressed item takes the
|
|
3145
|
+
// highlighted item's wash.
|
|
3146
|
+
item: { states: { pressed: { background: 'var(--color-base-200)' } } },
|
|
3147
|
+
'clear-trigger': {
|
|
3148
|
+
base: { top: 'var(--space-xs)', bottom: 'var(--space-xs)', right: 'calc(var(--size-field) * 4 + 1em + var(--space-xs))', minWidth: '1.5rem' },
|
|
3149
|
+
states: lynxFocus(),
|
|
3150
|
+
},
|
|
2683
3151
|
},
|
|
2684
3152
|
variants: {
|
|
3153
|
+
// The popup's accent follows the root's colour axis — the
|
|
3154
|
+
// popup carries the axis class itself (lynx stamps every part).
|
|
3155
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { popup: { base: {
|
|
3156
|
+
'--select-accent': `var(--color-${c})`,
|
|
3157
|
+
} } }])),
|
|
2685
3158
|
size: { ...lynxBtnSizes('trigger'), md: { trigger: { base: lynxBtnPad(4) } } },
|
|
2686
3159
|
},
|
|
2687
3160
|
},
|
|
@@ -2706,6 +3179,40 @@ const BTN_STEPS = { xs: 6, sm: 8, md: 10, lg: 12, xl: 14 } as const;
|
|
|
2706
3179
|
const btnHeight = (step: keyof typeof BTN_STEPS): string =>
|
|
2707
3180
|
`calc(var(--size-field) * ${BTN_STEPS[step]})`;
|
|
2708
3181
|
|
|
3182
|
+
/**
|
|
3183
|
+
* The btn padding ramp — `[size, block space, inline space]` per step. One
|
|
3184
|
+
* table for the web's `padding` shorthand and the lynx section's longhands,
|
|
3185
|
+
* so the two spellings cannot drift apart.
|
|
3186
|
+
*/
|
|
3187
|
+
const BTN_PAD = [
|
|
3188
|
+
['xs', '2xs', 'xs'],
|
|
3189
|
+
['sm', 'xs', 'sm'],
|
|
3190
|
+
['md', 'sm', 'lg'],
|
|
3191
|
+
['lg', 'md', 'xl'],
|
|
3192
|
+
['xl', 'lg', '2xl'],
|
|
3193
|
+
] as const satisfies readonly (readonly [keyof typeof BTN_STEPS, string, string])[];
|
|
3194
|
+
const btnPadding = (step: keyof typeof BTN_STEPS): string => {
|
|
3195
|
+
const [, block, inline] = BTN_PAD.find(([size]) => size === step)!;
|
|
3196
|
+
return `var(--space-${block}) var(--space-${inline})`;
|
|
3197
|
+
};
|
|
3198
|
+
|
|
3199
|
+
/**
|
|
3200
|
+
* daisy 5's disabled btn, for the lynx target (signalxjs/lynx#1143). The web
|
|
3201
|
+
* fades the whole button (`opacity: var(--disabled-opacity)`, 0.3 on the
|
|
3202
|
+
* dark themes), which on device left a solid button's label barely legible.
|
|
3203
|
+
* daisy's own `.btn:disabled` paints instead: a base-content wash (not on
|
|
3204
|
+
* ghost/link), a transparent border, no shadow, and a muted base-content
|
|
3205
|
+
* label. daisy mutes that label to 20%; this keeps it at 50%, still plainly
|
|
3206
|
+
* inert but readable on a phone at arm's length.
|
|
3207
|
+
*/
|
|
3208
|
+
const lynxBtnDisabled: CssProps = {
|
|
3209
|
+
opacity: '1',
|
|
3210
|
+
background: 'color-mix(in oklab, var(--color-base-content) 10%, transparent)',
|
|
3211
|
+
color: 'color-mix(in oklab, var(--color-base-content) 50%, transparent)',
|
|
3212
|
+
borderColor: 'transparent',
|
|
3213
|
+
boxShadow: 'none',
|
|
3214
|
+
};
|
|
3215
|
+
|
|
2709
3216
|
export const button: RecipeInput = {
|
|
2710
3217
|
component: 'button',
|
|
2711
3218
|
// Public to a design system derived from this one (#73).
|
|
@@ -2886,11 +3393,11 @@ export const button: RecipeInput = {
|
|
|
2886
3393
|
// The `<button>` element is UA `border-box`, so the stated height is
|
|
2887
3394
|
// the rendered height, border included — as in daisy.
|
|
2888
3395
|
size: {
|
|
2889
|
-
xs: { root: { base: { height: btnHeight('xs'), padding: '
|
|
2890
|
-
sm: { root: { base: { height: btnHeight('sm'), padding: '
|
|
2891
|
-
md: { root: { base: { height: btnHeight('md'), padding: '
|
|
2892
|
-
lg: { root: { base: { height: btnHeight('lg'), padding: '
|
|
2893
|
-
xl: { root: { base: { height: btnHeight('xl'), padding: '
|
|
3396
|
+
xs: { root: { base: { height: btnHeight('xs'), padding: btnPadding('xs'), fontSize: 'var(--text-xs)' } } },
|
|
3397
|
+
sm: { root: { base: { height: btnHeight('sm'), padding: btnPadding('sm'), fontSize: 'var(--text-sm)' } } },
|
|
3398
|
+
md: { root: { base: { height: btnHeight('md'), padding: btnPadding('md'), fontSize: 'var(--text-md)' } } },
|
|
3399
|
+
lg: { root: { base: { height: btnHeight('lg'), padding: btnPadding('lg'), fontSize: 'var(--text-lg)' } } },
|
|
3400
|
+
xl: { root: { base: { height: btnHeight('xl'), padding: btnPadding('xl'), fontSize: 'var(--text-xl)' } } },
|
|
2894
3401
|
},
|
|
2895
3402
|
},
|
|
2896
3403
|
/**
|
|
@@ -2921,14 +3428,96 @@ export const button: RecipeInput = {
|
|
|
2921
3428
|
},
|
|
2922
3429
|
defaultVariants: { color: 'primary', variant: 'solid', size: 'md' },
|
|
2923
3430
|
keyframes: { 'zero-daisyui-btn-spin': 'to { transform: rotate(360deg) }' },
|
|
2924
|
-
// The icon chips' zeroed inline padding, restated physically: logical
|
|
2925
|
-
// spellings resolve on iOS but not on Android (measured,
|
|
2926
|
-
// signalxjs/lynx#1084), so the emitter refuses them.
|
|
2927
3431
|
targets: {
|
|
2928
3432
|
lynx: {
|
|
3433
|
+
parts: {
|
|
3434
|
+
// The ring, restated: `currentColor` never resolves on lynx
|
|
3435
|
+
// (signalxjs/lynx#1079) and the logical sizes/edge are
|
|
3436
|
+
// unproven there, so the lynx ring is physical longhands in
|
|
3437
|
+
// the variant's own ink (the variants below pick it). Every
|
|
3438
|
+
// var-bearing edge is a longhand (`lynxSides`): a var-bearing
|
|
3439
|
+
// `border` expanded after the transparent top and closed the
|
|
3440
|
+
// ring (signalxjs/lynx#1161). The shorthand stays, static, so
|
|
3441
|
+
// the shared `currentColor` spelling never ships.
|
|
3442
|
+
spinner: {
|
|
3443
|
+
base: {
|
|
3444
|
+
width: '1em',
|
|
3445
|
+
height: '1em',
|
|
3446
|
+
border: '0 solid transparent',
|
|
3447
|
+
...lynxSides('border{}Width', 'calc(var(--border) * 2)'),
|
|
3448
|
+
...lynxSides('border{}Color', 'var(--btn-ink)', ['Right', 'Bottom', 'Left']),
|
|
3449
|
+
borderTopColor: 'transparent',
|
|
3450
|
+
},
|
|
3451
|
+
},
|
|
3452
|
+
root: {
|
|
3453
|
+
// daisy's content-sized, never-squeezed btn (#1165), and
|
|
3454
|
+
// the hairline as longhands: a var-bearing `border`
|
|
3455
|
+
// shorthand would expand over the variants' and the
|
|
3456
|
+
// disabled state's edge colours (see `lynxSides`).
|
|
3457
|
+
base: {
|
|
3458
|
+
...lynxBtnFit,
|
|
3459
|
+
border: '0 solid transparent',
|
|
3460
|
+
...lynxSides('border{}Width', 'var(--border)'),
|
|
3461
|
+
},
|
|
3462
|
+
// The ring in the variant's own ink (signalxjs/lynx#1163).
|
|
3463
|
+
states: { disabled: lynxBtnDisabled, 'focus-visible': lynxFocusRing('var(--btn-ink)') },
|
|
3464
|
+
},
|
|
3465
|
+
},
|
|
3466
|
+
variants: {
|
|
3467
|
+
// The baked disabled paint above outranks every variant's fill
|
|
3468
|
+
// (the theme host adds a class), but its plain declarations
|
|
3469
|
+
// do not: outline/dash restate the border, ghost/link drop
|
|
3470
|
+
// the wash (as in daisy). outline/dash draw their ink edge as
|
|
3471
|
+
// longhands, so that transparent restatement wins by
|
|
3472
|
+
// specificity instead of losing to a var-bearing
|
|
3473
|
+
// `border-color` (signalxjs/lynx#1162); the shared shorthand
|
|
3474
|
+
// is neutralised to a static value.
|
|
3475
|
+
variant: {
|
|
3476
|
+
solid: { spinner: { base: lynxSides('border{}Color', 'var(--btn-on-accent)', ['Right', 'Bottom', 'Left']) } },
|
|
3477
|
+
outline: {
|
|
3478
|
+
root: {
|
|
3479
|
+
base: { borderColor: 'transparent', ...lynxSides('border{}Color', 'var(--btn-ink)') },
|
|
3480
|
+
states: { disabled: { borderColor: 'transparent' } },
|
|
3481
|
+
},
|
|
3482
|
+
},
|
|
3483
|
+
dash: {
|
|
3484
|
+
root: {
|
|
3485
|
+
base: { borderColor: 'transparent', ...lynxSides('border{}Color', 'var(--btn-ink)') },
|
|
3486
|
+
states: { disabled: { borderColor: 'transparent' } },
|
|
3487
|
+
},
|
|
3488
|
+
},
|
|
3489
|
+
ghost: { root: { states: { disabled: { background: 'transparent' } } } },
|
|
3490
|
+
// `link` zeroes the box-shadow in a variant rule that
|
|
3491
|
+
// lands after the ring, so it restates the ring.
|
|
3492
|
+
link: { root: { states: { disabled: { background: 'transparent' }, 'focus-visible': lynxFocusRing('var(--btn-ink)') } } },
|
|
3493
|
+
},
|
|
3494
|
+
// The size ramp's paddings as longhands, so square/circle's
|
|
3495
|
+
// zeroed sides below win by the cascade rather than by the
|
|
3496
|
+
// order lynx happens to expand a var-bearing `padding` in.
|
|
3497
|
+
size: Object.fromEntries(BTN_PAD.map(([size, block, inline]) => [size, {
|
|
3498
|
+
root: {
|
|
3499
|
+
base: {
|
|
3500
|
+
padding: '0',
|
|
3501
|
+
...lynxSides('padding{}', `var(--space-${block})`, ['Top', 'Bottom']),
|
|
3502
|
+
...lynxSides('padding{}', `var(--space-${inline})`, ['Right', 'Left']),
|
|
3503
|
+
},
|
|
3504
|
+
},
|
|
3505
|
+
}])),
|
|
3506
|
+
},
|
|
3507
|
+
// The icon chips' zeroed inline padding, restated physically: logical
|
|
3508
|
+
// spellings resolve on iOS but not on Android (measured,
|
|
3509
|
+
// signalxjs/lynx#1084), so the emitter refuses them. `width: auto`
|
|
3510
|
+
// undoes the root's `max-content`, which would otherwise switch
|
|
3511
|
+
// off the `aspect-ratio` that squares the chip. `wide`/`block`
|
|
3512
|
+
// restate their width physically: `inline-size` and
|
|
3513
|
+
// `max-inline-size` are not lynx properties at all.
|
|
2929
3514
|
modifiers: {
|
|
2930
|
-
|
|
2931
|
-
|
|
3515
|
+
wide: { root: { base: { width: '100%', maxWidth: '16rem' } } },
|
|
3516
|
+
block: { root: { base: { width: '100%' } } },
|
|
3517
|
+
square: { root: { base: { width: 'auto', paddingLeft: '0', paddingRight: '0' } } },
|
|
3518
|
+
circle: { root: { base: { width: 'auto', paddingLeft: '0', paddingRight: '0' } } },
|
|
3519
|
+
// `active` zeroes the box-shadow too (the held press look).
|
|
3520
|
+
active: { root: { states: { 'focus-visible': lynxFocusRing('var(--btn-ink)') } } },
|
|
2932
3521
|
},
|
|
2933
3522
|
},
|
|
2934
3523
|
},
|
|
@@ -3010,6 +3599,24 @@ export const avatar: RecipeInput = {
|
|
|
3010
3599
|
},
|
|
3011
3600
|
};
|
|
3012
3601
|
|
|
3602
|
+
/**
|
|
3603
|
+
* A toast whose indicator is rendered (#292): the grid grows a leading column
|
|
3604
|
+
* for the mark, and the text, action and close step one column along.
|
|
3605
|
+
*/
|
|
3606
|
+
const TOAST_MARKED = '[data-scope="toast"][data-part="root"]:has(> [data-scope="toast"][data-part="indicator"]) > &';
|
|
3607
|
+
/** The indicator's marks, cut from its fill — not mirrored in RTL (a tick is not a direction). */
|
|
3608
|
+
const TOAST_CHECK = 'polygon(15.1% 41.3%, 1% 55%, 37.6% 92.8%, 99% 19.8%, 83.9% 7.2%, 36.6% 63.5%)';
|
|
3609
|
+
const TOAST_CROSS = 'polygon(20% 8%, 50% 38%, 80% 8%, 92% 20%, 62% 50%, 92% 80%, 80% 92%, 50% 62%, 20% 92%, 8% 80%, 38% 50%, 8% 20%)';
|
|
3610
|
+
|
|
3611
|
+
/**
|
|
3612
|
+
* daisy's success and error are FILLS — light enough in `light` and `nord`
|
|
3613
|
+
* that a bare mark in them reads under 2:1 on the toast's base-200. Deepened
|
|
3614
|
+
* toward base-content (which flips with the theme), the hue survives and the
|
|
3615
|
+
* mark clears the 3:1 non-text floor in every theme.
|
|
3616
|
+
*/
|
|
3617
|
+
const TOAST_SUCCESS_INK = 'color-mix(in oklab, var(--color-success) 55%, var(--color-base-content))';
|
|
3618
|
+
const TOAST_ERROR_INK = 'color-mix(in oklab, var(--color-error) 55%, var(--color-base-content))';
|
|
3619
|
+
|
|
3013
3620
|
/**
|
|
3014
3621
|
* Toast presence is runtime-managed (see the SKILL's Toast section): plain
|
|
3015
3622
|
* two-state transitions only — no `@starting-style`, no `allow-discrete`.
|
|
@@ -3020,6 +3627,7 @@ export const toast: RecipeInput = {
|
|
|
3020
3627
|
'--toast-bg': 'var(--color-base-200)',
|
|
3021
3628
|
'--toast-ink': 'var(--color-base-content)',
|
|
3022
3629
|
'--toast-from': '8px',
|
|
3630
|
+
'--toast-mark': '1rem',
|
|
3023
3631
|
},
|
|
3024
3632
|
parts: {
|
|
3025
3633
|
viewport: {
|
|
@@ -3073,6 +3681,7 @@ export const toast: RecipeInput = {
|
|
|
3073
3681
|
},
|
|
3074
3682
|
selectors: {
|
|
3075
3683
|
'&[data-placement^="top"]': { '--toast-from': '-8px' },
|
|
3684
|
+
'&:has(> [data-scope="toast"][data-part="indicator"])': { gridTemplateColumns: 'auto 1fr auto auto' },
|
|
3076
3685
|
},
|
|
3077
3686
|
states: {
|
|
3078
3687
|
open: { opacity: '1', transform: 'none' },
|
|
@@ -3082,8 +3691,39 @@ export const toast: RecipeInput = {
|
|
|
3082
3691
|
'reduced-motion': { base: { transition: 'none' }, states: { open: { transform: 'none' } } },
|
|
3083
3692
|
},
|
|
3084
3693
|
},
|
|
3694
|
+
// daisy's `loading-spinner` ring in the alert's own ink, then the
|
|
3695
|
+
// success tick or the error cross — the promise toast's status.
|
|
3696
|
+
indicator: {
|
|
3697
|
+
base: {
|
|
3698
|
+
gridColumn: '1',
|
|
3699
|
+
gridRow: '1 / span 2',
|
|
3700
|
+
inlineSize: 'var(--toast-mark)',
|
|
3701
|
+
blockSize: 'var(--toast-mark)',
|
|
3702
|
+
boxSizing: 'border-box',
|
|
3703
|
+
},
|
|
3704
|
+
states: {
|
|
3705
|
+
loading: {
|
|
3706
|
+
borderRadius: '50%',
|
|
3707
|
+
border: 'calc(var(--border) * 2) solid color-mix(in oklab, var(--toast-ink) 25%, transparent)',
|
|
3708
|
+
borderBlockStartColor: 'var(--toast-ink)',
|
|
3709
|
+
animation: 'zero-daisyui-toast-spin 0.75s linear infinite',
|
|
3710
|
+
},
|
|
3711
|
+
complete: { background: TOAST_SUCCESS_INK, clipPath: TOAST_CHECK },
|
|
3712
|
+
error: { background: TOAST_ERROR_INK, clipPath: TOAST_CROSS },
|
|
3713
|
+
},
|
|
3714
|
+
at: {
|
|
3715
|
+
'reduced-motion': { states: { loading: { animation: 'none' } } },
|
|
3716
|
+
'forced-colors': {
|
|
3717
|
+
states: {
|
|
3718
|
+
complete: { background: 'CanvasText', forcedColorAdjust: 'none' },
|
|
3719
|
+
error: { background: 'CanvasText', forcedColorAdjust: 'none' },
|
|
3720
|
+
},
|
|
3721
|
+
},
|
|
3722
|
+
},
|
|
3723
|
+
},
|
|
3085
3724
|
title: {
|
|
3086
3725
|
base: { gridColumn: '1', fontWeight: 'var(--weight-semibold)' },
|
|
3726
|
+
selectors: { [TOAST_MARKED]: { gridColumn: '2' } },
|
|
3087
3727
|
},
|
|
3088
3728
|
description: {
|
|
3089
3729
|
base: {
|
|
@@ -3091,6 +3731,7 @@ export const toast: RecipeInput = {
|
|
|
3091
3731
|
fontSize: 'var(--text-xs)',
|
|
3092
3732
|
color: 'color-mix(in oklab, var(--toast-ink) 75%, transparent)',
|
|
3093
3733
|
},
|
|
3734
|
+
selectors: { [TOAST_MARKED]: { gridColumn: '2' } },
|
|
3094
3735
|
},
|
|
3095
3736
|
action: {
|
|
3096
3737
|
base: {
|
|
@@ -3111,6 +3752,7 @@ export const toast: RecipeInput = {
|
|
|
3111
3752
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
3112
3753
|
...focusRing,
|
|
3113
3754
|
},
|
|
3755
|
+
selectors: { [TOAST_MARKED]: { gridColumn: '3' } },
|
|
3114
3756
|
},
|
|
3115
3757
|
close: {
|
|
3116
3758
|
base: {
|
|
@@ -3130,6 +3772,7 @@ export const toast: RecipeInput = {
|
|
|
3130
3772
|
disabled: { opacity: 'var(--disabled-opacity)' },
|
|
3131
3773
|
...focusRing,
|
|
3132
3774
|
},
|
|
3775
|
+
selectors: { [TOAST_MARKED]: { gridColumn: '4' } },
|
|
3133
3776
|
},
|
|
3134
3777
|
},
|
|
3135
3778
|
variants: {
|
|
@@ -3166,24 +3809,79 @@ export const toast: RecipeInput = {
|
|
|
3166
3809
|
// signalxjs/lynx#1070. No `color` default: the un-attributed toast is a
|
|
3167
3810
|
// neutral base-200 surface, outside the color vocabulary.
|
|
3168
3811
|
defaultVariants: { size: 'md' },
|
|
3169
|
-
|
|
3170
|
-
// resolve on iOS but not on Android (measured, signalxjs/lynx#1084), so
|
|
3171
|
-
// the emitter refuses them. Physical is the lynx target's norm — no RTL
|
|
3172
|
-
// flow there, so `start` IS left and `end` IS right.
|
|
3812
|
+
keyframes: { 'zero-daisyui-toast-spin': 'to { transform: rotate(360deg); }' },
|
|
3173
3813
|
targets: {
|
|
3174
3814
|
lynx: {
|
|
3175
3815
|
parts: {
|
|
3816
|
+
// The viewport is a full-width strip on lynx (the runtime pins
|
|
3817
|
+
// it to the outlet's top or bottom edge). What the web gets
|
|
3818
|
+
// from `:popover-open` and `min(24rem, 100vw)` is restated:
|
|
3819
|
+
// flex (a lynx view defaults to linear layout, which ignores
|
|
3820
|
+
// flex-direction and gap), and the cards aligned to the
|
|
3821
|
+
// placement's edge. The centred pair's `left: 50%` +
|
|
3822
|
+
// `translateX(-50%)` pull-back is for a shrink-wrapped popover;
|
|
3823
|
+
// on the strip it shifted everything half a width left, so it
|
|
3824
|
+
// is cancelled. The corner anchors are physical: logical
|
|
3825
|
+
// insets resolve on iOS but not on Android (measured,
|
|
3826
|
+
// signalxjs/lynx#1084), and lynx has no RTL flow, so `start`
|
|
3827
|
+
// IS left and `end` IS right.
|
|
3176
3828
|
viewport: {
|
|
3829
|
+
base: { display: 'flex', width: 'auto' },
|
|
3830
|
+
selectors: {
|
|
3831
|
+
'&[data-placement="top-start"]': { left: '0', alignItems: 'flex-start' },
|
|
3832
|
+
'&[data-placement="top"]': { left: '0', right: '0', transform: 'none', alignItems: 'center' },
|
|
3833
|
+
'&[data-placement="top-end"]': { right: '0', alignItems: 'flex-end' },
|
|
3834
|
+
'&[data-placement="bottom-start"]': { left: '0', alignItems: 'flex-start' },
|
|
3835
|
+
'&[data-placement="bottom"]': { left: '0', right: '0', transform: 'none', alignItems: 'center' },
|
|
3836
|
+
'&[data-placement="bottom-end"]': { right: '0', alignItems: 'flex-end' },
|
|
3837
|
+
},
|
|
3838
|
+
},
|
|
3839
|
+
// No grid on lynx (signalxjs/lynx#1075): the card is a flex
|
|
3840
|
+
// column (title, description, then the action), with the
|
|
3841
|
+
// close button pinned to the top-right corner and the title
|
|
3842
|
+
// kept clear of it. The web's 24rem viewport cap becomes the
|
|
3843
|
+
// card's own cap. The `^="top"` entry offset has no class
|
|
3844
|
+
// form, so each top placement restates it.
|
|
3845
|
+
root: {
|
|
3846
|
+
base: {
|
|
3847
|
+
display: 'flex',
|
|
3848
|
+
flexDirection: 'column',
|
|
3849
|
+
alignItems: 'flex-start',
|
|
3850
|
+
position: 'relative',
|
|
3851
|
+
boxSizing: 'border-box',
|
|
3852
|
+
width: '100%',
|
|
3853
|
+
maxWidth: '24rem',
|
|
3854
|
+
},
|
|
3177
3855
|
selectors: {
|
|
3178
|
-
'&[data-placement="top-start"]': {
|
|
3179
|
-
'&[data-placement="top
|
|
3180
|
-
'&[data-placement="
|
|
3181
|
-
|
|
3856
|
+
'&[data-placement="top-start"]': { '--toast-from': '-8px' },
|
|
3857
|
+
'&[data-placement="top"]': { '--toast-from': '-8px' },
|
|
3858
|
+
'&[data-placement="top-end"]': { '--toast-from': '-8px' },
|
|
3859
|
+
},
|
|
3860
|
+
},
|
|
3861
|
+
title: { base: { paddingRight: '1.75em' } },
|
|
3862
|
+
// The 75% ink mix over a recipe-local property cannot bake;
|
|
3863
|
+
// the same ink at 75% opacity reads the same on the soft fill.
|
|
3864
|
+
description: { base: { color: 'var(--toast-ink)', opacity: '0.75' } },
|
|
3865
|
+
// The 25% ink border cannot bake either: it takes the full ink.
|
|
3866
|
+
// Their rings' gap is the card's own fill.
|
|
3867
|
+
action: {
|
|
3868
|
+
base: {
|
|
3869
|
+
marginTop: 'var(--space-xs)',
|
|
3870
|
+
border: 'var(--border) solid var(--toast-ink)',
|
|
3182
3871
|
},
|
|
3872
|
+
states: lynxFocus({ gap: 'var(--toast-bg)' }),
|
|
3873
|
+
},
|
|
3874
|
+
close: {
|
|
3875
|
+
base: { position: 'absolute', top: 'var(--space-xs)', right: 'var(--space-xs)' },
|
|
3876
|
+
states: lynxFocus({ gap: 'var(--toast-bg)' }),
|
|
3183
3877
|
},
|
|
3184
3878
|
},
|
|
3185
3879
|
},
|
|
3186
3880
|
},
|
|
3881
|
+
// The viewport's `open` (the stack expanded) is a fact this skin has no
|
|
3882
|
+
// use for: its toasts are always a plain column, so both states look the
|
|
3883
|
+
// same by design (#292).
|
|
3884
|
+
skipStates: { viewport: ['open', 'closed'] },
|
|
3187
3885
|
};
|
|
3188
3886
|
|
|
3189
3887
|
export const combobox: RecipeInput = {
|
|
@@ -3999,12 +4697,29 @@ const treeRowSelectors: NonNullable<PartStyles['selectors']> = {
|
|
|
3999
4697
|
},
|
|
4000
4698
|
};
|
|
4001
4699
|
|
|
4700
|
+
/**
|
|
4701
|
+
* `tickGlyphFallback` for the node check box, whose tick is its `::after`:
|
|
4702
|
+
* the geometry drops and the same pseudo carries the glyph, in the medium's
|
|
4703
|
+
* own ink (the #233 divergence, restated).
|
|
4704
|
+
*/
|
|
4705
|
+
const nodeTickFallback = (ink: string): PartStyles => ({
|
|
4706
|
+
selectors: {
|
|
4707
|
+
'&::after': { clipPath: 'none', backgroundColor: '#0000', rotate: '0deg', inset: '0', display: 'grid', placeItems: 'center', color: ink, fontSize: '0.85em', lineHeight: '1' },
|
|
4708
|
+
'&[data-state="checked"]::after': { content: '"✔︎"', clipPath: 'none', rotate: '0deg' },
|
|
4709
|
+
'&[data-state="indeterminate"]::after': { content: '"−"', clipPath: 'none', rotate: '0deg', translate: 'none' },
|
|
4710
|
+
},
|
|
4711
|
+
});
|
|
4712
|
+
|
|
4002
4713
|
export const treeView: RecipeInput = {
|
|
4003
4714
|
component: 'tree-view',
|
|
4004
4715
|
tokens: {
|
|
4005
4716
|
'--tree-accent': 'var(--color-primary)',
|
|
4006
4717
|
'--tree-text': 'var(--text-sm)',
|
|
4007
4718
|
'--tree-on-accent': 'var(--color-primary-content)',
|
|
4719
|
+
// The node check box's pair — the tree's own accent, so a colour
|
|
4720
|
+
// variant retints the box with the row.
|
|
4721
|
+
'--tree-check': 'var(--tree-accent)',
|
|
4722
|
+
'--tree-on-check': 'var(--tree-on-accent)',
|
|
4008
4723
|
},
|
|
4009
4724
|
parts: {
|
|
4010
4725
|
root: {
|
|
@@ -4053,7 +4768,59 @@ export const treeView: RecipeInput = {
|
|
|
4053
4768
|
'reduced-motion': { base: { transition: 'none' } },
|
|
4054
4769
|
},
|
|
4055
4770
|
},
|
|
4056
|
-
//
|
|
4771
|
+
// daisy's `.checkbox` at row scale: the accent outline, the recessed
|
|
4772
|
+
// depth shade, the fill on check — and daisy's own tick, the six
|
|
4773
|
+
// clip-path points rotated 45°, drawn on the box's `::after` since
|
|
4774
|
+
// the part is one span. On a selected row (the accent fill) the pair
|
|
4775
|
+
// swaps, so the box never vanishes into the row it sits on.
|
|
4776
|
+
'node-checkbox': {
|
|
4777
|
+
base: {
|
|
4778
|
+
display: 'inline-block',
|
|
4779
|
+
position: 'relative',
|
|
4780
|
+
flexShrink: '0',
|
|
4781
|
+
boxSizing: 'border-box',
|
|
4782
|
+
width: '1.25em',
|
|
4783
|
+
height: '1.25em',
|
|
4784
|
+
border: 'var(--border) solid var(--tree-check)',
|
|
4785
|
+
borderRadius: 'var(--radius-selector)',
|
|
4786
|
+
boxShadow: '0 1px var(--depth-shade) inset',
|
|
4787
|
+
cursor: 'pointer',
|
|
4788
|
+
transition: 'background-color var(--duration-normal) var(--ease-standard)',
|
|
4789
|
+
},
|
|
4790
|
+
states: {
|
|
4791
|
+
checked: { backgroundColor: 'var(--tree-check)' },
|
|
4792
|
+
indeterminate: { backgroundColor: 'var(--tree-check)' },
|
|
4793
|
+
unchecked: {},
|
|
4794
|
+
disabled: { cursor: 'not-allowed' },
|
|
4795
|
+
},
|
|
4796
|
+
selectors: {
|
|
4797
|
+
'&::after': {
|
|
4798
|
+
content: '""',
|
|
4799
|
+
position: 'absolute',
|
|
4800
|
+
inset: '15%',
|
|
4801
|
+
opacity: '0',
|
|
4802
|
+
rotate: '45deg',
|
|
4803
|
+
backgroundColor: 'var(--tree-on-check)',
|
|
4804
|
+
clipPath: TICK_COLLAPSED,
|
|
4805
|
+
transition: 'clip-path var(--duration-slow) var(--ease-standard) var(--duration-instant), '
|
|
4806
|
+
+ 'opacity var(--duration-instant) var(--ease-standard) var(--duration-instant), '
|
|
4807
|
+
+ 'rotate var(--duration-slow) var(--ease-standard) var(--duration-instant), '
|
|
4808
|
+
+ 'translate var(--duration-slow) var(--ease-standard) var(--duration-instant)',
|
|
4809
|
+
},
|
|
4810
|
+
'&[data-state="checked"]::after': { clipPath: TICK_DRAWN, opacity: '1' },
|
|
4811
|
+
'&[data-state="indeterminate"]::after': { clipPath: DASH_DRAWN, opacity: '1', translate: '0 -35%', rotate: '0deg' },
|
|
4812
|
+
'[data-scope="tree-view"][data-selected] &': {
|
|
4813
|
+
'--tree-check': 'var(--tree-on-accent)',
|
|
4814
|
+
'--tree-on-check': 'var(--tree-accent)',
|
|
4815
|
+
},
|
|
4816
|
+
},
|
|
4817
|
+
at: {
|
|
4818
|
+
'reduced-motion': { base: { transition: 'none' }, selectors: { '&::after': { transition: 'none' } } },
|
|
4819
|
+
'forced-colors': nodeTickFallback('CanvasText'),
|
|
4820
|
+
print: nodeTickFallback('var(--print-ink)'),
|
|
4821
|
+
},
|
|
4822
|
+
},
|
|
4823
|
+
// Indentation comes from this inline padding — depth is the DOM
|
|
4057
4824
|
// nesting, no per-level rules needed.
|
|
4058
4825
|
'branch-content': {
|
|
4059
4826
|
base: { display: 'flex', flexDirection: 'column', paddingInlineStart: 'var(--space-lg)' },
|
|
@@ -4337,6 +5104,36 @@ export const card: RecipeInput = {
|
|
|
4337
5104
|
overflow: 'hidden',
|
|
4338
5105
|
},
|
|
4339
5106
|
},
|
|
5107
|
+
/**
|
|
5108
|
+
* Media (#302) — daisy's `card > figure`: a centred flex box, flush
|
|
5109
|
+
* to the card's edges, the image filling it.
|
|
5110
|
+
*/
|
|
5111
|
+
media: {
|
|
5112
|
+
base: {
|
|
5113
|
+
display: 'flex',
|
|
5114
|
+
alignItems: 'center',
|
|
5115
|
+
justifyContent: 'center',
|
|
5116
|
+
margin: '0',
|
|
5117
|
+
inlineSize: '100%',
|
|
5118
|
+
objectFit: 'cover',
|
|
5119
|
+
},
|
|
5120
|
+
selectors: {
|
|
5121
|
+
// The media's own image, when the band wraps one (a div or a
|
|
5122
|
+
// figure): block-level, the band's full width, cropped rather
|
|
5123
|
+
// than stretched when the band is given a height.
|
|
5124
|
+
'& > :is(img, picture, video, svg, canvas)': {
|
|
5125
|
+
display: 'block',
|
|
5126
|
+
inlineSize: '100%',
|
|
5127
|
+
blockSize: '100%',
|
|
5128
|
+
objectFit: 'cover',
|
|
5129
|
+
},
|
|
5130
|
+
// The corners it shares with the card (#302): the root clips
|
|
5131
|
+
// too, but an asChild <img> is the band itself and rounds on
|
|
5132
|
+
// its own.
|
|
5133
|
+
'&:first-child': { borderStartStartRadius: 'var(--radius-box)', borderStartEndRadius: 'var(--radius-box)' },
|
|
5134
|
+
'&:last-child': { borderEndStartRadius: 'var(--radius-box)', borderEndEndRadius: 'var(--radius-box)' },
|
|
5135
|
+
},
|
|
5136
|
+
},
|
|
4340
5137
|
header: {
|
|
4341
5138
|
base: {
|
|
4342
5139
|
display: 'flex',
|
|
@@ -4670,16 +5467,51 @@ export const divider: RecipeInput = {
|
|
|
4670
5467
|
tokens: { '--divider-ink': 'var(--color-base-300)', '--divider-thickness': 'var(--border)' },
|
|
4671
5468
|
parts: {
|
|
4672
5469
|
root: {
|
|
4673
|
-
|
|
5470
|
+
// The rule is two flex segments (#298), drawn as borders: with no
|
|
5471
|
+
// label they meet and read as one line, and a label sits between
|
|
5472
|
+
// them. Borders rather than a background, so forced colours keep
|
|
5473
|
+
// the line instead of revaluing it to Canvas.
|
|
5474
|
+
base: {
|
|
5475
|
+
display: 'flex',
|
|
5476
|
+
alignItems: 'center',
|
|
5477
|
+
border: 'none',
|
|
5478
|
+
alignSelf: 'stretch',
|
|
5479
|
+
},
|
|
4674
5480
|
selectors: {
|
|
4675
|
-
'
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
},
|
|
4679
|
-
'&[data-orientation="
|
|
4680
|
-
|
|
4681
|
-
|
|
5481
|
+
'&::before': { content: '""', flex: '1 1 0', minInlineSize: '0' },
|
|
5482
|
+
'&::after': { content: '""', flex: '1 1 0', minInlineSize: '0' },
|
|
5483
|
+
'&[data-orientation="horizontal"]': { inlineSize: '100%' },
|
|
5484
|
+
'&[data-orientation="horizontal"]::before': { borderBlockStart: 'var(--divider-thickness) solid var(--divider-ink)' },
|
|
5485
|
+
'&[data-orientation="horizontal"]::after': { borderBlockStart: 'var(--divider-thickness) solid var(--divider-ink)' },
|
|
5486
|
+
'&[data-orientation="vertical"]': { flexDirection: 'column', minBlockSize: '1em' },
|
|
5487
|
+
'&[data-orientation="vertical"]::before': { borderInlineStart: 'var(--divider-thickness) solid var(--divider-ink)' },
|
|
5488
|
+
'&[data-orientation="vertical"]::after': { borderInlineStart: 'var(--divider-thickness) solid var(--divider-ink)' },
|
|
5489
|
+
// A placed label sits flush at its edge: the segment on
|
|
5490
|
+
// that side goes, and the other one takes the whole run.
|
|
5491
|
+
'&:has(> [data-scope="divider"][data-part="label"][data-placement="start"])::before': { display: 'none' },
|
|
5492
|
+
'&:has(> [data-scope="divider"][data-part="label"][data-placement="end"])::after': { display: 'none' },
|
|
5493
|
+
},
|
|
5494
|
+
},
|
|
5495
|
+
label: {
|
|
5496
|
+
base: {
|
|
5497
|
+
// daisy's `divider` text: the content colour, the rule
|
|
5498
|
+
// stopping a gap short of it on either side.
|
|
5499
|
+
flex: 'none',
|
|
5500
|
+
paddingInline: 'var(--space-md)',
|
|
5501
|
+
fontSize: 'var(--text-sm)',
|
|
5502
|
+
whiteSpace: 'nowrap',
|
|
5503
|
+
color: 'var(--color-base-content)',
|
|
5504
|
+
},
|
|
5505
|
+
selectors: {
|
|
5506
|
+
'[data-scope="divider"][data-part="root"][data-orientation="vertical"] > &': {
|
|
5507
|
+
paddingInline: '0',
|
|
5508
|
+
paddingBlock: 'var(--space-sm)',
|
|
4682
5509
|
},
|
|
5510
|
+
// Flush means flush: no inset on the side the rule gave up.
|
|
5511
|
+
'&[data-placement="start"]': { paddingInlineStart: '0' },
|
|
5512
|
+
'&[data-placement="end"]': { paddingInlineEnd: '0' },
|
|
5513
|
+
'[data-scope="divider"][data-part="root"][data-orientation="vertical"] > &[data-placement="start"]': { paddingBlockStart: '0' },
|
|
5514
|
+
'[data-scope="divider"][data-part="root"][data-orientation="vertical"] > &[data-placement="end"]': { paddingBlockEnd: '0' },
|
|
4683
5515
|
},
|
|
4684
5516
|
},
|
|
4685
5517
|
},
|
|
@@ -5230,6 +6062,10 @@ export const timeline: RecipeInput = {
|
|
|
5230
6062
|
},
|
|
5231
6063
|
},
|
|
5232
6064
|
},
|
|
6065
|
+
// Title and description (#302): daisy's timeline-box leads with a
|
|
6066
|
+
// bold line; the detail under it in the card description's ink.
|
|
6067
|
+
title: { base: { display: 'block', margin: '0', fontWeight: 'var(--weight-semibold)', lineHeight: 'var(--leading-tight)' } },
|
|
6068
|
+
description: { base: { display: 'block', margin: '0', marginBlockStart: 'var(--space-2xs)', color: 'color-mix(in oklab, var(--color-base-content) 70%, transparent)' } },
|
|
5233
6069
|
},
|
|
5234
6070
|
variants: {
|
|
5235
6071
|
color: Object.fromEntries(ROLES.map((c) => [c, { marker: { base: {
|
|
@@ -5244,6 +6080,87 @@ export const timeline: RecipeInput = {
|
|
|
5244
6080
|
xl: { marker: { base: { '--timeline-marker-size': 'calc(var(--size-selector) * 4)' } }, content: { base: { fontSize: 'var(--text-md)' } } },
|
|
5245
6081
|
},
|
|
5246
6082
|
},
|
|
6083
|
+
/**
|
|
6084
|
+
* Lynx has no grid, and it does not resolve `inline-size`/`block-size`:
|
|
6085
|
+
* the item fell back to the engine's default layout and the marker,
|
|
6086
|
+
* sized only logically, stretched into a pill (signalxjs/lynx#1145).
|
|
6087
|
+
*
|
|
6088
|
+
* The item is a wrapping flex row instead. Vertical: line one is the
|
|
6089
|
+
* marker beside its content (start-side content ordered before the
|
|
6090
|
+
* marker), and the connector takes line two by itself (`flex-basis:
|
|
6091
|
+
* 100%`), drawn as a left border under the marker's centre, which is the
|
|
6092
|
+
* web's row-2 segment. Horizontal: line one is the marker with the
|
|
6093
|
+
* connector running on to the item's end, and the content wraps under it
|
|
6094
|
+
* (start-side content above). Content grows from a zero basis, so long
|
|
6095
|
+
* text never wraps onto its own line.
|
|
6096
|
+
*
|
|
6097
|
+
* The web collapses the start track when no item has start-side content
|
|
6098
|
+
* (`:has()`); lynx has no such test. The vertical connector sits under a
|
|
6099
|
+
* marker at the item's start edge, which is where the marker is unless
|
|
6100
|
+
* that item has start-side content: an alternating vertical timeline is
|
|
6101
|
+
* web-only for now.
|
|
6102
|
+
*/
|
|
6103
|
+
targets: {
|
|
6104
|
+
lynx: {
|
|
6105
|
+
parts: {
|
|
6106
|
+
item: { base: { display: 'flex', flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center' } },
|
|
6107
|
+
marker: {
|
|
6108
|
+
base: {
|
|
6109
|
+
width: 'var(--timeline-marker-size)',
|
|
6110
|
+
height: 'var(--timeline-marker-size)',
|
|
6111
|
+
flexGrow: '0',
|
|
6112
|
+
flexShrink: '0',
|
|
6113
|
+
},
|
|
6114
|
+
},
|
|
6115
|
+
connector: {
|
|
6116
|
+
selectors: {
|
|
6117
|
+
'&[data-orientation="vertical"]': {
|
|
6118
|
+
flexGrow: '0',
|
|
6119
|
+
flexShrink: '1',
|
|
6120
|
+
flexBasis: '100%',
|
|
6121
|
+
height: 'var(--space-lg)',
|
|
6122
|
+
background: 'transparent',
|
|
6123
|
+
borderLeftWidth: 'var(--border)',
|
|
6124
|
+
borderLeftStyle: 'solid',
|
|
6125
|
+
borderLeftColor: 'var(--color-base-300)',
|
|
6126
|
+
marginLeft: 'calc(var(--timeline-marker-size) / 2 - var(--border) / 2)',
|
|
6127
|
+
},
|
|
6128
|
+
'&[data-orientation="horizontal"]': {
|
|
6129
|
+
order: '1',
|
|
6130
|
+
flexGrow: '1',
|
|
6131
|
+
flexShrink: '1',
|
|
6132
|
+
flexBasis: '0',
|
|
6133
|
+
height: 'var(--border)',
|
|
6134
|
+
minWidth: 'var(--space-lg)',
|
|
6135
|
+
},
|
|
6136
|
+
},
|
|
6137
|
+
},
|
|
6138
|
+
content: {
|
|
6139
|
+
selectors: {
|
|
6140
|
+
'&[data-orientation="vertical"][data-placement="start"]': {
|
|
6141
|
+
order: '-1',
|
|
6142
|
+
flexGrow: '1',
|
|
6143
|
+
flexShrink: '1',
|
|
6144
|
+
flexBasis: '0',
|
|
6145
|
+
},
|
|
6146
|
+
'&[data-orientation="vertical"][data-placement="end"]': {
|
|
6147
|
+
flexGrow: '1',
|
|
6148
|
+
flexShrink: '1',
|
|
6149
|
+
flexBasis: '0',
|
|
6150
|
+
},
|
|
6151
|
+
'&[data-orientation="horizontal"][data-placement="start"]': {
|
|
6152
|
+
order: '-1',
|
|
6153
|
+
flexBasis: '100%',
|
|
6154
|
+
},
|
|
6155
|
+
'&[data-orientation="horizontal"][data-placement="end"]': {
|
|
6156
|
+
order: '2',
|
|
6157
|
+
flexBasis: '100%',
|
|
6158
|
+
},
|
|
6159
|
+
},
|
|
6160
|
+
},
|
|
6161
|
+
},
|
|
6162
|
+
},
|
|
6163
|
+
},
|
|
5247
6164
|
};
|
|
5248
6165
|
|
|
5249
6166
|
/** daisy chat: the classic bubble, colour-refilled per role. */
|
|
@@ -5355,6 +6272,96 @@ export const chat: RecipeInput = {
|
|
|
5355
6272
|
},
|
|
5356
6273
|
};
|
|
5357
6274
|
|
|
6275
|
+
/**
|
|
6276
|
+
* ChatLog — daisy's `bg-base-200 rounded-box` well that the chat bubbles
|
|
6277
|
+
* already sit in, scrolling; the jump trigger is a `btn btn-sm` pill in the
|
|
6278
|
+
* role's solid pair with daisy's `shadow-xs` and its 1px press sink, floated
|
|
6279
|
+
* over the rows by `position: sticky` at the box's foot. Its negative block
|
|
6280
|
+
* margin gives back the line it would take, so appearing moves no row.
|
|
6281
|
+
*/
|
|
6282
|
+
export const chatLog: RecipeInput = {
|
|
6283
|
+
component: 'chat-log',
|
|
6284
|
+
tokens: {
|
|
6285
|
+
'--chat-log-fill': 'var(--color-primary)',
|
|
6286
|
+
'--chat-log-on-fill': 'var(--color-primary-content)',
|
|
6287
|
+
'--chat-log-jump-size': 'calc(var(--size-field) * 8)',
|
|
6288
|
+
},
|
|
6289
|
+
parts: {
|
|
6290
|
+
root: {
|
|
6291
|
+
base: {
|
|
6292
|
+
display: 'flex',
|
|
6293
|
+
flexDirection: 'column',
|
|
6294
|
+
overflowY: 'auto',
|
|
6295
|
+
overscrollBehaviorY: 'contain',
|
|
6296
|
+
borderRadius: 'var(--radius-box)',
|
|
6297
|
+
background: 'var(--color-base-100)',
|
|
6298
|
+
color: 'var(--color-base-content)',
|
|
6299
|
+
border: 'var(--border) solid var(--color-base-300)',
|
|
6300
|
+
padding: 'var(--space-sm) var(--space-md)',
|
|
6301
|
+
},
|
|
6302
|
+
// Drawn inside the box (negative offset): an outward ring would
|
|
6303
|
+
// be clipped by whatever scrolls around the transcript.
|
|
6304
|
+
states: { 'focus-visible': { outline: '2px solid var(--color-base-content)', outlineOffset: '-2px' } },
|
|
6305
|
+
},
|
|
6306
|
+
content: {
|
|
6307
|
+
base: {
|
|
6308
|
+
// `auto` gathers a short transcript at the foot; a long one
|
|
6309
|
+
// overflows and the margin resolves to nothing.
|
|
6310
|
+
marginBlockStart: 'auto',
|
|
6311
|
+
flex: 'none',
|
|
6312
|
+
display: 'grid',
|
|
6313
|
+
rowGap: 'var(--space-xs)',
|
|
6314
|
+
},
|
|
6315
|
+
},
|
|
6316
|
+
'jump-trigger': {
|
|
6317
|
+
base: {
|
|
6318
|
+
position: 'sticky',
|
|
6319
|
+
insetBlockEnd: 'var(--space-sm)',
|
|
6320
|
+
alignSelf: 'center',
|
|
6321
|
+
flex: 'none',
|
|
6322
|
+
marginBlockStart: 'calc(var(--chat-log-jump-size) * -1)',
|
|
6323
|
+
blockSize: 'var(--chat-log-jump-size)',
|
|
6324
|
+
appearance: 'none',
|
|
6325
|
+
display: 'inline-flex',
|
|
6326
|
+
alignItems: 'center',
|
|
6327
|
+
gap: 'var(--space-2xs)',
|
|
6328
|
+
paddingInline: 'var(--space-md)',
|
|
6329
|
+
border: 'var(--border) solid transparent',
|
|
6330
|
+
borderRadius: '9999px',
|
|
6331
|
+
background: 'var(--chat-log-fill)',
|
|
6332
|
+
color: 'var(--chat-log-on-fill)',
|
|
6333
|
+
boxShadow: 'var(--shadow-xs)',
|
|
6334
|
+
fontFamily: 'inherit',
|
|
6335
|
+
fontSize: 'var(--text-xs)',
|
|
6336
|
+
fontWeight: 'var(--weight-semibold)',
|
|
6337
|
+
lineHeight: 'var(--leading-none)',
|
|
6338
|
+
cursor: 'pointer',
|
|
6339
|
+
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
6340
|
+
},
|
|
6341
|
+
states: {
|
|
6342
|
+
open: {},
|
|
6343
|
+
closed: {},
|
|
6344
|
+
hover: { background: 'color-mix(in oklab, var(--chat-log-fill) 90%, black)' },
|
|
6345
|
+
'focus-visible': { outline: '2px solid var(--color-base-content)', outlineOffset: '2px' },
|
|
6346
|
+
},
|
|
6347
|
+
selectors: { '&[data-pressed]:not([data-disabled])': { transform: 'translateY(1px)', boxShadow: 'none' } },
|
|
6348
|
+
},
|
|
6349
|
+
},
|
|
6350
|
+
variants: {
|
|
6351
|
+
color: Object.fromEntries(ROLES.map((c) => [c, { 'jump-trigger': { base: {
|
|
6352
|
+
'--chat-log-fill': `var(--color-${c})`,
|
|
6353
|
+
'--chat-log-on-fill': `var(--color-${c}-content)`,
|
|
6354
|
+
} } }])),
|
|
6355
|
+
size: {
|
|
6356
|
+
xs: { content: { base: { rowGap: 'var(--space-2xs)' } }, 'jump-trigger': { base: { '--chat-log-jump-size': 'calc(var(--size-field) * 6)', fontSize: 'var(--text-xs)' } } },
|
|
6357
|
+
sm: { content: { base: { rowGap: 'var(--space-2xs)' } }, 'jump-trigger': { base: { '--chat-log-jump-size': 'calc(var(--size-field) * 7)', fontSize: 'var(--text-xs)' } } },
|
|
6358
|
+
md: {},
|
|
6359
|
+
lg: { content: { base: { rowGap: 'var(--space-sm)' } }, 'jump-trigger': { base: { '--chat-log-jump-size': 'calc(var(--size-field) * 9)', fontSize: 'var(--text-sm)' } } },
|
|
6360
|
+
xl: { content: { base: { rowGap: 'var(--space-md)' } }, 'jump-trigger': { base: { '--chat-log-jump-size': 'calc(var(--size-field) * 10)', fontSize: 'var(--text-sm)' } } },
|
|
6361
|
+
},
|
|
6362
|
+
},
|
|
6363
|
+
};
|
|
6364
|
+
|
|
5358
6365
|
/** daisy radial-progress: --value became --progress-percent; same ring. */
|
|
5359
6366
|
export const radialProgress: RecipeInput = {
|
|
5360
6367
|
component: 'radial-progress',
|
|
@@ -5745,6 +6752,8 @@ export const breadcrumbs: RecipeInput = {
|
|
|
5745
6752
|
alignItems: 'center',
|
|
5746
6753
|
gap: 'var(--space-xs)',
|
|
5747
6754
|
},
|
|
6755
|
+
// `closed` is presence: the runtime hides the part (#295).
|
|
6756
|
+
states: { open: {}, closed: {} },
|
|
5748
6757
|
},
|
|
5749
6758
|
link: {
|
|
5750
6759
|
base: {
|
|
@@ -5773,6 +6782,37 @@ export const breadcrumbs: RecipeInput = {
|
|
|
5773
6782
|
userSelect: 'none',
|
|
5774
6783
|
},
|
|
5775
6784
|
},
|
|
6785
|
+
ellipsis: {
|
|
6786
|
+
base: {
|
|
6787
|
+
display: 'flex',
|
|
6788
|
+
alignItems: 'center',
|
|
6789
|
+
gap: 'var(--space-xs)',
|
|
6790
|
+
},
|
|
6791
|
+
// `closed` is presence: the runtime hides the part (#295).
|
|
6792
|
+
states: { open: {}, closed: {} },
|
|
6793
|
+
},
|
|
6794
|
+
'ellipsis-trigger': {
|
|
6795
|
+
base: {
|
|
6796
|
+
appearance: 'none',
|
|
6797
|
+
background: 'transparent',
|
|
6798
|
+
border: '0',
|
|
6799
|
+
padding: '0 var(--space-2xs)',
|
|
6800
|
+
margin: '0',
|
|
6801
|
+
font: 'inherit',
|
|
6802
|
+
lineHeight: 'inherit',
|
|
6803
|
+
color: 'color-mix(in oklch, var(--color-base-content) 75%, transparent)',
|
|
6804
|
+
borderRadius: 'var(--radius-selector)',
|
|
6805
|
+
cursor: 'pointer',
|
|
6806
|
+
transition: 'color var(--duration-fast) var(--ease-standard)',
|
|
6807
|
+
},
|
|
6808
|
+
states: {
|
|
6809
|
+
hover: { color: 'var(--color-base-content)', textDecoration: 'underline' },
|
|
6810
|
+
'focus-visible': {
|
|
6811
|
+
outline: '2px solid var(--color-primary)',
|
|
6812
|
+
outlineOffset: '2px',
|
|
6813
|
+
},
|
|
6814
|
+
},
|
|
6815
|
+
},
|
|
5776
6816
|
},
|
|
5777
6817
|
variants: {
|
|
5778
6818
|
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
@@ -5788,11 +6828,47 @@ export const breadcrumbs: RecipeInput = {
|
|
|
5788
6828
|
},
|
|
5789
6829
|
};
|
|
5790
6830
|
|
|
6831
|
+
/**
|
|
6832
|
+
* The four pagination triggers — prev/next and, with `withEdges`, the
|
|
6833
|
+
* first/last jumps (#294) — are one cell: the same box, the same
|
|
6834
|
+
* glyph-sized type, and the same flip under the rtl guard, since every
|
|
6835
|
+
* glyph (`‹ › « »`) is physical ink pointing at a reading edge.
|
|
6836
|
+
*/
|
|
6837
|
+
const pageTrigger: PartStyles = {
|
|
6838
|
+
base: {
|
|
6839
|
+
display: 'inline-flex',
|
|
6840
|
+
alignItems: 'center',
|
|
6841
|
+
justifyContent: 'center',
|
|
6842
|
+
minInlineSize: 'var(--pg-size)',
|
|
6843
|
+
blockSize: 'var(--pg-size)',
|
|
6844
|
+
background: 'var(--color-base-200)',
|
|
6845
|
+
color: 'var(--color-base-content)',
|
|
6846
|
+
border: 'none',
|
|
6847
|
+
borderRadius: 'var(--radius-field)',
|
|
6848
|
+
fontSize: 'calc(var(--pg-font) * 1.2)',
|
|
6849
|
+
lineHeight: 'var(--leading-none)',
|
|
6850
|
+
appearance: 'none',
|
|
6851
|
+
// Link mode renders an <a> (#294): no UA underline.
|
|
6852
|
+
textDecoration: 'none',
|
|
6853
|
+
cursor: 'pointer',
|
|
6854
|
+
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
6855
|
+
},
|
|
6856
|
+
states: {
|
|
6857
|
+
hover: { background: 'var(--color-base-300)' },
|
|
6858
|
+
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
6859
|
+
...focusRing,
|
|
6860
|
+
},
|
|
6861
|
+
selectors: {
|
|
6862
|
+
'&[data-pressed]:not([data-disabled])': { transform: 'translateY(1px)' },
|
|
6863
|
+
[`&${rtl}`]: { scale: '-1 1' },
|
|
6864
|
+
},
|
|
6865
|
+
};
|
|
6866
|
+
|
|
5791
6867
|
/**
|
|
5792
6868
|
* Pagination — daisy's join-of-buttons made of standalone btn cells: the
|
|
5793
6869
|
* quiet base-200 fill, base-300 hover, the current page inverted into the
|
|
5794
6870
|
* accent pair (primary by default). Pressed is the daisy 1px sink. The
|
|
5795
|
-
*
|
|
6871
|
+
* `‹ › « »` glyphs flip under the shared rtl guard.
|
|
5796
6872
|
*/
|
|
5797
6873
|
export const pagination: RecipeInput = {
|
|
5798
6874
|
component: 'pagination',
|
|
@@ -5838,6 +6914,7 @@ export const pagination: RecipeInput = {
|
|
|
5838
6914
|
fontWeight: 'var(--weight-semibold)',
|
|
5839
6915
|
fontVariantNumeric: 'tabular-nums',
|
|
5840
6916
|
appearance: 'none',
|
|
6917
|
+
textDecoration: 'none',
|
|
5841
6918
|
cursor: 'pointer',
|
|
5842
6919
|
transition: 'background var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard)',
|
|
5843
6920
|
},
|
|
@@ -5865,60 +6942,10 @@ export const pagination: RecipeInput = {
|
|
|
5865
6942
|
userSelect: 'none',
|
|
5866
6943
|
},
|
|
5867
6944
|
},
|
|
5868
|
-
'
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
justifyContent: 'center',
|
|
5873
|
-
minInlineSize: 'var(--pg-size)',
|
|
5874
|
-
blockSize: 'var(--pg-size)',
|
|
5875
|
-
background: 'var(--color-base-200)',
|
|
5876
|
-
color: 'var(--color-base-content)',
|
|
5877
|
-
border: 'none',
|
|
5878
|
-
borderRadius: 'var(--radius-field)',
|
|
5879
|
-
fontSize: 'calc(var(--pg-font) * 1.2)',
|
|
5880
|
-
lineHeight: 'var(--leading-none)',
|
|
5881
|
-
appearance: 'none',
|
|
5882
|
-
cursor: 'pointer',
|
|
5883
|
-
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
5884
|
-
},
|
|
5885
|
-
states: {
|
|
5886
|
-
hover: { background: 'var(--color-base-300)' },
|
|
5887
|
-
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
5888
|
-
...focusRing,
|
|
5889
|
-
},
|
|
5890
|
-
selectors: {
|
|
5891
|
-
'&[data-pressed]:not([data-disabled])': { transform: 'translateY(1px)' },
|
|
5892
|
-
[`&${rtl}`]: { scale: '-1 1' },
|
|
5893
|
-
},
|
|
5894
|
-
},
|
|
5895
|
-
'next-trigger': {
|
|
5896
|
-
base: {
|
|
5897
|
-
display: 'inline-flex',
|
|
5898
|
-
alignItems: 'center',
|
|
5899
|
-
justifyContent: 'center',
|
|
5900
|
-
minInlineSize: 'var(--pg-size)',
|
|
5901
|
-
blockSize: 'var(--pg-size)',
|
|
5902
|
-
background: 'var(--color-base-200)',
|
|
5903
|
-
color: 'var(--color-base-content)',
|
|
5904
|
-
border: 'none',
|
|
5905
|
-
borderRadius: 'var(--radius-field)',
|
|
5906
|
-
fontSize: 'calc(var(--pg-font) * 1.2)',
|
|
5907
|
-
lineHeight: 'var(--leading-none)',
|
|
5908
|
-
appearance: 'none',
|
|
5909
|
-
cursor: 'pointer',
|
|
5910
|
-
transition: 'background var(--duration-fast) var(--ease-standard)',
|
|
5911
|
-
},
|
|
5912
|
-
states: {
|
|
5913
|
-
hover: { background: 'var(--color-base-300)' },
|
|
5914
|
-
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
5915
|
-
...focusRing,
|
|
5916
|
-
},
|
|
5917
|
-
selectors: {
|
|
5918
|
-
'&[data-pressed]:not([data-disabled])': { transform: 'translateY(1px)' },
|
|
5919
|
-
[`&${rtl}`]: { scale: '-1 1' },
|
|
5920
|
-
},
|
|
5921
|
-
},
|
|
6945
|
+
'first-trigger': pageTrigger,
|
|
6946
|
+
'prev-trigger': pageTrigger,
|
|
6947
|
+
'next-trigger': pageTrigger,
|
|
6948
|
+
'last-trigger': pageTrigger,
|
|
5922
6949
|
},
|
|
5923
6950
|
variants: {
|
|
5924
6951
|
color: Object.fromEntries(ROLES.map((c) => [c, { root: { base: {
|
|
@@ -5959,6 +6986,22 @@ const stepInk = (accent: string, role: string): string =>
|
|
|
5959
6986
|
`color-mix(in oklab, ${accent} `
|
|
5960
6987
|
+ `${Math.min(ROLE_INK_KEEP[role as keyof typeof roles] ?? 55, 70)}%, var(--color-base-content))`;
|
|
5961
6988
|
|
|
6989
|
+
/** A Steps root holding wizard parts (#296) — the panel or the triggers. */
|
|
6990
|
+
const STEPS_WIZARD = '&:has(> [data-scope="steps"]:is([data-part="content"], [data-part="prev-trigger"], [data-part="next-trigger"]))';
|
|
6991
|
+
|
|
6992
|
+
/** Steps' Back/Next (#296): the neutral daisy btn, with its 1px sink. */
|
|
6993
|
+
const stepsTrigger: PartStyles = {
|
|
6994
|
+
base: { ...btn, flex: '0 0 auto', alignSelf: 'flex-start' },
|
|
6995
|
+
states: {
|
|
6996
|
+
hover: { background: 'var(--color-base-300)' },
|
|
6997
|
+
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
6998
|
+
...focusRing,
|
|
6999
|
+
},
|
|
7000
|
+
selectors: {
|
|
7001
|
+
'&[data-pressed]:not([data-disabled])': { transform: 'translateY(1px)' },
|
|
7002
|
+
},
|
|
7003
|
+
};
|
|
7004
|
+
|
|
5962
7005
|
/**
|
|
5963
7006
|
* Steps — daisy's steps translated to the richer rail: bold numbered discs
|
|
5964
7007
|
* on base-200, the walked disc and line refilled with the accent pair
|
|
@@ -5985,6 +7028,10 @@ export const steps: RecipeInput = {
|
|
|
5985
7028
|
},
|
|
5986
7029
|
selectors: {
|
|
5987
7030
|
'&[data-orientation="vertical"]': { flexDirection: 'column' },
|
|
7031
|
+
// A wizard (#296): the rail keeps its own line and the panel
|
|
7032
|
+
// and triggers wrap below it. Only then — a bare rail never
|
|
7033
|
+
// wraps, so its steps stay one row at any width.
|
|
7034
|
+
[STEPS_WIZARD]: { flexWrap: 'wrap', rowGap: 'var(--space-md)' },
|
|
5988
7035
|
},
|
|
5989
7036
|
},
|
|
5990
7037
|
/**
|
|
@@ -6023,6 +7070,10 @@ export const steps: RecipeInput = {
|
|
|
6023
7070
|
active: { color: 'var(--steps-accent-ink)', fontWeight: 'var(--weight-semibold)' },
|
|
6024
7071
|
complete: { color: 'var(--color-base-content)' },
|
|
6025
7072
|
inactive: { color: 'color-mix(in oklch, var(--color-base-content) 65%, transparent)' },
|
|
7073
|
+
// daisy's `step-error`, whatever the phase: after the phases,
|
|
7074
|
+
// so it wins at equal weight. `stepInk`, not `roleInk`: the
|
|
7075
|
+
// 85% error ink measured 3.79:1 on light's page as text.
|
|
7076
|
+
invalid: { color: stepInk('var(--color-error)', 'error') },
|
|
6026
7077
|
disabled: { opacity: 'var(--disabled-opacity)', cursor: 'not-allowed' },
|
|
6027
7078
|
...focusRing,
|
|
6028
7079
|
},
|
|
@@ -6054,6 +7105,7 @@ export const steps: RecipeInput = {
|
|
|
6054
7105
|
// the disc (#112).
|
|
6055
7106
|
complete: { background: 'color-mix(in oklab, var(--steps-accent) 20%, var(--color-base-100))', color: 'var(--steps-complete-ink)' },
|
|
6056
7107
|
inactive: { background: 'var(--color-base-200)', color: 'var(--color-base-content)' },
|
|
7108
|
+
invalid: { background: 'var(--color-error)', color: 'var(--color-error-content)' },
|
|
6057
7109
|
},
|
|
6058
7110
|
},
|
|
6059
7111
|
/**
|
|
@@ -6085,6 +7137,7 @@ export const steps: RecipeInput = {
|
|
|
6085
7137
|
states: {
|
|
6086
7138
|
complete: { background: 'var(--steps-accent)' },
|
|
6087
7139
|
inactive: { background: 'var(--color-base-300)' },
|
|
7140
|
+
invalid: { background: 'var(--color-error)' },
|
|
6088
7141
|
},
|
|
6089
7142
|
},
|
|
6090
7143
|
title: {
|
|
@@ -6099,6 +7152,25 @@ export const steps: RecipeInput = {
|
|
|
6099
7152
|
fontWeight: 'var(--weight-normal)',
|
|
6100
7153
|
},
|
|
6101
7154
|
},
|
|
7155
|
+
/**
|
|
7156
|
+
* The active step's panel: a full-width line under the rail (the
|
|
7157
|
+
* wizard wrap above). Inactive panels are `hidden`, so they need no
|
|
7158
|
+
* rule.
|
|
7159
|
+
*/
|
|
7160
|
+
content: {
|
|
7161
|
+
base: {
|
|
7162
|
+
flex: '1 0 100%',
|
|
7163
|
+
fontSize: 'var(--text-md)',
|
|
7164
|
+
color: 'var(--color-base-content)',
|
|
7165
|
+
},
|
|
7166
|
+
states: { active: {}, inactive: {} },
|
|
7167
|
+
},
|
|
7168
|
+
// Back/Next are daisy btns; Next takes the far end of their line.
|
|
7169
|
+
'prev-trigger': stepsTrigger,
|
|
7170
|
+
'next-trigger': {
|
|
7171
|
+
...stepsTrigger,
|
|
7172
|
+
base: { ...stepsTrigger.base, marginInlineStart: 'auto' },
|
|
7173
|
+
},
|
|
6102
7174
|
},
|
|
6103
7175
|
variants: {
|
|
6104
7176
|
// Keyed on the ITEM, not the root (#112): the item re-carries
|
|
@@ -6288,6 +7360,9 @@ export const drawer: RecipeInput = {
|
|
|
6288
7360
|
*/
|
|
6289
7361
|
export const table: RecipeInput = {
|
|
6290
7362
|
component: 'table',
|
|
7363
|
+
// A sortable header cell carries the sort state for `aria-sort`'s sake;
|
|
7364
|
+
// its trigger and indicator paint it.
|
|
7365
|
+
skipStates: { 'header-cell': ['ascending', 'descending', 'none'] },
|
|
6291
7366
|
// Public to a design system derived from this one (#73).
|
|
6292
7367
|
hooks: {
|
|
6293
7368
|
properties: {
|
|
@@ -6373,6 +7448,55 @@ export const table: RecipeInput = {
|
|
|
6373
7448
|
},
|
|
6374
7449
|
at: tableStackAt(tokens, 'header-cell', { paddingInline: '0' }),
|
|
6375
7450
|
},
|
|
7451
|
+
// Sorting (#286): daisy's header label made a button — the sorted
|
|
7452
|
+
// column's label takes the full accent ink, and the mark beside it
|
|
7453
|
+
// turns with the direction.
|
|
7454
|
+
'sort-trigger': {
|
|
7455
|
+
base: {
|
|
7456
|
+
appearance: 'none',
|
|
7457
|
+
display: 'inline-flex',
|
|
7458
|
+
alignItems: 'center',
|
|
7459
|
+
margin: '0',
|
|
7460
|
+
border: '0',
|
|
7461
|
+
font: 'inherit',
|
|
7462
|
+
letterSpacing: 'inherit',
|
|
7463
|
+
textTransform: 'inherit',
|
|
7464
|
+
textAlign: 'inherit',
|
|
7465
|
+
cursor: 'pointer',
|
|
7466
|
+
gap: 'var(--space-2xs)',
|
|
7467
|
+
padding: '0',
|
|
7468
|
+
borderRadius: 'var(--radius-selector)',
|
|
7469
|
+
background: 'transparent',
|
|
7470
|
+
color: 'inherit',
|
|
7471
|
+
transition: 'color var(--duration-fast) var(--ease-standard)',
|
|
7472
|
+
},
|
|
7473
|
+
states: {
|
|
7474
|
+
ascending: { color: 'var(--table-accent)' },
|
|
7475
|
+
descending: { color: 'var(--table-accent)' },
|
|
7476
|
+
none: {},
|
|
7477
|
+
hover: { color: 'var(--table-accent)' },
|
|
7478
|
+
disabled: { cursor: 'not-allowed', opacity: 'var(--disabled-opacity)' },
|
|
7479
|
+
...focusRing,
|
|
7480
|
+
},
|
|
7481
|
+
at: { 'reduced-motion': { base: { transition: 'none' } } },
|
|
7482
|
+
},
|
|
7483
|
+
// zero's ▲, turned for descending; an unsorted column shows it only
|
|
7484
|
+
// under a hovered or keyboard-focused trigger.
|
|
7485
|
+
'sort-indicator': {
|
|
7486
|
+
base: {
|
|
7487
|
+
display: 'inline-block',
|
|
7488
|
+
fontSize: '0.75em',
|
|
7489
|
+
lineHeight: '1',
|
|
7490
|
+
transition: 'transform var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard)',
|
|
7491
|
+
},
|
|
7492
|
+
states: {
|
|
7493
|
+
ascending: {},
|
|
7494
|
+
descending: { transform: 'rotate(180deg)' },
|
|
7495
|
+
none: { opacity: '0' },
|
|
7496
|
+
},
|
|
7497
|
+
selectors: { '[data-scope="table"][data-part="sort-trigger"]:is(:hover, [data-focus-visible]) > &[data-state="none"]': { opacity: '0.6' } },
|
|
7498
|
+
at: { 'reduced-motion': { base: { transition: 'none' } } },
|
|
7499
|
+
},
|
|
6376
7500
|
cell: {
|
|
6377
7501
|
base: {
|
|
6378
7502
|
padding: 'var(--table-pad-block) var(--table-pad-inline)',
|
|
@@ -7026,11 +8150,11 @@ export const diff: RecipeInput = {
|
|
|
7026
8150
|
};
|
|
7027
8151
|
|
|
7028
8152
|
export const recipes: RecipeInput[] = [
|
|
7029
|
-
tabs, collapsible, switchRecipe, dialog, popover, tooltip, menu,
|
|
7030
|
-
field, checkbox, checkboxGroup, radioGroup, progress, slider, accordion, select, button, avatar, toast, combobox,
|
|
8153
|
+
tabs, collapsible, switchRecipe, dialog, popover, tooltip, hoverCard, menu, menubar,
|
|
8154
|
+
field, fieldset, checkbox, checkboxGroup, radioGroup, progress, slider, accordion, select, button, avatar, toast, combobox,
|
|
7031
8155
|
toggle, toggleGroup, numberInput, ratingGroup, treeView, input, textarea,
|
|
7032
8156
|
card, alert, emptyState, badge, divider, skeleton, spinner,
|
|
7033
|
-
kbd, status, indicator, stats, timeline, chat, radialProgress, join,
|
|
8157
|
+
kbd, status, indicator, stats, timeline, chat, chatLog, radialProgress, join,
|
|
7034
8158
|
navbar, navList, breadcrumbs, pagination, steps, drawer,
|
|
7035
8159
|
table,
|
|
7036
8160
|
fileUpload,
|