@blurrow/ui 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -20
- package/dist/Accordion.d.ts +17 -0
- package/dist/Accordion.js +138 -0
- package/dist/Accordion.js.map +1 -0
- package/dist/Alert.d.ts +13 -0
- package/dist/Alert.js +69 -0
- package/dist/Alert.js.map +1 -0
- package/dist/AuthorCard.d.ts +12 -0
- package/dist/AuthorCard.js +57 -0
- package/dist/AuthorCard.js.map +1 -0
- package/dist/Avatar.d.ts +21 -0
- package/dist/Avatar.js +96 -0
- package/dist/Avatar.js.map +1 -0
- package/dist/Badge.d.ts +12 -0
- package/dist/Badge.js +46 -0
- package/dist/Badge.js.map +1 -0
- package/dist/Banner.d.ts +15 -0
- package/dist/Banner.js +68 -0
- package/dist/Banner.js.map +1 -0
- package/dist/Breadcrumb.d.ts +13 -0
- package/dist/Breadcrumb.js +45 -0
- package/dist/Breadcrumb.js.map +1 -0
- package/dist/Button.d.ts +19 -0
- package/dist/Button.js +282 -0
- package/dist/Button.js.map +1 -0
- package/dist/ButtonGroup.d.ts +19 -0
- package/dist/ButtonGroup.js +62 -0
- package/dist/ButtonGroup.js.map +1 -0
- package/dist/Card.d.ts +40 -0
- package/dist/Card.js +175 -0
- package/dist/Card.js.map +1 -0
- package/dist/CardShell.d.ts +18 -0
- package/dist/CardShell.js +58 -0
- package/dist/CardShell.js.map +1 -0
- package/dist/Carousel.d.ts +22 -0
- package/dist/Carousel.js +247 -0
- package/dist/Carousel.js.map +1 -0
- package/dist/Checkbox.d.ts +15 -0
- package/dist/Checkbox.js +84 -0
- package/dist/Checkbox.js.map +1 -0
- package/dist/CloseButton.d.ts +12 -0
- package/dist/CloseButton.js +63 -0
- package/dist/CloseButton.js.map +1 -0
- package/dist/CodeBlock.d.ts +12 -0
- package/dist/CodeBlock.js +221 -0
- package/dist/CodeBlock.js.map +1 -0
- package/dist/ColorPicker.d.ts +24 -0
- package/dist/ColorPicker.js +366 -0
- package/dist/ColorPicker.js.map +1 -0
- package/dist/Combobox.d.ts +34 -0
- package/dist/Combobox.js +255 -0
- package/dist/Combobox.js.map +1 -0
- package/dist/CommandPalette.d.ts +25 -0
- package/dist/CommandPalette.js +304 -0
- package/dist/CommandPalette.js.map +1 -0
- package/dist/ContextMenu.d.ts +16 -0
- package/dist/ContextMenu.js +209 -0
- package/dist/ContextMenu.js.map +1 -0
- package/dist/DataList.d.ts +15 -0
- package/dist/DataList.js +42 -0
- package/dist/DataList.js.map +1 -0
- package/dist/DatePicker.d.ts +65 -0
- package/dist/DatePicker.js +391 -0
- package/dist/DatePicker.js.map +1 -0
- package/dist/DateRangePicker.d.ts +34 -0
- package/dist/DateRangePicker.js +180 -0
- package/dist/DateRangePicker.js.map +1 -0
- package/dist/Drawer.d.ts +17 -0
- package/dist/Drawer.js +132 -0
- package/dist/Drawer.js.map +1 -0
- package/dist/DropdownMenu.d.ts +23 -0
- package/dist/DropdownMenu.js +185 -0
- package/dist/DropdownMenu.js.map +1 -0
- package/dist/EmptyState.d.ts +13 -0
- package/dist/EmptyState.js +55 -0
- package/dist/EmptyState.js.map +1 -0
- package/dist/FieldMessage.d.ts +26 -0
- package/dist/FieldMessage.js +95 -0
- package/dist/FieldMessage.js.map +1 -0
- package/dist/FileUpload.d.ts +32 -0
- package/dist/FileUpload.js +253 -0
- package/dist/FileUpload.js.map +1 -0
- package/dist/Floating.d.ts +32 -0
- package/dist/Floating.js +95 -0
- package/dist/Floating.js.map +1 -0
- package/dist/HoverCard.d.ts +18 -0
- package/dist/HoverCard.js +90 -0
- package/dist/HoverCard.js.map +1 -0
- package/dist/Input.d.ts +17 -0
- package/dist/Input.js +96 -0
- package/dist/Input.js.map +1 -0
- package/dist/Kbd.d.ts +9 -0
- package/dist/Kbd.js +24 -0
- package/dist/Kbd.js.map +1 -0
- package/dist/Modal.d.ts +15 -0
- package/dist/Modal.js +109 -0
- package/dist/Modal.js.map +1 -0
- package/dist/NumberInput.d.ts +23 -0
- package/dist/NumberInput.js +162 -0
- package/dist/NumberInput.js.map +1 -0
- package/dist/OtpInput.d.ts +26 -0
- package/dist/OtpInput.js +173 -0
- package/dist/OtpInput.js.map +1 -0
- package/dist/Pagination.d.ts +11 -0
- package/dist/Pagination.js +148 -0
- package/dist/Pagination.js.map +1 -0
- package/dist/Popover.d.ts +13 -0
- package/dist/Popover.js +98 -0
- package/dist/Popover.js.map +1 -0
- package/dist/Progress.d.ts +12 -0
- package/dist/Progress.js +61 -0
- package/dist/Progress.js.map +1 -0
- package/dist/QuoteCard.d.ts +13 -0
- package/dist/QuoteCard.js +92 -0
- package/dist/QuoteCard.js.map +1 -0
- package/dist/Radio.d.ts +19 -0
- package/dist/Radio.js +161 -0
- package/dist/Radio.js.map +1 -0
- package/dist/RadioCards.d.ts +27 -0
- package/dist/RadioCards.js +205 -0
- package/dist/RadioCards.js.map +1 -0
- package/dist/ReactionBar.d.ts +15 -0
- package/dist/ReactionBar.js +92 -0
- package/dist/ReactionBar.js.map +1 -0
- package/dist/ReadingProgress.d.ts +8 -0
- package/dist/ReadingProgress.js +51 -0
- package/dist/ReadingProgress.js.map +1 -0
- package/dist/ReadingTime.d.ts +9 -0
- package/dist/ReadingTime.js +39 -0
- package/dist/ReadingTime.js.map +1 -0
- package/dist/Select.d.ts +20 -0
- package/dist/Select.js +244 -0
- package/dist/Select.js.map +1 -0
- package/dist/Separator.d.ts +14 -0
- package/dist/Separator.js +33 -0
- package/dist/Separator.js.map +1 -0
- package/dist/ShareButtons.d.ts +10 -0
- package/dist/ShareButtons.js +107 -0
- package/dist/ShareButtons.js.map +1 -0
- package/dist/Sidebar.d.ts +30 -0
- package/dist/Sidebar.js +140 -0
- package/dist/Sidebar.js.map +1 -0
- package/dist/Skeleton.d.ts +15 -0
- package/dist/Skeleton.js +67 -0
- package/dist/Skeleton.js.map +1 -0
- package/dist/Slider.d.ts +27 -0
- package/dist/Slider.js +182 -0
- package/dist/Slider.js.map +1 -0
- package/dist/Spinner.d.ts +12 -0
- package/dist/Spinner.js +24 -0
- package/dist/Spinner.js.map +1 -0
- package/dist/SpotlightCard.d.ts +17 -0
- package/dist/SpotlightCard.js +87 -0
- package/dist/SpotlightCard.js.map +1 -0
- package/dist/Stat.d.ts +19 -0
- package/dist/Stat.js +71 -0
- package/dist/Stat.js.map +1 -0
- package/dist/StatCard.d.ts +24 -0
- package/dist/StatCard.js +212 -0
- package/dist/StatCard.js.map +1 -0
- package/dist/Steps.d.ts +14 -0
- package/dist/Steps.js +94 -0
- package/dist/Steps.js.map +1 -0
- package/dist/Table.d.ts +21 -0
- package/dist/Table.js +93 -0
- package/dist/Table.js.map +1 -0
- package/dist/TableOfContents.d.ts +17 -0
- package/dist/TableOfContents.js +92 -0
- package/dist/TableOfContents.js.map +1 -0
- package/dist/Tabs.d.ts +17 -0
- package/dist/Tabs.js +123 -0
- package/dist/Tabs.js.map +1 -0
- package/dist/TagInput.d.ts +16 -0
- package/dist/TagInput.js +169 -0
- package/dist/TagInput.js.map +1 -0
- package/dist/Textarea.d.ts +13 -0
- package/dist/Textarea.js +74 -0
- package/dist/Textarea.js.map +1 -0
- package/dist/TimePicker.d.ts +28 -0
- package/dist/TimePicker.js +264 -0
- package/dist/TimePicker.js.map +1 -0
- package/dist/Timeline.d.ts +19 -0
- package/dist/Timeline.js +76 -0
- package/dist/Timeline.js.map +1 -0
- package/dist/Toast.d.ts +18 -0
- package/dist/Toast.js +318 -0
- package/dist/Toast.js.map +1 -0
- package/dist/Toggle.d.ts +13 -0
- package/dist/Toggle.js +84 -0
- package/dist/Toggle.js.map +1 -0
- package/dist/Tooltip.d.ts +13 -0
- package/dist/Tooltip.js +138 -0
- package/dist/Tooltip.js.map +1 -0
- package/dist/cli.js +1644 -0
- package/dist/index.d.ts +68 -631
- package/dist/index.js +67 -5558
- package/dist/index.js.map +1 -1
- package/dist/registry/Accordion.tsx +140 -0
- package/dist/registry/Alert.tsx +95 -0
- package/dist/registry/AuthorCard.tsx +67 -0
- package/dist/registry/Avatar.tsx +116 -0
- package/dist/registry/Badge.tsx +55 -0
- package/dist/registry/Banner.tsx +86 -0
- package/dist/registry/Breadcrumb.tsx +60 -0
- package/dist/registry/Button.tsx +361 -0
- package/dist/registry/ButtonGroup.tsx +82 -0
- package/dist/registry/Card.tsx +241 -0
- package/dist/registry/CardShell.tsx +78 -0
- package/dist/registry/Carousel.tsx +296 -0
- package/dist/registry/Checkbox.tsx +104 -0
- package/dist/registry/CloseButton.tsx +68 -0
- package/dist/registry/CodeBlock.tsx +202 -0
- package/dist/registry/ColorPicker.tsx +404 -0
- package/dist/registry/Combobox.tsx +318 -0
- package/dist/registry/CommandPalette.tsx +356 -0
- package/dist/registry/ContextMenu.tsx +241 -0
- package/dist/registry/DataList.tsx +49 -0
- package/dist/registry/DatePicker.tsx +494 -0
- package/dist/registry/DateRangePicker.tsx +232 -0
- package/dist/registry/Drawer.tsx +166 -0
- package/dist/registry/DropdownMenu.tsx +219 -0
- package/dist/registry/EmptyState.tsx +55 -0
- package/dist/registry/FieldMessage.tsx +123 -0
- package/dist/registry/FileUpload.tsx +318 -0
- package/dist/registry/Floating.tsx +134 -0
- package/dist/registry/HoverCard.tsx +113 -0
- package/dist/registry/Input.tsx +108 -0
- package/dist/registry/Kbd.tsx +23 -0
- package/dist/registry/Modal.tsx +130 -0
- package/dist/registry/NumberInput.tsx +183 -0
- package/dist/registry/OtpInput.tsx +219 -0
- package/dist/registry/Pagination.tsx +160 -0
- package/dist/registry/Popover.tsx +116 -0
- package/dist/registry/Progress.tsx +66 -0
- package/dist/registry/QuoteCard.tsx +93 -0
- package/dist/registry/Radio.tsx +205 -0
- package/dist/registry/RadioCards.tsx +247 -0
- package/dist/registry/ReactionBar.tsx +101 -0
- package/dist/registry/ReadingProgress.tsx +51 -0
- package/dist/registry/ReadingTime.tsx +35 -0
- package/dist/registry/Select.tsx +265 -0
- package/dist/registry/Separator.tsx +52 -0
- package/dist/registry/ShareButtons.tsx +134 -0
- package/dist/registry/Sidebar.tsx +182 -0
- package/dist/registry/Skeleton.tsx +83 -0
- package/dist/registry/Slider.tsx +229 -0
- package/dist/registry/Spinner.tsx +32 -0
- package/dist/registry/SpotlightCard.tsx +106 -0
- package/dist/registry/Stat.tsx +82 -0
- package/dist/registry/StatCard.tsx +256 -0
- package/dist/registry/Steps.tsx +109 -0
- package/dist/registry/Table.tsx +123 -0
- package/dist/registry/TableOfContents.tsx +116 -0
- package/dist/registry/Tabs.tsx +136 -0
- package/dist/registry/TagInput.tsx +191 -0
- package/dist/registry/Textarea.tsx +84 -0
- package/dist/registry/TimePicker.tsx +315 -0
- package/dist/registry/Timeline.tsx +102 -0
- package/dist/registry/Toast.tsx +388 -0
- package/dist/registry/Toggle.tsx +96 -0
- package/dist/registry/Tooltip.tsx +160 -0
- package/dist/registry/styles.css +1207 -0
- package/dist/registry/useFloatingPanel.ts +112 -0
- package/dist/registry/useModalBehavior.ts +77 -0
- package/dist/styles.css +868 -62
- package/dist/useFloatingPanel.d.ts +24 -0
- package/dist/useFloatingPanel.js +89 -0
- package/dist/useFloatingPanel.js.map +1 -0
- package/dist/useModalBehavior.d.ts +15 -0
- package/dist/useModalBehavior.js +57 -0
- package/dist/useModalBehavior.js.map +1 -0
- package/package.json +23 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../components/ui/Progress.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useEffect, useRef, useState } from \"react\";\n\ninterface ProgressProps {\n value: number;\n max?: number;\n label?: string;\n showValue?: boolean;\n className?: string;\n}\n\nexport function Progress({\n value,\n max = 100,\n label,\n showValue = false,\n className = \"\",\n}: ProgressProps) {\n const pct = Math.min(Math.max((value / max) * 100, 0), 100);\n const [displayed, setDisplayed] = useState(0);\n const t = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n t.current = setTimeout(() => setDisplayed(pct), 80);\n return () => { if (t.current) clearTimeout(t.current); };\n }, [pct]);\n\n return (\n <div className={[\"flex flex-col gap-2\", className].join(\" \")}>\n {(label || showValue) && (\n <div className=\"flex items-center justify-between gap-4\">\n {label && (\n <span className=\"text-[13px] font-medium text-ink-label truncate\">{label}</span>\n )}\n {showValue && (\n <span aria-hidden className=\"text-[12px] tabular-nums text-ink-muted shrink-0\">\n {Math.round(pct)}%\n </span>\n )}\n </div>\n )}\n <div\n className=\"w-full h-2 rounded-full p-px overflow-hidden\"\n role=\"progressbar\"\n aria-label={label}\n aria-valuenow={value}\n aria-valuemin={0}\n aria-valuemax={max}\n style={{ background: \"var(--well)\", boxShadow: \"var(--well-shadow)\" }}\n >\n <div\n className=\"h-full rounded-full relative overflow-hidden\"\n style={{\n width: `${displayed}%`,\n /* Never collapses below a pill, so 1% still reads as started */\n minWidth: displayed > 0 ? 6 : 0,\n background: \"var(--face-inverse)\",\n boxShadow: \"var(--fill-shadow)\",\n transition: \"width 700ms var(--ease-out-soft)\",\n }}\n />\n </div>\n </div>\n );\n}\n"],"mappings":";;;AAEA,SAAgB,WAAW,QAAQ,gBAAgB;AA+BvC,cAGA,YAHA;AArBL,SAAS,SAAS;AAAA,EACvB;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA,YAAY;AAAA,EACZ,YAAY;AACd,GAAkB;AAChB,QAAM,MAAM,KAAK,IAAI,KAAK,IAAK,QAAQ,MAAO,KAAK,CAAC,GAAG,GAAG;AAC1D,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,CAAC;AAC5C,QAAM,IAAI,OAA6C,IAAI;AAE3D,YAAU,MAAM;AACd,MAAE,UAAU,WAAW,MAAM,aAAa,GAAG,GAAG,EAAE;AAClD,WAAO,MAAM;AAAE,UAAI,EAAE,QAAS,cAAa,EAAE,OAAO;AAAA,IAAG;AAAA,EACzD,GAAG,CAAC,GAAG,CAAC;AAER,SACE,qBAAC,SAAI,WAAW,CAAC,uBAAuB,SAAS,EAAE,KAAK,GAAG,GACvD;AAAA,cAAS,cACT,qBAAC,SAAI,WAAU,2CACZ;AAAA,eACC,oBAAC,UAAK,WAAU,mDAAmD,iBAAM;AAAA,MAE1E,aACC,qBAAC,UAAK,eAAW,MAAC,WAAU,oDACzB;AAAA,aAAK,MAAM,GAAG;AAAA,QAAE;AAAA,SACnB;AAAA,OAEJ;AAAA,IAEF;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,MAAK;AAAA,QACL,cAAY;AAAA,QACZ,iBAAe;AAAA,QACf,iBAAe;AAAA,QACf,iBAAe;AAAA,QACf,OAAO,EAAE,YAAY,eAAe,WAAW,qBAAqB;AAAA,QAEpE;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO;AAAA,cACL,OAAO,GAAG,SAAS;AAAA;AAAA,cAEnB,UAAU,YAAY,IAAI,IAAI;AAAA,cAC9B,YAAY;AAAA,cACZ,WAAW;AAAA,cACX,YAAY;AAAA,YACd;AAAA;AAAA,QACF;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface QuoteCardProps {
|
|
4
|
+
quote: string;
|
|
5
|
+
author: string;
|
|
6
|
+
role?: string;
|
|
7
|
+
avatarSrc?: string;
|
|
8
|
+
theme?: "light" | "dark";
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
declare function QuoteCard({ quote, author, role, avatarSrc, theme, className, }: QuoteCardProps): react_jsx_runtime.JSX.Element;
|
|
12
|
+
|
|
13
|
+
export { QuoteCard };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// ../../components/ui/QuoteCard.tsx
|
|
2
|
+
import { CardShell } from "./CardShell.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
function QuoteCard({
|
|
5
|
+
quote,
|
|
6
|
+
author,
|
|
7
|
+
role,
|
|
8
|
+
avatarSrc,
|
|
9
|
+
theme = "light",
|
|
10
|
+
className = ""
|
|
11
|
+
}) {
|
|
12
|
+
const dark = theme === "dark";
|
|
13
|
+
const initial = (author.match(/[\p{L}\p{N}]/u)?.[0] ?? "?").toLowerCase();
|
|
14
|
+
const rail = /* @__PURE__ */ jsxs("figcaption", { className: "flex items-center gap-2.5 min-w-0", children: [
|
|
15
|
+
avatarSrc ? (
|
|
16
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
17
|
+
/* @__PURE__ */ jsx(
|
|
18
|
+
"img",
|
|
19
|
+
{
|
|
20
|
+
src: avatarSrc,
|
|
21
|
+
alt: "",
|
|
22
|
+
loading: "lazy",
|
|
23
|
+
className: "w-7 h-7 rounded-full object-cover shrink-0",
|
|
24
|
+
style: { boxShadow: dark ? "0 0 0 0.5px var(--photo-edge-dark)" : "0 0 0 0.5px var(--photo-edge)" }
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
) : /* @__PURE__ */ jsx(
|
|
28
|
+
"span",
|
|
29
|
+
{
|
|
30
|
+
"aria-hidden": true,
|
|
31
|
+
className: [
|
|
32
|
+
"w-7 h-7 rounded-full flex items-center justify-center text-[11.5px] font-semibold shrink-0",
|
|
33
|
+
dark ? "text-white" : "text-ink-label"
|
|
34
|
+
].join(" "),
|
|
35
|
+
style: {
|
|
36
|
+
background: dark ? "var(--tile-dark)" : "var(--tile)",
|
|
37
|
+
boxShadow: dark ? "var(--tile-dark-shadow)" : "var(--tile-shadow)"
|
|
38
|
+
},
|
|
39
|
+
children: initial
|
|
40
|
+
}
|
|
41
|
+
),
|
|
42
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate", children: [
|
|
43
|
+
/* @__PURE__ */ jsx(
|
|
44
|
+
"cite",
|
|
45
|
+
{
|
|
46
|
+
className: [
|
|
47
|
+
"not-italic font-semibold tracking-[-0.008em]",
|
|
48
|
+
dark ? "text-white" : "text-ink"
|
|
49
|
+
].join(" "),
|
|
50
|
+
children: author
|
|
51
|
+
}
|
|
52
|
+
),
|
|
53
|
+
role && /* @__PURE__ */ jsxs("span", { children: [
|
|
54
|
+
" \xB7 ",
|
|
55
|
+
role
|
|
56
|
+
] })
|
|
57
|
+
] })
|
|
58
|
+
] });
|
|
59
|
+
return /* @__PURE__ */ jsx(CardShell, { as: "figure", theme, className, rail, railKind: "text", children: /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col p-8", children: [
|
|
60
|
+
/* @__PURE__ */ jsx(
|
|
61
|
+
"svg",
|
|
62
|
+
{
|
|
63
|
+
"aria-hidden": true,
|
|
64
|
+
width: "22",
|
|
65
|
+
height: "17",
|
|
66
|
+
viewBox: "0 0 56 44",
|
|
67
|
+
className: ["shrink-0 mb-auto", dark ? "text-white/25" : "text-ink-ghost"].join(" "),
|
|
68
|
+
children: /* @__PURE__ */ jsx(
|
|
69
|
+
"path",
|
|
70
|
+
{
|
|
71
|
+
fill: "currentColor",
|
|
72
|
+
d: "M0 44V27.5C0 12.6 7.4 3.4 22.2 0l2.6 6.2C16.9 8.7 13 13.7 12.6 21H24v23H0zm32 0V27.5C32 12.6 39.4 3.4 54.2 0l2.6 6.2C48.9 8.7 45 13.7 44.6 21H56v23H32z"
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
),
|
|
77
|
+
/* @__PURE__ */ jsx(
|
|
78
|
+
"blockquote",
|
|
79
|
+
{
|
|
80
|
+
className: [
|
|
81
|
+
"m-0 pt-8 text-[19px] leading-[1.55] tracking-[-0.015em] text-pretty",
|
|
82
|
+
dark ? "text-neutral-50" : "text-ink"
|
|
83
|
+
].join(" "),
|
|
84
|
+
children: quote
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
] }) });
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
QuoteCard
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=QuoteCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../components/ui/QuoteCard.tsx"],"sourcesContent":["import React from \"react\";\nimport { CardShell } from \"./CardShell\";\n\ninterface QuoteCardProps {\n quote: string;\n author: string;\n role?: string;\n avatarSrc?: string;\n theme?: \"light\" | \"dark\";\n className?: string;\n}\n\n/* Built on CardShell: the quote owns the face; who said it sits on the rail,\n so a stretched card grows the face instead of opening a gap mid-card. */\nexport function QuoteCard({\n quote,\n author,\n role,\n avatarSrc,\n theme = \"light\",\n className = \"\",\n}: QuoteCardProps) {\n const dark = theme === \"dark\";\n // Handles like \"@name\" shouldn't render an \"@\" monogram\n const initial = (author.match(/[\\p{L}\\p{N}]/u)?.[0] ?? \"?\").toLowerCase();\n\n const rail = (\n <figcaption className=\"flex items-center gap-2.5 min-w-0\">\n {avatarSrc ? (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={avatarSrc}\n alt=\"\"\n loading=\"lazy\"\n className=\"w-7 h-7 rounded-full object-cover shrink-0\"\n style={{ boxShadow: dark ? \"0 0 0 0.5px var(--photo-edge-dark)\" : \"0 0 0 0.5px var(--photo-edge)\" }}\n />\n ) : (\n <span\n aria-hidden\n className={[\n \"w-7 h-7 rounded-full flex items-center justify-center text-[11.5px] font-semibold shrink-0\",\n dark ? \"text-white\" : \"text-ink-label\",\n ].join(\" \")}\n style={{\n background: dark ? \"var(--tile-dark)\" : \"var(--tile)\",\n boxShadow: dark ? \"var(--tile-dark-shadow)\" : \"var(--tile-shadow)\",\n }}\n >\n {initial}\n </span>\n )}\n <span className=\"min-w-0 truncate\">\n <cite\n className={[\n \"not-italic font-semibold tracking-[-0.008em]\",\n dark ? \"text-white\" : \"text-ink\",\n ].join(\" \")}\n >\n {author}\n </cite>\n {role && <span> · {role}</span>}\n </span>\n </figcaption>\n );\n\n return (\n <CardShell as=\"figure\" theme={theme} className={className} rail={rail} railKind=\"text\">\n <div className=\"flex-1 flex flex-col p-8\">\n <svg\n aria-hidden\n width=\"22\"\n height=\"17\"\n viewBox=\"0 0 56 44\"\n className={[\"shrink-0 mb-auto\", dark ? \"text-white/25\" : \"text-ink-ghost\"].join(\" \")}\n >\n <path\n fill=\"currentColor\"\n d=\"M0 44V27.5C0 12.6 7.4 3.4 22.2 0l2.6 6.2C16.9 8.7 13 13.7 12.6 21H24v23H0zm32 0V27.5C32 12.6 39.4 3.4 54.2 0l2.6 6.2C48.9 8.7 45 13.7 44.6 21H56v23H32z\"\n />\n </svg>\n <blockquote\n className={[\n \"m-0 pt-8 text-[19px] leading-[1.55] tracking-[-0.015em] text-pretty\",\n dark ? \"text-neutral-50\" : \"text-ink\",\n ].join(\" \")}\n >\n {quote}\n </blockquote>\n </div>\n </CardShell>\n );\n}\n"],"mappings":";AACA,SAAS,iBAAiB;AA6BlB,cA+BS,YA/BT;AAhBD,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,YAAY;AACd,GAAmB;AACjB,QAAM,OAAO,UAAU;AAEvB,QAAM,WAAW,OAAO,MAAM,eAAe,IAAI,CAAC,KAAK,KAAK,YAAY;AAExE,QAAM,OACJ,qBAAC,gBAAW,WAAU,qCACnB;AAAA;AAAA;AAAA,MAEC;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,KAAI;AAAA,UACJ,SAAQ;AAAA,UACR,WAAU;AAAA,UACV,OAAO,EAAE,WAAW,OAAO,uCAAuC,gCAAgC;AAAA;AAAA,MACpG;AAAA,QAEA;AAAA,MAAC;AAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAW;AAAA,UACT;AAAA,UACA,OAAO,eAAe;AAAA,QACxB,EAAE,KAAK,GAAG;AAAA,QACV,OAAO;AAAA,UACL,YAAY,OAAO,qBAAqB;AAAA,UACxC,WAAW,OAAO,4BAA4B;AAAA,QAChD;AAAA,QAEC;AAAA;AAAA,IACH;AAAA,IAEF,qBAAC,UAAK,WAAU,oBACd;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA,OAAO,eAAe;AAAA,UACxB,EAAE,KAAK,GAAG;AAAA,UAET;AAAA;AAAA,MACH;AAAA,MACC,QAAQ,qBAAC,UAAK;AAAA;AAAA,QAAI;AAAA,SAAK;AAAA,OAC1B;AAAA,KACF;AAGF,SACE,oBAAC,aAAU,IAAG,UAAS,OAAc,WAAsB,MAAY,UAAS,QAC9E,+BAAC,SAAI,WAAU,4BACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,eAAW;AAAA,QACX,OAAM;AAAA,QACN,QAAO;AAAA,QACP,SAAQ;AAAA,QACR,WAAW,CAAC,oBAAoB,OAAO,kBAAkB,gBAAgB,EAAE,KAAK,GAAG;AAAA,QAEnF;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,GAAE;AAAA;AAAA,QACJ;AAAA;AAAA,IACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,OAAO,oBAAoB;AAAA,QAC7B,EAAE,KAAK,GAAG;AAAA,QAET;AAAA;AAAA,IACH;AAAA,KACF,GACF;AAEJ;","names":[]}
|
package/dist/Radio.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface RadioOption {
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface RadioGroupProps {
|
|
10
|
+
options: RadioOption[];
|
|
11
|
+
value?: string;
|
|
12
|
+
defaultValue?: string;
|
|
13
|
+
onChange?: (value: string) => void;
|
|
14
|
+
name?: string;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
declare function RadioGroup({ options, value, defaultValue, onChange, name, className, }: RadioGroupProps): react_jsx_runtime.JSX.Element;
|
|
18
|
+
|
|
19
|
+
export { RadioGroup };
|
package/dist/Radio.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// ../../components/ui/Radio.tsx
|
|
4
|
+
import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
var LEAD = "260ms var(--ease-out-soft)";
|
|
7
|
+
var TRAIL = "480ms cubic-bezier(0.34, 1.25, 0.64, 1) 50ms";
|
|
8
|
+
function wellBox(group, el, dir) {
|
|
9
|
+
if (!group || !el) return null;
|
|
10
|
+
return {
|
|
11
|
+
top: el.offsetTop,
|
|
12
|
+
bottom: group.clientHeight - el.offsetTop - el.offsetHeight,
|
|
13
|
+
left: el.offsetLeft,
|
|
14
|
+
size: el.offsetWidth,
|
|
15
|
+
dir
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function RadioGroup({
|
|
19
|
+
options,
|
|
20
|
+
value,
|
|
21
|
+
defaultValue = "",
|
|
22
|
+
onChange,
|
|
23
|
+
name,
|
|
24
|
+
className = ""
|
|
25
|
+
}) {
|
|
26
|
+
const [internal, setInternal] = useState(defaultValue);
|
|
27
|
+
const selected = value !== void 0 ? value : internal;
|
|
28
|
+
const uid = useId();
|
|
29
|
+
const refs = useRef([]);
|
|
30
|
+
const groupRef = useRef(null);
|
|
31
|
+
const prevIndex = useRef(-1);
|
|
32
|
+
const [blob, setBlob] = useState(null);
|
|
33
|
+
const select = (val, disabled) => {
|
|
34
|
+
if (disabled) return;
|
|
35
|
+
setInternal(val);
|
|
36
|
+
onChange?.(val);
|
|
37
|
+
};
|
|
38
|
+
const enabled = options.map((o, i) => o.disabled ? -1 : i).filter((i) => i >= 0);
|
|
39
|
+
const selectedIndex = options.findIndex((o) => o.value === selected);
|
|
40
|
+
const tabStop = selectedIndex >= 0 ? selectedIndex : enabled[0];
|
|
41
|
+
const measure = (dir) => setBlob(wellBox(groupRef.current, refs.current[prevIndex.current], dir));
|
|
42
|
+
useLayoutEffect(() => {
|
|
43
|
+
const prev = prevIndex.current;
|
|
44
|
+
prevIndex.current = selectedIndex;
|
|
45
|
+
measure(prev < 0 || selectedIndex < 0 ? 0 : selectedIndex > prev ? 1 : selectedIndex < prev ? -1 : 0);
|
|
46
|
+
}, [selectedIndex]);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const group = groupRef.current;
|
|
49
|
+
if (!group) return;
|
|
50
|
+
let attached = false;
|
|
51
|
+
const ro = new ResizeObserver(() => {
|
|
52
|
+
if (attached) measure(0);
|
|
53
|
+
attached = true;
|
|
54
|
+
});
|
|
55
|
+
ro.observe(group);
|
|
56
|
+
return () => ro.disconnect();
|
|
57
|
+
}, []);
|
|
58
|
+
const move = (from, step) => {
|
|
59
|
+
const at = enabled.indexOf(from);
|
|
60
|
+
const next = enabled[(at + step + enabled.length) % enabled.length];
|
|
61
|
+
refs.current[next]?.focus();
|
|
62
|
+
select(options[next].value);
|
|
63
|
+
};
|
|
64
|
+
return /* @__PURE__ */ jsxs(
|
|
65
|
+
"div",
|
|
66
|
+
{
|
|
67
|
+
ref: groupRef,
|
|
68
|
+
role: "radiogroup",
|
|
69
|
+
"aria-label": name,
|
|
70
|
+
className: ["relative flex flex-col gap-1", className].join(" "),
|
|
71
|
+
children: [
|
|
72
|
+
options.map((opt, i) => {
|
|
73
|
+
const active = selected === opt.value;
|
|
74
|
+
return /* @__PURE__ */ jsxs(
|
|
75
|
+
"div",
|
|
76
|
+
{
|
|
77
|
+
onClick: () => select(opt.value, opt.disabled),
|
|
78
|
+
className: [
|
|
79
|
+
"flex items-center justify-between gap-4 select-none py-1.5",
|
|
80
|
+
opt.disabled ? "opacity-40 pointer-events-none" : "cursor-pointer group/radio"
|
|
81
|
+
].join(" "),
|
|
82
|
+
children: [
|
|
83
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5 min-w-0", children: [
|
|
84
|
+
/* @__PURE__ */ jsx("span", { id: `${uid}-${i}-label`, className: "text-[13px] font-medium text-ink leading-snug", children: opt.label }),
|
|
85
|
+
opt.description && /* @__PURE__ */ jsx("span", { id: `${uid}-${i}-desc`, className: "text-[12px] text-ink-muted leading-relaxed", children: opt.description })
|
|
86
|
+
] }),
|
|
87
|
+
/* @__PURE__ */ jsx(
|
|
88
|
+
"button",
|
|
89
|
+
{
|
|
90
|
+
ref: (el) => {
|
|
91
|
+
refs.current[i] = el;
|
|
92
|
+
},
|
|
93
|
+
type: "button",
|
|
94
|
+
role: "radio",
|
|
95
|
+
"aria-checked": active,
|
|
96
|
+
"aria-labelledby": `${uid}-${i}-label`,
|
|
97
|
+
"aria-describedby": opt.description ? `${uid}-${i}-desc` : void 0,
|
|
98
|
+
disabled: opt.disabled,
|
|
99
|
+
tabIndex: i === tabStop ? 0 : -1,
|
|
100
|
+
onKeyDown: (e) => {
|
|
101
|
+
if (e.key === "ArrowDown" || e.key === "ArrowRight") {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
move(i, 1);
|
|
104
|
+
} else if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
move(i, -1);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
className: [
|
|
110
|
+
"shrink-0 w-[22px] h-[22px] rounded-full cursor-pointer",
|
|
111
|
+
"transition-[box-shadow] duration-200 ease-out",
|
|
112
|
+
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink",
|
|
113
|
+
active ? "" : "group-hover/radio:[box-shadow:var(--field-shadow-hover)]"
|
|
114
|
+
].join(" "),
|
|
115
|
+
style: { background: "var(--field)", boxShadow: "var(--field-shadow)" }
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
opt.value
|
|
121
|
+
);
|
|
122
|
+
}),
|
|
123
|
+
blob && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
124
|
+
/* @__PURE__ */ jsx(
|
|
125
|
+
"span",
|
|
126
|
+
{
|
|
127
|
+
"aria-hidden": true,
|
|
128
|
+
className: "pointer-events-none absolute rounded-full",
|
|
129
|
+
style: {
|
|
130
|
+
top: blob.top,
|
|
131
|
+
bottom: blob.bottom,
|
|
132
|
+
left: blob.left,
|
|
133
|
+
width: blob.size,
|
|
134
|
+
background: "var(--face-inverse)",
|
|
135
|
+
boxShadow: "var(--face-inverse-shadow)",
|
|
136
|
+
transition: blob.dir === 0 ? "none" : blob.dir > 0 ? `top ${TRAIL}, bottom ${LEAD}` : `top ${LEAD}, bottom ${TRAIL}`
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
),
|
|
140
|
+
/* @__PURE__ */ jsx(
|
|
141
|
+
"span",
|
|
142
|
+
{
|
|
143
|
+
"aria-hidden": true,
|
|
144
|
+
className: "pointer-events-none absolute w-2 h-2 rounded-full bg-ink-inverse",
|
|
145
|
+
style: {
|
|
146
|
+
top: blob.top + (blob.size - 8) / 2,
|
|
147
|
+
left: blob.left + (blob.size - 8) / 2,
|
|
148
|
+
boxShadow: "var(--dot-shadow)",
|
|
149
|
+
transition: blob.dir === 0 ? "none" : `top ${TRAIL}`
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
] })
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
export {
|
|
159
|
+
RadioGroup
|
|
160
|
+
};
|
|
161
|
+
//# sourceMappingURL=Radio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../components/ui/Radio.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useEffect, useId, useLayoutEffect, useRef, useState } from \"react\";\n\ninterface RadioOption {\n value: string;\n label: string;\n description?: string;\n disabled?: boolean;\n}\n\ninterface RadioGroupProps {\n options: RadioOption[];\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n name?: string;\n className?: string;\n}\n\n/* Every radio is a sunken field; the selection is one dark face that\n flows between them. Its leading edge leaves first and the trailing edge\n catches up on a spring, so it stretches across the gap like a drop of\n liquid and settles into the new well. One Tab stop for the group,\n arrows move between options (ARIA radio group pattern). */\ninterface Blob {\n top: number;\n bottom: number;\n left: number;\n size: number;\n dir: -1 | 0 | 1;\n}\n\nconst LEAD = \"260ms var(--ease-out-soft)\";\nconst TRAIL = \"480ms cubic-bezier(0.34, 1.25, 0.64, 1) 50ms\";\n\n/* Where the face sits over a well, in the group's own coordinates. Read\n straight off the DOM, so setting it from an effect is a measurement,\n not derived state */\nfunction wellBox(group: HTMLElement | null, el: HTMLElement | null, dir: Blob[\"dir\"]): Blob | null {\n if (!group || !el) return null;\n return {\n top: el.offsetTop,\n bottom: group.clientHeight - el.offsetTop - el.offsetHeight,\n left: el.offsetLeft,\n size: el.offsetWidth,\n dir,\n };\n}\n\nexport function RadioGroup({\n options,\n value,\n defaultValue = \"\",\n onChange,\n name,\n className = \"\",\n}: RadioGroupProps) {\n const [internal, setInternal] = useState(defaultValue);\n const selected = value !== undefined ? value : internal;\n const uid = useId();\n const refs = useRef<(HTMLButtonElement | null)[]>([]);\n const groupRef = useRef<HTMLDivElement>(null);\n const prevIndex = useRef(-1);\n const [blob, setBlob] = useState<Blob | null>(null);\n\n const select = (val: string, disabled?: boolean) => {\n if (disabled) return;\n setInternal(val);\n onChange?.(val);\n };\n\n const enabled = options.map((o, i) => (o.disabled ? -1 : i)).filter((i) => i >= 0);\n const selectedIndex = options.findIndex((o) => o.value === selected);\n const tabStop = selectedIndex >= 0 ? selectedIndex : enabled[0];\n\n const measure = (dir: Blob[\"dir\"]) =>\n setBlob(wellBox(groupRef.current, refs.current[prevIndex.current], dir));\n\n useLayoutEffect(() => {\n const prev = prevIndex.current;\n prevIndex.current = selectedIndex;\n measure(prev < 0 || selectedIndex < 0 ? 0 : selectedIndex > prev ? 1 : selectedIndex < prev ? -1 : 0);\n }, [selectedIndex]);\n\n // Rewrapped descriptions move the wells; follow them without animating.\n // An observer reports once on attach — that one is skipped, or it would\n // cut short a move already under way.\n useEffect(() => {\n const group = groupRef.current;\n if (!group) return;\n let attached = false;\n const ro = new ResizeObserver(() => {\n if (attached) measure(0);\n attached = true;\n });\n ro.observe(group);\n return () => ro.disconnect();\n }, []);\n\n const move = (from: number, step: 1 | -1) => {\n const at = enabled.indexOf(from);\n const next = enabled[(at + step + enabled.length) % enabled.length];\n refs.current[next]?.focus();\n select(options[next].value);\n };\n\n return (\n <div\n ref={groupRef}\n role=\"radiogroup\"\n aria-label={name}\n className={[\"relative flex flex-col gap-1\", className].join(\" \")}\n >\n {options.map((opt, i) => {\n const active = selected === opt.value;\n return (\n <div\n key={opt.value}\n onClick={() => select(opt.value, opt.disabled)}\n className={[\n \"flex items-center justify-between gap-4 select-none py-1.5\",\n opt.disabled ? \"opacity-40 pointer-events-none\" : \"cursor-pointer group/radio\",\n ].join(\" \")}\n >\n <div className=\"flex flex-col gap-0.5 min-w-0\">\n <span id={`${uid}-${i}-label`} className=\"text-[13px] font-medium text-ink leading-snug\">\n {opt.label}\n </span>\n {opt.description && (\n <span id={`${uid}-${i}-desc`} className=\"text-[12px] text-ink-muted leading-relaxed\">\n {opt.description}\n </span>\n )}\n </div>\n\n {/* Clicks bubble to the row, so the control itself has no onClick */}\n <button\n ref={(el) => {\n refs.current[i] = el;\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n aria-labelledby={`${uid}-${i}-label`}\n aria-describedby={opt.description ? `${uid}-${i}-desc` : undefined}\n disabled={opt.disabled}\n tabIndex={i === tabStop ? 0 : -1}\n onKeyDown={(e) => {\n if (e.key === \"ArrowDown\" || e.key === \"ArrowRight\") {\n e.preventDefault();\n move(i, 1);\n } else if (e.key === \"ArrowUp\" || e.key === \"ArrowLeft\") {\n e.preventDefault();\n move(i, -1);\n }\n }}\n className={[\n \"shrink-0 w-[22px] h-[22px] rounded-full cursor-pointer\",\n \"transition-[box-shadow] duration-200 ease-out\",\n \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink\",\n active ? \"\" : \"group-hover/radio:[box-shadow:var(--field-shadow-hover)]\",\n ].join(\" \")}\n style={{ background: \"var(--field)\", boxShadow: \"var(--field-shadow)\" }}\n />\n </div>\n );\n })}\n\n {blob && (\n <>\n <span\n aria-hidden\n className=\"pointer-events-none absolute rounded-full\"\n style={{\n top: blob.top,\n bottom: blob.bottom,\n left: blob.left,\n width: blob.size,\n background: \"var(--face-inverse)\",\n boxShadow: \"var(--face-inverse-shadow)\",\n transition:\n blob.dir === 0\n ? \"none\"\n : blob.dir > 0\n ? `top ${TRAIL}, bottom ${LEAD}`\n : `top ${LEAD}, bottom ${TRAIL}`,\n }}\n />\n {/* The dot rides the trailing edge's spring, arriving as it settles */}\n <span\n aria-hidden\n className=\"pointer-events-none absolute w-2 h-2 rounded-full bg-ink-inverse\"\n style={{\n top: blob.top + (blob.size - 8) / 2,\n left: blob.left + (blob.size - 8) / 2,\n boxShadow: \"var(--dot-shadow)\",\n transition: blob.dir === 0 ? \"none\" : `top ${TRAIL}`,\n }}\n />\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
interface RadioCardOption {
|
|
5
|
+
value: string;
|
|
6
|
+
label: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
/** Something on the right of the title, like a price */
|
|
9
|
+
aside?: React.ReactNode;
|
|
10
|
+
icon?: React.ReactNode;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface RadioCardsProps {
|
|
14
|
+
options: RadioCardOption[];
|
|
15
|
+
value?: string;
|
|
16
|
+
defaultValue?: string;
|
|
17
|
+
onChange?: (value: string) => void;
|
|
18
|
+
/** Read out as the group's name */
|
|
19
|
+
label?: string;
|
|
20
|
+
/** Options per row from the small breakpoint up; they stack on phones */
|
|
21
|
+
columns?: 1 | 2 | 3;
|
|
22
|
+
name?: string;
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
declare function RadioCards({ options, value, defaultValue, onChange, label, columns, name, className, }: RadioCardsProps): react_jsx_runtime.JSX.Element;
|
|
26
|
+
|
|
27
|
+
export { type RadioCardOption, RadioCards };
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// ../../components/ui/RadioCards.tsx
|
|
4
|
+
import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
var COLS = { 1: "", 2: "sm:grid-cols-2", 3: "sm:grid-cols-3" };
|
|
7
|
+
var LEAD = "260ms var(--ease-out-soft)";
|
|
8
|
+
var TRAIL = "480ms cubic-bezier(0.34, 1.25, 0.64, 1) 50ms";
|
|
9
|
+
function RadioCards({
|
|
10
|
+
options,
|
|
11
|
+
value,
|
|
12
|
+
defaultValue = "",
|
|
13
|
+
onChange,
|
|
14
|
+
label,
|
|
15
|
+
columns = 1,
|
|
16
|
+
name,
|
|
17
|
+
className = ""
|
|
18
|
+
}) {
|
|
19
|
+
const uid = useId();
|
|
20
|
+
const [internal, setInternal] = useState(defaultValue);
|
|
21
|
+
const selected = value !== void 0 ? value : internal;
|
|
22
|
+
const wellRef = useRef(null);
|
|
23
|
+
const refs = useRef([]);
|
|
24
|
+
const thumbRef = useRef(null);
|
|
25
|
+
const last = useRef(null);
|
|
26
|
+
const enabled = options.map((o, i) => o.disabled ? -1 : i).filter((i) => i >= 0);
|
|
27
|
+
const selectedIndex = options.findIndex((o) => o.value === selected);
|
|
28
|
+
const tabStop = selectedIndex >= 0 ? selectedIndex : enabled[0];
|
|
29
|
+
const selectedRef = useRef(selectedIndex);
|
|
30
|
+
const place = (index, animate) => {
|
|
31
|
+
const thumb = thumbRef.current;
|
|
32
|
+
const el = refs.current[index];
|
|
33
|
+
const well = wellRef.current;
|
|
34
|
+
if (!thumb || !well) return;
|
|
35
|
+
if (!el) {
|
|
36
|
+
thumb.style.opacity = "0";
|
|
37
|
+
last.current = null;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const top = el.offsetTop;
|
|
41
|
+
const left = el.offsetLeft;
|
|
42
|
+
const from = animate ? last.current : null;
|
|
43
|
+
const edge = (d, near, far) => d === 0 ? [] : d > 0 ? [`${near} ${TRAIL}`, `${far} ${LEAD}`] : [`${near} ${LEAD}`, `${far} ${TRAIL}`];
|
|
44
|
+
const dy = from ? Math.sign(top - from.top) : 0;
|
|
45
|
+
const dx = from ? Math.sign(left - from.left) : 0;
|
|
46
|
+
thumb.style.transition = [...edge(dy, "top", "bottom"), ...edge(dx, "left", "right")].join(", ") || "none";
|
|
47
|
+
thumb.style.top = `${top}px`;
|
|
48
|
+
thumb.style.left = `${left}px`;
|
|
49
|
+
thumb.style.right = `${well.clientWidth - left - el.offsetWidth}px`;
|
|
50
|
+
thumb.style.bottom = `${well.clientHeight - top - el.offsetHeight}px`;
|
|
51
|
+
thumb.style.opacity = "1";
|
|
52
|
+
last.current = { top, left };
|
|
53
|
+
};
|
|
54
|
+
useLayoutEffect(() => {
|
|
55
|
+
selectedRef.current = selectedIndex;
|
|
56
|
+
place(selectedIndex, true);
|
|
57
|
+
}, [selectedIndex]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const well = wellRef.current;
|
|
60
|
+
if (!well) return;
|
|
61
|
+
let attached = false;
|
|
62
|
+
const ro = new ResizeObserver(() => {
|
|
63
|
+
if (attached) place(selectedRef.current, false);
|
|
64
|
+
attached = true;
|
|
65
|
+
});
|
|
66
|
+
ro.observe(well);
|
|
67
|
+
return () => ro.disconnect();
|
|
68
|
+
}, []);
|
|
69
|
+
const select = (i) => {
|
|
70
|
+
const o = options[i];
|
|
71
|
+
if (!o || o.disabled) return;
|
|
72
|
+
setInternal(o.value);
|
|
73
|
+
onChange?.(o.value);
|
|
74
|
+
};
|
|
75
|
+
const move = (from, step) => {
|
|
76
|
+
const at = enabled.indexOf(from);
|
|
77
|
+
const next = enabled[(at + step + enabled.length) % enabled.length];
|
|
78
|
+
refs.current[next]?.focus();
|
|
79
|
+
select(next);
|
|
80
|
+
};
|
|
81
|
+
return /* @__PURE__ */ jsxs(
|
|
82
|
+
"div",
|
|
83
|
+
{
|
|
84
|
+
ref: wellRef,
|
|
85
|
+
role: "radiogroup",
|
|
86
|
+
"aria-label": label,
|
|
87
|
+
className: ["relative grid grid-cols-1 gap-1 rounded-[18px] p-1.5", COLS[columns], className].join(" "),
|
|
88
|
+
style: { background: "var(--well)", boxShadow: "var(--well-shadow)" },
|
|
89
|
+
children: [
|
|
90
|
+
/* @__PURE__ */ jsx(
|
|
91
|
+
"span",
|
|
92
|
+
{
|
|
93
|
+
ref: thumbRef,
|
|
94
|
+
"aria-hidden": true,
|
|
95
|
+
className: "pointer-events-none absolute rounded-[13px] opacity-0",
|
|
96
|
+
style: { background: "var(--face)", boxShadow: "var(--thumb-shadow)" }
|
|
97
|
+
}
|
|
98
|
+
),
|
|
99
|
+
options.map((o, i) => {
|
|
100
|
+
const on = i === selectedIndex;
|
|
101
|
+
return /* @__PURE__ */ jsxs(
|
|
102
|
+
"button",
|
|
103
|
+
{
|
|
104
|
+
ref: (el) => {
|
|
105
|
+
refs.current[i] = el;
|
|
106
|
+
},
|
|
107
|
+
type: "button",
|
|
108
|
+
role: "radio",
|
|
109
|
+
"aria-checked": on,
|
|
110
|
+
"aria-labelledby": `${uid}-${i}-label`,
|
|
111
|
+
"aria-describedby": o.description ? `${uid}-${i}-desc` : void 0,
|
|
112
|
+
disabled: o.disabled,
|
|
113
|
+
tabIndex: i === tabStop ? 0 : -1,
|
|
114
|
+
onClick: () => select(i),
|
|
115
|
+
onKeyDown: (e) => {
|
|
116
|
+
if (e.key === "ArrowDown" || e.key === "ArrowRight") {
|
|
117
|
+
e.preventDefault();
|
|
118
|
+
move(i, 1);
|
|
119
|
+
} else if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
|
|
120
|
+
e.preventDefault();
|
|
121
|
+
move(i, -1);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
className: [
|
|
125
|
+
"group/card relative z-10 flex items-start gap-3 rounded-[13px] px-4 py-3.5 text-left",
|
|
126
|
+
"focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ink",
|
|
127
|
+
"disabled:opacity-40 disabled:cursor-not-allowed",
|
|
128
|
+
on ? "cursor-default" : "cursor-pointer"
|
|
129
|
+
].join(" "),
|
|
130
|
+
children: [
|
|
131
|
+
o.icon && /* @__PURE__ */ jsx(
|
|
132
|
+
"span",
|
|
133
|
+
{
|
|
134
|
+
"aria-hidden": true,
|
|
135
|
+
className: [
|
|
136
|
+
"mt-px flex h-5 w-5 shrink-0 items-center justify-center transition-colors duration-200",
|
|
137
|
+
on ? "text-ink" : "text-ink-faint group-hover/card:text-ink-body"
|
|
138
|
+
].join(" "),
|
|
139
|
+
children: o.icon
|
|
140
|
+
}
|
|
141
|
+
),
|
|
142
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
|
|
143
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-baseline justify-between gap-3", children: [
|
|
144
|
+
/* @__PURE__ */ jsx(
|
|
145
|
+
"span",
|
|
146
|
+
{
|
|
147
|
+
id: `${uid}-${i}-label`,
|
|
148
|
+
className: [
|
|
149
|
+
"text-[13.5px] font-medium leading-snug transition-colors duration-150",
|
|
150
|
+
on ? "text-ink" : "text-ink-soft group-hover/card:text-ink"
|
|
151
|
+
].join(" "),
|
|
152
|
+
children: o.label
|
|
153
|
+
}
|
|
154
|
+
),
|
|
155
|
+
o.aside && /* @__PURE__ */ jsx(
|
|
156
|
+
"span",
|
|
157
|
+
{
|
|
158
|
+
className: [
|
|
159
|
+
"shrink-0 text-[13px] tabular-nums transition-colors duration-150",
|
|
160
|
+
on ? "font-medium text-ink" : "text-ink-muted"
|
|
161
|
+
].join(" "),
|
|
162
|
+
children: o.aside
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
] }),
|
|
166
|
+
o.description && /* @__PURE__ */ jsx("span", { id: `${uid}-${i}-desc`, className: "text-[12px] leading-relaxed text-ink-muted", children: o.description })
|
|
167
|
+
] }),
|
|
168
|
+
/* @__PURE__ */ jsx(
|
|
169
|
+
"span",
|
|
170
|
+
{
|
|
171
|
+
"aria-hidden": true,
|
|
172
|
+
className: [
|
|
173
|
+
"relative mt-px h-[18px] w-[18px] shrink-0 rounded-full transition-[box-shadow] duration-200",
|
|
174
|
+
on ? "" : "group-hover/card:[box-shadow:var(--field-shadow-hover)]"
|
|
175
|
+
].join(" "),
|
|
176
|
+
style: { background: "var(--field)", boxShadow: "var(--field-shadow)" },
|
|
177
|
+
children: /* @__PURE__ */ jsx(
|
|
178
|
+
"span",
|
|
179
|
+
{
|
|
180
|
+
className: "absolute inset-0 grid place-items-center rounded-full transition-[opacity,transform] duration-300 ease-[var(--ease-spring)]",
|
|
181
|
+
style: {
|
|
182
|
+
background: "var(--face-inverse)",
|
|
183
|
+
boxShadow: "var(--face-inverse-shadow)",
|
|
184
|
+
opacity: on ? 1 : 0,
|
|
185
|
+
transform: on ? "scale(1)" : "scale(0.4)"
|
|
186
|
+
},
|
|
187
|
+
children: /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-ink-inverse", style: { boxShadow: "var(--dot-shadow)" } })
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
]
|
|
193
|
+
},
|
|
194
|
+
o.value
|
|
195
|
+
);
|
|
196
|
+
}),
|
|
197
|
+
name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selected })
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
export {
|
|
203
|
+
RadioCards
|
|
204
|
+
};
|
|
205
|
+
//# sourceMappingURL=RadioCards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../components/ui/RadioCards.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useEffect, useId, useLayoutEffect, useRef, useState } from \"react\";\n\n\nexport interface RadioCardOption {\n value: string;\n label: string;\n description?: string;\n /** Something on the right of the title, like a price */\n aside?: React.ReactNode;\n icon?: React.ReactNode;\n disabled?: boolean;\n}\n\ninterface RadioCardsProps {\n options: RadioCardOption[];\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n /** Read out as the group's name */\n label?: string;\n /** Options per row from the small breakpoint up; they stack on phones */\n columns?: 1 | 2 | 3;\n name?: string;\n className?: string;\n}\n\nconst COLS = { 1: \"\", 2: \"sm:grid-cols-2\", 3: \"sm:grid-cols-3\" } as const;\n\n// RadioGroup's timings: the leading edge leaves at once, the trailing\n// edge follows a beat later on a spring and settles into place\nconst LEAD = \"260ms var(--ease-out-soft)\";\nconst TRAIL = \"480ms cubic-bezier(0.34, 1.25, 0.64, 1) 50ms\";\n\n/* Bigger choices, like a plan or a delivery speed: the segmented control\n from Tabs, grown up. The options sit flat in one sunken well and a\n single raised thumb flows to whichever is chosen, across rows and\n columns: its leading edge goes first and the trailing edge catches\n up, so it stretches toward the new option the way RadioGroup's\n selection does, and you can see which way it went. Each keeps RadioGroup's own control on its right, so the two\n read as one family. One Tab stop, arrows move. */\nexport function RadioCards({\n options,\n value,\n defaultValue = \"\",\n onChange,\n label,\n columns = 1,\n name,\n className = \"\",\n}: RadioCardsProps) {\n const uid = useId();\n const [internal, setInternal] = useState(defaultValue);\n const selected = value !== undefined ? value : internal;\n const wellRef = useRef<HTMLDivElement>(null);\n const refs = useRef<(HTMLButtonElement | null)[]>([]);\n const thumbRef = useRef<HTMLSpanElement>(null);\n // Where the thumb last landed, to tell which way the next move goes\n const last = useRef<{ top: number; left: number } | null>(null);\n\n const enabled = options.map((o, i) => (o.disabled ? -1 : i)).filter((i) => i >= 0);\n const selectedIndex = options.findIndex((o) => o.value === selected);\n const tabStop = selectedIndex >= 0 ? selectedIndex : enabled[0];\n\n const selectedRef = useRef(selectedIndex);\n\n /* The thumb is held by its four edges, measured from the well's, and\n written straight onto it, so a move never re-renders anything.\n Heading forward on an axis, the far edge leads and the near one\n trails on a spring; heading back, the other way round. */\n const place = (index: number, animate: boolean) => {\n const thumb = thumbRef.current;\n const el = refs.current[index];\n const well = wellRef.current;\n if (!thumb || !well) return;\n if (!el) {\n thumb.style.opacity = \"0\";\n last.current = null;\n return;\n }\n const top = el.offsetTop;\n const left = el.offsetLeft;\n const from = animate ? last.current : null;\n const edge = (d: number, near: string, far: string) =>\n d === 0 ? [] : d > 0 ? [`${near} ${TRAIL}`, `${far} ${LEAD}`] : [`${near} ${LEAD}`, `${far} ${TRAIL}`];\n const dy = from ? Math.sign(top - from.top) : 0;\n const dx = from ? Math.sign(left - from.left) : 0;\n thumb.style.transition = [...edge(dy, \"top\", \"bottom\"), ...edge(dx, \"left\", \"right\")].join(\", \") || \"none\";\n thumb.style.top = `${top}px`;\n thumb.style.left = `${left}px`;\n thumb.style.right = `${well.clientWidth - left - el.offsetWidth}px`;\n thumb.style.bottom = `${well.clientHeight - top - el.offsetHeight}px`;\n thumb.style.opacity = \"1\";\n last.current = { top, left };\n };\n\n // Before paint, so the thumb is never seen out of place\n useLayoutEffect(() => {\n selectedRef.current = selectedIndex;\n place(selectedIndex, true);\n }, [selectedIndex]);\n\n // Rewrapped text or a new breakpoint moves the options; follow them\n // without animating. An observer reports once as soon as it's attached;\n // that one is skipped, or it would cut short a move already under way.\n useEffect(() => {\n const well = wellRef.current;\n if (!well) return;\n let attached = false;\n const ro = new ResizeObserver(() => {\n if (attached) place(selectedRef.current, false);\n attached = true;\n });\n ro.observe(well);\n return () => ro.disconnect();\n }, []);\n\n const select = (i: number) => {\n const o = options[i];\n if (!o || o.disabled) return;\n setInternal(o.value);\n onChange?.(o.value);\n };\n\n const move = (from: number, step: 1 | -1) => {\n const at = enabled.indexOf(from);\n const next = enabled[(at + step + enabled.length) % enabled.length];\n refs.current[next]?.focus();\n select(next);\n };\n\n return (\n <div\n ref={wellRef}\n role=\"radiogroup\"\n aria-label={label}\n className={[\"relative grid grid-cols-1 gap-1 rounded-[18px] p-1.5\", COLS[columns], className].join(\" \")}\n style={{ background: \"var(--well)\", boxShadow: \"var(--well-shadow)\" }}\n >\n <span\n ref={thumbRef}\n aria-hidden\n className=\"pointer-events-none absolute rounded-[13px] opacity-0\"\n style={{ background: \"var(--face)\", boxShadow: \"var(--thumb-shadow)\" }}\n />\n\n {options.map((o, i) => {\n const on = i === selectedIndex;\n return (\n <button\n key={o.value}\n ref={(el) => {\n refs.current[i] = el;\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={on}\n aria-labelledby={`${uid}-${i}-label`}\n aria-describedby={o.description ? `${uid}-${i}-desc` : undefined}\n disabled={o.disabled}\n tabIndex={i === tabStop ? 0 : -1}\n onClick={() => select(i)}\n onKeyDown={(e) => {\n if (e.key === \"ArrowDown\" || e.key === \"ArrowRight\") {\n e.preventDefault();\n move(i, 1);\n } else if (e.key === \"ArrowUp\" || e.key === \"ArrowLeft\") {\n e.preventDefault();\n move(i, -1);\n }\n }}\n className={[\n \"group/card relative z-10 flex items-start gap-3 rounded-[13px] px-4 py-3.5 text-left\",\n \"focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ink\",\n \"disabled:opacity-40 disabled:cursor-not-allowed\",\n on ? \"cursor-default\" : \"cursor-pointer\",\n ].join(\" \")}\n >\n {o.icon && (\n <span\n aria-hidden\n className={[\n \"mt-px flex h-5 w-5 shrink-0 items-center justify-center transition-colors duration-200\",\n on ? \"text-ink\" : \"text-ink-faint group-hover/card:text-ink-body\",\n ].join(\" \")}\n >\n {o.icon}\n </span>\n )}\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"flex items-baseline justify-between gap-3\">\n <span\n id={`${uid}-${i}-label`}\n className={[\n \"text-[13.5px] font-medium leading-snug transition-colors duration-150\",\n on ? \"text-ink\" : \"text-ink-soft group-hover/card:text-ink\",\n ].join(\" \")}\n >\n {o.label}\n </span>\n {o.aside && (\n <span\n className={[\n \"shrink-0 text-[13px] tabular-nums transition-colors duration-150\",\n on ? \"font-medium text-ink\" : \"text-ink-muted\",\n ].join(\" \")}\n >\n {o.aside}\n </span>\n )}\n </span>\n {o.description && (\n <span id={`${uid}-${i}-desc`} className=\"text-[12px] leading-relaxed text-ink-muted\">\n {o.description}\n </span>\n )}\n </span>\n {/* RadioGroup's control: a sunken well, and when chosen a face in\n the inverse material with a dot pressed into it */}\n <span\n aria-hidden\n className={[\n \"relative mt-px h-[18px] w-[18px] shrink-0 rounded-full transition-[box-shadow] duration-200\",\n on ? \"\" : \"group-hover/card:[box-shadow:var(--field-shadow-hover)]\",\n ].join(\" \")}\n style={{ background: \"var(--field)\", boxShadow: \"var(--field-shadow)\" }}\n >\n <span\n className=\"absolute inset-0 grid place-items-center rounded-full transition-[opacity,transform] duration-300 ease-[var(--ease-spring)]\"\n style={{\n background: \"var(--face-inverse)\",\n boxShadow: \"var(--face-inverse-shadow)\",\n opacity: on ? 1 : 0,\n transform: on ? \"scale(1)\" : \"scale(0.4)\",\n }}\n >\n <span className=\"h-1.5 w-1.5 rounded-full bg-ink-inverse\" style={{ boxShadow: \"var(--dot-shadow)\" }} />\n </span>\n </span>\n </button>\n );\n })}\n {name && <input type=\"hidden\" name={name} value={selected} />}\n </div>\n );\n}\n"],"mappings":";;;AAEA,SAAgB,WAAW,OAAO,iBAAiB,QAAQ,gBAAgB;AA0IrE,cAmDQ,YAnDR;AAhHN,IAAM,OAAO,EAAE,GAAG,IAAI,GAAG,kBAAkB,GAAG,iBAAiB;AAI/D,IAAM,OAAO;AACb,IAAM,QAAQ;AASP,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA,YAAY;AACd,GAAoB;AAClB,QAAM,MAAM,MAAM;AAClB,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,YAAY;AACrD,QAAM,WAAW,UAAU,SAAY,QAAQ;AAC/C,QAAM,UAAU,OAAuB,IAAI;AAC3C,QAAM,OAAO,OAAqC,CAAC,CAAC;AACpD,QAAM,WAAW,OAAwB,IAAI;AAE7C,QAAM,OAAO,OAA6C,IAAI;AAE9D,QAAM,UAAU,QAAQ,IAAI,CAAC,GAAG,MAAO,EAAE,WAAW,KAAK,CAAE,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;AACjF,QAAM,gBAAgB,QAAQ,UAAU,CAAC,MAAM,EAAE,UAAU,QAAQ;AACnE,QAAM,UAAU,iBAAiB,IAAI,gBAAgB,QAAQ,CAAC;AAE9D,QAAM,cAAc,OAAO,aAAa;AAMxC,QAAM,QAAQ,CAAC,OAAe,YAAqB;AACjD,UAAM,QAAQ,SAAS;AACvB,UAAM,KAAK,KAAK,QAAQ,KAAK;AAC7B,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,SAAS,CAAC,KAAM;AACrB,QAAI,CAAC,IAAI;AACP,YAAM,MAAM,UAAU;AACtB,WAAK,UAAU;AACf;AAAA,IACF;AACA,UAAM,MAAM,GAAG;AACf,UAAM,OAAO,GAAG;AAChB,UAAM,OAAO,UAAU,KAAK,UAAU;AACtC,UAAM,OAAO,CAAC,GAAW,MAAc,QACrC,MAAM,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,GAAG,IAAI,IAAI,EAAE,IAAI,CAAC,GAAG,IAAI,IAAI,IAAI,IAAI,GAAG,GAAG,IAAI,KAAK,EAAE;AACvG,UAAM,KAAK,OAAO,KAAK,KAAK,MAAM,KAAK,GAAG,IAAI;AAC9C,UAAM,KAAK,OAAO,KAAK,KAAK,OAAO,KAAK,IAAI,IAAI;AAChD,UAAM,MAAM,aAAa,CAAC,GAAG,KAAK,IAAI,OAAO,QAAQ,GAAG,GAAG,KAAK,IAAI,QAAQ,OAAO,CAAC,EAAE,KAAK,IAAI,KAAK;AACpG,UAAM,MAAM,MAAM,GAAG,GAAG;AACxB,UAAM,MAAM,OAAO,GAAG,IAAI;AAC1B,UAAM,MAAM,QAAQ,GAAG,KAAK,cAAc,OAAO,GAAG,WAAW;AAC/D,UAAM,MAAM,SAAS,GAAG,KAAK,eAAe,MAAM,GAAG,YAAY;AACjE,UAAM,MAAM,UAAU;AACtB,SAAK,UAAU,EAAE,KAAK,KAAK;AAAA,EAC7B;AAGA,kBAAgB,MAAM;AACpB,gBAAY,UAAU;AACtB,UAAM,eAAe,IAAI;AAAA,EAC3B,GAAG,CAAC,aAAa,CAAC;AAKlB,YAAU,MAAM;AACd,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AACX,QAAI,WAAW;AACf,UAAM,KAAK,IAAI,eAAe,MAAM;AAClC,UAAI,SAAU,OAAM,YAAY,SAAS,KAAK;AAC9C,iBAAW;AAAA,IACb,CAAC;AACD,OAAG,QAAQ,IAAI;AACf,WAAO,MAAM,GAAG,WAAW;AAAA,EAC7B,GAAG,CAAC,CAAC;AAEL,QAAM,SAAS,CAAC,MAAc;AAC5B,UAAM,IAAI,QAAQ,CAAC;AACnB,QAAI,CAAC,KAAK,EAAE,SAAU;AACtB,gBAAY,EAAE,KAAK;AACnB,eAAW,EAAE,KAAK;AAAA,EACpB;AAEA,QAAM,OAAO,CAAC,MAAc,SAAiB;AAC3C,UAAM,KAAK,QAAQ,QAAQ,IAAI;AAC/B,UAAM,OAAO,SAAS,KAAK,OAAO,QAAQ,UAAU,QAAQ,MAAM;AAClE,SAAK,QAAQ,IAAI,GAAG,MAAM;AAC1B,WAAO,IAAI;AAAA,EACb;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,WAAW,CAAC,wDAAwD,KAAK,OAAO,GAAG,SAAS,EAAE,KAAK,GAAG;AAAA,MACtG,OAAO,EAAE,YAAY,eAAe,WAAW,qBAAqB;AAAA,MAEpE;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,eAAW;AAAA,YACX,WAAU;AAAA,YACV,OAAO,EAAE,YAAY,eAAe,WAAW,sBAAsB;AAAA;AAAA,QACvE;AAAA,QAEC,QAAQ,IAAI,CAAC,GAAG,MAAM;AACrB,gBAAM,KAAK,MAAM;AACjB,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC,KAAK,CAAC,OAAO;AACX,qBAAK,QAAQ,CAAC,IAAI;AAAA,cACpB;AAAA,cACA,MAAK;AAAA,cACL,MAAK;AAAA,cACL,gBAAc;AAAA,cACd,mBAAiB,GAAG,GAAG,IAAI,CAAC;AAAA,cAC5B,oBAAkB,EAAE,cAAc,GAAG,GAAG,IAAI,CAAC,UAAU;AAAA,cACvD,UAAU,EAAE;AAAA,cACZ,UAAU,MAAM,UAAU,IAAI;AAAA,cAC9B,SAAS,MAAM,OAAO,CAAC;AAAA,cACvB,WAAW,CAAC,MAAM;AAChB,oBAAI,EAAE,QAAQ,eAAe,EAAE,QAAQ,cAAc;AACnD,oBAAE,eAAe;AACjB,uBAAK,GAAG,CAAC;AAAA,gBACX,WAAW,EAAE,QAAQ,aAAa,EAAE,QAAQ,aAAa;AACvD,oBAAE,eAAe;AACjB,uBAAK,GAAG,EAAE;AAAA,gBACZ;AAAA,cACF;AAAA,cACA,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,KAAK,mBAAmB;AAAA,cAC1B,EAAE,KAAK,GAAG;AAAA,cAET;AAAA,kBAAE,QACD;AAAA,kBAAC;AAAA;AAAA,oBACC,eAAW;AAAA,oBACX,WAAW;AAAA,sBACT;AAAA,sBACA,KAAK,aAAa;AAAA,oBACpB,EAAE,KAAK,GAAG;AAAA,oBAET,YAAE;AAAA;AAAA,gBACL;AAAA,gBAEF,qBAAC,UAAK,WAAU,wCACd;AAAA,uCAAC,UAAK,WAAU,6CACd;AAAA;AAAA,sBAAC;AAAA;AAAA,wBACC,IAAI,GAAG,GAAG,IAAI,CAAC;AAAA,wBACf,WAAW;AAAA,0BACT;AAAA,0BACA,KAAK,aAAa;AAAA,wBACpB,EAAE,KAAK,GAAG;AAAA,wBAET,YAAE;AAAA;AAAA,oBACL;AAAA,oBACC,EAAE,SACD;AAAA,sBAAC;AAAA;AAAA,wBACC,WAAW;AAAA,0BACT;AAAA,0BACA,KAAK,yBAAyB;AAAA,wBAChC,EAAE,KAAK,GAAG;AAAA,wBAET,YAAE;AAAA;AAAA,oBACL;AAAA,qBAEJ;AAAA,kBACC,EAAE,eACD,oBAAC,UAAK,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,WAAU,8CACrC,YAAE,aACL;AAAA,mBAEJ;AAAA,gBAGA;AAAA,kBAAC;AAAA;AAAA,oBACC,eAAW;AAAA,oBACX,WAAW;AAAA,sBACT;AAAA,sBACA,KAAK,KAAK;AAAA,oBACZ,EAAE,KAAK,GAAG;AAAA,oBACV,OAAO,EAAE,YAAY,gBAAgB,WAAW,sBAAsB;AAAA,oBAEtE;AAAA,sBAAC;AAAA;AAAA,wBACC,WAAU;AAAA,wBACV,OAAO;AAAA,0BACL,YAAY;AAAA,0BACZ,WAAW;AAAA,0BACX,SAAS,KAAK,IAAI;AAAA,0BAClB,WAAW,KAAK,aAAa;AAAA,wBAC/B;AAAA,wBAEA,8BAAC,UAAK,WAAU,2CAA0C,OAAO,EAAE,WAAW,oBAAoB,GAAG;AAAA;AAAA,oBACvG;AAAA;AAAA,gBACF;AAAA;AAAA;AAAA,YAxFK,EAAE;AAAA,UAyFT;AAAA,QAEJ,CAAC;AAAA,QACA,QAAQ,oBAAC,WAAM,MAAK,UAAS,MAAY,OAAO,UAAU;AAAA;AAAA;AAAA,EAC7D;AAEJ;","names":[]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface ReactionItem {
|
|
4
|
+
emoji: string;
|
|
5
|
+
label: string;
|
|
6
|
+
count: number;
|
|
7
|
+
active?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface ReactionBarProps {
|
|
10
|
+
reactions?: ReactionItem[];
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
declare function ReactionBar({ reactions: initialReactions, className, }: ReactionBarProps): react_jsx_runtime.JSX.Element;
|
|
14
|
+
|
|
15
|
+
export { ReactionBar, type ReactionItem };
|