@viax.io/uxm 4.44.0 → 4.46.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/CHANGELOG.md +14 -0
- package/dist/studio/lib/registry/inputs/radio-group.cjs +33 -1
- package/dist/studio/lib/registry/inputs/radio-group.cjs.map +1 -1
- package/dist/studio/lib/registry/inputs/radio-group.d.ts.map +1 -1
- package/dist/studio/lib/registry/inputs/radio-group.js +33 -1
- package/dist/studio/lib/registry/inputs/radio-group.js.map +1 -1
- package/dist/ui/button/button.cjs +10 -10
- package/dist/ui/button/button.cjs.map +1 -1
- package/dist/ui/button/button.d.ts +20 -8
- package/dist/ui/button/button.d.ts.map +1 -1
- package/dist/ui/button/button.js +10 -10
- package/dist/ui/button/button.js.map +1 -1
- package/dist/ui/icon-button/icon-button.cjs +2 -0
- package/dist/ui/icon-button/icon-button.cjs.map +1 -1
- package/dist/ui/icon-button/icon-button.d.ts +3 -3
- package/dist/ui/icon-button/icon-button.d.ts.map +1 -1
- package/dist/ui/icon-button/icon-button.js +2 -0
- package/dist/ui/icon-button/icon-button.js.map +1 -1
- package/dist/ui/radio-group/radio-group-preview.cjs +86 -35
- package/dist/ui/radio-group/radio-group-preview.cjs.map +1 -1
- package/dist/ui/radio-group/radio-group-preview.d.ts.map +1 -1
- package/dist/ui/radio-group/radio-group-preview.js +87 -36
- package/dist/ui/radio-group/radio-group-preview.js.map +1 -1
- package/dist/ui/radio-group/radio-group.cjs +46 -18
- package/dist/ui/radio-group/radio-group.cjs.map +1 -1
- package/dist/ui/radio-group/radio-group.css +82 -0
- package/dist/ui/radio-group/radio-group.d.ts +24 -1
- package/dist/ui/radio-group/radio-group.d.ts.map +1 -1
- package/dist/ui/radio-group/radio-group.js +46 -18
- package/dist/ui/radio-group/radio-group.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/radio-group/radio-group-preview.tsx"],"sourcesContent":["import { type CSSProperties } from 'react';\n\nimport type { PreviewProps } from '@/previews/types';\nimport { RadioGroup, RadioOption } from '@/ui';\n\ntype Styles = PreviewProps['styles'];\n\nfunction ShowcaseRow({\n selected,\n state,\n styles,\n label,\n}: {\n selected: boolean;\n state: string;\n styles: Styles;\n label: string;\n}) {\n // Static showcase — paints the selected forced state regardless of\n // pointer interaction. Inline JSX (not the real <RadioOption>) so each\n // forced state can override the circle + dot directly; the interactive\n // instance below is where production CSS rules exercise.\n const isHover = state === 'hover';\n const isFocus = state === 'focus';\n const isDisabled = state === 'disabled';\n\n const bg = isHover\n ? styles.hoverUnselectedBg\n : styles.unselectedBg;\n const border = isHover\n ? (selected ? styles.hoverSelectedBorder : styles.hoverUnselectedBorder)\n : (selected ? styles.selectedBorder : styles.unselectedBorder);\n const dot = isHover ? styles.hoverDotColor : styles.dotColor;\n const ring = styles.focusRing;\n const size = (styles.size as number) ?? 20;\n\n return (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 14,\n color: 'var(--color-text)',\n opacity: isDisabled ? ((styles.disabledOpacity as number) ?? 0.4) : 1,\n cursor: isDisabled ? 'not-allowed' : 'pointer',\n }}\n >\n <span\n aria-hidden=\"true\"\n style={{\n width: size,\n height: size,\n flexShrink: 0,\n borderRadius: '50%',\n border: `2px solid ${border as string}`,\n backgroundColor: bg as string,\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n transition: 'border-color 0.15s, background-color 0.15s',\n ...(isFocus\n ? { outline: `2px solid ${ring as string}`, outlineOffset: 2 }\n : {}),\n }}\n >\n {selected && (\n <span\n style={{\n width: size * 0.5,\n height: size * 0.5,\n borderRadius: '50%',\n backgroundColor: dot as string,\n }}\n />\n )}\n </span>\n {label}\n </div>\n );\n}\n\nconst INTERACTIVE_OPTIONS = ['Small', 'Medium', 'Large'];\n\nexport function RadioGroupPreview({ styles, variants }: PreviewProps) {\n const state = (variants.state as string) ?? 'default';\n const direction = (variants.direction as 'vertical' | 'horizontal' | undefined) ?? 'vertical';\n\n // Project every registry knob as a `--uxm-radio-group-*` custom property\n // on the wrapper. The interactive <RadioGroup> below renders the real\n // component whose styles.css rules read these vars — live editor edits\n // paint immediately, and production `:hover` / `:focus-visible` rules\n // exercise exactly as they would in prod.\n const cssVars: Record<string, string | undefined> = {\n '--uxm-radio-group-size': styles.size != null ? `${styles.size}px` : undefined,\n '--uxm-radio-group-gap': styles.gap != null ? `${styles.gap}px` : undefined,\n '--uxm-radio-group-unselected-bg': styles.unselectedBg as string | undefined,\n '--uxm-radio-group-unselected-border': styles.unselectedBorder as string | undefined,\n '--uxm-radio-group-selected-border': styles.selectedBorder as string | undefined,\n '--uxm-radio-group-dot-color': styles.dotColor as string | undefined,\n '--uxm-radio-group-hover-unselected-bg': styles.hoverUnselectedBg as string | undefined,\n '--uxm-radio-group-hover-unselected-border': styles.hoverUnselectedBorder as string | undefined,\n '--uxm-radio-group-hover-selected-border': styles.hoverSelectedBorder as string | undefined,\n '--uxm-radio-group-hover-dot-color': styles.hoverDotColor as string | undefined,\n '--uxm-radio-group-focus-ring': styles.focusRing as string | undefined,\n '--uxm-radio-group-disabled-opacity':\n styles.disabledOpacity != null ? String(styles.disabledOpacity) : undefined,\n '--uxm-radio-group-error-color': styles.errorColor as string | undefined,\n '--uxm-radio-group-error-message-size':\n styles.errorMessageSize != null ? `${styles.errorMessageSize}px` : undefined,\n };\n\n const sectionLabel = {\n fontSize: 11,\n color: 'var(--color-text-muted)',\n marginBottom: 12,\n letterSpacing: 0.5,\n textTransform: 'uppercase' as const,\n };\n\n // Pair the state and direction into the interactive `key` so that\n // toggling either variant resets the radio group cleanly (matches the\n // freshly-{re,dis}abled UX and clears any stale selection when the user\n // flips layout).\n const interactiveKey = `${state}-${direction}`;\n const interactiveDisabled = state === 'disabled';\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 32, ...cssVars } as CSSProperties}>\n <div>\n <div style={sectionLabel}>{state} state</div>\n <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>\n <ShowcaseRow selected={false} state={state} styles={styles} label=\"Unselected\" />\n <ShowcaseRow selected={true} state={state} styles={styles} label=\"Selected\" />\n </div>\n </div>\n\n {/* Interactive instance — uncontrolled so the browser owns the\n checked state. React (and the canvas's event-capture re-renders)\n can't fight the DOM here, so click/keyboard navigation toggle\n reliably between options. Three options share a unique `name`\n so the browser's radio-group semantics work natively. */}\n <div style={{ borderTop: '1px solid var(--color-border)', paddingTop: 16 }}>\n <div style={sectionLabel}>Interactive</div>\n <RadioGroup\n key={interactiveKey}\n name={`uxm-radio-preview-${interactiveKey}`}\n direction={direction}\n error={state === 'error' ? 'Pick an option to continue' : undefined}\n >\n {INTERACTIVE_OPTIONS.map((opt) => (\n <RadioOption\n key={opt}\n name={`uxm-radio-preview-${interactiveKey}`}\n value={opt}\n disabled={interactiveDisabled}\n >\n {opt}\n </RadioOption>\n ))}\n </RadioGroup>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/radio-group/radio-group-preview.tsx"],"sourcesContent":["import { type CSSProperties } from 'react';\n\nimport type { PreviewProps } from '@/previews/types';\nimport { RadioGroup, RadioOption } from '@/ui';\n\ntype Styles = PreviewProps['styles'];\n\nfunction ShowcaseRow({\n selected,\n state,\n styles,\n label,\n variant,\n indicator,\n}: {\n selected: boolean;\n state: string;\n styles: Styles;\n label: string;\n variant: 'default' | 'card';\n indicator: 'hidden' | 'corner';\n}) {\n // Static showcase — paints the selected forced state regardless of\n // pointer interaction. Inline JSX (not the real <RadioOption>) so each\n // forced state can override the circle + dot directly; the interactive\n // instance below is where production CSS rules exercise.\n //\n // It has to follow `variant`: this is the ONLY thing that paints the forced\n // hover / focus / disabled states — the interactive group below shows the\n // resting state and real input. Left as a row while the variant is `card`,\n // the States picker would show a hovered CIRCLE for a control that is a\n // tile, and the hover / focus knobs (which the card frame genuinely reads)\n // would be tuned against feedback that is not what ships.\n const isHover = state === 'hover';\n const isFocus = state === 'focus';\n const isDisabled = state === 'disabled';\n const isCard = variant === 'card';\n\n const bg = isHover\n ? styles.hoverUnselectedBg\n : styles.unselectedBg;\n const border = isHover\n ? (selected ? styles.hoverSelectedBorder : styles.hoverUnselectedBorder)\n : (selected ? styles.selectedBorder : styles.unselectedBorder);\n const dot = isHover ? styles.hoverDotColor : styles.dotColor;\n const ring = styles.focusRing;\n const size = (styles.size as number) ?? 20;\n\n const circle = (\n <span\n aria-hidden=\"true\"\n style={{\n width: size,\n height: size,\n flexShrink: 0,\n borderRadius: '50%',\n border: `2px solid ${border as string}`,\n backgroundColor: bg as string,\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n transition: 'border-color 0.15s, background-color 0.15s',\n ...(isFocus && !isCard\n ? { outline: `2px solid ${ring as string}`, outlineOffset: 2 }\n : {}),\n ...(isCard\n ? { position: 'absolute' as const, top: styles.cardPadding as number, right: styles.cardPadding as number }\n : {}),\n }}\n >\n {selected && (\n <span\n style={{\n width: size * 0.5,\n height: size * 0.5,\n borderRadius: '50%',\n backgroundColor: dot as string,\n }}\n />\n )}\n </span>\n );\n\n if (isCard) {\n // Mirrors `.uxm-radio--card`: the tile carries the frame and the focus\n // ring, and the circle is either gone or pinned to the corner.\n return (\n <div\n style={{\n position: 'relative',\n display: 'flex',\n flexDirection: 'column',\n gap: styles.cardGap as number,\n minWidth: 150,\n boxSizing: 'border-box',\n padding: styles.cardPadding as number,\n borderRadius: styles.cardRadius as number,\n border: `${styles.cardBorderWidth}px solid ${border as string}`,\n backgroundColor: styles.cardBg as string,\n fontSize: 14,\n color: 'var(--color-text)',\n opacity: isDisabled ? ((styles.disabledOpacity as number) ?? 0.4) : 1,\n cursor: isDisabled ? 'not-allowed' : 'pointer',\n transition: 'border-color 0.15s, background-color 0.15s',\n ...(isFocus ? { outline: `2px solid ${ring as string}`, outlineOffset: 2 } : {}),\n }}\n >\n {indicator === 'corner' && circle}\n <strong>{label}</strong>\n <span style={{ color: 'var(--color-text-muted)', fontSize: 12 }}>\n What this option does\n </span>\n </div>\n );\n }\n\n return (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 14,\n color: 'var(--color-text)',\n opacity: isDisabled ? ((styles.disabledOpacity as number) ?? 0.4) : 1,\n cursor: isDisabled ? 'not-allowed' : 'pointer',\n }}\n >\n {circle}\n {label}\n </div>\n );\n}\n\nconst INTERACTIVE_OPTIONS = ['Small', 'Medium', 'Large'];\n\nexport function RadioGroupPreview({ styles, variants }: PreviewProps) {\n const state = (variants.state as string) ?? 'default';\n const direction = (variants.direction as 'vertical' | 'horizontal' | undefined) ?? 'vertical';\n const variant = (variants.variant as 'default' | 'card' | undefined) ?? 'default';\n const indicator = (variants.indicator as 'hidden' | 'corner' | undefined) ?? 'hidden';\n\n // Project every registry knob as a `--uxm-radio-group-*` custom property\n // on the wrapper. The interactive <RadioGroup> below renders the real\n // component whose styles.css rules read these vars — live editor edits\n // paint immediately, and production `:hover` / `:focus-visible` rules\n // exercise exactly as they would in prod.\n const cssVars: Record<string, string | undefined> = {\n '--uxm-radio-group-size': styles.size != null ? `${styles.size}px` : undefined,\n '--uxm-radio-group-gap': styles.gap != null ? `${styles.gap}px` : undefined,\n '--uxm-radio-group-unselected-bg': styles.unselectedBg as string | undefined,\n '--uxm-radio-group-unselected-border': styles.unselectedBorder as string | undefined,\n '--uxm-radio-group-selected-border': styles.selectedBorder as string | undefined,\n '--uxm-radio-group-dot-color': styles.dotColor as string | undefined,\n '--uxm-radio-group-hover-unselected-bg': styles.hoverUnselectedBg as string | undefined,\n '--uxm-radio-group-hover-unselected-border': styles.hoverUnselectedBorder as string | undefined,\n '--uxm-radio-group-hover-selected-border': styles.hoverSelectedBorder as string | undefined,\n '--uxm-radio-group-hover-dot-color': styles.hoverDotColor as string | undefined,\n '--uxm-radio-group-focus-ring': styles.focusRing as string | undefined,\n '--uxm-radio-group-disabled-opacity':\n styles.disabledOpacity != null ? String(styles.disabledOpacity) : undefined,\n '--uxm-radio-group-error-color': styles.errorColor as string | undefined,\n '--uxm-radio-group-error-message-size':\n styles.errorMessageSize != null ? `${styles.errorMessageSize}px` : undefined,\n '--uxm-radio-card-padding': `${styles.cardPadding}px`,\n '--uxm-radio-card-radius': `${styles.cardRadius}px`,\n '--uxm-radio-card-gap': `${styles.cardGap}px`,\n '--uxm-radio-card-border-width': `${styles.cardBorderWidth}px`,\n '--uxm-radio-card-bg': styles.cardBg as string,\n };\n\n const sectionLabel = {\n fontSize: 11,\n color: 'var(--color-text-muted)',\n marginBottom: 12,\n letterSpacing: 0.5,\n textTransform: 'uppercase' as const,\n };\n\n // Pair the state and direction into the interactive `key` so that\n // toggling either variant resets the radio group cleanly (matches the\n // freshly-{re,dis}abled UX and clears any stale selection when the user\n // flips layout).\n const interactiveKey = `${state}-${direction}`;\n const interactiveDisabled = state === 'disabled';\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 32, ...cssVars } as CSSProperties}>\n <div>\n <div style={sectionLabel}>{state} state</div>\n <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>\n <ShowcaseRow selected={false} state={state} styles={styles} label=\"Unselected\" variant={variant} indicator={indicator} />\n <ShowcaseRow selected={true} state={state} styles={styles} label=\"Selected\" variant={variant} indicator={indicator} />\n </div>\n </div>\n\n {/* Interactive instance — uncontrolled so the browser owns the\n checked state. React (and the canvas's event-capture re-renders)\n can't fight the DOM here, so click/keyboard navigation toggle\n reliably between options. Three options share a unique `name`\n so the browser's radio-group semantics work natively. */}\n <div style={{ borderTop: '1px solid var(--color-border)', paddingTop: 16 }}>\n <div style={sectionLabel}>Interactive</div>\n <RadioGroup\n key={interactiveKey}\n name={`uxm-radio-preview-${interactiveKey}`}\n direction={direction}\n variant={variant}\n indicator={indicator}\n error={state === 'error' ? 'Pick an option to continue' : undefined}\n >\n {INTERACTIVE_OPTIONS.map((opt) => (\n <RadioOption\n key={opt}\n name={`uxm-radio-preview-${interactiveKey}`}\n value={opt}\n disabled={interactiveDisabled}\n >\n {variant === 'card' ? (\n // Card mode is for rich content — a one-word label in a tile\n // would demo a box, not the shape the variant exists for.\n <>\n <strong>{opt}</strong>\n <span style={{ color: 'var(--color-text-muted)', fontSize: 12 }}>\n What this option does\n </span>\n </>\n ) : (\n opt\n )}\n </RadioOption>\n ))}\n </RadioGroup>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio-group-preview.d.ts","sourceRoot":"","sources":["../../../src/ui/radio-group/radio-group-preview.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"radio-group-preview.d.ts","sourceRoot":"","sources":["../../../src/ui/radio-group/radio-group-preview.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAsIrD,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,YAAY,+BAoGnE"}
|
|
@@ -1,19 +1,83 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { RadioGroup, RadioOption } from "../index.js";
|
|
3
3
|
function ShowcaseRow({
|
|
4
4
|
selected,
|
|
5
5
|
state,
|
|
6
6
|
styles,
|
|
7
|
-
label
|
|
7
|
+
label,
|
|
8
|
+
variant,
|
|
9
|
+
indicator
|
|
8
10
|
}) {
|
|
9
11
|
const isHover = state === "hover";
|
|
10
12
|
const isFocus = state === "focus";
|
|
11
13
|
const isDisabled = state === "disabled";
|
|
14
|
+
const isCard = variant === "card";
|
|
12
15
|
const bg = isHover ? styles.hoverUnselectedBg : styles.unselectedBg;
|
|
13
16
|
const border = isHover ? selected ? styles.hoverSelectedBorder : styles.hoverUnselectedBorder : selected ? styles.selectedBorder : styles.unselectedBorder;
|
|
14
17
|
const dot = isHover ? styles.hoverDotColor : styles.dotColor;
|
|
15
18
|
const ring = styles.focusRing;
|
|
16
19
|
const size = styles.size ?? 20;
|
|
20
|
+
const circle = /* @__PURE__ */ jsx(
|
|
21
|
+
"span",
|
|
22
|
+
{
|
|
23
|
+
"aria-hidden": "true",
|
|
24
|
+
style: {
|
|
25
|
+
width: size,
|
|
26
|
+
height: size,
|
|
27
|
+
flexShrink: 0,
|
|
28
|
+
borderRadius: "50%",
|
|
29
|
+
border: `2px solid ${border}`,
|
|
30
|
+
backgroundColor: bg,
|
|
31
|
+
display: "inline-flex",
|
|
32
|
+
alignItems: "center",
|
|
33
|
+
justifyContent: "center",
|
|
34
|
+
transition: "border-color 0.15s, background-color 0.15s",
|
|
35
|
+
...isFocus && !isCard ? { outline: `2px solid ${ring}`, outlineOffset: 2 } : {},
|
|
36
|
+
...isCard ? { position: "absolute", top: styles.cardPadding, right: styles.cardPadding } : {}
|
|
37
|
+
},
|
|
38
|
+
children: selected && /* @__PURE__ */ jsx(
|
|
39
|
+
"span",
|
|
40
|
+
{
|
|
41
|
+
style: {
|
|
42
|
+
width: size * 0.5,
|
|
43
|
+
height: size * 0.5,
|
|
44
|
+
borderRadius: "50%",
|
|
45
|
+
backgroundColor: dot
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
if (isCard) {
|
|
52
|
+
return /* @__PURE__ */ jsxs(
|
|
53
|
+
"div",
|
|
54
|
+
{
|
|
55
|
+
style: {
|
|
56
|
+
position: "relative",
|
|
57
|
+
display: "flex",
|
|
58
|
+
flexDirection: "column",
|
|
59
|
+
gap: styles.cardGap,
|
|
60
|
+
minWidth: 150,
|
|
61
|
+
boxSizing: "border-box",
|
|
62
|
+
padding: styles.cardPadding,
|
|
63
|
+
borderRadius: styles.cardRadius,
|
|
64
|
+
border: `${styles.cardBorderWidth}px solid ${border}`,
|
|
65
|
+
backgroundColor: styles.cardBg,
|
|
66
|
+
fontSize: 14,
|
|
67
|
+
color: "var(--color-text)",
|
|
68
|
+
opacity: isDisabled ? styles.disabledOpacity ?? 0.4 : 1,
|
|
69
|
+
cursor: isDisabled ? "not-allowed" : "pointer",
|
|
70
|
+
transition: "border-color 0.15s, background-color 0.15s",
|
|
71
|
+
...isFocus ? { outline: `2px solid ${ring}`, outlineOffset: 2 } : {}
|
|
72
|
+
},
|
|
73
|
+
children: [
|
|
74
|
+
indicator === "corner" && circle,
|
|
75
|
+
/* @__PURE__ */ jsx("strong", { children: label }),
|
|
76
|
+
/* @__PURE__ */ jsx("span", { style: { color: "var(--color-text-muted)", fontSize: 12 }, children: "What this option does" })
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
}
|
|
17
81
|
return /* @__PURE__ */ jsxs(
|
|
18
82
|
"div",
|
|
19
83
|
{
|
|
@@ -27,36 +91,7 @@ function ShowcaseRow({
|
|
|
27
91
|
cursor: isDisabled ? "not-allowed" : "pointer"
|
|
28
92
|
},
|
|
29
93
|
children: [
|
|
30
|
-
|
|
31
|
-
"span",
|
|
32
|
-
{
|
|
33
|
-
"aria-hidden": "true",
|
|
34
|
-
style: {
|
|
35
|
-
width: size,
|
|
36
|
-
height: size,
|
|
37
|
-
flexShrink: 0,
|
|
38
|
-
borderRadius: "50%",
|
|
39
|
-
border: `2px solid ${border}`,
|
|
40
|
-
backgroundColor: bg,
|
|
41
|
-
display: "inline-flex",
|
|
42
|
-
alignItems: "center",
|
|
43
|
-
justifyContent: "center",
|
|
44
|
-
transition: "border-color 0.15s, background-color 0.15s",
|
|
45
|
-
...isFocus ? { outline: `2px solid ${ring}`, outlineOffset: 2 } : {}
|
|
46
|
-
},
|
|
47
|
-
children: selected && /* @__PURE__ */ jsx(
|
|
48
|
-
"span",
|
|
49
|
-
{
|
|
50
|
-
style: {
|
|
51
|
-
width: size * 0.5,
|
|
52
|
-
height: size * 0.5,
|
|
53
|
-
borderRadius: "50%",
|
|
54
|
-
backgroundColor: dot
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
),
|
|
94
|
+
circle,
|
|
60
95
|
label
|
|
61
96
|
]
|
|
62
97
|
}
|
|
@@ -66,6 +101,8 @@ const INTERACTIVE_OPTIONS = ["Small", "Medium", "Large"];
|
|
|
66
101
|
function RadioGroupPreview({ styles, variants }) {
|
|
67
102
|
const state = variants.state ?? "default";
|
|
68
103
|
const direction = variants.direction ?? "vertical";
|
|
104
|
+
const variant = variants.variant ?? "default";
|
|
105
|
+
const indicator = variants.indicator ?? "hidden";
|
|
69
106
|
const cssVars = {
|
|
70
107
|
"--uxm-radio-group-size": styles.size != null ? `${styles.size}px` : void 0,
|
|
71
108
|
"--uxm-radio-group-gap": styles.gap != null ? `${styles.gap}px` : void 0,
|
|
@@ -80,7 +117,12 @@ function RadioGroupPreview({ styles, variants }) {
|
|
|
80
117
|
"--uxm-radio-group-focus-ring": styles.focusRing,
|
|
81
118
|
"--uxm-radio-group-disabled-opacity": styles.disabledOpacity != null ? String(styles.disabledOpacity) : void 0,
|
|
82
119
|
"--uxm-radio-group-error-color": styles.errorColor,
|
|
83
|
-
"--uxm-radio-group-error-message-size": styles.errorMessageSize != null ? `${styles.errorMessageSize}px` : void 0
|
|
120
|
+
"--uxm-radio-group-error-message-size": styles.errorMessageSize != null ? `${styles.errorMessageSize}px` : void 0,
|
|
121
|
+
"--uxm-radio-card-padding": `${styles.cardPadding}px`,
|
|
122
|
+
"--uxm-radio-card-radius": `${styles.cardRadius}px`,
|
|
123
|
+
"--uxm-radio-card-gap": `${styles.cardGap}px`,
|
|
124
|
+
"--uxm-radio-card-border-width": `${styles.cardBorderWidth}px`,
|
|
125
|
+
"--uxm-radio-card-bg": styles.cardBg
|
|
84
126
|
};
|
|
85
127
|
const sectionLabel = {
|
|
86
128
|
fontSize: 11,
|
|
@@ -98,8 +140,8 @@ function RadioGroupPreview({ styles, variants }) {
|
|
|
98
140
|
" state"
|
|
99
141
|
] }),
|
|
100
142
|
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
|
|
101
|
-
/* @__PURE__ */ jsx(ShowcaseRow, { selected: false, state, styles, label: "Unselected" }),
|
|
102
|
-
/* @__PURE__ */ jsx(ShowcaseRow, { selected: true, state, styles, label: "Selected" })
|
|
143
|
+
/* @__PURE__ */ jsx(ShowcaseRow, { selected: false, state, styles, label: "Unselected", variant, indicator }),
|
|
144
|
+
/* @__PURE__ */ jsx(ShowcaseRow, { selected: true, state, styles, label: "Selected", variant, indicator })
|
|
103
145
|
] })
|
|
104
146
|
] }),
|
|
105
147
|
/* @__PURE__ */ jsxs("div", { style: { borderTop: "1px solid var(--color-border)", paddingTop: 16 }, children: [
|
|
@@ -109,6 +151,8 @@ function RadioGroupPreview({ styles, variants }) {
|
|
|
109
151
|
{
|
|
110
152
|
name: `uxm-radio-preview-${interactiveKey}`,
|
|
111
153
|
direction,
|
|
154
|
+
variant,
|
|
155
|
+
indicator,
|
|
112
156
|
error: state === "error" ? "Pick an option to continue" : void 0,
|
|
113
157
|
children: INTERACTIVE_OPTIONS.map((opt) => /* @__PURE__ */ jsx(
|
|
114
158
|
RadioOption,
|
|
@@ -116,7 +160,14 @@ function RadioGroupPreview({ styles, variants }) {
|
|
|
116
160
|
name: `uxm-radio-preview-${interactiveKey}`,
|
|
117
161
|
value: opt,
|
|
118
162
|
disabled: interactiveDisabled,
|
|
119
|
-
children:
|
|
163
|
+
children: variant === "card" ? (
|
|
164
|
+
// Card mode is for rich content — a one-word label in a tile
|
|
165
|
+
// would demo a box, not the shape the variant exists for.
|
|
166
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
167
|
+
/* @__PURE__ */ jsx("strong", { children: opt }),
|
|
168
|
+
/* @__PURE__ */ jsx("span", { style: { color: "var(--color-text-muted)", fontSize: 12 }, children: "What this option does" })
|
|
169
|
+
] })
|
|
170
|
+
) : opt
|
|
120
171
|
},
|
|
121
172
|
opt
|
|
122
173
|
))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/radio-group/radio-group-preview.tsx"],"sourcesContent":["import { type CSSProperties } from 'react';\n\nimport type { PreviewProps } from '@/previews/types';\nimport { RadioGroup, RadioOption } from '@/ui';\n\ntype Styles = PreviewProps['styles'];\n\nfunction ShowcaseRow({\n selected,\n state,\n styles,\n label,\n}: {\n selected: boolean;\n state: string;\n styles: Styles;\n label: string;\n}) {\n // Static showcase — paints the selected forced state regardless of\n // pointer interaction. Inline JSX (not the real <RadioOption>) so each\n // forced state can override the circle + dot directly; the interactive\n // instance below is where production CSS rules exercise.\n const isHover = state === 'hover';\n const isFocus = state === 'focus';\n const isDisabled = state === 'disabled';\n\n const bg = isHover\n ? styles.hoverUnselectedBg\n : styles.unselectedBg;\n const border = isHover\n ? (selected ? styles.hoverSelectedBorder : styles.hoverUnselectedBorder)\n : (selected ? styles.selectedBorder : styles.unselectedBorder);\n const dot = isHover ? styles.hoverDotColor : styles.dotColor;\n const ring = styles.focusRing;\n const size = (styles.size as number) ?? 20;\n\n return (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 14,\n color: 'var(--color-text)',\n opacity: isDisabled ? ((styles.disabledOpacity as number) ?? 0.4) : 1,\n cursor: isDisabled ? 'not-allowed' : 'pointer',\n }}\n >\n <span\n aria-hidden=\"true\"\n style={{\n width: size,\n height: size,\n flexShrink: 0,\n borderRadius: '50%',\n border: `2px solid ${border as string}`,\n backgroundColor: bg as string,\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n transition: 'border-color 0.15s, background-color 0.15s',\n ...(isFocus\n ? { outline: `2px solid ${ring as string}`, outlineOffset: 2 }\n : {}),\n }}\n >\n {selected && (\n <span\n style={{\n width: size * 0.5,\n height: size * 0.5,\n borderRadius: '50%',\n backgroundColor: dot as string,\n }}\n />\n )}\n </span>\n {label}\n </div>\n );\n}\n\nconst INTERACTIVE_OPTIONS = ['Small', 'Medium', 'Large'];\n\nexport function RadioGroupPreview({ styles, variants }: PreviewProps) {\n const state = (variants.state as string) ?? 'default';\n const direction = (variants.direction as 'vertical' | 'horizontal' | undefined) ?? 'vertical';\n\n // Project every registry knob as a `--uxm-radio-group-*` custom property\n // on the wrapper. The interactive <RadioGroup> below renders the real\n // component whose styles.css rules read these vars — live editor edits\n // paint immediately, and production `:hover` / `:focus-visible` rules\n // exercise exactly as they would in prod.\n const cssVars: Record<string, string | undefined> = {\n '--uxm-radio-group-size': styles.size != null ? `${styles.size}px` : undefined,\n '--uxm-radio-group-gap': styles.gap != null ? `${styles.gap}px` : undefined,\n '--uxm-radio-group-unselected-bg': styles.unselectedBg as string | undefined,\n '--uxm-radio-group-unselected-border': styles.unselectedBorder as string | undefined,\n '--uxm-radio-group-selected-border': styles.selectedBorder as string | undefined,\n '--uxm-radio-group-dot-color': styles.dotColor as string | undefined,\n '--uxm-radio-group-hover-unselected-bg': styles.hoverUnselectedBg as string | undefined,\n '--uxm-radio-group-hover-unselected-border': styles.hoverUnselectedBorder as string | undefined,\n '--uxm-radio-group-hover-selected-border': styles.hoverSelectedBorder as string | undefined,\n '--uxm-radio-group-hover-dot-color': styles.hoverDotColor as string | undefined,\n '--uxm-radio-group-focus-ring': styles.focusRing as string | undefined,\n '--uxm-radio-group-disabled-opacity':\n styles.disabledOpacity != null ? String(styles.disabledOpacity) : undefined,\n '--uxm-radio-group-error-color': styles.errorColor as string | undefined,\n '--uxm-radio-group-error-message-size':\n styles.errorMessageSize != null ? `${styles.errorMessageSize}px` : undefined,\n };\n\n const sectionLabel = {\n fontSize: 11,\n color: 'var(--color-text-muted)',\n marginBottom: 12,\n letterSpacing: 0.5,\n textTransform: 'uppercase' as const,\n };\n\n // Pair the state and direction into the interactive `key` so that\n // toggling either variant resets the radio group cleanly (matches the\n // freshly-{re,dis}abled UX and clears any stale selection when the user\n // flips layout).\n const interactiveKey = `${state}-${direction}`;\n const interactiveDisabled = state === 'disabled';\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 32, ...cssVars } as CSSProperties}>\n <div>\n <div style={sectionLabel}>{state} state</div>\n <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>\n <ShowcaseRow selected={false} state={state} styles={styles} label=\"Unselected\" />\n <ShowcaseRow selected={true} state={state} styles={styles} label=\"Selected\" />\n </div>\n </div>\n\n {/* Interactive instance — uncontrolled so the browser owns the\n checked state. React (and the canvas's event-capture re-renders)\n can't fight the DOM here, so click/keyboard navigation toggle\n reliably between options. Three options share a unique `name`\n so the browser's radio-group semantics work natively. */}\n <div style={{ borderTop: '1px solid var(--color-border)', paddingTop: 16 }}>\n <div style={sectionLabel}>Interactive</div>\n <RadioGroup\n key={interactiveKey}\n name={`uxm-radio-preview-${interactiveKey}`}\n direction={direction}\n error={state === 'error' ? 'Pick an option to continue' : undefined}\n >\n {INTERACTIVE_OPTIONS.map((opt) => (\n <RadioOption\n key={opt}\n name={`uxm-radio-preview-${interactiveKey}`}\n value={opt}\n disabled={interactiveDisabled}\n >\n {opt}\n </RadioOption>\n ))}\n </RadioGroup>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/radio-group/radio-group-preview.tsx"],"sourcesContent":["import { type CSSProperties } from 'react';\n\nimport type { PreviewProps } from '@/previews/types';\nimport { RadioGroup, RadioOption } from '@/ui';\n\ntype Styles = PreviewProps['styles'];\n\nfunction ShowcaseRow({\n selected,\n state,\n styles,\n label,\n variant,\n indicator,\n}: {\n selected: boolean;\n state: string;\n styles: Styles;\n label: string;\n variant: 'default' | 'card';\n indicator: 'hidden' | 'corner';\n}) {\n // Static showcase — paints the selected forced state regardless of\n // pointer interaction. Inline JSX (not the real <RadioOption>) so each\n // forced state can override the circle + dot directly; the interactive\n // instance below is where production CSS rules exercise.\n //\n // It has to follow `variant`: this is the ONLY thing that paints the forced\n // hover / focus / disabled states — the interactive group below shows the\n // resting state and real input. Left as a row while the variant is `card`,\n // the States picker would show a hovered CIRCLE for a control that is a\n // tile, and the hover / focus knobs (which the card frame genuinely reads)\n // would be tuned against feedback that is not what ships.\n const isHover = state === 'hover';\n const isFocus = state === 'focus';\n const isDisabled = state === 'disabled';\n const isCard = variant === 'card';\n\n const bg = isHover\n ? styles.hoverUnselectedBg\n : styles.unselectedBg;\n const border = isHover\n ? (selected ? styles.hoverSelectedBorder : styles.hoverUnselectedBorder)\n : (selected ? styles.selectedBorder : styles.unselectedBorder);\n const dot = isHover ? styles.hoverDotColor : styles.dotColor;\n const ring = styles.focusRing;\n const size = (styles.size as number) ?? 20;\n\n const circle = (\n <span\n aria-hidden=\"true\"\n style={{\n width: size,\n height: size,\n flexShrink: 0,\n borderRadius: '50%',\n border: `2px solid ${border as string}`,\n backgroundColor: bg as string,\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n transition: 'border-color 0.15s, background-color 0.15s',\n ...(isFocus && !isCard\n ? { outline: `2px solid ${ring as string}`, outlineOffset: 2 }\n : {}),\n ...(isCard\n ? { position: 'absolute' as const, top: styles.cardPadding as number, right: styles.cardPadding as number }\n : {}),\n }}\n >\n {selected && (\n <span\n style={{\n width: size * 0.5,\n height: size * 0.5,\n borderRadius: '50%',\n backgroundColor: dot as string,\n }}\n />\n )}\n </span>\n );\n\n if (isCard) {\n // Mirrors `.uxm-radio--card`: the tile carries the frame and the focus\n // ring, and the circle is either gone or pinned to the corner.\n return (\n <div\n style={{\n position: 'relative',\n display: 'flex',\n flexDirection: 'column',\n gap: styles.cardGap as number,\n minWidth: 150,\n boxSizing: 'border-box',\n padding: styles.cardPadding as number,\n borderRadius: styles.cardRadius as number,\n border: `${styles.cardBorderWidth}px solid ${border as string}`,\n backgroundColor: styles.cardBg as string,\n fontSize: 14,\n color: 'var(--color-text)',\n opacity: isDisabled ? ((styles.disabledOpacity as number) ?? 0.4) : 1,\n cursor: isDisabled ? 'not-allowed' : 'pointer',\n transition: 'border-color 0.15s, background-color 0.15s',\n ...(isFocus ? { outline: `2px solid ${ring as string}`, outlineOffset: 2 } : {}),\n }}\n >\n {indicator === 'corner' && circle}\n <strong>{label}</strong>\n <span style={{ color: 'var(--color-text-muted)', fontSize: 12 }}>\n What this option does\n </span>\n </div>\n );\n }\n\n return (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 14,\n color: 'var(--color-text)',\n opacity: isDisabled ? ((styles.disabledOpacity as number) ?? 0.4) : 1,\n cursor: isDisabled ? 'not-allowed' : 'pointer',\n }}\n >\n {circle}\n {label}\n </div>\n );\n}\n\nconst INTERACTIVE_OPTIONS = ['Small', 'Medium', 'Large'];\n\nexport function RadioGroupPreview({ styles, variants }: PreviewProps) {\n const state = (variants.state as string) ?? 'default';\n const direction = (variants.direction as 'vertical' | 'horizontal' | undefined) ?? 'vertical';\n const variant = (variants.variant as 'default' | 'card' | undefined) ?? 'default';\n const indicator = (variants.indicator as 'hidden' | 'corner' | undefined) ?? 'hidden';\n\n // Project every registry knob as a `--uxm-radio-group-*` custom property\n // on the wrapper. The interactive <RadioGroup> below renders the real\n // component whose styles.css rules read these vars — live editor edits\n // paint immediately, and production `:hover` / `:focus-visible` rules\n // exercise exactly as they would in prod.\n const cssVars: Record<string, string | undefined> = {\n '--uxm-radio-group-size': styles.size != null ? `${styles.size}px` : undefined,\n '--uxm-radio-group-gap': styles.gap != null ? `${styles.gap}px` : undefined,\n '--uxm-radio-group-unselected-bg': styles.unselectedBg as string | undefined,\n '--uxm-radio-group-unselected-border': styles.unselectedBorder as string | undefined,\n '--uxm-radio-group-selected-border': styles.selectedBorder as string | undefined,\n '--uxm-radio-group-dot-color': styles.dotColor as string | undefined,\n '--uxm-radio-group-hover-unselected-bg': styles.hoverUnselectedBg as string | undefined,\n '--uxm-radio-group-hover-unselected-border': styles.hoverUnselectedBorder as string | undefined,\n '--uxm-radio-group-hover-selected-border': styles.hoverSelectedBorder as string | undefined,\n '--uxm-radio-group-hover-dot-color': styles.hoverDotColor as string | undefined,\n '--uxm-radio-group-focus-ring': styles.focusRing as string | undefined,\n '--uxm-radio-group-disabled-opacity':\n styles.disabledOpacity != null ? String(styles.disabledOpacity) : undefined,\n '--uxm-radio-group-error-color': styles.errorColor as string | undefined,\n '--uxm-radio-group-error-message-size':\n styles.errorMessageSize != null ? `${styles.errorMessageSize}px` : undefined,\n '--uxm-radio-card-padding': `${styles.cardPadding}px`,\n '--uxm-radio-card-radius': `${styles.cardRadius}px`,\n '--uxm-radio-card-gap': `${styles.cardGap}px`,\n '--uxm-radio-card-border-width': `${styles.cardBorderWidth}px`,\n '--uxm-radio-card-bg': styles.cardBg as string,\n };\n\n const sectionLabel = {\n fontSize: 11,\n color: 'var(--color-text-muted)',\n marginBottom: 12,\n letterSpacing: 0.5,\n textTransform: 'uppercase' as const,\n };\n\n // Pair the state and direction into the interactive `key` so that\n // toggling either variant resets the radio group cleanly (matches the\n // freshly-{re,dis}abled UX and clears any stale selection when the user\n // flips layout).\n const interactiveKey = `${state}-${direction}`;\n const interactiveDisabled = state === 'disabled';\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 32, ...cssVars } as CSSProperties}>\n <div>\n <div style={sectionLabel}>{state} state</div>\n <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>\n <ShowcaseRow selected={false} state={state} styles={styles} label=\"Unselected\" variant={variant} indicator={indicator} />\n <ShowcaseRow selected={true} state={state} styles={styles} label=\"Selected\" variant={variant} indicator={indicator} />\n </div>\n </div>\n\n {/* Interactive instance — uncontrolled so the browser owns the\n checked state. React (and the canvas's event-capture re-renders)\n can't fight the DOM here, so click/keyboard navigation toggle\n reliably between options. Three options share a unique `name`\n so the browser's radio-group semantics work natively. */}\n <div style={{ borderTop: '1px solid var(--color-border)', paddingTop: 16 }}>\n <div style={sectionLabel}>Interactive</div>\n <RadioGroup\n key={interactiveKey}\n name={`uxm-radio-preview-${interactiveKey}`}\n direction={direction}\n variant={variant}\n indicator={indicator}\n error={state === 'error' ? 'Pick an option to continue' : undefined}\n >\n {INTERACTIVE_OPTIONS.map((opt) => (\n <RadioOption\n key={opt}\n name={`uxm-radio-preview-${interactiveKey}`}\n value={opt}\n disabled={interactiveDisabled}\n >\n {variant === 'card' ? (\n // Card mode is for rich content — a one-word label in a tile\n // would demo a box, not the shape the variant exists for.\n <>\n <strong>{opt}</strong>\n <span style={{ color: 'var(--color-text-muted)', fontSize: 12 }}>\n What this option does\n </span>\n </>\n ) : (\n opt\n )}\n </RadioOption>\n ))}\n </RadioGroup>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -33,6 +33,8 @@ function RadioGroup({
|
|
|
33
33
|
defaultValue,
|
|
34
34
|
onChange,
|
|
35
35
|
direction = "vertical",
|
|
36
|
+
variant = "default",
|
|
37
|
+
indicator = "hidden",
|
|
36
38
|
className,
|
|
37
39
|
children,
|
|
38
40
|
error,
|
|
@@ -56,13 +58,20 @@ function RadioGroup({
|
|
|
56
58
|
className: (0, import_helpers.cn)(
|
|
57
59
|
"uxm-radio-group",
|
|
58
60
|
`uxm-radio-group--${direction}`,
|
|
61
|
+
variant === "card" && "uxm-radio-group--card",
|
|
59
62
|
error && "uxm-radio-group--error",
|
|
60
63
|
className
|
|
61
64
|
),
|
|
62
65
|
id,
|
|
63
66
|
"aria-invalid": error ? true : void 0,
|
|
64
67
|
"aria-describedby": (0, import_helpers.mergeDescribedBy)(describedBy, error ? errorId : void 0),
|
|
65
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
68
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
69
|
+
RadioGroupContext.Provider,
|
|
70
|
+
{
|
|
71
|
+
value: { name, value, hasValue, onChange: handleChange, variant, indicator },
|
|
72
|
+
children
|
|
73
|
+
}
|
|
74
|
+
)
|
|
66
75
|
}
|
|
67
76
|
),
|
|
68
77
|
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_field_error.FieldError, { id: errorId, className: "uxm-radio-group__error-message", children: error })
|
|
@@ -86,23 +95,42 @@ function RadioOption({
|
|
|
86
95
|
onChange?.(value, e);
|
|
87
96
|
group?.onChange?.(value, e);
|
|
88
97
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
98
|
+
const isCard = group?.variant === "card";
|
|
99
|
+
const indicator = group?.indicator ?? "hidden";
|
|
100
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
101
|
+
"label",
|
|
102
|
+
{
|
|
103
|
+
className: (0, import_helpers.cn)(
|
|
104
|
+
"uxm-radio",
|
|
105
|
+
isCard && "uxm-radio--card",
|
|
106
|
+
isCard && indicator === "corner" && "uxm-radio--indicator-corner",
|
|
107
|
+
disabled && "uxm-radio--disabled",
|
|
108
|
+
className
|
|
109
|
+
),
|
|
110
|
+
children: [
|
|
111
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
112
|
+
"input",
|
|
113
|
+
{
|
|
114
|
+
type: "radio",
|
|
115
|
+
className: "uxm-radio__input",
|
|
116
|
+
name: resolvedName,
|
|
117
|
+
value,
|
|
118
|
+
checked: resolvedChecked,
|
|
119
|
+
defaultChecked,
|
|
120
|
+
disabled,
|
|
121
|
+
onChange: handleChange
|
|
122
|
+
}
|
|
123
|
+
),
|
|
124
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uxm-radio__circle", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uxm-radio__dot" }) }),
|
|
125
|
+
isCard ? (
|
|
126
|
+
// The tile itself. A general-sibling selector off the input paints its
|
|
127
|
+
// selected frame, so it works for a controlled option and an
|
|
128
|
+
// uncontrolled one alike — the DOM, not React, knows which is checked.
|
|
129
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uxm-radio__card", children })
|
|
130
|
+
) : children && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uxm-radio__label", children })
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
);
|
|
106
134
|
}
|
|
107
135
|
// Annotate the CommonJS export names for ESM import in node:
|
|
108
136
|
0 && (module.exports = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/radio-group/radio-group.tsx"],"sourcesContent":["import { createContext, useContext, useId, useState } from 'react';\n\nimport { cn, mergeDescribedBy } from '@/helpers';\nimport { FieldError } from '@/ui/field-error';\n\nimport type { ChangeEvent, ReactNode } from 'react';\n\nexport type RadioGroupDirection = 'vertical' | 'horizontal';\n\nexport interface RadioGroupProps {\n name: string;\n value?: string;\n defaultValue?: string;\n onChange?: (value: string, e: ChangeEvent<HTMLInputElement>) => void;\n direction?: RadioGroupDirection;\n className?: string;\n children: ReactNode;\n /**\n * When set to a non-empty string, marks the group invalid: `aria-invalid`\n * lands on the group and the message renders below in the error color. Per\n * the input family's small-control convention the circles AND the option\n * labels all stay neutral — the message is the sole signal. The\n * `.uxm-radio-group--error` class still rides on the root as a state hook.\n * Omit (or pass an empty string) for normal.\n */\n error?: string;\n /**\n * Forwarded to the `role=\"radiogroup\"` root. `FormField` injects both — `id` for its\n * label association, `aria-describedby` for the hint — so wiring these\n * makes the atom hint-associable; the describedby is merged with the\n * atom's own error-message id, consumer ids first.\n */\n id?: string;\n 'aria-describedby'?: string;\n}\n\n// Carries the group-level `name` / selected `value` / change handler down to\n// each `RadioOption` without forcing every consumer to repeat `name` and wire\n// `checked`/`onChange` by hand on every option. `RadioOption` still accepts\n// its own `name` / `checked` / `defaultChecked` / `onChange` so existing call\n// sites that set those directly (bypassing the group) keep working unchanged.\n//\n// `hasValue` is fixed for the lifetime of the group (derived from whether\n// `value`/`defaultValue` was passed at all, not from the current value) so a\n// `RadioOption` that defers to the group never flips between an\n// uncontrolled (`checked={undefined}`) and controlled (`checked={boolean}`)\n// input across renders — that flip is what triggers React's \"changing an\n// uncontrolled input to be controlled\" warning.\ninterface RadioGroupContextValue {\n name?: string;\n value?: string;\n hasValue: boolean;\n onChange: (value: string, e: ChangeEvent<HTMLInputElement>) => void;\n}\n\nconst RadioGroupContext = createContext<RadioGroupContextValue | null>(null);\n\nexport function RadioGroup({\n name,\n value: controlledValue,\n defaultValue,\n onChange,\n direction = 'vertical',\n className,\n children,\n error,\n id,\n 'aria-describedby': describedBy,\n}: RadioGroupProps) {\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const isControlled = controlledValue !== undefined;\n const value = isControlled ? controlledValue : uncontrolledValue;\n const hasValue = controlledValue !== undefined || defaultValue !== undefined;\n const errorId = useId();\n\n function handleChange(nextValue: string, e: ChangeEvent<HTMLInputElement>) {\n if (!isControlled) setUncontrolledValue(nextValue);\n onChange?.(nextValue, e);\n }\n\n return (\n <>\n <div\n role=\"radiogroup\"\n className={cn(\n 'uxm-radio-group',\n `uxm-radio-group--${direction}`,\n error && 'uxm-radio-group--error',\n className,\n )}\n id={id}\n aria-invalid={error ? true : undefined}\n aria-describedby={mergeDescribedBy(describedBy, error ? errorId : undefined)}\n >\n <RadioGroupContext.Provider value={{ name, value, hasValue, onChange: handleChange }}>\n {children}\n </RadioGroupContext.Provider>\n </div>\n {error && (\n <FieldError id={errorId} className=\"uxm-radio-group__error-message\">\n {error}\n </FieldError>\n )}\n </>\n );\n}\n// Static marker so FormField only forwards its `error` prop into children that\n// accept one (avoids React unknown-prop warnings on non-input children).\nRadioGroup.hasError = true;\n\nexport interface RadioOptionProps {\n name?: string;\n value: string;\n checked?: boolean;\n defaultChecked?: boolean;\n disabled?: boolean;\n onChange?: (value: string, e: ChangeEvent<HTMLInputElement>) => void;\n children?: ReactNode;\n className?: string;\n}\n\nexport function RadioOption({\n name,\n value,\n checked,\n defaultChecked,\n disabled,\n onChange,\n children,\n className,\n}: RadioOptionProps) {\n const group = useContext(RadioGroupContext);\n const resolvedName = name ?? group?.name;\n // Only defer to the group's selected value when this option doesn't\n // already manage its own checked state (explicit `checked`/`defaultChecked`\n // always wins, matching the controlled/uncontrolled convention) AND the\n // group was actually given a `value`/`defaultValue` to manage — otherwise\n // `checked` stays `undefined` so the input remains a plain native\n // uncontrolled radio (as it always has been for consumers who only use\n // `RadioGroup` for the shared `name` + layout, not selection state).\n const resolvedChecked =\n checked !== undefined\n ? checked\n : defaultChecked !== undefined || !group?.hasValue\n ? undefined\n : group.value === value;\n\n function handleChange(e: ChangeEvent<HTMLInputElement>) {\n onChange?.(value, e);\n group?.onChange?.(value, e);\n }\n\n return (\n <label className={cn('uxm-radio', disabled && 'uxm-radio--disabled', className)}>\n <input\n type=\"radio\"\n className=\"uxm-radio__input\"\n name={resolvedName}\n value={value}\n checked={resolvedChecked}\n defaultChecked={defaultChecked}\n disabled={disabled}\n onChange={handleChange}\n />\n <span className=\"uxm-radio__circle\" aria-hidden=\"true\">\n <span className=\"uxm-radio__dot\" />\n </span>\n {children && <span className=\"uxm-radio__label\">{children}</span>}\n </label>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiFI;AAjFJ,mBAA2D;AAE3D,qBAAqC;AACrC,yBAA2B;AAoD3B,MAAM,wBAAoB,4BAA6C,IAAI;AAEpE,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,oBAAoB;AACtB,GAAoB;AAClB,QAAM,CAAC,mBAAmB,oBAAoB,QAAI,uBAAS,YAAY;AACvE,QAAM,eAAe,oBAAoB;AACzC,QAAM,QAAQ,eAAe,kBAAkB;AAC/C,QAAM,WAAW,oBAAoB,UAAa,iBAAiB;AACnE,QAAM,cAAU,oBAAM;AAEtB,WAAS,aAAa,WAAmB,GAAkC;AACzE,QAAI,CAAC,aAAc,sBAAqB,SAAS;AACjD,eAAW,WAAW,CAAC;AAAA,EACzB;AAEA,SACE,4EACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,eAAW;AAAA,UACT;AAAA,UACA,oBAAoB,SAAS;AAAA,UAC7B,SAAS;AAAA,UACT;AAAA,QACF;AAAA,QACA;AAAA,QACA,gBAAc,QAAQ,OAAO;AAAA,QAC7B,wBAAkB,iCAAiB,aAAa,QAAQ,UAAU,MAAS;AAAA,QAE3E,sDAAC,kBAAkB,UAAlB,EAA2B,OAAO,EAAE,MAAM,OAAO,UAAU,UAAU,aAAa,GAChF,UACH;AAAA;AAAA,IACF;AAAA,IACC,SACC,4CAAC,iCAAW,IAAI,SAAS,WAAU,kCAChC,iBACH;AAAA,KAEJ;AAEJ;AAGA,WAAW,WAAW;AAaf,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,YAAQ,yBAAW,iBAAiB;AAC1C,QAAM,eAAe,QAAQ,OAAO;AAQpC,QAAM,kBACJ,YAAY,SACR,UACA,mBAAmB,UAAa,CAAC,OAAO,WACtC,SACA,MAAM,UAAU;AAExB,WAAS,aAAa,GAAkC;AACtD,eAAW,OAAO,CAAC;AACnB,WAAO,WAAW,OAAO,CAAC;AAAA,EAC5B;AAEA,SACE,6CAAC,WAAM,eAAW,mBAAG,aAAa,YAAY,uBAAuB,SAAS,GAC5E;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,QACV,MAAM;AAAA,QACN;AAAA,QACA,SAAS;AAAA,QACT;AAAA,QACA;AAAA,QACA,UAAU;AAAA;AAAA,IACZ;AAAA,IACA,4CAAC,UAAK,WAAU,qBAAoB,eAAY,QAC9C,sDAAC,UAAK,WAAU,kBAAiB,GACnC;AAAA,IACC,YAAY,4CAAC,UAAK,WAAU,oBAAoB,UAAS;AAAA,KAC5D;AAEJ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/radio-group/radio-group.tsx"],"sourcesContent":["import { createContext, useContext, useId, useState } from 'react';\n\nimport { cn, mergeDescribedBy } from '@/helpers';\nimport { FieldError } from '@/ui/field-error';\n\nimport type { ChangeEvent, ReactNode } from 'react';\n\nexport type RadioGroupDirection = 'vertical' | 'horizontal';\n\n/** Option presentation. See `RadioGroupProps.variant`. */\nexport type RadioGroupVariant = 'default' | 'card';\n\n/** Where the radio dot goes in card mode. See `RadioGroupProps.indicator`. */\nexport type RadioGroupIndicator = 'hidden' | 'corner';\n\nexport interface RadioGroupProps {\n name: string;\n value?: string;\n defaultValue?: string;\n onChange?: (value: string, e: ChangeEvent<HTMLInputElement>) => void;\n direction?: RadioGroupDirection;\n /**\n * Option presentation. `default` (unchanged) renders each option as a\n * `[circle] label` row. `card` renders each one as a selectable tile: the\n * option's `children` fill the card body and the selected state is an accent\n * frame around the whole tile — for a visual single-select (a layout picker,\n * plan tiers, theme swatches).\n *\n * The control stays a native radio group either way, so `role=\"radiogroup\"`,\n * `aria-checked` and arrow-key selection are unaffected by this prop.\n */\n variant?: RadioGroupVariant;\n /**\n * Card mode only. `hidden` (default) drops the radio circle — the frame is\n * the affordance. `corner` keeps it, pinned to the tile's top-right, for\n * redundancy where a frame alone is too subtle.\n *\n * Ignored when `variant` is `default`.\n */\n indicator?: RadioGroupIndicator;\n className?: string;\n children: ReactNode;\n /**\n * When set to a non-empty string, marks the group invalid: `aria-invalid`\n * lands on the group and the message renders below in the error color. Per\n * the input family's small-control convention the circles AND the option\n * labels all stay neutral — the message is the sole signal. The\n * `.uxm-radio-group--error` class still rides on the root as a state hook.\n * Omit (or pass an empty string) for normal.\n */\n error?: string;\n /**\n * Forwarded to the `role=\"radiogroup\"` root. `FormField` injects both — `id` for its\n * label association, `aria-describedby` for the hint — so wiring these\n * makes the atom hint-associable; the describedby is merged with the\n * atom's own error-message id, consumer ids first.\n */\n id?: string;\n 'aria-describedby'?: string;\n}\n\n// Carries the group-level `name` / selected `value` / change handler down to\n// each `RadioOption` without forcing every consumer to repeat `name` and wire\n// `checked`/`onChange` by hand on every option. `RadioOption` still accepts\n// its own `name` / `checked` / `defaultChecked` / `onChange` so existing call\n// sites that set those directly (bypassing the group) keep working unchanged.\n//\n// `hasValue` is fixed for the lifetime of the group (derived from whether\n// `value`/`defaultValue` was passed at all, not from the current value) so a\n// `RadioOption` that defers to the group never flips between an\n// uncontrolled (`checked={undefined}`) and controlled (`checked={boolean}`)\n// input across renders — that flip is what triggers React's \"changing an\n// uncontrolled input to be controlled\" warning.\ninterface RadioGroupContextValue {\n name?: string;\n value?: string;\n hasValue: boolean;\n onChange: (value: string, e: ChangeEvent<HTMLInputElement>) => void;\n // Presentation travels down the same channel as `name`/`value` so an option\n // never has to be told twice what group it is in.\n variant: RadioGroupVariant;\n indicator: RadioGroupIndicator;\n}\n\nconst RadioGroupContext = createContext<RadioGroupContextValue | null>(null);\n\nexport function RadioGroup({\n name,\n value: controlledValue,\n defaultValue,\n onChange,\n direction = 'vertical',\n variant = 'default',\n indicator = 'hidden',\n className,\n children,\n error,\n id,\n 'aria-describedby': describedBy,\n}: RadioGroupProps) {\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const isControlled = controlledValue !== undefined;\n const value = isControlled ? controlledValue : uncontrolledValue;\n const hasValue = controlledValue !== undefined || defaultValue !== undefined;\n const errorId = useId();\n\n function handleChange(nextValue: string, e: ChangeEvent<HTMLInputElement>) {\n if (!isControlled) setUncontrolledValue(nextValue);\n onChange?.(nextValue, e);\n }\n\n return (\n <>\n <div\n role=\"radiogroup\"\n className={cn(\n 'uxm-radio-group',\n `uxm-radio-group--${direction}`,\n variant === 'card' && 'uxm-radio-group--card',\n error && 'uxm-radio-group--error',\n className,\n )}\n id={id}\n aria-invalid={error ? true : undefined}\n aria-describedby={mergeDescribedBy(describedBy, error ? errorId : undefined)}\n >\n <RadioGroupContext.Provider\n value={{ name, value, hasValue, onChange: handleChange, variant, indicator }}\n >\n {children}\n </RadioGroupContext.Provider>\n </div>\n {error && (\n <FieldError id={errorId} className=\"uxm-radio-group__error-message\">\n {error}\n </FieldError>\n )}\n </>\n );\n}\n// Static marker so FormField only forwards its `error` prop into children that\n// accept one (avoids React unknown-prop warnings on non-input children).\nRadioGroup.hasError = true;\n\nexport interface RadioOptionProps {\n name?: string;\n value: string;\n checked?: boolean;\n defaultChecked?: boolean;\n disabled?: boolean;\n onChange?: (value: string, e: ChangeEvent<HTMLInputElement>) => void;\n children?: ReactNode;\n className?: string;\n}\n\nexport function RadioOption({\n name,\n value,\n checked,\n defaultChecked,\n disabled,\n onChange,\n children,\n className,\n}: RadioOptionProps) {\n const group = useContext(RadioGroupContext);\n const resolvedName = name ?? group?.name;\n // Only defer to the group's selected value when this option doesn't\n // already manage its own checked state (explicit `checked`/`defaultChecked`\n // always wins, matching the controlled/uncontrolled convention) AND the\n // group was actually given a `value`/`defaultValue` to manage — otherwise\n // `checked` stays `undefined` so the input remains a plain native\n // uncontrolled radio (as it always has been for consumers who only use\n // `RadioGroup` for the shared `name` + layout, not selection state).\n const resolvedChecked =\n checked !== undefined\n ? checked\n : defaultChecked !== undefined || !group?.hasValue\n ? undefined\n : group.value === value;\n\n function handleChange(e: ChangeEvent<HTMLInputElement>) {\n onChange?.(value, e);\n group?.onChange?.(value, e);\n }\n\n const isCard = group?.variant === 'card';\n const indicator = group?.indicator ?? 'hidden';\n\n return (\n <label\n className={cn(\n 'uxm-radio',\n isCard && 'uxm-radio--card',\n isCard && indicator === 'corner' && 'uxm-radio--indicator-corner',\n disabled && 'uxm-radio--disabled',\n className,\n )}\n >\n <input\n type=\"radio\"\n className=\"uxm-radio__input\"\n name={resolvedName}\n value={value}\n checked={resolvedChecked}\n defaultChecked={defaultChecked}\n disabled={disabled}\n onChange={handleChange}\n />\n {/* The circle stays a SIBLING of the input in both presentations, even\n in card mode where it is pinned to a corner by CSS. Nesting it inside\n the card body would have put it out of reach of every existing\n `__input:checked + __circle` / hover / focus rule, which would then\n need a parallel card-mode copy — two sets of state rules to keep in\n step. Card mode only moves it, or hides it. */}\n <span className=\"uxm-radio__circle\" aria-hidden=\"true\">\n <span className=\"uxm-radio__dot\" />\n </span>\n {isCard ? (\n // The tile itself. A general-sibling selector off the input paints its\n // selected frame, so it works for a controlled option and an\n // uncontrolled one alike — the DOM, not React, knows which is checked.\n <span className=\"uxm-radio__card\">{children}</span>\n ) : (\n children && <span className=\"uxm-radio__label\">{children}</span>\n )}\n </label>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgHI;AAhHJ,mBAA2D;AAE3D,qBAAqC;AACrC,yBAA2B;AAiF3B,MAAM,wBAAoB,4BAA6C,IAAI;AAEpE,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,oBAAoB;AACtB,GAAoB;AAClB,QAAM,CAAC,mBAAmB,oBAAoB,QAAI,uBAAS,YAAY;AACvE,QAAM,eAAe,oBAAoB;AACzC,QAAM,QAAQ,eAAe,kBAAkB;AAC/C,QAAM,WAAW,oBAAoB,UAAa,iBAAiB;AACnE,QAAM,cAAU,oBAAM;AAEtB,WAAS,aAAa,WAAmB,GAAkC;AACzE,QAAI,CAAC,aAAc,sBAAqB,SAAS;AACjD,eAAW,WAAW,CAAC;AAAA,EACzB;AAEA,SACE,4EACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,eAAW;AAAA,UACT;AAAA,UACA,oBAAoB,SAAS;AAAA,UAC7B,YAAY,UAAU;AAAA,UACtB,SAAS;AAAA,UACT;AAAA,QACF;AAAA,QACA;AAAA,QACA,gBAAc,QAAQ,OAAO;AAAA,QAC7B,wBAAkB,iCAAiB,aAAa,QAAQ,UAAU,MAAS;AAAA,QAE3E;AAAA,UAAC,kBAAkB;AAAA,UAAlB;AAAA,YACC,OAAO,EAAE,MAAM,OAAO,UAAU,UAAU,cAAc,SAAS,UAAU;AAAA,YAE1E;AAAA;AAAA,QACH;AAAA;AAAA,IACF;AAAA,IACC,SACC,4CAAC,iCAAW,IAAI,SAAS,WAAU,kCAChC,iBACH;AAAA,KAEJ;AAEJ;AAGA,WAAW,WAAW;AAaf,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,YAAQ,yBAAW,iBAAiB;AAC1C,QAAM,eAAe,QAAQ,OAAO;AAQpC,QAAM,kBACJ,YAAY,SACR,UACA,mBAAmB,UAAa,CAAC,OAAO,WACtC,SACA,MAAM,UAAU;AAExB,WAAS,aAAa,GAAkC;AACtD,eAAW,OAAO,CAAC;AACnB,WAAO,WAAW,OAAO,CAAC;AAAA,EAC5B;AAEA,QAAM,SAAS,OAAO,YAAY;AAClC,QAAM,YAAY,OAAO,aAAa;AAEtC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAW;AAAA,QACT;AAAA,QACA,UAAU;AAAA,QACV,UAAU,cAAc,YAAY;AAAA,QACpC,YAAY;AAAA,QACZ;AAAA,MACF;AAAA,MAEA;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAU;AAAA,YACV,MAAM;AAAA,YACN;AAAA,YACA,SAAS;AAAA,YACT;AAAA,YACA;AAAA,YACA,UAAU;AAAA;AAAA,QACZ;AAAA,QAOA,4CAAC,UAAK,WAAU,qBAAoB,eAAY,QAC9C,sDAAC,UAAK,WAAU,kBAAiB,GACnC;AAAA,QACC;AAAA;AAAA;AAAA;AAAA,UAIC,4CAAC,UAAK,WAAU,mBAAmB,UAAS;AAAA,YAE5C,YAAY,4CAAC,UAAK,WAAU,oBAAoB,UAAS;AAAA;AAAA;AAAA,EAE7D;AAEJ;","names":[]}
|
|
@@ -100,4 +100,86 @@
|
|
|
100
100
|
margin-top: 4px;
|
|
101
101
|
font-size: calc(var(--uxm-radio-group-error-message-size, 12px) * var(--type-scale, 1));
|
|
102
102
|
color: var(--uxm-radio-group-error-color, var(--color-danger-text));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* ── Card variant ───────────────────────────────────────────────────────────
|
|
106
|
+
A selectable tile instead of a `[circle] label` row: the option's children
|
|
107
|
+
fill the card body and the selected state is a frame around the whole tile.
|
|
108
|
+
The control underneath is untouched — still a native radio in a label, so
|
|
109
|
+
arrow-key selection, `aria-checked` and the focus behaviour are the same
|
|
110
|
+
ones the default presentation gets.
|
|
111
|
+
|
|
112
|
+
Every colour here resolves through a var that the default presentation
|
|
113
|
+
already exposes (`--uxm-radio-group-selected-border`, `-focus-ring`), so a
|
|
114
|
+
theme that re-tints the circle re-tints the frame with it and there is no
|
|
115
|
+
second accent to keep in step. The card-specific knobs are geometry only. */
|
|
116
|
+
.uxm-radio--card {
|
|
117
|
+
/* Containing block for the corner indicator. */
|
|
118
|
+
position: relative;
|
|
119
|
+
/* The label is now a frame around one block, not a row of two. */
|
|
120
|
+
align-items: stretch;
|
|
121
|
+
display: inline-flex;
|
|
122
|
+
gap: 0;
|
|
123
|
+
}
|
|
124
|
+
.uxm-radio--card .uxm-radio__card {
|
|
125
|
+
background-color: var(--uxm-radio-card-bg, var(--color-card));
|
|
126
|
+
border: var(--uxm-radio-card-border-width, 1px) solid var(--uxm-radio-group-unselected-border, var(--uxm-radio-group-border-color, var(--color-border)));
|
|
127
|
+
border-radius: var(--uxm-radio-card-radius, 8px);
|
|
128
|
+
box-sizing: border-box;
|
|
129
|
+
display: flex;
|
|
130
|
+
flex: 1;
|
|
131
|
+
flex-direction: column;
|
|
132
|
+
gap: var(--uxm-radio-card-gap, 4px);
|
|
133
|
+
padding: var(--uxm-radio-card-padding, 12px);
|
|
134
|
+
transition: background-color 0.15s, border-color 0.15s;
|
|
135
|
+
width: 100%;
|
|
136
|
+
}
|
|
137
|
+
.uxm-radio--card {
|
|
138
|
+
/* General sibling, not adjacent: the circle sits between the input and the
|
|
139
|
+
card in the DOM, and is absent from the flow when the indicator is
|
|
140
|
+
hidden. `~` is indifferent to both. */
|
|
141
|
+
}
|
|
142
|
+
.uxm-radio--card .uxm-radio__input:checked ~ .uxm-radio__card {
|
|
143
|
+
background-color: var(--uxm-radio-card-selected-bg, var(--uxm-radio-card-bg, var(--color-card)));
|
|
144
|
+
border-color: var(--uxm-radio-group-selected-border, var(--uxm-radio-group-active-color, var(--color-accent)));
|
|
145
|
+
}
|
|
146
|
+
.uxm-radio--card {
|
|
147
|
+
/* The frame carries focus in card mode. The default presentation rings the
|
|
148
|
+
circle, which here is hidden or shrunk into a corner — ringing it would
|
|
149
|
+
put the focus indicator on the smallest thing on screen instead of the
|
|
150
|
+
control the user is actually choosing. */
|
|
151
|
+
}
|
|
152
|
+
.uxm-radio--card .uxm-radio__input:focus-visible ~ .uxm-radio__card {
|
|
153
|
+
outline: 2px solid var(--uxm-radio-group-focus-ring, var(--color-accent));
|
|
154
|
+
outline-offset: 2px;
|
|
155
|
+
}
|
|
156
|
+
.uxm-radio--card:not(.uxm-radio--disabled):hover .uxm-radio__input:not(:checked) ~ .uxm-radio__card {
|
|
157
|
+
border-color: var(--uxm-radio-group-hover-unselected-border, var(--color-text-strong));
|
|
158
|
+
}
|
|
159
|
+
.uxm-radio--card:not(.uxm-radio--disabled):hover .uxm-radio__input:checked ~ .uxm-radio__card {
|
|
160
|
+
border-color: var(--uxm-radio-group-hover-selected-border, var(--color-accent-bold));
|
|
161
|
+
}
|
|
162
|
+
.uxm-radio--card {
|
|
163
|
+
/* Default indicator: the frame IS the affordance, so the circle goes. */
|
|
164
|
+
}
|
|
165
|
+
.uxm-radio--card .uxm-radio__circle {
|
|
166
|
+
display: none;
|
|
167
|
+
}
|
|
168
|
+
.uxm-radio--card {
|
|
169
|
+
/* …unless asked for, in which case it is pinned to the corner. Absolute, so
|
|
170
|
+
it never takes part in the card's own column layout. */
|
|
171
|
+
}
|
|
172
|
+
.uxm-radio--card.uxm-radio--indicator-corner .uxm-radio__circle {
|
|
173
|
+
display: inline-flex;
|
|
174
|
+
position: absolute;
|
|
175
|
+
right: var(--uxm-radio-card-padding, 12px);
|
|
176
|
+
top: var(--uxm-radio-card-padding, 12px);
|
|
177
|
+
z-index: 1;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Tiles wrap. A row of cards that cannot wrap either overflows its container
|
|
181
|
+
or squeezes every tile below its content — neither is what a picker wants,
|
|
182
|
+
and the default row presentation is unaffected. */
|
|
183
|
+
.uxm-radio-group--card.uxm-radio-group--horizontal {
|
|
184
|
+
flex-wrap: wrap;
|
|
103
185
|
}
|
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
import type { ChangeEvent, ReactNode } from 'react';
|
|
2
2
|
export type RadioGroupDirection = 'vertical' | 'horizontal';
|
|
3
|
+
/** Option presentation. See `RadioGroupProps.variant`. */
|
|
4
|
+
export type RadioGroupVariant = 'default' | 'card';
|
|
5
|
+
/** Where the radio dot goes in card mode. See `RadioGroupProps.indicator`. */
|
|
6
|
+
export type RadioGroupIndicator = 'hidden' | 'corner';
|
|
3
7
|
export interface RadioGroupProps {
|
|
4
8
|
name: string;
|
|
5
9
|
value?: string;
|
|
6
10
|
defaultValue?: string;
|
|
7
11
|
onChange?: (value: string, e: ChangeEvent<HTMLInputElement>) => void;
|
|
8
12
|
direction?: RadioGroupDirection;
|
|
13
|
+
/**
|
|
14
|
+
* Option presentation. `default` (unchanged) renders each option as a
|
|
15
|
+
* `[circle] label` row. `card` renders each one as a selectable tile: the
|
|
16
|
+
* option's `children` fill the card body and the selected state is an accent
|
|
17
|
+
* frame around the whole tile — for a visual single-select (a layout picker,
|
|
18
|
+
* plan tiers, theme swatches).
|
|
19
|
+
*
|
|
20
|
+
* The control stays a native radio group either way, so `role="radiogroup"`,
|
|
21
|
+
* `aria-checked` and arrow-key selection are unaffected by this prop.
|
|
22
|
+
*/
|
|
23
|
+
variant?: RadioGroupVariant;
|
|
24
|
+
/**
|
|
25
|
+
* Card mode only. `hidden` (default) drops the radio circle — the frame is
|
|
26
|
+
* the affordance. `corner` keeps it, pinned to the tile's top-right, for
|
|
27
|
+
* redundancy where a frame alone is too subtle.
|
|
28
|
+
*
|
|
29
|
+
* Ignored when `variant` is `default`.
|
|
30
|
+
*/
|
|
31
|
+
indicator?: RadioGroupIndicator;
|
|
9
32
|
className?: string;
|
|
10
33
|
children: ReactNode;
|
|
11
34
|
/**
|
|
@@ -26,7 +49,7 @@ export interface RadioGroupProps {
|
|
|
26
49
|
id?: string;
|
|
27
50
|
'aria-describedby'?: string;
|
|
28
51
|
}
|
|
29
|
-
export declare function RadioGroup({ name, value: controlledValue, defaultValue, onChange, direction, className, children, error, id, 'aria-describedby': describedBy, }: RadioGroupProps): import("react").JSX.Element;
|
|
52
|
+
export declare function RadioGroup({ name, value: controlledValue, defaultValue, onChange, direction, variant, indicator, className, children, error, id, 'aria-describedby': describedBy, }: RadioGroupProps): import("react").JSX.Element;
|
|
30
53
|
export declare namespace RadioGroup {
|
|
31
54
|
var hasError: boolean;
|
|
32
55
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio-group.d.ts","sourceRoot":"","sources":["../../../src/ui/radio-group/radio-group.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEpD,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,YAAY,CAAC;AAE5D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IACrE,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;
|
|
1
|
+
{"version":3,"file":"radio-group.d.ts","sourceRoot":"","sources":["../../../src/ui/radio-group/radio-group.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEpD,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,YAAY,CAAC;AAE5D,0DAA0D;AAC1D,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,MAAM,CAAC;AAEnD,8EAA8E;AAC9E,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IACrE,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AA2BD,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,KAAK,EAAE,eAAe,EACtB,YAAY,EACZ,QAAQ,EACR,SAAsB,EACtB,OAAmB,EACnB,SAAoB,EACpB,SAAS,EACT,QAAQ,EACR,KAAK,EACL,EAAE,EACF,kBAAkB,EAAE,WAAW,GAChC,EAAE,eAAe,+BAwCjB;yBArDe,UAAU;;;AA0D1B,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,KAAK,EACL,OAAO,EACP,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,SAAS,GACV,EAAE,gBAAgB,+BAgElB"}
|