@mitcsutt/kiln-ui 0.2.2 → 0.4.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 +6 -6
- package/dist/assets/fonts/archivo-latin-ext-wdth-italic.woff2 +0 -0
- package/dist/assets/fonts/archivo-latin-ext-wdth-normal.woff2 +0 -0
- package/dist/assets/fonts/archivo-latin-wdth-italic.woff2 +0 -0
- package/dist/assets/fonts/archivo-latin-wdth-normal.woff2 +0 -0
- package/dist/assets/fonts/atkinson-hyperlegible-mono-latin-ext-wght-italic.woff2 +0 -0
- package/dist/assets/fonts/atkinson-hyperlegible-mono-latin-ext-wght-normal.woff2 +0 -0
- package/dist/assets/fonts/atkinson-hyperlegible-mono-latin-wght-italic.woff2 +0 -0
- package/dist/assets/fonts/atkinson-hyperlegible-mono-latin-wght-normal.woff2 +0 -0
- package/dist/assets/fonts/b612-latin-400-italic.woff2 +0 -0
- package/dist/assets/fonts/b612-latin-400-normal.woff2 +0 -0
- package/dist/assets/fonts/b612-latin-700-italic.woff2 +0 -0
- package/dist/assets/fonts/b612-latin-700-normal.woff2 +0 -0
- package/dist/assets/fonts/b612-mono-latin-400-italic.woff2 +0 -0
- package/dist/assets/fonts/b612-mono-latin-400-normal.woff2 +0 -0
- package/dist/assets/fonts/b612-mono-latin-700-italic.woff2 +0 -0
- package/dist/assets/fonts/b612-mono-latin-700-normal.woff2 +0 -0
- package/dist/assets/fonts/golos-text-latin-ext-wght-normal.woff2 +0 -0
- package/dist/assets/fonts/golos-text-latin-wght-normal.woff2 +0 -0
- package/dist/assets/fonts/licenses/archivo.txt +93 -0
- package/dist/assets/fonts/licenses/atkinson-hyperlegible-mono.txt +93 -0
- package/dist/assets/fonts/licenses/b612-mono.txt +93 -0
- package/dist/assets/fonts/licenses/b612.txt +93 -0
- package/dist/assets/fonts/licenses/golos-text.txt +93 -0
- package/dist/assets/fonts/licenses/shantell-sans.txt +93 -0
- package/dist/assets/fonts/shantell-sans-latin-ext-full-italic.woff2 +0 -0
- package/dist/assets/fonts/shantell-sans-latin-ext-full-normal.woff2 +0 -0
- package/dist/assets/fonts/shantell-sans-latin-full-italic.woff2 +0 -0
- package/dist/assets/fonts/shantell-sans-latin-full-normal.woff2 +0 -0
- package/dist/components/actions/Button/Button.module.js.map +1 -1
- package/dist/components/actions/ToggleChip/ToggleChip.d.ts +5 -0
- package/dist/components/actions/ToggleChip/ToggleChip.js +10 -6
- package/dist/components/actions/ToggleChip/ToggleChip.js.map +1 -1
- package/dist/components/actions/ToggleChip/chip.module.js +4 -3
- package/dist/components/actions/ToggleChip/chip.module.js.map +1 -1
- package/dist/components/display/Avatar/Avatar.d.ts +3 -3
- package/dist/components/display/Avatar/Avatar.js.map +1 -1
- package/dist/components/display/AvatarGroup/AvatarGroup.d.ts +2 -2
- package/dist/components/display/AvatarGroup/AvatarGroup.js +1 -1
- package/dist/components/display/AvatarGroup/AvatarGroup.js.map +1 -1
- package/dist/components/display/Card/Card.d.ts +7 -4
- package/dist/components/display/Card/Card.js +8 -7
- package/dist/components/display/Card/Card.js.map +1 -1
- package/dist/components/display/Card/Card.module.js.map +1 -1
- package/dist/components/display/List/List.d.ts +10 -5
- package/dist/components/display/List/List.js +49 -40
- package/dist/components/display/List/List.js.map +1 -1
- package/dist/components/display/List/List.module.js.map +1 -1
- package/dist/components/display/Marquee/Marquee.js +1 -1
- package/dist/components/display/Marquee/Marquee.js.map +1 -1
- package/dist/components/display/Media/Media.d.ts +13 -0
- package/dist/components/display/Media/Media.js +23 -19
- package/dist/components/display/Media/Media.js.map +1 -1
- package/dist/components/display/Media/Media.module.js.map +1 -1
- package/dist/components/display/Media/index.d.ts +1 -1
- package/dist/components/display/Stat/Stat.module.js.map +1 -1
- package/dist/components/display/Table/Table.d.ts +6 -1
- package/dist/components/display/Table/Table.js +6 -4
- package/dist/components/display/Table/Table.js.map +1 -1
- package/dist/components/display/Table/Table.module.js.map +1 -1
- package/dist/components/display/Tag/Tag.d.ts +3 -0
- package/dist/components/display/Tag/Tag.js +11 -10
- package/dist/components/display/Tag/Tag.js.map +1 -1
- package/dist/components/display/Tag/Tag.module.js.map +1 -1
- package/dist/components/display/Tag/index.d.ts +1 -1
- package/dist/components/feedback/Flash/Flash.d.ts +47 -0
- package/dist/components/feedback/Flash/Flash.js +36 -0
- package/dist/components/feedback/Flash/Flash.js.map +1 -0
- package/dist/components/feedback/Flash/Flash.module.js +10 -0
- package/dist/components/feedback/Flash/Flash.module.js.map +1 -0
- package/dist/components/feedback/Flash/index.d.ts +2 -0
- package/dist/components/feedback/RelativeTime/RelativeTime.d.ts +7 -0
- package/dist/components/feedback/RelativeTime/RelativeTime.js +11 -11
- package/dist/components/feedback/RelativeTime/RelativeTime.js.map +1 -1
- package/dist/components/feedback/StatusDot/StatusDot.d.ts +1 -1
- package/dist/components/feedback/StatusDot/StatusDot.js.map +1 -1
- package/dist/components/inputs/ChoiceCards/ChoiceCards.js +9 -9
- package/dist/components/inputs/Combobox/Combobox.js +5 -5
- package/dist/components/inputs/PasswordInput/PasswordInput.js +4 -4
- package/dist/components/layout/AppShell/AppShell.d.ts +4 -1
- package/dist/components/layout/AppShell/AppShell.js +58 -44
- package/dist/components/layout/AppShell/AppShell.js.map +1 -1
- package/dist/components/layout/AppShell/AppShell.module.js.map +1 -1
- package/dist/components/layout/Box/Box.d.ts +6 -3
- package/dist/components/layout/Box/Box.js.map +1 -1
- package/dist/components/layout/Box/Box.module.js.map +1 -1
- package/dist/components/layout/Divider/Divider.d.ts +7 -1
- package/dist/components/layout/Divider/Divider.js +18 -20
- package/dist/components/layout/Divider/Divider.js.map +1 -1
- package/dist/components/layout/Section/Section.d.ts +8 -6
- package/dist/components/layout/Section/Section.js.map +1 -1
- package/dist/components/layout/Section/Section.module.js.map +1 -1
- package/dist/components/navigation/Accordion/Accordion.d.ts +6 -1
- package/dist/components/navigation/Accordion/Accordion.js +19 -11
- package/dist/components/navigation/Accordion/Accordion.js.map +1 -1
- package/dist/components/navigation/Accordion/Accordion.module.js +7 -6
- package/dist/components/navigation/Accordion/Accordion.module.js.map +1 -1
- package/dist/components/navigation/NavLinks/NavLinks.d.ts +4 -1
- package/dist/components/navigation/NavLinks/NavLinks.js.map +1 -1
- package/dist/components/navigation/NavLinks/NavLinks.module.js.map +1 -1
- package/dist/components/overlays/Tooltip/Tooltip.d.ts +23 -0
- package/dist/components/overlays/Tooltip/Tooltip.js +77 -31
- package/dist/components/overlays/Tooltip/Tooltip.js.map +1 -1
- package/dist/components/typography/Numeral/Numeral.d.ts +26 -2
- package/dist/components/typography/Numeral/Numeral.js +25 -21
- package/dist/components/typography/Numeral/Numeral.js.map +1 -1
- package/dist/components/typography/Numeral/Numeral.module.js +4 -3
- package/dist/components/typography/Numeral/Numeral.module.js.map +1 -1
- package/dist/components/typography/Text/Text.d.ts +5 -2
- package/dist/components/typography/Text/Text.js.map +1 -1
- package/dist/components/typography/Text/Text.module.js.map +1 -1
- package/dist/icons/Icon.d.ts +1 -1
- package/dist/icons/Icon.js +1 -0
- package/dist/icons/Icon.js.map +1 -1
- package/dist/icons/icons.d.ts +12 -0
- package/dist/icons/icons.js +63 -13
- package/dist/icons/icons.js.map +1 -1
- package/dist/index.d.ts +16 -23
- package/dist/index.js +113 -112
- package/dist/styles.css +1 -1
- package/dist/theme/ThemeProvider.d.ts +4 -4
- package/dist/theme/ThemeProvider.js.map +1 -1
- package/dist/theme/themes.d.ts +1 -1
- package/dist/theme/themes.js +15 -3
- package/dist/theme/themes.js.map +1 -1
- package/dist/themes/fiesta.css +1 -1
- package/dist/themes/flightdeck.css +1 -0
- package/dist/themes/ledger.css +1 -1
- package/dist/themes/monograph.css +1 -1
- package/dist/themes/riso.css +1 -0
- package/dist/utils/tokens.d.ts +2 -0
- package/dist/utils/tokens.js.map +1 -1
- package/package.json +5 -3
- package/skills/custom-theme/SKILL.md +45 -43
- package/skills/custom-theme/references/colour.md +1 -1
- package/skills/custom-theme/references/motion.md +2 -2
- package/skills/custom-theme/references/tokens.md +42 -42
- package/skills/custom-theme/references/type.md +13 -10
- package/skills/design-rules/SKILL.md +36 -5
- package/skills/layout-composition/references/app-shell.md +2 -2
- package/skills/layout-composition/references/box.md +1 -1
- package/skills/layout-composition/references/divider.md +30 -1
- package/skills/layout-composition/references/section.md +23 -2
- package/skills/setup-and-theming/SKILL.md +13 -9
- package/skills/setup-and-theming/references/fiesta.md +3 -1
- package/skills/setup-and-theming/references/flightdeck.md +39 -0
- package/skills/setup-and-theming/references/ledger.md +7 -7
- package/skills/setup-and-theming/references/monograph.md +4 -0
- package/skills/setup-and-theming/references/paper.md +7 -7
- package/skills/setup-and-theming/references/riso.md +32 -0
|
@@ -14,7 +14,7 @@ var m = {
|
|
|
14
14
|
slow: 7,
|
|
15
15
|
normal: 4.5,
|
|
16
16
|
fast: 2.5
|
|
17
|
-
}, g = t("PauseIcon", /* @__PURE__ */ f("path", { d: "M7.5 5.5v9M12.5 5.5v9" })), _ = t("PlayIcon", /* @__PURE__ */ f("path", { d: "M7 5.25v9.5L14.75 10z" })), v = o(function({ items: t, children: o, label: v, speed: y = "normal", direction: b = "left", pauseOnHover: x = !0, pauseControl: S = !0, gap: C = 5, separator: w, surface: T = "plain", className: E, style: D, ...O }, k) {
|
|
17
|
+
}, g = /* @__PURE__ */ t("PauseIcon", /* @__PURE__ */ f("path", { d: "M7.5 5.5v9M12.5 5.5v9" })), _ = /* @__PURE__ */ t("PlayIcon", /* @__PURE__ */ f("path", { d: "M7 5.25v9.5L14.75 10z" })), v = o(function({ items: t, children: o, label: v, speed: y = "normal", direction: b = "left", pauseOnHover: x = !0, pauseControl: S = !0, gap: C = 5, separator: w, surface: T = "plain", className: E, style: D, ...O }, k) {
|
|
18
18
|
let A = (t ?? a.toArray(o)).filter((e) => e != null && e !== !1), [j, M] = d(!1), [N, P] = d(null), F = u(null);
|
|
19
19
|
l(() => {
|
|
20
20
|
let e = F.current;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Marquee.js","names":["styles","Marquee_module_default","SPEED","FALLBACK_PER_ITEM","PauseIcon","
|
|
1
|
+
{"version":3,"file":"Marquee.js","names":["styles","Marquee_module_default","SPEED","FALLBACK_PER_ITEM","PauseIcon","PlayIcon","Marquee","forwardRef","rest","list","items","Children","children","item","paused","setPaused","useState","duration","setDuration","groupRef","useRef","group","measure","width","speed","observer","inertRef","useCallback","seconds","renderSeparator","separator","renderGroup","isValidElement","i","label","cx","className","surface","direction","pauseOnHover","mergeStyles","space","gap","style","pauseControl","p","jsx"],"sources":["../../../../src/components/display/Marquee/Marquee.tsx"],"sourcesContent":["import {\n Children,\n forwardRef,\n isValidElement,\n useCallback,\n useEffect,\n useRef,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n} from 'react'\nimport { createIcon } from '#icons'\nimport { cx } from '#utils/cx'\nimport { mergeStyles } from '#utils/responsive'\nimport { space, type Space } from '#utils/tokens'\nimport styles from './Marquee.module.css'\n\nexport type MarqueeSpeed = 'slow' | 'normal' | 'fast'\nexport type MarqueeDirection = 'left' | 'right'\nexport type MarqueeSurface = 'plain' | 'inverse' | 'accent'\n\nexport interface MarqueeProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {\n /**\n * The things that scroll. Pass an array (or several children) — each entry becomes\n * one item with a separator after it, so the loop is seamless.\n */\n items?: ReactNode[]\n /** Alternative to `items`: each direct child is one item. */\n children?: ReactNode\n /** Accessible name for the region, e.g. \"Deploy status\". Required: a ticker needs a name. */\n label: string\n /** Travel speed. Measured in px/s once mounted, so long and short tickers feel the same. */\n speed?: MarqueeSpeed\n /** Which way the content travels. Default `left`. */\n direction?: MarqueeDirection\n /** Pause while hovered or while anything inside has focus. Default `true`. */\n pauseOnHover?: boolean\n /**\n * Show a pause/play button at the end of the band (WCAG 2.2.2: moving content needs a\n * way to stop it that works for keyboard and touch). Default `true`.\n */\n pauseControl?: boolean\n /** Space between an item and its separator, as a step on the space scale. Default 5. */\n gap?: Space\n /**\n * Replaces the separator drawn between items. The default is a slanted hairline\n * themed through `--marquee-separator-*`. Pass `null` for none.\n */\n separator?: ReactNode\n /** Band colour. `plain` sits on the page between hairlines; `inverse` and `accent` fill. */\n surface?: MarqueeSurface\n}\n\n/* Pixels per second. Tuned so text stays readable at a glance. */\nconst SPEED: Record<MarqueeSpeed, number> = { slow: 28, normal: 48, fast: 84 }\n/* Seconds per item, used before the track has been measured (and on the server). */\nconst FALLBACK_PER_ITEM: Record<MarqueeSpeed, number> = { slow: 7, normal: 4.5, fast: 2.5 }\n\nconst PauseIcon = createIcon('PauseIcon', <path d=\"M7.5 5.5v9M12.5 5.5v9\" />)\nconst PlayIcon = createIcon('PlayIcon', <path d=\"M7 5.25v9.5L14.75 10z\" />)\n\n/**\n * A horizontal ticker for live updates, with a pause control, that stops for reduced motion.\n *\n * @remarks\n * `Marquee` scrolls a row of items in a loop, like live departures along a station board. The\n * content is rendered twice for a seamless loop, but the copy is `aria-hidden` and inert, so\n * screen readers and the tab order meet it once.\n *\n * @privateRemarks\n * A horizontal ticker: live status, a features strip, a top-accounts summary.\n *\n * The content is rendered twice for a seamless loop; the copy is `aria-hidden` and\n * `inert`, so screen readers and the tab order see it once. Under\n * `prefers-reduced-motion` the band stops and becomes a scrollable row.\n *\n * <Marquee label=\"Deploy status\" items={[<>Atlas 2.4 deploying</>, …]} surface=\"inverse\" />\n */\nexport const Marquee = forwardRef<HTMLDivElement, MarqueeProps>(function Marquee(\n {\n items,\n children,\n label,\n speed = 'normal',\n direction = 'left',\n pauseOnHover = true,\n pauseControl = true,\n gap = 5,\n separator,\n surface = 'plain',\n className,\n style,\n ...rest\n },\n ref,\n) {\n const list = (items ?? Children.toArray(children)).filter(\n (item) => item !== null && item !== undefined && item !== false,\n )\n const [paused, setPaused] = useState(false)\n const [duration, setDuration] = useState<number | null>(null)\n const groupRef = useRef<HTMLUListElement>(null)\n\n // Measure one copy of the content and derive the loop duration from a constant speed.\n useEffect(() => {\n const group = groupRef.current\n if (!group || typeof ResizeObserver === 'undefined') return\n const measure = () => {\n const width = group.getBoundingClientRect().width\n if (width > 0) setDuration(width / SPEED[speed])\n }\n measure()\n const observer = new ResizeObserver(measure)\n observer.observe(group)\n return () => {\n observer.disconnect()\n }\n }, [speed])\n\n // `inert` is only a known prop in React 19, so set the attribute directly (works in 18 too).\n const inertRef = useCallback((node: HTMLUListElement | null) => {\n node?.setAttribute('inert', '')\n }, [])\n\n const seconds = duration ?? Math.max(list.length, 1) * FALLBACK_PER_ITEM[speed]\n\n const renderSeparator = () =>\n separator === undefined ? (\n <span className={styles.rule} />\n ) : separator === null ? null : (\n <span className={styles.custom}>{separator}</span>\n )\n\n const renderGroup = () =>\n list.map((item, i) => (\n <li key={isValidElement(item) && item.key !== null ? item.key : i} className={styles.item}>\n <span className={styles.content}>{item}</span>\n <span className={styles.separator} aria-hidden=\"true\">\n {renderSeparator()}\n </span>\n </li>\n ))\n\n return (\n <div\n ref={ref}\n role=\"region\"\n aria-label={label}\n className={cx(styles.marquee, className)}\n data-surface={surface}\n data-direction={direction}\n data-speed={speed}\n data-pause-on-hover={pauseOnHover || undefined}\n data-paused={paused || undefined}\n style={mergeStyles(\n {\n '--marquee-gap': space(gap),\n '--marquee-duration': `${seconds.toFixed(2)}s`,\n } as CSSProperties,\n style,\n )}\n {...rest}\n >\n <div className={styles.viewport}>\n <div className={styles.track}>\n <ul ref={groupRef} role=\"list\" className={styles.group}>\n {renderGroup()}\n </ul>\n <ul\n ref={inertRef}\n role=\"list\"\n className={cx(styles.group, styles.copy)}\n aria-hidden=\"true\"\n >\n {renderGroup()}\n </ul>\n </div>\n </div>\n {pauseControl ? (\n <button\n type=\"button\"\n className={styles.control}\n aria-pressed={paused}\n aria-label={`Pause ${label}`}\n onClick={() => {\n setPaused((p) => !p)\n }}\n >\n {paused ? <PlayIcon /> : <PauseIcon />}\n </button>\n ) : null}\n </div>\n )\n})\n"],"mappings":"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"}
|
|
@@ -2,6 +2,8 @@ import { type HTMLAttributes, type ImgHTMLAttributes, type ReactNode } from 'rea
|
|
|
2
2
|
export type MediaRatio = '1/1' | '4/3' | '3/2' | '16/9' | '21/9' | '3/4' | '2/3' | 'auto';
|
|
3
3
|
export type MediaFit = 'cover' | 'contain';
|
|
4
4
|
export type MediaRadius = 'none' | 'media' | 'surface';
|
|
5
|
+
/** Fixed heights for inline thumbnails: about 16, 20, 24, 32 and 96px. */
|
|
6
|
+
export type MediaSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
5
7
|
export interface MediaProps extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
|
|
6
8
|
src: string;
|
|
7
9
|
/** Required. Describe the image; pass `""` only when it's purely decorative. */
|
|
@@ -12,6 +14,17 @@ export interface MediaProps extends Omit<HTMLAttributes<HTMLElement>, 'children'
|
|
|
12
14
|
fit?: MediaFit;
|
|
13
15
|
/** Corner role. Default `media`. */
|
|
14
16
|
radius?: MediaRadius;
|
|
17
|
+
/**
|
|
18
|
+
* A fixed height, for a small image that sits inline beside text (a flag by a name, a logo
|
|
19
|
+
* in a row). The width follows `ratio`, or the image's own ratio when `ratio` is `auto`.
|
|
20
|
+
* Default: none, so the frame fills its container.
|
|
21
|
+
*/
|
|
22
|
+
size?: MediaSize;
|
|
23
|
+
/**
|
|
24
|
+
* Out of play: the image fades toward grey but stays recognisable, like a knocked-out
|
|
25
|
+
* team's flag or a past event's photo. Pair it with a muted `List.Item` or `Table.Row`.
|
|
26
|
+
*/
|
|
27
|
+
dimmed?: boolean;
|
|
15
28
|
/** Renders a `<figure>` with this as its `<figcaption>`. */
|
|
16
29
|
caption?: ReactNode;
|
|
17
30
|
/** Shown inside the frame if the image fails to load. Default: an empty sunken frame. */
|
|
@@ -3,41 +3,45 @@ import t from "./Media.module.js";
|
|
|
3
3
|
import { forwardRef as n, useEffect as r, useRef as i, useState as a } from "react";
|
|
4
4
|
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
5
5
|
//#region src/components/display/Media/Media.tsx
|
|
6
|
-
var c = n(function({ src: n, alt: c, ratio: l = "auto", fit: u = "cover", radius: d = "media",
|
|
7
|
-
let [
|
|
8
|
-
|
|
9
|
-
let e =
|
|
10
|
-
|
|
11
|
-
}, [n])
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
var c = n(function({ src: n, alt: c, ratio: l = "auto", fit: u = "cover", radius: d = "media", size: f, dimmed: p = !1, caption: m, fallback: h, loading: g = "lazy", imgProps: _, className: v, ...y }, b) {
|
|
7
|
+
let [x, S] = a(!1), C = i(null);
|
|
8
|
+
r(() => {
|
|
9
|
+
let e = C.current;
|
|
10
|
+
S(!!(e?.complete && e.naturalWidth === 0 && typeof e.currentSrc == "string" && e.currentSrc !== ""));
|
|
11
|
+
}, [n]);
|
|
12
|
+
let w = m === void 0 ? f ? "span" : "div" : "figure", T = f ? "span" : "div";
|
|
13
|
+
return /* @__PURE__ */ s(w, {
|
|
14
|
+
ref: b,
|
|
15
|
+
className: e(t.media, v),
|
|
14
16
|
"data-ratio": l,
|
|
15
17
|
"data-fit": u,
|
|
16
18
|
"data-radius": d,
|
|
17
|
-
"data-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
"data-size": f,
|
|
20
|
+
"data-dimmed": p || void 0,
|
|
21
|
+
"data-failed": x || void 0,
|
|
22
|
+
...y,
|
|
23
|
+
children: [/* @__PURE__ */ o(T, {
|
|
20
24
|
className: t.frame,
|
|
21
|
-
children:
|
|
25
|
+
children: x ? /* @__PURE__ */ o(T, {
|
|
22
26
|
className: t.fallback,
|
|
23
27
|
role: c ? "img" : void 0,
|
|
24
28
|
"aria-label": c || void 0,
|
|
25
|
-
children:
|
|
29
|
+
children: h
|
|
26
30
|
}) : /* @__PURE__ */ o("img", {
|
|
27
|
-
ref:
|
|
31
|
+
ref: C,
|
|
28
32
|
className: t.image,
|
|
29
33
|
src: n,
|
|
30
34
|
alt: c,
|
|
31
|
-
loading:
|
|
35
|
+
loading: g,
|
|
32
36
|
decoding: "async",
|
|
33
37
|
onError: () => {
|
|
34
|
-
|
|
38
|
+
S(!0);
|
|
35
39
|
},
|
|
36
|
-
...
|
|
40
|
+
..._
|
|
37
41
|
})
|
|
38
|
-
}),
|
|
42
|
+
}), m === void 0 ? null : /* @__PURE__ */ o("figcaption", {
|
|
39
43
|
className: t.caption,
|
|
40
|
-
children:
|
|
44
|
+
children: m
|
|
41
45
|
})]
|
|
42
46
|
});
|
|
43
47
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Media.js","names":["styles","Media_module_default","Media","forwardRef","rest","failed","setFailed","useState","imgRef","useRef","
|
|
1
|
+
{"version":3,"file":"Media.js","names":["styles","Media_module_default","Media","forwardRef","rest","failed","setFailed","useState","imgRef","useRef","img","src","Root","caption","size","Box","cx","className","ratio","fit","radius","dimmed","alt","fallback","imgProps"],"sources":["../../../../src/components/display/Media/Media.tsx"],"sourcesContent":["import {\n forwardRef,\n useEffect,\n useRef,\n useState,\n type HTMLAttributes,\n type ImgHTMLAttributes,\n type ReactNode,\n} from 'react'\nimport { cx } from '#utils/cx'\nimport styles from './Media.module.css'\n\nexport type MediaRatio = '1/1' | '4/3' | '3/2' | '16/9' | '21/9' | '3/4' | '2/3' | 'auto'\nexport type MediaFit = 'cover' | 'contain'\nexport type MediaRadius = 'none' | 'media' | 'surface'\n/** Fixed heights for inline thumbnails: about 16, 20, 24, 32 and 96px. */\nexport type MediaSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\nexport interface MediaProps extends Omit<HTMLAttributes<HTMLElement>, 'children'> {\n src: string\n /** Required. Describe the image; pass `\"\"` only when it's purely decorative. */\n alt: string\n /** Frame ratio. The image is fitted inside. Default `auto` (the image's own ratio). */\n ratio?: MediaRatio\n /** Default `cover`. Use `contain` for logos, diagrams and screenshots that must not crop. */\n fit?: MediaFit\n /** Corner role. Default `media`. */\n radius?: MediaRadius\n /**\n * A fixed height, for a small image that sits inline beside text (a flag by a name, a logo\n * in a row). The width follows `ratio`, or the image's own ratio when `ratio` is `auto`.\n * Default: none, so the frame fills its container.\n */\n size?: MediaSize\n /**\n * Out of play: the image fades toward grey but stays recognisable, like a knocked-out\n * team's flag or a past event's photo. Pair it with a muted `List.Item` or `Table.Row`.\n */\n dimmed?: boolean\n /** Renders a `<figure>` with this as its `<figcaption>`. */\n caption?: ReactNode\n /** Shown inside the frame if the image fails to load. Default: an empty sunken frame. */\n fallback?: ReactNode\n /** Default `lazy`. Use `eager` for the image above the fold. */\n loading?: 'lazy' | 'eager'\n /** Extra attributes for the `<img>` (srcSet, sizes, width/height, fetchPriority). */\n imgProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, 'src' | 'alt' | 'loading'>\n}\n\n/**\n * A framed image with a fixed ratio, lazy loading, a caption, and a calm failure state.\n *\n * @remarks\n * `Media` frames an image: it holds a ratio while loading, fits the image, loads lazily, and when\n * the image fails it keeps its shape and shows a quiet fallback instead of a broken-image icon.\n *\n * @privateRemarks\n * A framed image: fixed ratio, fitted, lazy by default, with a caption and a graceful\n * failure state. Screenshots, company logos, scanned documents.\n *\n * <Media src=\"/images/dashboard.png\" alt=\"Revenue dashboard, September\" ratio=\"16/9\" caption=\"Dashboard, 2026\" />\n */\nexport const Media = forwardRef<HTMLElement, MediaProps>(function Media(\n {\n src,\n alt,\n ratio = 'auto',\n fit = 'cover',\n radius = 'media',\n size,\n dimmed = false,\n caption,\n fallback,\n loading = 'lazy',\n imgProps,\n className,\n ...rest\n },\n ref,\n) {\n const [failed, setFailed] = useState(false)\n const imgRef = useRef<HTMLImageElement>(null)\n\n // Reset when the source changes; also catch an error that fired before hydration.\n useEffect(() => {\n const img = imgRef.current\n setFailed(\n Boolean(\n img?.complete &&\n img.naturalWidth === 0 &&\n typeof img.currentSrc === 'string' &&\n img.currentSrc !== '',\n ),\n )\n }, [src])\n\n // A sized thumbnail is phrasing content (spans), so it can sit in a button or a line of text.\n const Root = caption !== undefined ? 'figure' : size ? 'span' : 'div'\n const Box = size ? 'span' : 'div'\n return (\n <Root\n // @ts-expect-error — polymorphic ref across figure/div is safe here\n ref={ref}\n className={cx(styles.media, className)}\n data-ratio={ratio}\n data-fit={fit}\n data-radius={radius}\n data-size={size}\n data-dimmed={dimmed || undefined}\n data-failed={failed || undefined}\n {...rest}\n >\n <Box className={styles.frame}>\n {failed ? (\n <Box\n className={styles.fallback}\n role={alt ? 'img' : undefined}\n aria-label={alt || undefined}\n >\n {fallback}\n </Box>\n ) : (\n <img\n ref={imgRef}\n className={styles.image}\n src={src}\n alt={alt}\n loading={loading}\n decoding=\"async\"\n onError={() => {\n setFailed(true)\n }}\n {...imgProps}\n />\n )}\n </Box>\n {caption !== undefined ? <figcaption className={styles.caption}>{caption}</figcaption> : null}\n </Root>\n )\n})\n"],"mappings":";;;;;AA8DA,IAAaE,IAAQC,EAAoC,SACvD,EACE,QACA,QACA,WAAQ,QACR,SAAM,SACN,YAAS,SACT,SACA,YAAS,IACT,YACA,aACA,aAAU,QACV,aACA,cACA,GAAGC,KAEL,GACA;CACA,IAAM,CAACC,GAAQC,KAAaC,EAAS,EAAK,GACpCC,IAASC,EAAyB,IAAI;CAG5C,QAAgB;EACd,IAAMC,IAAMF,EAAO;EACnB,EACE,GACEE,GAAK,YACLA,EAAI,iBAAiB,KACrB,OAAOA,EAAI,cAAe,YAC1BA,EAAI,eAAe,GAEvB;CACF,GAAG,CAACC,CAAG,CAAC;CAGR,IAAMC,IAAOC,MAAY,KAAA,IAAuBC,IAAO,SAAS,QAA3B,UAC/BC,IAAMD,IAAO,SAAS;CAC5B,OACE,kBAACF,GAAD;EAEO;EACL,WAAWI,EAAGhB,EAAO,OAAOiB,CAAS;EACrC,cAAYC;EACZ,YAAUC;EACV,eAAaC;EACb,aAAWN;EACX,eAAaO,KAAU,KAAA;EACvB,eAAahB,KAAU,KAAA;EACvB,GAAID;EAVN,UAAA,CAYE,kBAACW,GAAD;GAAK,WAAWf,EAAO;GACpB,UAAAK,IACC,kBAACU,GAAD;IACE,WAAWf,EAAO;IAClB,MAAMsB,IAAM,QAAQ,KAAA;IACpB,cAAYA,KAAO,KAAA;IAElB,UAAAC;GACE,CAAA,IAEL,kBAAC,OAAD;IACE,KAAKf;IACL,WAAWR,EAAO;IACb;IACA;IACI;IACT,UAAS;IACT,eAAe;KACb,EAAU,EAAI;IAChB;IACA,GAAIwB;GACL,CAAA;EAEA,CAAA,GACJX,MAAY,KAAA,IAA4E,OAAhE,kBAAC,cAAD;GAAY,WAAWb,EAAO;GAAU,UAAAa;EAAoB,CAAA,CACjF;;AAEV,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Media.module.js","names":["media","frame","image","fallback","caption","Media_module_default"],"sources":["../../../../src/components/display/Media/Media.module.css"],"sourcesContent":["/*\n * Component tokens (themes may set any of these on [data-theme]):\n * --media-bg default var(--color-surface-sunken) — the frame while loading / on failure\n */\n.media {\n --_radius: var(--radius-media);\n\n display: flex;\n flex-direction: column;\n gap: var(--space-2);\n min-inline-size: 0;\n margin: 0;\n}\n.media[data-radius='none'] {\n --_radius: 0;\n}\n.media[data-radius='surface'] {\n --_radius: var(--radius-surface);\n}\n\n.frame {\n position: relative;\n overflow: hidden;\n border-radius: var(--_radius);\n background: var(--media-bg, var(--color-surface-sunken));\n}\n/* A hairline inside the frame so light images don't dissolve into light canvases. */\n.frame::after {\n content: '';\n position: absolute;\n inset: 0;\n border-radius: inherit;\n box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--color-ink) 8%, transparent);\n pointer-events: none;\n}\n\n.media[data-ratio='1/1'] .frame {\n aspect-ratio: 1;\n}\n.media[data-ratio='4/3'] .frame {\n aspect-ratio: 4 / 3;\n}\n.media[data-ratio='3/2'] .frame {\n aspect-ratio: 3 / 2;\n}\n.media[data-ratio='16/9'] .frame {\n aspect-ratio: 16 / 9;\n}\n.media[data-ratio='21/9'] .frame {\n aspect-ratio: 21 / 9;\n}\n.media[data-ratio='3/4'] .frame {\n aspect-ratio: 3 / 4;\n}\n.media[data-ratio='2/3'] .frame {\n aspect-ratio: 2 / 3;\n}\n\n.image {\n display: block;\n inline-size: 100%;\n block-size: auto;\n}\n.media:not([data-ratio='auto']) .image {\n position: absolute;\n inset: 0;\n block-size: 100%;\n object-fit: cover;\n}\n.media[data-fit='contain'] .image {\n object-fit: contain;\n}\n\n.fallback {\n display: grid;\n place-items: center;\n inline-size: 100%;\n block-size: 100%;\n min-block-size: var(--space-9);\n padding: var(--space-4);\n color: var(--color-ink-subtle);\n font-family: var(--font-text);\n font-size: var(--text-sm);\n text-align: center;\n}\n.media:not([data-ratio='auto']) .fallback {\n position: absolute;\n inset: 0;\n min-block-size: 0;\n}\n\n.caption {\n color: var(--color-ink-muted);\n font-family: var(--font-text);\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n text-wrap: pretty;\n}\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"Media.module.js","names":["media","frame","image","fallback","caption","Media_module_default"],"sources":["../../../../src/components/display/Media/Media.module.css"],"sourcesContent":["/*\n * Component tokens (themes may set any of these on [data-theme]):\n * --media-bg default var(--color-surface-sunken) — the frame while loading / on failure\n * --media-dim-filter default saturate(0.3) contrast(0.7) — how a `dimmed` image recedes\n */\n.media {\n --_radius: var(--radius-media);\n\n display: flex;\n flex-direction: column;\n gap: var(--space-2);\n min-inline-size: 0;\n margin: 0;\n}\n.media[data-radius='none'] {\n --_radius: 0;\n}\n.media[data-radius='surface'] {\n --_radius: var(--radius-surface);\n}\n\n/* Sized: an inline thumbnail of fixed height, as wide as its ratio makes it. */\n.media[data-size] {\n --_size: var(--space-4);\n\n display: inline-flex;\n flex: none;\n align-items: flex-start;\n vertical-align: middle;\n}\n.media[data-size='sm'] {\n --_size: calc(var(--space-4) + var(--space-1));\n}\n.media[data-size='md'] {\n --_size: var(--space-5);\n}\n.media[data-size='lg'] {\n --_size: var(--space-6);\n}\n.media[data-size='xl'] {\n --_size: var(--space-9);\n}\n\n.frame {\n position: relative;\n overflow: hidden;\n border-radius: var(--_radius);\n background: var(--media-bg, var(--color-surface-sunken));\n}\n/* A hairline inside the frame so light images don't dissolve into light canvases. */\n.frame::after {\n content: '';\n position: absolute;\n inset: 0;\n border-radius: inherit;\n box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--color-ink) 8%, transparent);\n pointer-events: none;\n}\n\n.media[data-ratio='1/1'] .frame {\n aspect-ratio: 1;\n}\n.media[data-ratio='4/3'] .frame {\n aspect-ratio: 4 / 3;\n}\n.media[data-ratio='3/2'] .frame {\n aspect-ratio: 3 / 2;\n}\n.media[data-ratio='16/9'] .frame {\n aspect-ratio: 16 / 9;\n}\n.media[data-ratio='21/9'] .frame {\n aspect-ratio: 21 / 9;\n}\n.media[data-ratio='3/4'] .frame {\n aspect-ratio: 3 / 4;\n}\n.media[data-ratio='2/3'] .frame {\n aspect-ratio: 2 / 3;\n}\n\n.image {\n display: block;\n inline-size: 100%;\n block-size: auto;\n}\n.media[data-size] .frame {\n block-size: var(--_size);\n /* A theme's media radius would round a small thumbnail into a pill: cap it to its height. */\n border-radius: min(var(--_radius), calc(var(--_size) / 5));\n}\n.media[data-size] .image {\n inline-size: auto;\n block-size: 100%;\n}\n.media:not([data-ratio='auto']) .image {\n position: absolute;\n inset: 0;\n block-size: 100%;\n object-fit: cover;\n}\n.media[data-fit='contain'] .image {\n object-fit: contain;\n}\n\n.fallback {\n display: grid;\n place-items: center;\n inline-size: 100%;\n block-size: 100%;\n min-block-size: var(--space-9);\n padding: var(--space-4);\n color: var(--color-ink-subtle);\n font-family: var(--font-text);\n font-size: var(--text-sm);\n text-align: center;\n}\n.media:not([data-ratio='auto']) .fallback {\n position: absolute;\n inset: 0;\n min-block-size: 0;\n}\n\n.media[data-size][data-ratio='auto'][data-failed] .frame {\n aspect-ratio: 1;\n}\n/* No room for words in a thumbnail: the fallback is an empty frame, or a glyph if one is given. */\n.media[data-size] .fallback {\n position: absolute;\n inset: 0;\n min-block-size: 0;\n padding: 0;\n font-size: calc(var(--_size) * 0.6);\n}\n\n/* Dimmed: faded toward a neutral grey, never the canvas's hue, so a flag stays recognisable\n on Fiesta's apricot or Riso's pink and still reads as out of play. */\n.media[data-dimmed] .image {\n filter: var(--media-dim-filter, saturate(0.3) contrast(0.7));\n}\n\n.caption {\n color: var(--color-ink-muted);\n font-family: var(--font-text);\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n text-wrap: pretty;\n}\n"],"mappings":""}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Media } from './Media.js';
|
|
2
|
-
export type { MediaProps, MediaRatio, MediaFit, MediaRadius } from './Media.js';
|
|
2
|
+
export type { MediaProps, MediaRatio, MediaFit, MediaRadius, MediaSize } from './Media.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Stat.module.js","names":["stat","label","figure","value","delta","hint","Stat_module_default"],"sources":["../../../../src/components/display/Stat/Stat.module.css"],"sourcesContent":["/*\n * Component tokens (themes may set any of these on [data-theme]):\n * --stat-value-font default var(--font-numeric)\n * --stat-value-weight default var(--heading-weight)\n * --stat-fit how many value-ems of width a figure is assumed to need, default 6.5\n * (Ledger 7.5: its receipt mono is wide).\n * The value never grows past its size step, and shrinks below it\n * when the Stat is narrower than --stat-fit × that size.\n *\n * Fitting: the Stat is an inline-size container, so a figure in a narrow grid cell scales\n * down (cqi) instead of running into its neighbour. Containment would make a Stat inside a\n * flex row (Inline) zero-wide, so its intrinsic width is stated instead: --stat-fit value-ems.\n * A figure longer than that may still wrap as a last resort, never overlap.\n *\n * The change is a <Delta> set at --text-sm on the value's baseline.\n *\n * The value is set in the theme's numeric face, tabular and lining, so a row of stats\n * (or the same stat updating) never jitters. Label above, change on the value's baseline.\n */\n.stat {\n --_value-size: var(--text-2xl);\n --_value-leading: 1;\n --_fit: var(--stat-fit, 6.5);\n\n container-type: inline-size;\n contain-intrinsic-inline-size: auto calc(var(--_value-size) * var(--_fit));\n\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n min-inline-size: 0;\n font-family: var(--font-text);\n color: var(--color-ink);\n}\n\n.stat[data-size='sm'] {\n --_value-size: var(--text-lg);\n}\n.stat[data-size='md'] {\n --_value-size: var(--text-2xl);\n}\n.stat[data-size='lg'] {\n --_value-size: var(--text-3xl);\n}\n.stat[data-size='hero'] {\n --_value-size: var(--text-display-md);\n --_value-leading: 1;\n\n gap: var(--space-2);\n}\n\n.stat[data-rule] {\n padding-block-start: var(--space-3);\n border-block-start: var(--border-width) solid var(--color-line-strong);\n}\n\n.label {\n color: var(--color-ink-muted);\n font-size: var(--text-sm);\n font-weight: var(--label-weight);\n letter-spacing: var(--label-tracking);\n text-transform: var(--label-transform);\n line-height: var(--leading-snug);\n}\n\n.figure {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-1) var(--space-3);\n min-inline-size: 0;\n}\n\n.value {\n font-family: var(--stat-value-font, var(--font-numeric));\n font-weight: var(--stat-value-weight, var(--heading-weight));\n font-stretch: var(--numeric-stretch);\n font-variant-numeric: tabular-nums lining-nums;\n font-size: min(var(--_value-size), calc(100cqi / var(--_fit)));\n line-height: var(--_value-leading);\n letter-spacing: -0.01em;\n max-inline-size: 100%;\n overflow-wrap: anywhere;\n}\n.stat .value > * {\n white-space: normal; /* last resort (see top): a Numeral wraps rather than overlaps */\n}\n.stat[data-tone='positive'] .value {\n color: var(--tone-positive-text);\n}\n.stat[data-tone='critical'] .value {\n color: var(--tone-critical-text);\n}\n.stat[data-tone='accent'] .value {\n color: var(--color-accent-text);\n}\n\n.stat[data-size='hero'] .value {\n letter-spacing: var(--display-tracking);\n}\n\n.delta {\n font-size: var(--text-sm);\n}\n\n.hint {\n color: var(--color-ink-subtle);\n font-size: var(--text-xs);\n line-height: var(--leading-snug);\n}\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"Stat.module.js","names":["stat","label","figure","value","delta","hint","Stat_module_default"],"sources":["../../../../src/components/display/Stat/Stat.module.css"],"sourcesContent":["/*\n * Component tokens (themes may set any of these on [data-theme]):\n * --stat-align `start` (default) or `end`: the label, value and hint line up on\n * that edge (List.Trailing sets `end`)\n * --stat-container-type default inline-size, which fits the figure to a narrow cell (see\n * below). `normal` makes the Stat only as wide as its content\n * (List.Trailing sets it: a row's figure shouldn't claim the title's room)\n * --stat-value-font default var(--font-numeric)\n * --stat-value-weight default var(--heading-weight)\n * --stat-fit how many value-ems of width a figure is assumed to need, default 6.5\n * (Ledger 7.5: its receipt mono is wide).\n * The value never grows past its size step, and shrinks below it\n * when the Stat is narrower than --stat-fit × that size.\n *\n * Fitting: the Stat is an inline-size container, so a figure in a narrow grid cell scales\n * down (cqi) instead of running into its neighbour. Containment would make a Stat inside a\n * flex row (Inline) zero-wide, so its intrinsic width is stated instead: --stat-fit value-ems.\n * A figure longer than that may still wrap as a last resort, never overlap.\n *\n * The change is a <Delta> set at --text-sm on the value's baseline.\n *\n * The value is set in the theme's numeric face, tabular and lining, so a row of stats\n * (or the same stat updating) never jitters. Label above, change on the value's baseline.\n */\n.stat {\n --_align: var(--stat-align, start);\n --_value-size: var(--text-2xl);\n --_value-leading: 1;\n --_fit: var(--stat-fit, 6.5);\n\n container-type: var(--stat-container-type, inline-size);\n contain-intrinsic-inline-size: auto calc(var(--_value-size) * var(--_fit));\n\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n min-inline-size: 0;\n font-family: var(--font-text);\n color: var(--color-ink);\n text-align: var(--_align);\n}\n\n.stat[data-size='sm'] {\n --_value-size: var(--text-lg);\n}\n.stat[data-size='md'] {\n --_value-size: var(--text-2xl);\n}\n.stat[data-size='lg'] {\n --_value-size: var(--text-3xl);\n}\n.stat[data-size='hero'] {\n --_value-size: var(--text-display-md);\n --_value-leading: 1;\n\n gap: var(--space-2);\n}\n\n.stat[data-rule] {\n padding-block-start: var(--space-3);\n border-block-start: var(--border-width) solid var(--color-line-strong);\n}\n\n.label {\n color: var(--color-ink-muted);\n font-size: var(--text-sm);\n font-weight: var(--label-weight);\n letter-spacing: var(--label-tracking);\n text-transform: var(--label-transform);\n line-height: var(--leading-snug);\n}\n\n.figure {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n justify-content: var(--_align);\n gap: var(--space-1) var(--space-3);\n min-inline-size: 0;\n}\n\n.value {\n font-family: var(--stat-value-font, var(--font-numeric));\n font-weight: var(--stat-value-weight, var(--heading-weight));\n font-stretch: var(--numeric-stretch);\n font-variant-numeric: tabular-nums lining-nums;\n font-size: min(var(--_value-size), calc(100cqi / var(--_fit)));\n line-height: var(--_value-leading);\n letter-spacing: -0.01em;\n max-inline-size: 100%;\n overflow-wrap: anywhere;\n}\n.stat .value > * {\n white-space: normal; /* last resort (see top): a Numeral wraps rather than overlaps */\n}\n.stat[data-tone='positive'] .value {\n color: var(--tone-positive-text);\n}\n.stat[data-tone='critical'] .value {\n color: var(--tone-critical-text);\n}\n.stat[data-tone='accent'] .value {\n color: var(--color-accent-text);\n}\n\n.stat[data-size='hero'] .value {\n letter-spacing: var(--display-tracking);\n}\n\n.delta {\n font-size: var(--text-sm);\n}\n\n.hint {\n color: var(--color-ink-subtle);\n font-size: var(--text-xs);\n line-height: var(--leading-snug);\n}\n"],"mappings":""}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type HTMLAttributes, type TableHTMLAttributes, type TdHTMLAttributes, type ThHTMLAttributes } from 'react';
|
|
2
|
+
import type { CategoryColor } from '../../../utils/tokens.js';
|
|
2
3
|
import { type VisibilityProps } from '../../../utils/visibility.js';
|
|
3
4
|
export type TableDensity = 'compact' | 'regular';
|
|
4
5
|
export type TableVariant = 'rules' | 'plain';
|
|
@@ -25,11 +26,15 @@ export interface TableProps extends TableHTMLAttributes<HTMLTableElement> {
|
|
|
25
26
|
*/
|
|
26
27
|
label?: string;
|
|
27
28
|
}
|
|
28
|
-
export interface TableRowProps extends HTMLAttributes<HTMLTableRowElement> {
|
|
29
|
+
export interface TableRowProps extends Omit<HTMLAttributes<HTMLTableRowElement>, 'color'> {
|
|
29
30
|
/** `--color-highlight` fill — "you", your project, the circled line. */
|
|
30
31
|
highlighted?: boolean;
|
|
31
32
|
/** Hover fill for rows that respond to clicks (put the real link in a cell). */
|
|
32
33
|
interactive?: boolean;
|
|
34
|
+
/** Out of play (eliminated, archived, past): every ink drops to the muted step, which still reads. */
|
|
35
|
+
muted?: boolean;
|
|
36
|
+
/** A categorical colour drawn as a rail at the row's start: whose row it is, matching their `Tag`. */
|
|
37
|
+
color?: CategoryColor;
|
|
33
38
|
}
|
|
34
39
|
export interface TableHeaderCellProps extends Omit<ThHTMLAttributes<HTMLTableCellElement>, 'align' | 'width'>, VisibilityProps {
|
|
35
40
|
align?: TableAlign;
|
|
@@ -41,13 +41,15 @@ var c = a(function({ density: t = "regular", variant: n = "rules", striped: r =
|
|
|
41
41
|
className: e(i.foot, t),
|
|
42
42
|
...n
|
|
43
43
|
});
|
|
44
|
-
}), f = a(function({ highlighted: t = !1, interactive: n = !1,
|
|
44
|
+
}), f = a(function({ highlighted: t = !1, interactive: n = !1, muted: r = !1, color: a, className: s, ...c }, l) {
|
|
45
45
|
return /* @__PURE__ */ o("tr", {
|
|
46
|
-
ref:
|
|
47
|
-
className: e(i.row,
|
|
46
|
+
ref: l,
|
|
47
|
+
className: e(i.row, s),
|
|
48
48
|
"data-highlighted": t || void 0,
|
|
49
49
|
"data-interactive": n || void 0,
|
|
50
|
-
|
|
50
|
+
"data-muted": r || void 0,
|
|
51
|
+
"data-color": a,
|
|
52
|
+
...c
|
|
51
53
|
});
|
|
52
54
|
}), p = {
|
|
53
55
|
asc: "ascending",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Table.js","names":["styles","Table_module_default","TableRoot","forwardRef","rest","stickyHeader","label","cx","className","density","variant","striped","TableHead","TableBody","TableFoot","TableRow","highlighted","interactive","ARIA_SORT","TableHeaderCell","sortable","sort","onSort","visibilityClass","align","numeric","width","children","jsx","ChevronUpIcon","ChevronDownIcon","TableCell","shared","rowHeader","thRest","TableCaption","Table"],"sources":["../../../../src/components/display/Table/Table.tsx"],"sourcesContent":["import {\n forwardRef,\n type HTMLAttributes,\n type TableHTMLAttributes,\n type TdHTMLAttributes,\n type ThHTMLAttributes,\n} from 'react'\nimport { cx } from '#utils/cx'\nimport { visibilityClass, type VisibilityProps } from '#utils/visibility'\nimport { ChevronDownIcon, ChevronUpIcon } from '#icons'\nimport styles from './Table.module.css'\n\nexport type TableDensity = 'compact' | 'regular'\nexport type TableVariant = 'rules' | 'plain'\nexport type TableAlign = 'start' | 'center' | 'end'\nexport type TableSort = 'asc' | 'desc' | 'none'\n/** `fill` takes the spare width (the name column); `min` shrinks to its content. */\nexport type TableColumnWidth = 'fill' | 'min'\n\nexport interface TableProps extends TableHTMLAttributes<HTMLTableElement> {\n /** Row height. Default `regular`. */\n density?: TableDensity\n /** `rules` (default): a hairline under every row. `plain`: only the header rule. */\n variant?: TableVariant\n /** Alternate row fills. Off by default — rules usually read better. */\n striped?: boolean\n /**\n * Keep the header visible while rows scroll. The wrapper becomes the scroll container\n * (capped by `--table-max-height`, default 70vh), since a horizontally scrolling wrapper\n * would otherwise trap a page-sticky header.\n */\n stickyHeader?: boolean\n /**\n * Name for the scroll container. When set, the wrapper becomes a focusable, labelled\n * region so keyboard users can scroll a wide table.\n */\n label?: string\n}\n\n/**\n * A semantic data table with numeric columns that line up, sortable headers and a footer for\n * totals.\n *\n * @remarks\n * `Table` is a real `<table>` with Kiln's styles: rules between rows, numeric columns in tabular\n * figures, and a wrapper that scrolls sideways on narrow screens instead of breaking the layout.\n *\n * ## Density, stripes and sticky headers\n *\n * `density=\"compact\"` for dense data, `striped` for alternating fills (rules usually read better),\n * and `stickyHeader` to keep the header in view while the rows scroll inside the table.\n *\n * @privateRemarks\n * A semantic, styled data table: project boards, ledgers, audit logs. The wrapper scrolls\n * horizontally on narrow screens; `ref` and native props go to the `<table>`.\n *\n * <Table><Table.Head><Table.Row><Table.HeaderCell>Project</Table.HeaderCell>…</Table.Row></Table.Head>…</Table>\n */\nconst TableRoot = forwardRef<HTMLTableElement, TableProps>(function Table(\n {\n density = 'regular',\n variant = 'rules',\n striped = false,\n stickyHeader = false,\n label,\n className,\n ...rest\n },\n ref,\n) {\n return (\n <div\n className={styles.scroll}\n data-kiln-component=\"\"\n data-sticky-header={stickyHeader || undefined}\n role={label ? 'region' : undefined}\n aria-label={label}\n tabIndex={label ? 0 : undefined}\n >\n <table\n ref={ref}\n className={cx(styles.table, className)}\n data-density={density}\n data-variant={variant}\n data-striped={striped || undefined}\n data-sticky-header={stickyHeader || undefined}\n {...rest}\n />\n </div>\n )\n})\n\nconst TableHead = forwardRef<HTMLTableSectionElement, HTMLAttributes<HTMLTableSectionElement>>(\n function TableHead({ className, ...rest }, ref) {\n return <thead ref={ref} className={cx(styles.head, className)} {...rest} />\n },\n)\n\nconst TableBody = forwardRef<HTMLTableSectionElement, HTMLAttributes<HTMLTableSectionElement>>(\n function TableBody({ className, ...rest }, ref) {\n return <tbody ref={ref} className={cx(styles.body, className)} {...rest} />\n },\n)\n\n/** Totals row(s). Set above a strong rule. */\nconst TableFoot = forwardRef<HTMLTableSectionElement, HTMLAttributes<HTMLTableSectionElement>>(\n function TableFoot({ className, ...rest }, ref) {\n return <tfoot ref={ref} className={cx(styles.foot, className)} {...rest} />\n },\n)\n\nexport interface TableRowProps extends HTMLAttributes<HTMLTableRowElement> {\n /** `--color-highlight` fill — \"you\", your project, the circled line. */\n highlighted?: boolean\n /** Hover fill for rows that respond to clicks (put the real link in a cell). */\n interactive?: boolean\n}\n\nconst TableRow = forwardRef<HTMLTableRowElement, TableRowProps>(function TableRow(\n { highlighted = false, interactive = false, className, ...rest },\n ref,\n) {\n return (\n <tr\n ref={ref}\n className={cx(styles.row, className)}\n data-highlighted={highlighted || undefined}\n data-interactive={interactive || undefined}\n {...rest}\n />\n )\n})\n\nexport interface TableHeaderCellProps\n extends Omit<ThHTMLAttributes<HTMLTableCellElement>, 'align' | 'width'>, VisibilityProps {\n align?: TableAlign\n /** Numeric column: right-aligned with tabular figures. */\n numeric?: boolean\n /** Column sizing: `fill` takes the spare width, `min` shrinks to fit. Default: automatic. */\n width?: TableColumnWidth\n /** Current sort of this column. With `onSort`, the label becomes a button and `aria-sort` is set. */\n sort?: TableSort\n /** Called when the sort button is pressed. Decide the next direction yourself. */\n onSort?: () => void\n}\n\nconst ARIA_SORT = { asc: 'ascending', desc: 'descending', none: 'none' } as const\n\nconst TableHeaderCell = forwardRef<HTMLTableCellElement, TableHeaderCellProps>(\n function TableHeaderCell(\n {\n align,\n numeric = false,\n width,\n sort,\n onSort,\n hideBelow,\n hideAbove,\n scope = 'col',\n className,\n children,\n ...rest\n },\n ref,\n ) {\n const sortable = sort !== undefined && onSort !== undefined\n return (\n <th\n ref={ref}\n scope={scope}\n className={cx(styles.headerCell, visibilityClass({ hideBelow, hideAbove }), className)}\n data-align={align ?? (numeric ? 'end' : undefined)}\n data-numeric={numeric || undefined}\n data-width={width}\n data-sort={sortable ? sort : undefined}\n aria-sort={sort !== undefined ? ARIA_SORT[sort] : undefined}\n {...rest}\n >\n {sortable ? (\n <button type=\"button\" className={styles.sortButton} onClick={onSort}>\n {children}\n <span className={styles.sortGlyph} aria-hidden=\"true\">\n {sort === 'asc' ? <ChevronUpIcon /> : <ChevronDownIcon />}\n </span>\n </button>\n ) : (\n children\n )}\n </th>\n )\n },\n)\n\n/**\n * Hide a column on small screens: put the same `hideBelow` on its HeaderCell and on\n * every Cell in that column (cells are `display: none`, so the columns stay aligned).\n */\nexport interface TableCellProps\n extends Omit<TdHTMLAttributes<HTMLTableCellElement>, 'align'>, VisibilityProps {\n align?: TableAlign\n /** Figures: right-aligned, tabular, in the theme's numeric face (mono in Ledger). */\n numeric?: boolean\n /** Render as `<th scope=\"row\">` — the cell that names the row (project, client). */\n rowHeader?: boolean\n}\n\nconst TableCell = forwardRef<HTMLTableCellElement, TableCellProps>(function TableCell(\n { align, numeric = false, rowHeader = false, hideBelow, hideAbove, className, ...rest },\n ref,\n) {\n const shared = {\n className: cx(styles.cell, visibilityClass({ hideBelow, hideAbove }), className),\n 'data-align': align ?? (numeric ? 'end' : undefined),\n 'data-numeric': numeric || undefined,\n 'data-row-header': rowHeader || undefined,\n }\n if (rowHeader) {\n const { scope = 'row', ...thRest } = rest as ThHTMLAttributes<HTMLTableCellElement>\n return <th ref={ref} scope={scope} {...shared} {...thRest} />\n }\n return <td ref={ref} {...shared} {...rest} />\n})\n\nconst TableCaption = forwardRef<HTMLTableCaptionElement, HTMLAttributes<HTMLTableCaptionElement>>(\n function TableCaption({ className, ...rest }, ref) {\n return <caption ref={ref} className={cx(styles.caption, className)} {...rest} />\n },\n)\n\nexport const Table = Object.assign(TableRoot, {\n Head: TableHead,\n Body: TableBody,\n Foot: TableFoot,\n Row: TableRow,\n HeaderCell: TableHeaderCell,\n Cell: TableCell,\n Caption: TableCaption,\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Table.js","names":["styles","Table_module_default","TableRoot","forwardRef","rest","stickyHeader","label","cx","className","density","variant","striped","TableHead","TableBody","TableFoot","TableRow","highlighted","interactive","muted","color","ARIA_SORT","TableHeaderCell","sortable","sort","onSort","visibilityClass","align","numeric","width","children","jsx","ChevronUpIcon","ChevronDownIcon","TableCell","shared","rowHeader","thRest","TableCaption","Table"],"sources":["../../../../src/components/display/Table/Table.tsx"],"sourcesContent":["import {\n forwardRef,\n type HTMLAttributes,\n type TableHTMLAttributes,\n type TdHTMLAttributes,\n type ThHTMLAttributes,\n} from 'react'\nimport { cx } from '#utils/cx'\nimport type { CategoryColor } from '#utils/tokens'\nimport { visibilityClass, type VisibilityProps } from '#utils/visibility'\nimport { ChevronDownIcon, ChevronUpIcon } from '#icons'\nimport styles from './Table.module.css'\n\nexport type TableDensity = 'compact' | 'regular'\nexport type TableVariant = 'rules' | 'plain'\nexport type TableAlign = 'start' | 'center' | 'end'\nexport type TableSort = 'asc' | 'desc' | 'none'\n/** `fill` takes the spare width (the name column); `min` shrinks to its content. */\nexport type TableColumnWidth = 'fill' | 'min'\n\nexport interface TableProps extends TableHTMLAttributes<HTMLTableElement> {\n /** Row height. Default `regular`. */\n density?: TableDensity\n /** `rules` (default): a hairline under every row. `plain`: only the header rule. */\n variant?: TableVariant\n /** Alternate row fills. Off by default — rules usually read better. */\n striped?: boolean\n /**\n * Keep the header visible while rows scroll. The wrapper becomes the scroll container\n * (capped by `--table-max-height`, default 70vh), since a horizontally scrolling wrapper\n * would otherwise trap a page-sticky header.\n */\n stickyHeader?: boolean\n /**\n * Name for the scroll container. When set, the wrapper becomes a focusable, labelled\n * region so keyboard users can scroll a wide table.\n */\n label?: string\n}\n\n/**\n * A semantic data table with numeric columns that line up, sortable headers and a footer for\n * totals.\n *\n * @remarks\n * `Table` is a real `<table>` with Kiln's styles: rules between rows, numeric columns in tabular\n * figures, and a wrapper that scrolls sideways on narrow screens instead of breaking the layout.\n *\n * ## Density, stripes and sticky headers\n *\n * `density=\"compact\"` for dense data, `striped` for alternating fills (rules usually read better),\n * and `stickyHeader` to keep the header in view while the rows scroll inside the table.\n *\n * @privateRemarks\n * A semantic, styled data table: project boards, ledgers, audit logs. The wrapper scrolls\n * horizontally on narrow screens; `ref` and native props go to the `<table>`.\n *\n * <Table><Table.Head><Table.Row><Table.HeaderCell>Project</Table.HeaderCell>…</Table.Row></Table.Head>…</Table>\n */\nconst TableRoot = forwardRef<HTMLTableElement, TableProps>(function Table(\n {\n density = 'regular',\n variant = 'rules',\n striped = false,\n stickyHeader = false,\n label,\n className,\n ...rest\n },\n ref,\n) {\n return (\n <div\n className={styles.scroll}\n data-kiln-component=\"\"\n data-sticky-header={stickyHeader || undefined}\n role={label ? 'region' : undefined}\n aria-label={label}\n tabIndex={label ? 0 : undefined}\n >\n <table\n ref={ref}\n className={cx(styles.table, className)}\n data-density={density}\n data-variant={variant}\n data-striped={striped || undefined}\n data-sticky-header={stickyHeader || undefined}\n {...rest}\n />\n </div>\n )\n})\n\nconst TableHead = forwardRef<HTMLTableSectionElement, HTMLAttributes<HTMLTableSectionElement>>(\n function TableHead({ className, ...rest }, ref) {\n return <thead ref={ref} className={cx(styles.head, className)} {...rest} />\n },\n)\n\nconst TableBody = forwardRef<HTMLTableSectionElement, HTMLAttributes<HTMLTableSectionElement>>(\n function TableBody({ className, ...rest }, ref) {\n return <tbody ref={ref} className={cx(styles.body, className)} {...rest} />\n },\n)\n\n/** Totals row(s). Set above a strong rule. */\nconst TableFoot = forwardRef<HTMLTableSectionElement, HTMLAttributes<HTMLTableSectionElement>>(\n function TableFoot({ className, ...rest }, ref) {\n return <tfoot ref={ref} className={cx(styles.foot, className)} {...rest} />\n },\n)\n\nexport interface TableRowProps extends Omit<HTMLAttributes<HTMLTableRowElement>, 'color'> {\n /** `--color-highlight` fill — \"you\", your project, the circled line. */\n highlighted?: boolean\n /** Hover fill for rows that respond to clicks (put the real link in a cell). */\n interactive?: boolean\n /** Out of play (eliminated, archived, past): every ink drops to the muted step, which still reads. */\n muted?: boolean\n /** A categorical colour drawn as a rail at the row's start: whose row it is, matching their `Tag`. */\n color?: CategoryColor\n}\n\nconst TableRow = forwardRef<HTMLTableRowElement, TableRowProps>(function TableRow(\n { highlighted = false, interactive = false, muted = false, color, className, ...rest },\n ref,\n) {\n return (\n <tr\n ref={ref}\n className={cx(styles.row, className)}\n data-highlighted={highlighted || undefined}\n data-interactive={interactive || undefined}\n data-muted={muted || undefined}\n data-color={color}\n {...rest}\n />\n )\n})\n\nexport interface TableHeaderCellProps\n extends Omit<ThHTMLAttributes<HTMLTableCellElement>, 'align' | 'width'>, VisibilityProps {\n align?: TableAlign\n /** Numeric column: right-aligned with tabular figures. */\n numeric?: boolean\n /** Column sizing: `fill` takes the spare width, `min` shrinks to fit. Default: automatic. */\n width?: TableColumnWidth\n /** Current sort of this column. With `onSort`, the label becomes a button and `aria-sort` is set. */\n sort?: TableSort\n /** Called when the sort button is pressed. Decide the next direction yourself. */\n onSort?: () => void\n}\n\nconst ARIA_SORT = { asc: 'ascending', desc: 'descending', none: 'none' } as const\n\nconst TableHeaderCell = forwardRef<HTMLTableCellElement, TableHeaderCellProps>(\n function TableHeaderCell(\n {\n align,\n numeric = false,\n width,\n sort,\n onSort,\n hideBelow,\n hideAbove,\n scope = 'col',\n className,\n children,\n ...rest\n },\n ref,\n ) {\n const sortable = sort !== undefined && onSort !== undefined\n return (\n <th\n ref={ref}\n scope={scope}\n className={cx(styles.headerCell, visibilityClass({ hideBelow, hideAbove }), className)}\n data-align={align ?? (numeric ? 'end' : undefined)}\n data-numeric={numeric || undefined}\n data-width={width}\n data-sort={sortable ? sort : undefined}\n aria-sort={sort !== undefined ? ARIA_SORT[sort] : undefined}\n {...rest}\n >\n {sortable ? (\n <button type=\"button\" className={styles.sortButton} onClick={onSort}>\n {children}\n <span className={styles.sortGlyph} aria-hidden=\"true\">\n {sort === 'asc' ? <ChevronUpIcon /> : <ChevronDownIcon />}\n </span>\n </button>\n ) : (\n children\n )}\n </th>\n )\n },\n)\n\n/**\n * Hide a column on small screens: put the same `hideBelow` on its HeaderCell and on\n * every Cell in that column (cells are `display: none`, so the columns stay aligned).\n */\nexport interface TableCellProps\n extends Omit<TdHTMLAttributes<HTMLTableCellElement>, 'align'>, VisibilityProps {\n align?: TableAlign\n /** Figures: right-aligned, tabular, in the theme's numeric face (mono in Ledger). */\n numeric?: boolean\n /** Render as `<th scope=\"row\">` — the cell that names the row (project, client). */\n rowHeader?: boolean\n}\n\nconst TableCell = forwardRef<HTMLTableCellElement, TableCellProps>(function TableCell(\n { align, numeric = false, rowHeader = false, hideBelow, hideAbove, className, ...rest },\n ref,\n) {\n const shared = {\n className: cx(styles.cell, visibilityClass({ hideBelow, hideAbove }), className),\n 'data-align': align ?? (numeric ? 'end' : undefined),\n 'data-numeric': numeric || undefined,\n 'data-row-header': rowHeader || undefined,\n }\n if (rowHeader) {\n const { scope = 'row', ...thRest } = rest as ThHTMLAttributes<HTMLTableCellElement>\n return <th ref={ref} scope={scope} {...shared} {...thRest} />\n }\n return <td ref={ref} {...shared} {...rest} />\n})\n\nconst TableCaption = forwardRef<HTMLTableCaptionElement, HTMLAttributes<HTMLTableCaptionElement>>(\n function TableCaption({ className, ...rest }, ref) {\n return <caption ref={ref} className={cx(styles.caption, className)} {...rest} />\n },\n)\n\nexport const Table = Object.assign(TableRoot, {\n Head: TableHead,\n Body: TableBody,\n Foot: TableFoot,\n Row: TableRow,\n HeaderCell: TableHeaderCell,\n Cell: TableCell,\n Caption: TableCaption,\n})\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Table.module.js","names":["scroll","table","caption","headerCell","cell","body","row","foot","head","sortButton","sortGlyph","Table_module_default"],"sources":["../../../../src/components/display/Table/Table.module.css"],"sourcesContent":["/*\n * Component tokens (themes may set any of these on [data-theme]):\n * --table-head-font default var(--font-text) — Fiesta sets its display caps\n * --table-head-numeric-font numeric header cells, default var(--table-head-font, var(--font-text))\n * --table-head-size default var(--text-sm)\n * --table-head-weight default var(--label-weight)\n * --table-head-transform default var(--heading-transform)\n * --table-head-bg default var(--color-canvas) — only painted when stickyHeader\n * --table-max-height default 70vh — scroll height when stickyHeader\n * --table-foot-rule the rule over totals, default var(--border-width-strong) solid\n * var(--color-line-strong) — Ledger draws the accountant's double rule\n * --highlight-tone-{positive,caution,critical,info}-text, --highlight-accent-text\n * tone text inside a highlighted row (shared with List). Default:\n * the tone text mixed 25% toward --color-highlight-ink.\n *\n * Headers are labels, so they're set in the text face (monospace as decoration for small\n * labels is banned in DESIGN §2); figures below them carry the numeric face.\n */\n.scroll {\n inline-size: 100%;\n min-inline-size: 0;\n overflow-x: auto;\n overscroll-behavior-x: contain;\n}\n.scroll[data-sticky-header] {\n overflow: auto;\n max-block-size: var(--table-max-height, 70vh);\n}\n.scroll:focus-visible {\n outline: 2px solid var(--color-focus);\n outline-offset: 2px;\n}\n\n.table {\n --_py: var(--space-2);\n --_px: var(--space-3);\n\n inline-size: 100%;\n border-collapse: separate;\n border-spacing: 0;\n font-family: var(--font-text);\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n color: var(--color-ink);\n}\n.table[data-density='compact'] {\n --_py: var(--space-1);\n --_px: var(--space-2);\n}\n\n.caption {\n caption-side: top;\n padding-block-end: var(--space-3);\n color: var(--color-ink-muted);\n font-size: var(--text-sm);\n text-align: start;\n}\n\n/* ── Cells ─────────────────────────────────────────────────────────────── */\n\n.headerCell,\n.cell {\n padding: var(--_py) var(--_px);\n text-align: start;\n vertical-align: baseline;\n}\n.headerCell:first-child,\n.cell:first-child {\n padding-inline-start: var(--space-2);\n}\n.headerCell:last-child,\n.cell:last-child {\n padding-inline-end: var(--space-2);\n}\n/* Figure columns are narrow; tighter gutters keep a 10-column figures table on a phone. */\n:is(.headerCell, .cell)[data-numeric] {\n padding-inline: var(--space-2);\n}\n.headerCell abbr {\n text-decoration: none;\n cursor: help;\n}\n:is(.headerCell, .cell)[data-align='center'] {\n text-align: center;\n}\n:is(.headerCell, .cell)[data-align='end'] {\n text-align: end;\n}\n\n.headerCell {\n padding-block: var(--_py);\n color: var(--color-ink-muted);\n font-family: var(--table-head-font, var(--font-text));\n font-size: var(--table-head-size, var(--text-sm));\n font-weight: var(--table-head-weight, var(--label-weight));\n letter-spacing: var(--label-tracking);\n text-transform: var(--table-head-transform, var(--heading-transform));\n white-space: nowrap;\n vertical-align: bottom;\n border-block-end: var(--border-width-strong) solid var(--color-line-strong);\n}\n\n.headerCell[data-numeric] {\n font-family: var(--table-head-numeric-font, var(--table-head-font, var(--font-text)));\n font-variant-numeric: tabular-nums lining-nums;\n}\n.headerCell[data-width='fill'] {\n inline-size: 100%;\n}\n.headerCell[data-width='min'] {\n inline-size: 1%;\n}\n\n.cell[data-numeric] {\n font-family: var(--font-numeric);\n font-stretch: var(--numeric-stretch);\n font-variant-numeric: tabular-nums lining-nums;\n white-space: nowrap;\n}\n.cell[data-row-header] {\n font-weight: var(--label-weight);\n}\n\n/* ── Rows ──────────────────────────────────────────────────────────────── */\n\n.table[data-variant='rules'] .body .cell {\n border-block-end: var(--border-width) solid var(--color-line);\n}\n.table[data-variant='rules'] .body > .row:last-child > .cell {\n border-block-end-color: var(--color-line-strong);\n}\n\n.table[data-striped] .body > .row:nth-child(even) > .cell {\n background: var(--color-surface-sunken);\n}\n\n/*\n * A highlighted row re-points the colour roles for its cells' content, so a muted date,\n * a tone-coloured Numeral or a LiveIndicator stays legible on the highlight fill (gold in\n * Fiesta night, where the ordinary tone texts are pale). Same approach as Section's\n * inverse band: the row publishes its fill/ink and tone texts under private names, and\n * the cells swap the roles — setting e.g. --tone-positive-text from itself on one element\n * would be a var() cycle.\n */\n.row[data-highlighted] {\n --_band-fill: var(--color-highlight);\n --_band-ink: var(--color-highlight-ink);\n --_band-positive: var(\n --highlight-tone-positive-text,\n color-mix(in oklab, var(--tone-positive-text) 75%, var(--color-highlight-ink))\n );\n --_band-caution: var(\n --highlight-tone-caution-text,\n color-mix(in oklab, var(--tone-caution-text) 75%, var(--color-highlight-ink))\n );\n --_band-critical: var(\n --highlight-tone-critical-text,\n color-mix(in oklab, var(--tone-critical-text) 75%, var(--color-highlight-ink))\n );\n --_band-info: var(\n --highlight-tone-info-text,\n color-mix(in oklab, var(--tone-info-text) 75%, var(--color-highlight-ink))\n );\n --_band-accent: var(\n --highlight-accent-text,\n color-mix(in oklab, var(--color-accent-text) 75%, var(--color-highlight-ink))\n );\n}\n.row[data-highlighted] > .cell {\n --color-ink: var(--_band-ink);\n --color-ink-muted: color-mix(in oklab, var(--_band-ink), var(--_band-fill) 25%);\n --color-ink-subtle: color-mix(in oklab, var(--_band-ink), var(--_band-fill) 35%);\n --tone-positive-text: var(--_band-positive);\n --tone-caution-text: var(--_band-caution);\n --tone-critical-text: var(--_band-critical);\n --tone-info-text: var(--_band-info);\n --color-accent-text: var(--_band-accent);\n /* Soft fills (a soft Badge or Tag) tint the band, like Section's accent band, so the\n re-pointed tone texts above stay legible on them in every mode. */\n --tone-positive-soft: color-mix(in oklab, var(--_band-positive) 8%, var(--_band-fill));\n --tone-caution-soft: color-mix(in oklab, var(--_band-caution) 8%, var(--_band-fill));\n --tone-critical-soft: color-mix(in oklab, var(--_band-critical) 8%, var(--_band-fill));\n --tone-info-soft: color-mix(in oklab, var(--_band-info) 8%, var(--_band-fill));\n --color-accent-soft: color-mix(in oklab, var(--_band-accent) 8%, var(--_band-fill));\n\n background: var(--_band-fill);\n color: var(--_band-ink);\n}\n/* Round the ends of a highlighted row so the fill reads as a marker, not a stripe. */\n.row[data-highlighted] > .cell:first-child {\n border-start-start-radius: var(--radius-field);\n border-end-start-radius: var(--radius-field);\n}\n.row[data-highlighted] > .cell:last-child {\n border-start-end-radius: var(--radius-field);\n border-end-end-radius: var(--radius-field);\n}\n\n.row[data-interactive] {\n cursor: pointer;\n}\n.row > .cell {\n transition: background-color var(--dur-1) var(--ease-out);\n}\n@media (hover: hover) {\n .row[data-interactive]:not([data-highlighted]):hover > .cell {\n background: var(--color-surface-sunken);\n }\n}\n\n.foot > :first-child > .cell {\n border-block-start: var(\n --table-foot-rule,\n var(--border-width-strong) solid var(--color-line-strong)\n );\n}\n.foot .cell {\n font-weight: var(--label-weight);\n}\n/* The totals rule replaces the last row's hairline rather than stacking under it. */\n.table[data-variant='rules'] .body:has(+ .foot) > .row:last-child > .cell {\n border-block-end-width: 0;\n}\n\n/* ── Sticky header ─────────────────────────────────────────────────────── */\n\n.table[data-sticky-header] .head .headerCell {\n position: sticky;\n inset-block-start: 0;\n z-index: 1;\n background: var(--table-head-bg, var(--color-canvas));\n}\n\n/* ── Sort button ───────────────────────────────────────────────────────── */\n\n.sortButton {\n display: inline-flex;\n align-items: center;\n gap: 0.25em;\n margin: calc(var(--space-1) * -1);\n padding: var(--space-1);\n border: 0;\n border-radius: var(--radius-field);\n background: transparent;\n color: inherit;\n font: inherit;\n letter-spacing: inherit;\n text-transform: inherit;\n cursor: pointer;\n transition: color var(--dur-1) var(--ease-out);\n}\n.headerCell[data-align='end'] .sortButton {\n flex-direction: row-reverse;\n}\n@media (hover: hover) {\n .sortButton:hover {\n color: var(--color-ink);\n }\n}\n.sortButton:focus-visible {\n outline: 2px solid var(--color-focus);\n outline-offset: 0;\n}\n.sortGlyph {\n display: inline-flex;\n font-size: 1.1em;\n opacity: 0;\n transition: opacity var(--dur-1) var(--ease-out);\n}\n.headerCell:is([data-sort='asc'], [data-sort='desc']) {\n color: var(--color-ink);\n}\n.headerCell:is([data-sort='asc'], [data-sort='desc']) .sortGlyph,\n.sortButton:hover .sortGlyph,\n.sortButton:focus-visible .sortGlyph {\n opacity: 1;\n}\n.headerCell[data-sort='none'] .sortButton:is(:hover, :focus-visible) .sortGlyph {\n opacity: 0.45;\n}\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"Table.module.js","names":["scroll","table","caption","headerCell","cell","body","row","foot","head","sortButton","sortGlyph","Table_module_default"],"sources":["../../../../src/components/display/Table/Table.module.css"],"sourcesContent":["/*\n * Component tokens (themes may set any of these on [data-theme]):\n * --table-head-font default var(--font-text) — Fiesta sets its display caps\n * --table-head-numeric-font numeric header cells, default var(--table-head-font, var(--font-text))\n * --table-head-size default var(--text-sm)\n * --table-head-weight default var(--label-weight)\n * --table-head-transform default var(--heading-transform)\n * --table-head-bg default var(--color-canvas) — only painted when stickyHeader\n * --table-max-height default 70vh — scroll height when stickyHeader\n * --table-rail-width default var(--space-1) — the categorical rail of a `color` row\n * --table-foot-rule the rule over totals, default var(--border-width-strong) solid\n * var(--color-line-strong) — Ledger draws the accountant's double rule\n * --highlight-tone-{positive,caution,critical,info}-text, --highlight-accent-text\n * tone text inside a highlighted row (shared with List). Default:\n * the tone text mixed 25% toward --color-highlight-ink.\n *\n * Headers are labels, so they're set in the text face (monospace as decoration for small\n * labels is banned in DESIGN §2); figures below them carry the numeric face.\n */\n.scroll {\n inline-size: 100%;\n min-inline-size: 0;\n overflow-x: auto;\n overscroll-behavior-x: contain;\n}\n.scroll[data-sticky-header] {\n overflow: auto;\n max-block-size: var(--table-max-height, 70vh);\n}\n.scroll:focus-visible {\n outline: 2px solid var(--color-focus);\n outline-offset: 2px;\n}\n\n.table {\n --_py: var(--space-2);\n --_px: var(--space-3);\n\n inline-size: 100%;\n border-collapse: separate;\n border-spacing: 0;\n font-family: var(--font-text);\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n color: var(--color-ink);\n}\n.table[data-density='compact'] {\n --_py: var(--space-1);\n --_px: var(--space-2);\n}\n\n.caption {\n caption-side: top;\n padding-block-end: var(--space-3);\n color: var(--color-ink-muted);\n font-size: var(--text-sm);\n text-align: start;\n}\n\n/* ── Cells ─────────────────────────────────────────────────────────────── */\n\n.headerCell,\n.cell {\n padding: var(--_py) var(--_px);\n text-align: start;\n vertical-align: baseline;\n}\n.headerCell:first-child,\n.cell:first-child {\n padding-inline-start: var(--space-2);\n}\n.headerCell:last-child,\n.cell:last-child {\n padding-inline-end: var(--space-2);\n}\n/* Figure columns are narrow; tighter gutters keep a 10-column figures table on a phone. */\n:is(.headerCell, .cell)[data-numeric] {\n padding-inline: var(--space-2);\n}\n.headerCell abbr {\n text-decoration: none;\n cursor: help;\n}\n:is(.headerCell, .cell)[data-align='center'] {\n text-align: center;\n}\n:is(.headerCell, .cell)[data-align='end'] {\n text-align: end;\n}\n\n.headerCell {\n padding-block: var(--_py);\n color: var(--color-ink-muted);\n font-family: var(--table-head-font, var(--font-text));\n font-size: var(--table-head-size, var(--text-sm));\n font-weight: var(--table-head-weight, var(--label-weight));\n letter-spacing: var(--label-tracking);\n text-transform: var(--table-head-transform, var(--heading-transform));\n white-space: nowrap;\n vertical-align: bottom;\n border-block-end: var(--border-width-strong) solid var(--color-line-strong);\n}\n\n.headerCell[data-numeric] {\n font-family: var(--table-head-numeric-font, var(--table-head-font, var(--font-text)));\n font-variant-numeric: tabular-nums lining-nums;\n}\n.headerCell[data-width='fill'] {\n inline-size: 100%;\n}\n.headerCell[data-width='min'] {\n inline-size: 1%;\n}\n\n.cell[data-numeric] {\n font-family: var(--font-numeric);\n font-stretch: var(--numeric-stretch);\n font-variant-numeric: tabular-nums lining-nums;\n white-space: nowrap;\n}\n.cell[data-row-header] {\n font-weight: var(--label-weight);\n}\n\n/* ── Rows ──────────────────────────────────────────────────────────────── */\n\n.table[data-variant='rules'] .body .cell {\n border-block-end: var(--border-width) solid var(--color-line);\n}\n.table[data-variant='rules'] .body > .row:last-child > .cell {\n border-block-end-color: var(--color-line-strong);\n}\n\n.table[data-striped] .body > .row:nth-child(even) > .cell {\n background: var(--color-surface-sunken);\n}\n\n/*\n * A highlighted row re-points the colour roles for its cells' content, so a muted date,\n * a tone-coloured Numeral or a LiveIndicator stays legible on the highlight fill (gold in\n * Fiesta night, where the ordinary tone texts are pale). Same approach as Section's\n * inverse band: the row publishes its fill/ink and tone texts under private names, and\n * the cells swap the roles — setting e.g. --tone-positive-text from itself on one element\n * would be a var() cycle.\n */\n.row[data-highlighted] {\n --_band-fill: var(--color-highlight);\n --_band-ink: var(--color-highlight-ink);\n --_band-positive: var(\n --highlight-tone-positive-text,\n color-mix(in oklab, var(--tone-positive-text) 75%, var(--color-highlight-ink))\n );\n --_band-caution: var(\n --highlight-tone-caution-text,\n color-mix(in oklab, var(--tone-caution-text) 75%, var(--color-highlight-ink))\n );\n --_band-critical: var(\n --highlight-tone-critical-text,\n color-mix(in oklab, var(--tone-critical-text) 75%, var(--color-highlight-ink))\n );\n --_band-info: var(\n --highlight-tone-info-text,\n color-mix(in oklab, var(--tone-info-text) 75%, var(--color-highlight-ink))\n );\n --_band-accent: var(\n --highlight-accent-text,\n color-mix(in oklab, var(--color-accent-text) 75%, var(--color-highlight-ink))\n );\n}\n.row[data-highlighted] > .cell {\n --color-ink: var(--_band-ink);\n --color-ink-muted: color-mix(in oklab, var(--_band-ink), var(--_band-fill) 25%);\n --color-ink-subtle: color-mix(in oklab, var(--_band-ink), var(--_band-fill) 35%);\n --tone-positive-text: var(--_band-positive);\n --tone-caution-text: var(--_band-caution);\n --tone-critical-text: var(--_band-critical);\n --tone-info-text: var(--_band-info);\n --color-accent-text: var(--_band-accent);\n /* Soft fills (a soft Badge or Tag) tint the band, like Section's accent band, so the\n re-pointed tone texts above stay legible on them in every mode. */\n --tone-positive-soft: color-mix(in oklab, var(--_band-positive) 8%, var(--_band-fill));\n --tone-caution-soft: color-mix(in oklab, var(--_band-caution) 8%, var(--_band-fill));\n --tone-critical-soft: color-mix(in oklab, var(--_band-critical) 8%, var(--_band-fill));\n --tone-info-soft: color-mix(in oklab, var(--_band-info) 8%, var(--_band-fill));\n --color-accent-soft: color-mix(in oklab, var(--_band-accent) 8%, var(--_band-fill));\n\n background: var(--_band-fill);\n color: var(--_band-ink);\n}\n/* Round the ends of a highlighted row so the fill reads as a marker, not a stripe. */\n.row[data-highlighted] > .cell:first-child {\n border-start-start-radius: var(--radius-field);\n border-end-start-radius: var(--radius-field);\n}\n.row[data-highlighted] > .cell:last-child {\n border-start-end-radius: var(--radius-field);\n border-end-end-radius: var(--radius-field);\n}\n\n.row[data-interactive] {\n cursor: pointer;\n}\n\n/* Muted: out of play. Every ink drops to the muted step, which still clears AA. */\n.row[data-muted] > .cell {\n --color-ink: var(--color-ink-muted);\n\n color: var(--color-ink-muted);\n}\n\n/* A categorical rail at the row's start: whose row it is, in their Tag's colour. */\n.row[data-color] > .cell:first-child {\n position: relative;\n padding-inline-start: calc(var(--space-2) + var(--space-2));\n}\n.row[data-color] > .cell:first-child::before {\n content: '';\n position: absolute;\n inset-block: var(--space-1);\n inset-inline-start: 0;\n inline-size: var(--table-rail-width, var(--space-1));\n border-radius: var(--radius-chip);\n background: var(--_cat);\n}\n.row[data-color='1'] {\n --_cat: var(--color-cat-1);\n}\n.row[data-color='2'] {\n --_cat: var(--color-cat-2);\n}\n.row[data-color='3'] {\n --_cat: var(--color-cat-3);\n}\n.row[data-color='4'] {\n --_cat: var(--color-cat-4);\n}\n.row[data-color='5'] {\n --_cat: var(--color-cat-5);\n}\n.row[data-color='6'] {\n --_cat: var(--color-cat-6);\n}\n.row[data-color='7'] {\n --_cat: var(--color-cat-7);\n}\n.row[data-color='8'] {\n --_cat: var(--color-cat-8);\n}\n.row > .cell {\n transition: background-color var(--dur-1) var(--ease-out);\n}\n@media (hover: hover) {\n .row[data-interactive]:not([data-highlighted]):hover > .cell {\n background: var(--color-surface-sunken);\n }\n}\n\n.foot > :first-child > .cell {\n border-block-start: var(\n --table-foot-rule,\n var(--border-width-strong) solid var(--color-line-strong)\n );\n}\n.foot .cell {\n font-weight: var(--label-weight);\n}\n/* The totals rule replaces the last row's hairline rather than stacking under it. */\n.table[data-variant='rules'] .body:has(+ .foot) > .row:last-child > .cell {\n border-block-end-width: 0;\n}\n\n/* ── Sticky header ─────────────────────────────────────────────────────── */\n\n.table[data-sticky-header] .head .headerCell {\n position: sticky;\n inset-block-start: 0;\n z-index: 1;\n background: var(--table-head-bg, var(--color-canvas));\n}\n\n/* ── Sort button ───────────────────────────────────────────────────────── */\n\n.sortButton {\n display: inline-flex;\n align-items: center;\n gap: 0.25em;\n margin: calc(var(--space-1) * -1);\n padding: var(--space-1);\n border: 0;\n border-radius: var(--radius-field);\n background: transparent;\n color: inherit;\n font: inherit;\n letter-spacing: inherit;\n text-transform: inherit;\n cursor: pointer;\n transition: color var(--dur-1) var(--ease-out);\n}\n.headerCell[data-align='end'] .sortButton {\n flex-direction: row-reverse;\n}\n@media (hover: hover) {\n .sortButton:hover {\n color: var(--color-ink);\n }\n}\n.sortButton:focus-visible {\n outline: 2px solid var(--color-focus);\n outline-offset: 0;\n}\n.sortGlyph {\n display: inline-flex;\n font-size: 1.1em;\n opacity: 0;\n transition: opacity var(--dur-1) var(--ease-out);\n}\n.headerCell:is([data-sort='asc'], [data-sort='desc']) {\n color: var(--color-ink);\n}\n.headerCell:is([data-sort='asc'], [data-sort='desc']) .sortGlyph,\n.sortButton:hover .sortGlyph,\n.sortButton:focus-visible .sortGlyph {\n opacity: 1;\n}\n.headerCell[data-sort='none'] .sortButton:is(:hover, :focus-visible) .sortGlyph {\n opacity: 0.45;\n}\n"],"mappings":""}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { type HTMLAttributes, type MouseEvent } from 'react';
|
|
2
2
|
/** Categorical colour slot → `--color-cat-N`. */
|
|
3
3
|
export type TagColor = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
|
4
|
+
export type TagSize = 'sm' | 'md';
|
|
4
5
|
export interface TagProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'color'> {
|
|
5
6
|
/** A categorical colour (category, person, topic). Omit for a neutral tag. */
|
|
6
7
|
color?: TagColor;
|
|
8
|
+
/** `md` (default), or `sm` for dense lists of many tags. */
|
|
9
|
+
size?: TagSize;
|
|
7
10
|
/** Renders a remove button after the label. Not rendered when `asChild`. */
|
|
8
11
|
onRemove?: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
9
12
|
/** Accessible name for the remove button. Default `Remove <label>` for text labels. */
|
|
@@ -5,19 +5,20 @@ import { Children as r, forwardRef as i, isValidElement as a } from "react";
|
|
|
5
5
|
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
6
6
|
import { Slot as c } from "radix-ui";
|
|
7
7
|
//#region src/components/display/Tag/Tag.tsx
|
|
8
|
-
var l = i(function({ color: r,
|
|
9
|
-
let
|
|
10
|
-
return /* @__PURE__ */ s(
|
|
11
|
-
ref:
|
|
12
|
-
className: e(n.tag,
|
|
8
|
+
var l = i(function({ color: r, size: i = "md", onRemove: a, removeLabel: l, asChild: u = !1, className: d, children: f, ...p }, m) {
|
|
9
|
+
let h = u ? c.Root : "span", g = !!a && !u, _ = l ?? (typeof f == "string" ? `Remove ${f}` : "Remove");
|
|
10
|
+
return /* @__PURE__ */ s(h, {
|
|
11
|
+
ref: m,
|
|
12
|
+
className: e(n.tag, d),
|
|
13
13
|
"data-color": r,
|
|
14
|
-
"data-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
"data-size": i,
|
|
15
|
+
"data-removable": g || void 0,
|
|
16
|
+
...p,
|
|
17
|
+
children: [/* @__PURE__ */ o(c.Slottable, { children: f }), g ? /* @__PURE__ */ o("button", {
|
|
17
18
|
type: "button",
|
|
18
19
|
className: n.remove,
|
|
19
|
-
"aria-label":
|
|
20
|
-
onClick:
|
|
20
|
+
"aria-label": _,
|
|
21
|
+
onClick: a,
|
|
21
22
|
children: /* @__PURE__ */ o(t, {})
|
|
22
23
|
}) : null]
|
|
23
24
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tag.js","names":["styles","Tag_module_default","Tag","forwardRef","rest","Comp","asChild","Slot","canRemove","onRemove","label","removeLabel","children","cx","className","color","CloseIcon","TagList","Children","isValidElement","child"],"sources":["../../../../src/components/display/Tag/Tag.tsx"],"sourcesContent":["import { Children, forwardRef, isValidElement, type HTMLAttributes, type MouseEvent } from 'react'\nimport { Slot } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport { CloseIcon } from '#icons'\nimport styles from './Tag.module.css'\n\n/** Categorical colour slot → `--color-cat-N`. */\nexport type TagColor = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8\n\nexport interface TagProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'color'> {\n /** A categorical colour (category, person, topic). Omit for a neutral tag. */\n color?: TagColor\n /** Renders a remove button after the label. Not rendered when `asChild`. */\n onRemove?: (event: MouseEvent<HTMLButtonElement>) => void\n /** Accessible name for the remove button. Default `Remove <label>` for text labels. */\n removeLabel?: string\n /** Render the single child (e.g. a filter link) as the tag. Incompatible with `onRemove`. */\n asChild?: boolean\n}\n\n/**\n * Quiet metadata, like a category, a facility or a group, in a wrapping list that can be edited.\n *\n * @remarks\n * A `Tag` labels a thing with metadata, and a `TagList` lays several out as a real list that\n * wraps. Tags are quieter than {@link Badge | badges}, which are for status.\n *\n * @privateRemarks\n * Quiet metadata: a category, a stack item, a group. Quieter than `Badge`, which is\n * for status. Group several in a `TagList`.\n */\nexport const Tag = forwardRef<HTMLSpanElement, TagProps>(function Tag(\n { color, onRemove, removeLabel, asChild = false, className, children, ...rest },\n ref,\n) {\n const Comp = asChild ? Slot.Root : 'span'\n const canRemove = Boolean(onRemove) && !asChild\n const label = removeLabel ?? (typeof children === 'string' ? `Remove ${children}` : 'Remove')\n return (\n <Comp\n ref={ref}\n className={cx(styles.tag, className)}\n data-color={color}\n data-removable={canRemove || undefined}\n {...rest}\n >\n <Slot.Slottable>{children}</Slot.Slottable>\n {canRemove ? (\n <button type=\"button\" className={styles.remove} aria-label={label} onClick={onRemove}>\n <CloseIcon />\n </button>\n ) : null}\n </Comp>\n )\n})\n\nexport type TagListProps = HTMLAttributes<HTMLUListElement>\n\n/** A wrapping list of tags. Each child is wrapped in an `<li>`. */\nexport const TagList = forwardRef<HTMLUListElement, TagListProps>(function TagList(\n { className, children, ...rest },\n ref,\n) {\n return (\n <ul ref={ref} role=\"list\" className={cx(styles.list, className)} {...rest}>\n {Children.map(children, (child) =>\n isValidElement(child) || typeof child === 'string' || typeof child === 'number' ? (\n <li key={isValidElement(child) ? child.key : undefined}>{child}</li>\n ) : null,\n )}\n </ul>\n )\n})\n"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"Tag.js","names":["styles","Tag_module_default","Tag","forwardRef","rest","Comp","asChild","Slot","canRemove","onRemove","label","removeLabel","children","cx","className","color","size","CloseIcon","TagList","Children","isValidElement","child"],"sources":["../../../../src/components/display/Tag/Tag.tsx"],"sourcesContent":["import { Children, forwardRef, isValidElement, type HTMLAttributes, type MouseEvent } from 'react'\nimport { Slot } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport { CloseIcon } from '#icons'\nimport styles from './Tag.module.css'\n\n/** Categorical colour slot → `--color-cat-N`. */\nexport type TagColor = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8\nexport type TagSize = 'sm' | 'md'\n\nexport interface TagProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'color'> {\n /** A categorical colour (category, person, topic). Omit for a neutral tag. */\n color?: TagColor\n /** `md` (default), or `sm` for dense lists of many tags. */\n size?: TagSize\n /** Renders a remove button after the label. Not rendered when `asChild`. */\n onRemove?: (event: MouseEvent<HTMLButtonElement>) => void\n /** Accessible name for the remove button. Default `Remove <label>` for text labels. */\n removeLabel?: string\n /** Render the single child (e.g. a filter link) as the tag. Incompatible with `onRemove`. */\n asChild?: boolean\n}\n\n/**\n * Quiet metadata, like a category, a facility or a group, in a wrapping list that can be edited.\n *\n * @remarks\n * A `Tag` labels a thing with metadata, and a `TagList` lays several out as a real list that\n * wraps. Tags are quieter than {@link Badge | badges}, which are for status.\n *\n * @privateRemarks\n * Quiet metadata: a category, a stack item, a group. Quieter than `Badge`, which is\n * for status. Group several in a `TagList`.\n */\nexport const Tag = forwardRef<HTMLSpanElement, TagProps>(function Tag(\n { color, size = 'md', onRemove, removeLabel, asChild = false, className, children, ...rest },\n ref,\n) {\n const Comp = asChild ? Slot.Root : 'span'\n const canRemove = Boolean(onRemove) && !asChild\n const label = removeLabel ?? (typeof children === 'string' ? `Remove ${children}` : 'Remove')\n return (\n <Comp\n ref={ref}\n className={cx(styles.tag, className)}\n data-color={color}\n data-size={size}\n data-removable={canRemove || undefined}\n {...rest}\n >\n <Slot.Slottable>{children}</Slot.Slottable>\n {canRemove ? (\n <button type=\"button\" className={styles.remove} aria-label={label} onClick={onRemove}>\n <CloseIcon />\n </button>\n ) : null}\n </Comp>\n )\n})\n\nexport type TagListProps = HTMLAttributes<HTMLUListElement>\n\n/** A wrapping list of tags. Each child is wrapped in an `<li>`. */\nexport const TagList = forwardRef<HTMLUListElement, TagListProps>(function TagList(\n { className, children, ...rest },\n ref,\n) {\n return (\n <ul ref={ref} role=\"list\" className={cx(styles.list, className)} {...rest}>\n {Children.map(children, (child) =>\n isValidElement(child) || typeof child === 'string' || typeof child === 'number' ? (\n <li key={isValidElement(child) ? child.key : undefined}>{child}</li>\n ) : null,\n )}\n </ul>\n )\n})\n"],"mappings":";;;;;;;AAkCA,IAAaE,IAAMC,EAAsC,SACvD,EAAE,UAAO,UAAO,MAAM,aAAU,gBAAa,aAAU,IAAO,cAAW,aAAU,GAAGC,KACtF,GACA;CACA,IAAMC,IAAOC,IAAUC,EAAK,OAAO,QAC7BC,IAAY,EAAQC,KAAa,CAACH,GAClCI,IAAQC,MAAgB,OAAOC,KAAa,WAAW,UAAUA,MAAa;CACpF,OACE,kBAACP,GAAD;EACO;EACL,WAAWQ,EAAGb,EAAO,KAAKc,CAAS;EACnC,cAAYC;EACZ,aAAWC;EACX,kBAAgBR,KAAa,KAAA;EAC7B,GAAIJ;EANN,UAAA,CAQE,kBAACG,EAAK,WAAN,EAAiB,YAAyB,CAAA,GACzCC,IACC,kBAAC,UAAD;GAAQ,MAAK;GAAS,WAAWR,EAAO;GAAQ,cAAYU;GAAO,SAASD;GAC1E,UAAA,kBAACQ,GAAD,CAAY,CAAA;EACN,CAAA,IACN,IACA;;AAEV,CAAC,GAKYC,IAAUf,EAA2C,SAChE,EAAE,cAAW,aAAU,GAAGC,KAC1B,GACA;CACA,OACE,kBAAC,MAAD;EAAS;EAAK,MAAK;EAAO,WAAWS,EAAGb,EAAO,MAAMc,CAAS;EAAG,GAAIV;EAClE,UAAAe,EAAS,IAAIP,IAAW,MACvBQ,EAAeC,CAAK,KAAK,OAAOA,KAAU,YAAY,OAAOA,KAAU,WACrE,kBAAC,MAAD,EAAA,UAAyDA,EAAU,GAA1DD,EAAeC,CAAK,IAAIA,EAAM,MAAM,KAAA,CAAsB,IACjE,IACN;CACE,CAAA;AAER,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tag.module.js","names":["tag","remove","list","Tag_module_default"],"sources":["../../../../src/components/display/Tag/Tag.module.css"],"sourcesContent":["/*\n * Component tokens (themes may set any of these on [data-theme]):\n * --tag-radius default var(--radius-chip)\n *\n * Quieter than Badge: text weight, no status colour. Neutral tags are a hairline;\n * categorical tags are a flat fill of --color-cat-N with --color-cat-ink (AA on all 8).\n */\n.tag {\n --_bg: transparent;\n --_ink: var(--color-ink-muted);\n --_border: var(--color-line);\n\n display: inline-flex;\n flex: none;\n align-items: center;\n gap: var(--space-1);\n min-block-size: calc(var(--space-5) * 0.9);\n padding-inline: var(--space-2);\n border: var(--border-width) solid var(--_border);\n border-radius: var(--tag-radius, var(--radius-chip));\n background: var(--_bg);\n color: var(--_ink);\n font-family: var(--font-text);\n font-size: var(--text-xs);\n font-weight: 450;\n line-height: 1;\n white-space: nowrap;\n text-decoration: none;\n vertical-align: middle;\n transition:\n background-color var(--dur-1) var(--ease-out),\n border-color var(--dur-1) var(--ease-out),\n color var(--dur-1) var(--ease-out);\n}\n\n.tag[data-removable] {\n padding-inline-end: calc(var(--space-1) / 2);\n}\n\n.tag[data-color] {\n --_ink: var(--color-cat-ink);\n --_border: var(--_bg);\n}\n.tag[data-color='1'] {\n --_bg: var(--color-cat-1);\n}\n.tag[data-color='2'] {\n --_bg: var(--color-cat-2);\n}\n.tag[data-color='3'] {\n --_bg: var(--color-cat-3);\n}\n.tag[data-color='4'] {\n --_bg: var(--color-cat-4);\n}\n.tag[data-color='5'] {\n --_bg: var(--color-cat-5);\n}\n.tag[data-color='6'] {\n --_bg: var(--color-cat-6);\n}\n.tag[data-color='7'] {\n --_bg: var(--color-cat-7);\n}\n.tag[data-color='8'] {\n --_bg: var(--color-cat-8);\n}\n\n/* As a link (asChild): the edge firms up on hover. */\n@media (hover: hover) {\n a.tag:hover {\n --_border: var(--color-line-strong);\n --_ink: var(--color-ink);\n }\n a.tag[data-color]:hover {\n --_border: var(--color-cat-ink);\n --_ink: var(--color-cat-ink);\n }\n}\n.tag:focus-visible {\n outline: 2px solid var(--color-focus);\n outline-offset: 2px;\n}\n\n.remove {\n position: relative;\n display: inline-grid;\n place-items: center;\n flex: none;\n inline-size: 1.5em;\n block-size: 1.5em;\n padding: 0;\n border: 0;\n border-radius: var(--tag-radius, var(--radius-chip));\n background: transparent;\n color: inherit;\n font-size: inherit;\n opacity: 0.7;\n cursor: pointer;\n transition:\n opacity var(--dur-1) var(--ease-out),\n background-color var(--dur-1) var(--ease-out);\n}\n/* Larger hit target than the glyph, without growing the tag. */\n.remove::after {\n content: '';\n position: absolute;\n inset: -0.35em;\n}\n.remove > svg {\n inline-size: 1.1em;\n block-size: 1.1em;\n}\n@media (hover: hover) {\n .remove:hover {\n opacity: 1;\n background: color-mix(in oklch, currentColor 14%, transparent);\n }\n}\n.remove:focus-visible {\n opacity: 1;\n outline: 2px solid var(--color-focus);\n outline-offset: 1px;\n}\n\n.list {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-2);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.list > li {\n display: flex;\n}\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"Tag.module.js","names":["tag","remove","list","Tag_module_default"],"sources":["../../../../src/components/display/Tag/Tag.module.css"],"sourcesContent":["/*\n * Component tokens (themes may set any of these on [data-theme]):\n * --tag-radius default var(--radius-chip)\n *\n * Quieter than Badge: text weight, no status colour. Neutral tags are a hairline;\n * categorical tags are a flat fill of --color-cat-N with --color-cat-ink (AA on all 8).\n */\n.tag {\n --_bg: transparent;\n --_ink: var(--color-ink-muted);\n --_border: var(--color-line);\n\n display: inline-flex;\n flex: none;\n align-items: center;\n gap: var(--space-1);\n min-block-size: calc(var(--space-5) * 0.9);\n padding-inline: var(--space-2);\n border: var(--border-width) solid var(--_border);\n border-radius: var(--tag-radius, var(--radius-chip));\n background: var(--_bg);\n color: var(--_ink);\n font-family: var(--font-text);\n font-size: var(--text-xs);\n font-weight: 450;\n line-height: 1;\n white-space: nowrap;\n text-decoration: none;\n vertical-align: middle;\n transition:\n background-color var(--dur-1) var(--ease-out),\n border-color var(--dur-1) var(--ease-out),\n color var(--dur-1) var(--ease-out);\n}\n\n.tag[data-size='sm'] {\n gap: calc(var(--space-1) / 2);\n min-block-size: var(--space-4);\n padding-inline: var(--space-1);\n font-size: var(--text-2xs);\n}\n\n.tag[data-removable] {\n padding-inline-end: calc(var(--space-1) / 2);\n}\n\n.tag[data-color] {\n --_ink: var(--color-cat-ink);\n --_border: var(--_bg);\n}\n.tag[data-color='1'] {\n --_bg: var(--color-cat-1);\n}\n.tag[data-color='2'] {\n --_bg: var(--color-cat-2);\n}\n.tag[data-color='3'] {\n --_bg: var(--color-cat-3);\n}\n.tag[data-color='4'] {\n --_bg: var(--color-cat-4);\n}\n.tag[data-color='5'] {\n --_bg: var(--color-cat-5);\n}\n.tag[data-color='6'] {\n --_bg: var(--color-cat-6);\n}\n.tag[data-color='7'] {\n --_bg: var(--color-cat-7);\n}\n.tag[data-color='8'] {\n --_bg: var(--color-cat-8);\n}\n\n/* As a link (asChild): the edge firms up on hover. */\n@media (hover: hover) {\n a.tag:hover {\n --_border: var(--color-line-strong);\n --_ink: var(--color-ink);\n }\n a.tag[data-color]:hover {\n --_border: var(--color-cat-ink);\n --_ink: var(--color-cat-ink);\n }\n}\n.tag:focus-visible {\n outline: 2px solid var(--color-focus);\n outline-offset: 2px;\n}\n\n.remove {\n position: relative;\n display: inline-grid;\n place-items: center;\n flex: none;\n inline-size: 1.5em;\n block-size: 1.5em;\n padding: 0;\n border: 0;\n border-radius: var(--tag-radius, var(--radius-chip));\n background: transparent;\n color: inherit;\n font-size: inherit;\n opacity: 0.7;\n cursor: pointer;\n transition:\n opacity var(--dur-1) var(--ease-out),\n background-color var(--dur-1) var(--ease-out);\n}\n/* Larger hit target than the glyph, without growing the tag. */\n.remove::after {\n content: '';\n position: absolute;\n inset: -0.35em;\n}\n.remove > svg {\n inline-size: 1.1em;\n block-size: 1.1em;\n}\n@media (hover: hover) {\n .remove:hover {\n opacity: 1;\n background: color-mix(in oklch, currentColor 14%, transparent);\n }\n}\n.remove:focus-visible {\n opacity: 1;\n outline: 2px solid var(--color-focus);\n outline-offset: 1px;\n}\n\n.list {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-2);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.list > li {\n display: flex;\n}\n"],"mappings":""}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Tag, TagList } from './Tag.js';
|
|
2
|
-
export type { TagProps, TagColor, TagListProps } from './Tag.js';
|
|
2
|
+
export type { TagProps, TagColor, TagSize, TagListProps } from './Tag.js';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactElement } from 'react';
|
|
2
|
+
export interface FlashProps extends HTMLAttributes<HTMLElement> {
|
|
3
|
+
/**
|
|
4
|
+
* Flashes each time this changes after the first render: a score, a count, an updated-at
|
|
5
|
+
* time. Compared with `Object.is`.
|
|
6
|
+
*/
|
|
7
|
+
value?: unknown;
|
|
8
|
+
/**
|
|
9
|
+
* Flash once when the element mounts, for something that's new because of a change: the row
|
|
10
|
+
* a new goal adds. It doesn't flash during the page's first render. Pass it only to elements
|
|
11
|
+
* that are new, not to every row of a list that loads.
|
|
12
|
+
*/
|
|
13
|
+
appear?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Flash when this turns `true`: the element is the place a link just opened on. For routers
|
|
16
|
+
* that change the URL without the browser updating `:target` (most client-side routers).
|
|
17
|
+
* Plain `#hash` links and `hashchange`/`popstate` navigation are caught without it.
|
|
18
|
+
*/
|
|
19
|
+
target?: boolean;
|
|
20
|
+
/** The one element to flash. It receives the class, data attribute and ref. */
|
|
21
|
+
children: ReactElement;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Draws brief attention to an element that just changed, or that a link just jumped to.
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* `Flash` wraps any single element, a list row, a card, a figure, and gives it a short outline
|
|
28
|
+
* in the accent that fades away. It adds no element of its own. It flashes:
|
|
29
|
+
*
|
|
30
|
+
* - each time `value` changes after the first render;
|
|
31
|
+
* - once on mount with `appear`, for an element that's new because of a change;
|
|
32
|
+
* - when the element is the URL's `#target`, on a page load, a `#hash` link or back and forward;
|
|
33
|
+
* - when `target` turns `true`, for a client-side router, which changes the URL without
|
|
34
|
+
* updating `:target`: `target={location.hash === '#match-123'}`.
|
|
35
|
+
*
|
|
36
|
+
* With reduced motion the outline doesn't fade: it shows, holds, and goes. The colour and how
|
|
37
|
+
* long it stays are the `--flash-color` and `--flash-duration` tokens.
|
|
38
|
+
*
|
|
39
|
+
* @privateRemarks
|
|
40
|
+
* One primitive for "this just changed / you were sent here" instead of a flash prop on every
|
|
41
|
+
* component (DESIGN.md §2 Motion: motion that shows a state change). An outline, so it never
|
|
42
|
+
* shifts layout and works over any background. Two keyframe names alternate so each change
|
|
43
|
+
* restarts the animation.
|
|
44
|
+
*
|
|
45
|
+
* <Flash value={goals}><List.Item>…</List.Item></Flash>
|
|
46
|
+
*/
|
|
47
|
+
export declare const Flash: import("react").ForwardRefExoticComponent<FlashProps & import("react").RefAttributes<HTMLElement>>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { cx as e } from "../../../utils/cx.js";
|
|
2
|
+
import { useMergedRefs as t } from "../../inputs/internal/refs.js";
|
|
3
|
+
import n from "./Flash.module.js";
|
|
4
|
+
import { forwardRef as r, useEffect as i, useRef as a, useState as o, useSyncExternalStore as s } from "react";
|
|
5
|
+
import { jsx as c } from "react/jsx-runtime";
|
|
6
|
+
import { Slot as l } from "radix-ui";
|
|
7
|
+
//#region src/components/feedback/Flash/Flash.tsx
|
|
8
|
+
var u = !1, d = () => () => void 0, f = r(function({ value: r, appear: f = !1, target: p = !1, className: m, children: h, ...g }, _) {
|
|
9
|
+
let v = a(null), y = s(d, () => u, () => !1), [b, x] = o(f && y || p ? 1 : 0), [S, C] = o({
|
|
10
|
+
value: r,
|
|
11
|
+
target: p
|
|
12
|
+
});
|
|
13
|
+
return (!Object.is(S.value, r) || S.target !== p) && (C({
|
|
14
|
+
value: r,
|
|
15
|
+
target: p
|
|
16
|
+
}), (!Object.is(S.value, r) || p && !S.target) && x(b + 1)), i(() => {
|
|
17
|
+
u = !0;
|
|
18
|
+
let e = () => {
|
|
19
|
+
let e = v.current?.id;
|
|
20
|
+
e && window.location.hash === `#${e}` && x((e) => e + 1);
|
|
21
|
+
};
|
|
22
|
+
return window.addEventListener("hashchange", e), window.addEventListener("popstate", e), () => {
|
|
23
|
+
window.removeEventListener("hashchange", e), window.removeEventListener("popstate", e);
|
|
24
|
+
};
|
|
25
|
+
}, []), /* @__PURE__ */ c(l.Root, {
|
|
26
|
+
ref: t(_, v),
|
|
27
|
+
className: e(n.flash, m),
|
|
28
|
+
"data-flash": b === 0 ? void 0 : b % 2 ? "odd" : "even",
|
|
29
|
+
...g,
|
|
30
|
+
children: h
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
//#endregion
|
|
34
|
+
export { f as Flash };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=Flash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Flash.js","names":["styles","Flash_module_default","pageRendered","subscribe","Flash","forwardRef","rest","element","useRef","pageWasRendered","useSyncExternalStore","count","setCount","useState","appear","target","seen","setSeen","value","useEffect","check","id","n","Slot","useMergedRefs","ref","cx","className"],"sources":["../../../../src/components/feedback/Flash/Flash.tsx"],"sourcesContent":["import {\n forwardRef,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n type HTMLAttributes,\n type ReactElement,\n} from 'react'\nimport { Slot } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport { useMergedRefs } from '#components/inputs/internal/refs'\nimport styles from './Flash.module.css'\n\n/**\n * Whether any Flash has committed yet. Elements present at the first commit (the server render\n * and its hydration included) never flash on `appear`, so a page doesn't light up as it loads.\n */\nlet pageRendered = false\n\n/** Nothing to subscribe to: the snapshot only tells hydration (`false`) from a later mount. */\nconst subscribe = () => () => undefined\n\nexport interface FlashProps extends HTMLAttributes<HTMLElement> {\n /**\n * Flashes each time this changes after the first render: a score, a count, an updated-at\n * time. Compared with `Object.is`.\n */\n value?: unknown\n /**\n * Flash once when the element mounts, for something that's new because of a change: the row\n * a new goal adds. It doesn't flash during the page's first render. Pass it only to elements\n * that are new, not to every row of a list that loads.\n */\n appear?: boolean\n /**\n * Flash when this turns `true`: the element is the place a link just opened on. For routers\n * that change the URL without the browser updating `:target` (most client-side routers).\n * Plain `#hash` links and `hashchange`/`popstate` navigation are caught without it.\n */\n target?: boolean\n /** The one element to flash. It receives the class, data attribute and ref. */\n children: ReactElement\n}\n\n/**\n * Draws brief attention to an element that just changed, or that a link just jumped to.\n *\n * @remarks\n * `Flash` wraps any single element, a list row, a card, a figure, and gives it a short outline\n * in the accent that fades away. It adds no element of its own. It flashes:\n *\n * - each time `value` changes after the first render;\n * - once on mount with `appear`, for an element that's new because of a change;\n * - when the element is the URL's `#target`, on a page load, a `#hash` link or back and forward;\n * - when `target` turns `true`, for a client-side router, which changes the URL without\n * updating `:target`: `target={location.hash === '#match-123'}`.\n *\n * With reduced motion the outline doesn't fade: it shows, holds, and goes. The colour and how\n * long it stays are the `--flash-color` and `--flash-duration` tokens.\n *\n * @privateRemarks\n * One primitive for \"this just changed / you were sent here\" instead of a flash prop on every\n * component (DESIGN.md §2 Motion: motion that shows a state change). An outline, so it never\n * shifts layout and works over any background. Two keyframe names alternate so each change\n * restarts the animation.\n *\n * <Flash value={goals}><List.Item>…</List.Item></Flash>\n */\nexport const Flash = forwardRef<HTMLElement, FlashProps>(function Flash(\n { value, appear = false, target = false, className, children, ...rest },\n ref,\n) {\n const element = useRef<HTMLElement>(null)\n const pageWasRendered = useSyncExternalStore(\n subscribe,\n () => pageRendered,\n () => false,\n )\n const [count, setCount] = useState((appear && pageWasRendered) || target ? 1 : 0)\n const [seen, setSeen] = useState({ value, target })\n if (!Object.is(seen.value, value) || seen.target !== target) {\n setSeen({ value, target })\n if (!Object.is(seen.value, value) || (target && !seen.target)) setCount(count + 1)\n }\n\n useEffect(() => {\n pageRendered = true\n // `:target` covers page loads; these cover hash links and history moves after it.\n const check = () => {\n const id = element.current?.id\n if (id && window.location.hash === `#${id}`) setCount((n) => n + 1)\n }\n window.addEventListener('hashchange', check)\n window.addEventListener('popstate', check)\n return () => {\n window.removeEventListener('hashchange', check)\n window.removeEventListener('popstate', check)\n }\n }, [])\n\n return (\n <Slot.Root\n ref={useMergedRefs(ref, element)}\n className={cx(styles.flash, className)}\n data-flash={count === 0 ? undefined : count % 2 ? 'odd' : 'even'}\n {...rest}\n >\n {children}\n </Slot.Root>\n )\n})\n"],"mappings":";;;;;;;AAkBA,IAAIE,IAAe,IAGbC,gBAAwB,KAAA,GAgDjBC,IAAQC,EAAoC,SACvD,EAAE,UAAO,YAAS,IAAO,YAAS,IAAO,cAAW,aAAU,GAAGC,KACjE,GACA;CACA,IAAMC,IAAUC,EAAoB,IAAI,GAClCC,IAAkBC,EACtBP,SACMD,SACA,EACR,GACM,CAACS,GAAOC,KAAYC,EAAUC,KAAUL,KAAoBM,IAAS,IAAI,CAAC,GAC1E,CAACC,GAAMC,KAAWJ,EAAS;EAAE;EAAO;CAAO,CAAC;CAqBlD,QApBI,CAAC,OAAO,GAAGG,EAAK,OAAOE,CAAK,KAAKF,EAAK,WAAWD,OACnDE,EAAQ;EAAE;EAAO;CAAO,CAAC,IACrB,CAAC,OAAO,GAAGD,EAAK,OAAOE,CAAK,KAAMH,KAAU,CAACC,EAAK,WAASJ,EAASD,IAAQ,CAAC,IAGnFQ,QAAgB;EACd,IAAe;EAEf,IAAMC,UAAc;GAClB,IAAMC,IAAKd,EAAQ,SAAS;GAC5B,AAAIc,KAAM,OAAO,SAAS,SAAS,IAAIA,OAAMT,GAAU,MAAMU,IAAI,CAAC;EACpE;EAGA,OAFA,OAAO,iBAAiB,cAAcF,CAAK,GAC3C,OAAO,iBAAiB,YAAYA,CAAK,SAC5B;GAEX,AADA,OAAO,oBAAoB,cAAcA,CAAK,GAC9C,OAAO,oBAAoB,YAAYA,CAAK;EAC9C;CACF,GAAG,CAAC,CAAC,GAGH,kBAACG,EAAK,MAAN;EACE,KAAKC,EAAcC,GAAKlB,CAAO;EAC/B,WAAWmB,EAAG1B,EAAO,OAAO2B,CAAS;EACrC,cAAYhB,MAAU,IAAI,KAAA,IAAYA,IAAQ,IAAI,QAAQ;EAC1D,GAAIL;EAEH;CACQ,CAAA;AAEf,CAAC"}
|