@plexui/ui 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/es/components/Checkbox/Checkbox.js +2 -2
- package/dist/es/components/Checkbox/Checkbox.js.map +1 -1
- package/dist/es/components/Checkbox/Checkbox.module.css +58 -44
- package/dist/es/components/Field/Field.js +3 -2
- package/dist/es/components/Field/Field.js.map +1 -1
- package/dist/es/components/Field/Field.module.css +60 -20
- package/dist/es/components/FieldError/FieldError.module.css +0 -1
- package/dist/es/components/RadioGroup/RadioGroup.js +2 -2
- package/dist/es/components/RadioGroup/RadioGroup.js.map +1 -1
- package/dist/es/components/RadioGroup/RadioGroup.module.css +8 -0
- package/dist/es/styles/variables-components.css +7 -8
- package/dist/types/components/Checkbox/Checkbox.d.ts +13 -1
- package/dist/types/components/Field/Field.d.ts +8 -1
- package/dist/types/components/RadioGroup/RadioGroup.d.ts +8 -2
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
A modern React component library built on the same design foundations as OpenAI's ChatGPT UI. 35 production-ready components, 14 hooks, a three-layer design token system, and a unified 9-step size scale — all powered by Radix primitives and Tailwind CSS 4.
|
|
4
4
|
|
|
5
|
-
[Documentation](https://plexui.com) • [GitHub](https://github.com/plex-ui/docs)
|
|
5
|
+
[Documentation](https://plexui.com) • [GitHub](https://github.com/plex-ui/docs) • [Figma Kit](https://plexui.com/#pricing)
|
|
6
|
+
|
|
7
|
+
> **Figma Design System PRO** — 22,000+ variants, all on Figma Variables. [Get the kit →](https://plexui.com/#pricing)
|
|
6
8
|
|
|
7
9
|
---
|
|
8
10
|
|
|
@@ -12,6 +14,7 @@ A modern React component library built on the same design foundations as OpenAI'
|
|
|
12
14
|
- **9-step size scale** — all key controls (Button, Input, Select, SegmentedControl, etc.) share a unified `ControlSize` scale from `3xs` (22 px) to `3xl` (48 px). Most competitors offer only 3–4.
|
|
13
15
|
- **Three-layer design tokens** — primitive → semantic → component CSS custom properties with `light-dark()` theming, alpha transparency scale, and 4-level elevation system.
|
|
14
16
|
- **Radix + Tailwind 4** — accessible primitives under the hood, utility-first styling on top.
|
|
17
|
+
- **Built for AI code editors** — consistent naming, clear token system, and comprehensive props give Claude, Cursor, Codex & other AI tools the building blocks to generate professional UI.
|
|
15
18
|
|
|
16
19
|
---
|
|
17
20
|
|
|
@@ -4,10 +4,10 @@ import clsx from "clsx";
|
|
|
4
4
|
import { Checkbox as RadixCheckbox } from "radix-ui";
|
|
5
5
|
import { useId } from "react";
|
|
6
6
|
import s from "./Checkbox.module.css";
|
|
7
|
-
export const Checkbox = ({ className, label, id: propsId, disabled, orientation = "left", ...restProps }) => {
|
|
7
|
+
export const Checkbox = ({ className, label, id: propsId, disabled, orientation = "left", pill = false, variant = "solid", ...restProps }) => {
|
|
8
8
|
const reactId = useId();
|
|
9
9
|
const id = propsId ?? reactId;
|
|
10
|
-
return (_jsxs("div", { "data-disabled": disabled ? "" : undefined, "data-has-label": label ? "" : undefined, "data-orientation": orientation, className: clsx(className, s.Container), children: [_jsx(RadixCheckbox.Root, { className: s.Checkbox, id: id, disabled: disabled, ...restProps, children: _jsx(RadixCheckbox.Indicator, { className: s.CheckMark }) }), label && (_jsx("label", { htmlFor: id, className: s.Label, onMouseDown: (event) => {
|
|
10
|
+
return (_jsxs("div", { "data-disabled": disabled ? "" : undefined, "data-has-label": label ? "" : undefined, "data-orientation": orientation, className: clsx(className, s.Container), children: [_jsx(RadixCheckbox.Root, { className: s.Checkbox, id: id, disabled: disabled, "data-pill": pill ? "" : undefined, "data-variant": variant, ...restProps, children: _jsx(RadixCheckbox.Indicator, { className: s.CheckMark }) }), label && (_jsx("label", { htmlFor: id, className: s.Label, onMouseDown: (event) => {
|
|
11
11
|
if (!event.defaultPrevented && event.detail > 1)
|
|
12
12
|
event.preventDefault();
|
|
13
13
|
}, children: label }))] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.js","sourceRoot":"","sources":["../../../../src/components/Checkbox/Checkbox.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,QAAQ,IAAI,aAAa,EAAE,MAAM,UAAU,CAAA;AACpD,OAAO,EAA0C,KAAK,EAAE,MAAM,OAAO,CAAA;AACrE,OAAO,CAAC,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"Checkbox.js","sourceRoot":"","sources":["../../../../src/components/Checkbox/Checkbox.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,QAAQ,IAAI,aAAa,EAAE,MAAM,UAAU,CAAA;AACpD,OAAO,EAA0C,KAAK,EAAE,MAAM,OAAO,CAAA;AACrE,OAAO,CAAC,MAAM,uBAAuB,CAAA;AA+CrC,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EACvB,SAAS,EACT,KAAK,EACL,EAAE,EAAE,OAAO,EACX,QAAQ,EACR,WAAW,GAAG,MAAM,EACpB,IAAI,GAAG,KAAK,EACZ,OAAO,GAAG,OAAO,EACjB,GAAG,SAAS,EACE,EAAE,EAAE;IAClB,MAAM,OAAO,GAAG,KAAK,EAAE,CAAA;IACvB,MAAM,EAAE,GAAG,OAAO,IAAI,OAAO,CAAA;IAE7B,OAAO,CACL,gCACiB,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,oBACxB,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,sBACpB,WAAW,EAC7B,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,aAEvC,KAAC,aAAa,CAAC,IAAI,IACjB,SAAS,EAAE,CAAC,CAAC,QAAQ,EACrB,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,QAAQ,eACP,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,kBAClB,OAAO,KACjB,SAAS,YAEb,KAAC,aAAa,CAAC,SAAS,IAAC,SAAS,EAAE,CAAC,CAAC,SAAS,GAAI,GAChC,EACpB,KAAK,IAAI,CACR,gBACE,OAAO,EAAE,EAAE,EACX,SAAS,EAAE,CAAC,CAAC,KAAK,EAClB,WAAW,EAAE,CAAC,KAAK,EAAE,EAAE;oBACrB,IAAI,CAAC,KAAK,CAAC,gBAAgB,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;wBAAE,KAAK,CAAC,cAAc,EAAE,CAAA;gBACzE,CAAC,YAEA,KAAK,GACA,CACT,IACG,CACP,CAAA;AACH,CAAC,CAAA","sourcesContent":["\"use client\"\n\nimport clsx from \"clsx\"\nimport { Checkbox as RadixCheckbox } from \"radix-ui\"\nimport { type FocusEventHandler, type ReactNode, useId } from \"react\"\nimport s from \"./Checkbox.module.css\"\n\nexport type CheckboxProps = {\n /** The `id` of the checkbox. */\n id?: string\n /** The state of the checkbox when it is initially rendered. Use when you do not need to control its state. */\n defaultChecked?: boolean | \"indeterminate\"\n /** The controlled state of the checkbox. Must be used in conjunction with `onCheckedChange`. */\n checked?: boolean | \"indeterminate\"\n /** Optional accessible label rendered to the right of the checkbox. */\n label?: ReactNode\n /** Event handler called when the state of the checkbox changes. */\n onCheckedChange?: (nextState: boolean) => void\n /** Event handler called when the checkbox looses focus. */\n onBlur?: FocusEventHandler<HTMLButtonElement>\n /** Event handler called when the checkbox gains focus. */\n onFocus?: FocusEventHandler<HTMLButtonElement>\n /** When `true`, prevents the user from interacting with the checkbox. */\n disabled?: boolean\n /** When `true`, indicates that the user must check the checkbox before the owning form can be submitted. */\n required?: boolean\n /** The name of the checkbox. Submitted with its owning form as part of a name/value pair. */\n name?: string\n /** The value given as data when submitted with a `name`. */\n value?: string\n /** CSS classes applied to wrapper node */\n className?: string\n /**\n * The orientation of the checkbox relative to the label.\n *\n * @default \"left\"\n */\n orientation?: \"left\" | \"right\"\n /**\n * Determines if the checkbox should be a fully rounded pill shape.\n * @default false\n */\n pill?: boolean\n /**\n * Visual style variant for the checkbox indicator.\n * - `\"solid\"` — filled background when checked (default)\n * - `\"ghost\"` — no border or background, checkmark only\n * @default \"solid\"\n */\n variant?: \"solid\" | \"ghost\"\n}\n\nexport const Checkbox = ({\n className,\n label,\n id: propsId,\n disabled,\n orientation = \"left\",\n pill = false,\n variant = \"solid\",\n ...restProps\n}: CheckboxProps) => {\n const reactId = useId()\n const id = propsId ?? reactId\n\n return (\n <div\n data-disabled={disabled ? \"\" : undefined}\n data-has-label={label ? \"\" : undefined}\n data-orientation={orientation}\n className={clsx(className, s.Container)}\n >\n <RadixCheckbox.Root\n className={s.Checkbox}\n id={id}\n disabled={disabled}\n data-pill={pill ? \"\" : undefined}\n data-variant={variant}\n {...restProps}\n >\n <RadixCheckbox.Indicator className={s.CheckMark} />\n </RadixCheckbox.Root>\n {label && (\n <label\n htmlFor={id}\n className={s.Label}\n onMouseDown={(event) => {\n if (!event.defaultPrevented && event.detail > 1) event.preventDefault()\n }}\n >\n {label}\n </label>\n )}\n </div>\n )\n}\n"]}
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
flex-direction: row-reverse;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
.Container[data-disabled] {
|
|
15
|
+
opacity: 0.5;
|
|
16
|
+
cursor: not-allowed;
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
/* Radix pushes the inputs off screen in a weird way that
|
|
15
20
|
messes with error overlays */
|
|
16
21
|
.Container > input {
|
|
@@ -25,30 +30,20 @@
|
|
|
25
30
|
align-items: center;
|
|
26
31
|
justify-content: center;
|
|
27
32
|
flex-shrink: 0;
|
|
28
|
-
width:
|
|
29
|
-
max-width:
|
|
30
|
-
height:
|
|
33
|
+
width: var(--menu-checkbox-indicator-size);
|
|
34
|
+
max-width: var(--menu-checkbox-indicator-size);
|
|
35
|
+
height: var(--menu-checkbox-indicator-size);
|
|
31
36
|
padding: 0;
|
|
37
|
+
border: 1px solid var(--alpha-16);
|
|
32
38
|
border-radius: var(--radius-xs);
|
|
33
39
|
background-color: transparent;
|
|
34
40
|
cursor: pointer;
|
|
35
41
|
transition:
|
|
36
42
|
border-color 150ms ease,
|
|
37
43
|
background-color 150ms ease;
|
|
38
|
-
}.Checkbox, :where([data-theme="light"]) .Checkbox {
|
|
39
|
-
border: 1px solid var(--gray-200);
|
|
40
|
-
}:where([data-theme="dark"]) .Checkbox {
|
|
41
|
-
border: 1px solid var(--gray-500);
|
|
42
44
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
/* Adjust downward for label line-height - not using center to account for long labels than can wrap. */
|
|
46
|
-
top: 1px;
|
|
47
|
-
}
|
|
48
|
-
@media (hover: hover) and (pointer: fine) {.Checkbox:where(:not([data-disabled], [data-state="checked"])):hover, :where([data-theme="light"]) .Checkbox:where(:not([data-disabled], [data-state="checked"])):hover {
|
|
49
|
-
border-color: var(--gray-300);
|
|
50
|
-
}:where([data-theme="dark"]) .Checkbox:where(:not([data-disabled], [data-state="checked"])):hover {
|
|
51
|
-
border-color: var(--gray-600);
|
|
45
|
+
@media (hover: hover) and (pointer: fine) {.Checkbox:where(:not([data-disabled], [data-state="checked"])):hover {
|
|
46
|
+
border-color: var(--alpha-20);
|
|
52
47
|
}
|
|
53
48
|
}
|
|
54
49
|
|
|
@@ -67,29 +62,52 @@ border-color: var(--gray-600);
|
|
|
67
62
|
outline-offset: 2px;
|
|
68
63
|
}
|
|
69
64
|
|
|
65
|
+
/* =============================================
|
|
66
|
+
Invalid
|
|
67
|
+
============================================= */
|
|
68
|
+
.Checkbox[aria-invalid="true"] {
|
|
69
|
+
border-color: var(--input-border-color-invalid);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.Checkbox[aria-invalid="true"][data-state="indeterminate"],
|
|
73
|
+
.Checkbox[aria-invalid="true"][data-state="checked"] {
|
|
74
|
+
border-color: var(--input-border-color-invalid);
|
|
75
|
+
background-color: var(--input-border-color-invalid);
|
|
76
|
+
}
|
|
77
|
+
|
|
70
78
|
.Checkbox[data-disabled] {
|
|
71
79
|
cursor: not-allowed;
|
|
72
80
|
}
|
|
73
81
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
/* =============================================
|
|
83
|
+
Pill
|
|
84
|
+
============================================= */
|
|
85
|
+
.Checkbox[data-pill] {
|
|
86
|
+
border-radius: var(--radius-full);
|
|
87
|
+
}
|
|
78
88
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
/* =============================================
|
|
90
|
+
Ghost variant
|
|
91
|
+
============================================= */
|
|
92
|
+
.Checkbox[data-variant="ghost"] {
|
|
93
|
+
border-color: transparent;
|
|
94
|
+
background-color: transparent;
|
|
83
95
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
}
|
|
97
|
+
@media (hover: hover) and (pointer: fine) {.Checkbox[data-variant="ghost"]:where(:not([data-disabled], [data-state="checked"])):hover {
|
|
98
|
+
border-color: transparent;
|
|
99
|
+
}.Checkbox[data-variant="ghost"]:where(:not([data-disabled], [data-state="checked"])):hover, :where([data-theme="light"]) .Checkbox[data-variant="ghost"]:where(:not([data-disabled], [data-state="checked"])):hover {
|
|
100
|
+
background-color: var(--gray-50);
|
|
101
|
+
}:where([data-theme="dark"]) .Checkbox[data-variant="ghost"]:where(:not([data-disabled], [data-state="checked"])):hover {
|
|
102
|
+
background-color: var(--gray-800);
|
|
103
|
+
}
|
|
87
104
|
}
|
|
88
105
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
106
|
+
.Checkbox[data-variant="ghost"][data-state="indeterminate"],
|
|
107
|
+
.Checkbox[data-variant="ghost"][data-state="checked"] {
|
|
108
|
+
border-color: transparent;
|
|
109
|
+
background-color: transparent;
|
|
110
|
+
}.CheckMark {
|
|
93
111
|
position: absolute;
|
|
94
112
|
top: 0;
|
|
95
113
|
left: 0;
|
|
@@ -131,11 +149,12 @@ background-color: var(--gray-200);
|
|
|
131
149
|
transform: scale(0)
|
|
132
150
|
}
|
|
133
151
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
152
|
+
|
|
153
|
+
/* Ghost variant — checkmark uses text color instead of white */
|
|
154
|
+
[data-variant="ghost"] .CheckMark::before,
|
|
155
|
+
[data-variant="ghost"] .CheckMark::after {
|
|
156
|
+
background: var(--gray-900);
|
|
157
|
+
}
|
|
139
158
|
|
|
140
159
|
.CheckMark::before {
|
|
141
160
|
top: 0;
|
|
@@ -160,23 +179,18 @@ background-color: var(--gray-200);
|
|
|
160
179
|
transform-origin: 0 100%;
|
|
161
180
|
transition: transform 100ms ease 160ms;
|
|
162
181
|
}.Label {
|
|
163
|
-
display:
|
|
164
|
-
align-items: center;
|
|
182
|
+
display: block;
|
|
165
183
|
min-height: 20px;
|
|
166
184
|
cursor: pointer;
|
|
167
185
|
font-size: 14px;
|
|
168
186
|
line-height: 20px;
|
|
169
187
|
}
|
|
170
188
|
|
|
171
|
-
[data-disabled] .Label {
|
|
172
|
-
cursor: not-allowed;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
189
|
[data-orientation="left"] .Label {
|
|
176
|
-
padding-left:
|
|
190
|
+
padding-left: calc(var(--spacing) * 2);
|
|
177
191
|
}
|
|
178
192
|
|
|
179
193
|
[data-orientation="right"] .Label {
|
|
180
|
-
padding-right:
|
|
194
|
+
padding-right: calc(var(--spacing) * 3);
|
|
181
195
|
}
|
|
182
196
|
}
|
|
@@ -4,7 +4,7 @@ import clsx from "clsx";
|
|
|
4
4
|
import { cloneElement, isValidElement, useId } from "react";
|
|
5
5
|
import { FieldError } from "../FieldError";
|
|
6
6
|
import s from "./Field.module.css";
|
|
7
|
-
export function Field({ label, description, errorMessage, size = "md", required = false, orientation = "vertical", id: idProp, className, children, }) {
|
|
7
|
+
export function Field({ label, description, errorMessage, size = "md", required = false, orientation = "vertical", opticallyAlign, id: idProp, className, children, }) {
|
|
8
8
|
const generatedId = useId();
|
|
9
9
|
const fieldId = idProp || `field-${generatedId}`;
|
|
10
10
|
const descriptionId = description ? `${fieldId}-description` : undefined;
|
|
@@ -17,6 +17,7 @@ export function Field({ label, description, errorMessage, size = "md", required
|
|
|
17
17
|
id: fieldId,
|
|
18
18
|
"aria-describedby": ariaDescribedBy,
|
|
19
19
|
"aria-invalid": invalid || undefined,
|
|
20
|
+
...(opticallyAlign && { opticallyAlign }),
|
|
20
21
|
};
|
|
21
22
|
// Resolve the children
|
|
22
23
|
let resolvedChildren;
|
|
@@ -29,6 +30,6 @@ export function Field({ label, description, errorMessage, size = "md", required
|
|
|
29
30
|
else {
|
|
30
31
|
resolvedChildren = children;
|
|
31
32
|
}
|
|
32
|
-
return (_jsxs("div", { className: clsx(s.Field, className), "data-size": size, "data-orientation": orientation, children: [_jsxs("label", { className: s.Label, htmlFor: fieldId, children: [label, required && (_jsx("span", { className: s.RequiredIndicator, "aria-hidden": "true", children: "*" }))] }), description && (_jsx("div", { className: s.Description, id: descriptionId, children: description })), _jsx("div", { className: s.Control, children: resolvedChildren }), errorMessage && (_jsx(FieldError, { id: errorId, children: errorMessage }))] }));
|
|
33
|
+
return (_jsxs("div", { className: clsx(s.Field, className), "data-size": size, "data-orientation": orientation, "data-optically-align": opticallyAlign, children: [_jsxs("div", { className: s.LabelGroup, children: [_jsxs("label", { className: s.Label, htmlFor: fieldId, children: [label, required && (_jsx("span", { className: s.RequiredIndicator, "aria-hidden": "true", children: "*" }))] }), description && (_jsx("div", { className: s.Description, id: descriptionId, children: description }))] }), _jsxs("div", { className: s.Content, children: [_jsx("div", { className: s.Control, children: resolvedChildren }), errorMessage && (_jsx(FieldError, { id: errorId, children: errorMessage }))] })] }));
|
|
33
34
|
}
|
|
34
35
|
//# sourceMappingURL=Field.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Field.js","sourceRoot":"","sources":["../../../../src/components/Field/Field.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,OAAO,CAAA;AAG3D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAE1C,OAAO,CAAC,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"Field.js","sourceRoot":"","sources":["../../../../src/components/Field/Field.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,OAAO,CAAA;AAG3D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAE1C,OAAO,CAAC,MAAM,oBAAoB,CAAA;AAmElC,MAAM,UAAU,KAAK,CAAC,EACpB,KAAK,EACL,WAAW,EACX,YAAY,EACZ,IAAI,GAAG,IAAI,EACX,QAAQ,GAAG,KAAK,EAChB,WAAW,GAAG,UAAU,EACxB,cAAc,EACd,EAAE,EAAE,MAAM,EACV,SAAS,EACT,QAAQ,GACG;IACX,MAAM,WAAW,GAAG,KAAK,EAAE,CAAA;IAC3B,MAAM,OAAO,GAAG,MAAM,IAAI,SAAS,WAAW,EAAE,CAAA;IAChD,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,cAAc,CAAC,CAAC,CAAC,SAAS,CAAA;IACxE,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;IAE7D,sDAAsD;IACtD,MAAM,eAAe,GACnB,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS,CAAA;IAEjE,MAAM,OAAO,GAAG,CAAC,CAAC,YAAY,CAAA;IAE9B,yCAAyC;IACzC,MAAM,eAAe,GAAoB;QACvC,EAAE,EAAE,OAAO;QACX,kBAAkB,EAAE,eAAe;QACnC,cAAc,EAAE,OAAO,IAAI,SAAS;QACpC,GAAG,CAAC,cAAc,IAAI,EAAE,cAAc,EAAE,CAAC;KAC1C,CAAA;IAED,uBAAuB;IACvB,IAAI,gBAAiC,CAAA;IACrC,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;QACnC,gBAAgB,GAAG,QAAQ,CAAC,eAAe,CAAC,CAAA;IAC9C,CAAC;SAAM,IAAI,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpC,gBAAgB,GAAG,YAAY,CAC7B,QAAuD,EACvD,eAAe,CAChB,CAAA;IACH,CAAC;SAAM,CAAC;QACN,gBAAgB,GAAG,QAAQ,CAAA;IAC7B,CAAC;IAED,OAAO,CACL,eACE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC,eACxB,IAAI,sBACG,WAAW,0BACP,cAAc,aAEpC,eAAK,SAAS,EAAE,CAAC,CAAC,UAAU,aAC1B,iBAAO,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,aACxC,KAAK,EACL,QAAQ,IAAI,CACX,eAAM,SAAS,EAAE,CAAC,CAAC,iBAAiB,iBAAc,MAAM,kBAEjD,CACR,IACK,EACP,WAAW,IAAI,CACd,cAAK,SAAS,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,EAAE,aAAa,YAC7C,WAAW,GACR,CACP,IACG,EACN,eAAK,SAAS,EAAE,CAAC,CAAC,OAAO,aACvB,cAAK,SAAS,EAAE,CAAC,CAAC,OAAO,YAAG,gBAAgB,GAAO,EAClD,YAAY,IAAI,CACf,KAAC,UAAU,IAAC,EAAE,EAAE,OAAO,YAAG,YAAY,GAAc,CACrD,IACG,IACF,CACP,CAAA;AACH,CAAC","sourcesContent":["\"use client\"\n\nimport clsx from \"clsx\"\nimport { cloneElement, isValidElement, useId } from \"react\"\n\nimport type { ControlSize } from \"../../types\"\nimport { FieldError } from \"../FieldError\"\n\nimport s from \"./Field.module.css\"\n\nexport type FieldChildProps = {\n id: string\n \"aria-describedby\"?: string\n \"aria-invalid\"?: boolean\n opticallyAlign?: \"start\" | \"end\"\n}\n\nexport type FieldProps = {\n /**\n * Label text for the field\n */\n label: React.ReactNode\n /**\n * Helper/description text displayed below the label.\n * Automatically linked via aria-describedby.\n */\n description?: React.ReactNode\n /**\n * Error message displayed below the control.\n * When provided, the child control receives aria-invalid=\"true\".\n * Uses the existing FieldError component internally.\n */\n errorMessage?: React.ReactNode\n /**\n * Controls the font size of the label to visually match the child control's size.\n * Matches the ControlSize scale used by Input, Select, etc.\n * @default \"md\"\n */\n size?: ControlSize\n /**\n * Display a required indicator (asterisk) after the label.\n * This is purely visual — it does not add the `required` HTML attribute.\n * @default false\n */\n required?: boolean\n /**\n * Layout direction of label and control.\n * - \"vertical\": label stacked above control (default)\n * - \"horizontal\": label beside control\n * @default \"vertical\"\n */\n orientation?: \"vertical\" | \"horizontal\"\n /**\n * Allows overriding the auto-generated `id`. When provided, this becomes\n * the id set on the child control and the `htmlFor` on the label.\n */\n id?: string\n /**\n * Applies a negative margin on the child control using its gutter to\n * optically align the control's text with surrounding content.\n * Passed through to the child control via cloneElement / render prop.\n */\n opticallyAlign?: \"start\" | \"end\"\n /**\n * CSS class applied to the root wrapper\n */\n className?: string\n /**\n * The form control(s) to render.\n * - If a single ReactElement, Field clones it with { id, aria-describedby, aria-invalid }.\n * - If a function (render prop), it is called with { id, \"aria-describedby\", \"aria-invalid\" }.\n */\n children: React.ReactElement | ((fieldProps: FieldChildProps) => React.ReactNode)\n}\n\nexport function Field({\n label,\n description,\n errorMessage,\n size = \"md\",\n required = false,\n orientation = \"vertical\",\n opticallyAlign,\n id: idProp,\n className,\n children,\n}: FieldProps) {\n const generatedId = useId()\n const fieldId = idProp || `field-${generatedId}`\n const descriptionId = description ? `${fieldId}-description` : undefined\n const errorId = errorMessage ? `${fieldId}-error` : undefined\n\n // Build aria-describedby from description + error IDs\n const ariaDescribedBy =\n [descriptionId, errorId].filter(Boolean).join(\" \") || undefined\n\n const invalid = !!errorMessage\n\n // Props to inject into the child control\n const fieldChildProps: FieldChildProps = {\n id: fieldId,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": invalid || undefined,\n ...(opticallyAlign && { opticallyAlign }),\n }\n\n // Resolve the children\n let resolvedChildren: React.ReactNode\n if (typeof children === \"function\") {\n resolvedChildren = children(fieldChildProps)\n } else if (isValidElement(children)) {\n resolvedChildren = cloneElement(\n children as React.ReactElement<Record<string, unknown>>,\n fieldChildProps,\n )\n } else {\n resolvedChildren = children\n }\n\n return (\n <div\n className={clsx(s.Field, className)}\n data-size={size}\n data-orientation={orientation}\n data-optically-align={opticallyAlign}\n >\n <div className={s.LabelGroup}>\n <label className={s.Label} htmlFor={fieldId}>\n {label}\n {required && (\n <span className={s.RequiredIndicator} aria-hidden=\"true\">\n *\n </span>\n )}\n </label>\n {description && (\n <div className={s.Description} id={descriptionId}>\n {description}\n </div>\n )}\n </div>\n <div className={s.Content}>\n <div className={s.Control}>{resolvedChildren}</div>\n {errorMessage && (\n <FieldError id={errorId}>{errorMessage}</FieldError>\n )}\n </div>\n </div>\n )\n}\n"]}
|
|
@@ -8,61 +8,105 @@
|
|
|
8
8
|
--field-error-margin-bottom: 0;
|
|
9
9
|
--field-error-padding-inline: 0;
|
|
10
10
|
}/* =============================================
|
|
11
|
-
|
|
11
|
+
LabelGroup (label + description)
|
|
12
|
+
============================================= */.LabelGroup {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
gap: var(--field-label-description-gap, 0px);
|
|
16
|
+
}/* =============================================
|
|
17
|
+
Content (control + error)
|
|
18
|
+
============================================= */.Content {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
gap: var(--field-gap);
|
|
22
|
+
}/* =============================================
|
|
23
|
+
Horizontal orientation
|
|
12
24
|
============================================= */.Field:where([data-orientation="horizontal"]) {
|
|
13
25
|
flex-direction: row;
|
|
14
26
|
align-items: flex-start;
|
|
15
|
-
}.Field:where([data-orientation="horizontal"]) .
|
|
27
|
+
}.Field:where([data-orientation="horizontal"]) .LabelGroup {
|
|
16
28
|
flex-shrink: 0;
|
|
29
|
+
width: var(--field-horizontal-label-width);
|
|
17
30
|
padding-top: var(--field-label-horizontal-offset);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
min-width: 0;
|
|
31
|
+
}.Field:where([data-orientation="horizontal"]) .Content {
|
|
32
|
+
flex-shrink: 0;
|
|
33
|
+
width: var(--field-horizontal-control-width);
|
|
22
34
|
}/* =============================================
|
|
23
|
-
Sizes
|
|
35
|
+
Sizes — shared properties (grouped)
|
|
24
36
|
============================================= */.Field:where([data-size="3xs"]),
|
|
25
37
|
.Field:where([data-size="2xs"]) {
|
|
26
|
-
--field-gap: calc(var(--spacing) * 1
|
|
38
|
+
--field-gap: calc(var(--spacing) * 1);
|
|
39
|
+
--field-label-description-gap: 0px;
|
|
27
40
|
--field-label-font-size: var(--font-text-xs-size);
|
|
28
41
|
--field-label-line-height: var(--font-text-xs-line-height);
|
|
29
42
|
--field-description-font-size: var(--font-text-xs-size);
|
|
30
43
|
--field-description-line-height: var(--font-text-xs-line-height);
|
|
31
|
-
|
|
44
|
+
|
|
45
|
+
/* Scale error elements down for compact sizes */
|
|
46
|
+
--field-error-icon-size: 0.875rem; /* 14px */
|
|
47
|
+
--field-error-gap: calc(var(--spacing) * 1.5); /* 6px */
|
|
32
48
|
}.Field:where([data-size="xs"]),
|
|
33
49
|
.Field:where([data-size="sm"]) {
|
|
34
50
|
--field-gap: calc(var(--spacing) * 1.5);
|
|
51
|
+
--field-label-description-gap: 0px;
|
|
35
52
|
--field-label-font-size: var(--font-text-xs-size);
|
|
36
53
|
--field-label-line-height: var(--font-text-xs-line-height);
|
|
37
54
|
--field-description-font-size: var(--font-text-xs-size);
|
|
38
55
|
--field-description-line-height: var(--font-text-xs-line-height);
|
|
39
|
-
|
|
56
|
+
|
|
57
|
+
/* Scale error elements down for compact sizes */
|
|
58
|
+
--field-error-icon-size: 0.875rem; /* 14px */
|
|
59
|
+
--field-error-gap: calc(var(--spacing) * 1.5); /* 6px */
|
|
40
60
|
}.Field:where([data-size="md"]) {
|
|
41
61
|
--field-gap: calc(var(--spacing) * 2);
|
|
62
|
+
--field-label-description-gap: 2px;
|
|
42
63
|
--field-label-font-size: var(--font-text-sm-size);
|
|
43
64
|
--field-label-line-height: var(--font-text-sm-line-height);
|
|
44
65
|
--field-description-font-size: var(--font-text-xs-size);
|
|
45
66
|
--field-description-line-height: var(--font-text-xs-line-height);
|
|
46
|
-
--field-description-margin-top: calc(-1 * calc(var(--spacing) * 1));
|
|
47
|
-
--field-label-horizontal-offset: 0.5rem;
|
|
48
67
|
}.Field:where([data-size="lg"]),
|
|
49
68
|
.Field:where([data-size="xl"]) {
|
|
50
69
|
--field-gap: calc(var(--spacing) * 2);
|
|
70
|
+
--field-label-description-gap: 2px;
|
|
51
71
|
--field-label-font-size: var(--font-text-sm-size);
|
|
52
72
|
--field-label-line-height: var(--font-text-sm-line-height);
|
|
53
73
|
--field-description-font-size: var(--font-text-xs-size);
|
|
54
74
|
--field-description-line-height: var(--font-text-xs-line-height);
|
|
55
|
-
--field-description-margin-top: calc(-1 * calc(var(--spacing) * 1));
|
|
56
|
-
--field-label-horizontal-offset: 0.625rem;
|
|
57
75
|
}.Field:where([data-size="2xl"]),
|
|
58
76
|
.Field:where([data-size="3xl"]) {
|
|
59
77
|
--field-gap: calc(var(--spacing) * 2.5);
|
|
78
|
+
--field-label-description-gap: 2px;
|
|
60
79
|
--field-label-font-size: var(--font-text-md-size);
|
|
61
80
|
--field-label-line-height: var(--font-text-md-line-height);
|
|
62
81
|
--field-description-font-size: var(--font-text-sm-size);
|
|
63
82
|
--field-description-line-height: var(--font-text-sm-line-height);
|
|
64
|
-
|
|
65
|
-
|
|
83
|
+
|
|
84
|
+
/* Scale error elements up for spacious sizes */
|
|
85
|
+
--field-error-font-size: var(--font-text-sm-size); /* 14px */
|
|
86
|
+
--field-error-line-height: var(--font-text-sm-line-height); /* 20px */
|
|
87
|
+
--field-error-icon-size: 1.125rem; /* 18px */
|
|
88
|
+
}/* =============================================
|
|
89
|
+
Sizes — horizontal label offset (per-size)
|
|
90
|
+
Centers label text vertically with the input:
|
|
91
|
+
offset = (control-height − label-line-height) / 2
|
|
92
|
+
============================================= */.Field:where([data-size="3xs"]) {
|
|
93
|
+
--field-label-horizontal-offset: calc((var(--control-size-3xs) - var(--font-text-xs-line-height)) / 2); /* 2px */
|
|
94
|
+
}.Field:where([data-size="2xs"]) {
|
|
95
|
+
--field-label-horizontal-offset: calc((var(--control-size-2xs) - var(--font-text-xs-line-height)) / 2); /* 3px */
|
|
96
|
+
}.Field:where([data-size="xs"]) {
|
|
97
|
+
--field-label-horizontal-offset: calc((var(--control-size-xs) - var(--font-text-xs-line-height)) / 2); /* 4px */
|
|
98
|
+
}.Field:where([data-size="sm"]) {
|
|
99
|
+
--field-label-horizontal-offset: calc((var(--control-size-sm) - var(--font-text-xs-line-height)) / 2); /* 5px */
|
|
100
|
+
}.Field:where([data-size="md"]) {
|
|
101
|
+
--field-label-horizontal-offset: calc((var(--control-size-md) - var(--font-text-sm-line-height)) / 2); /* 6px */
|
|
102
|
+
}.Field:where([data-size="lg"]) {
|
|
103
|
+
--field-label-horizontal-offset: calc((var(--control-size-lg) - var(--font-text-sm-line-height)) / 2); /* 8px */
|
|
104
|
+
}.Field:where([data-size="xl"]) {
|
|
105
|
+
--field-label-horizontal-offset: calc((var(--control-size-xl) - var(--font-text-sm-line-height)) / 2); /* 10px */
|
|
106
|
+
}.Field:where([data-size="2xl"]) {
|
|
107
|
+
--field-label-horizontal-offset: calc((var(--control-size-2xl) - var(--font-text-md-line-height)) / 2); /* 10px */
|
|
108
|
+
}.Field:where([data-size="3xl"]) {
|
|
109
|
+
--field-label-horizontal-offset: calc((var(--control-size-3xl) - var(--font-text-md-line-height)) / 2); /* 12px */
|
|
66
110
|
}/* =============================================
|
|
67
111
|
Label
|
|
68
112
|
============================================= */.Label {
|
|
@@ -74,9 +118,6 @@
|
|
|
74
118
|
line-height: var(--field-label-line-height);
|
|
75
119
|
font-weight: var(--font-weight-semibold);
|
|
76
120
|
cursor: default;
|
|
77
|
-
-webkit-user-select: none;
|
|
78
|
-
-moz-user-select: none;
|
|
79
|
-
user-select: none;
|
|
80
121
|
}/* =============================================
|
|
81
122
|
Required Indicator
|
|
82
123
|
============================================= */.RequiredIndicator {
|
|
@@ -88,7 +129,6 @@
|
|
|
88
129
|
color: var(--color-text-secondary);
|
|
89
130
|
font-size: var(--field-description-font-size);
|
|
90
131
|
line-height: var(--field-description-line-height);
|
|
91
|
-
margin-top: var(--field-description-margin-top, calc(-1 * calc(var(--spacing) * 0.5)));
|
|
92
132
|
}/* =============================================
|
|
93
133
|
Control
|
|
94
134
|
============================================= */.Control {
|
|
@@ -19,7 +19,7 @@ export const RadioGroup = ({ onChange, children, className, direction = "row", d
|
|
|
19
19
|
}), [disabled, direction]);
|
|
20
20
|
return (_jsx(RadioGroupContext, { value: store, children: _jsx(RadixRadioGroup.Root, { className: clsx(s.RadioGroup, className), "data-direction": direction, onValueChange: onChange, disabled: disabled, ...restProps, children: children }) }));
|
|
21
21
|
};
|
|
22
|
-
const Item = ({ value, disabled: itemDisabled = false, required, children, className, block = false, ...restProps }) => {
|
|
22
|
+
const Item = ({ value, disabled: itemDisabled = false, required, children, className, block = false, orientation = "left", ...restProps }) => {
|
|
23
23
|
const { disabled: groupDisabled } = useRadioGroupContext();
|
|
24
24
|
const disabled = groupDisabled || itemDisabled;
|
|
25
25
|
const id = useId();
|
|
@@ -28,7 +28,7 @@ const Item = ({ value, disabled: itemDisabled = false, required, children, class
|
|
|
28
28
|
// Providing an extra wrapper enables `label` to be inline-flex, avoiding clickable whitespace
|
|
29
29
|
// when radio options are of varied lengths.
|
|
30
30
|
// NOTE: Important that this is `flex` to prevent the `inline-flex` label from extra, unintentional whitespace.
|
|
31
|
-
_jsx("div", { className: "flex", ...restProps, children: _jsxs("label", { htmlFor: itemId, className: clsx(s.RadioLabel, className), "data-disabled": disabled ? "" : undefined, "data-block": block ? "" : undefined, onMouseDown: (event) => {
|
|
31
|
+
_jsx("div", { className: clsx("flex", block && "w-full"), ...restProps, children: _jsxs("label", { htmlFor: itemId, className: clsx(s.RadioLabel, className), "data-disabled": disabled ? "" : undefined, "data-block": block ? "" : undefined, "data-orientation": orientation, onMouseDown: (event) => {
|
|
32
32
|
if (!event.defaultPrevented && event.detail > 1)
|
|
33
33
|
event.preventDefault();
|
|
34
34
|
}, children: [_jsx("div", { className: s.RadioIndicatorWrapper, children: _jsx(RadixRadioGroup.Item, { id: itemId, value: value, disabled: disabled, required: required, className: s.RadioItem, children: _jsx(RadixRadioGroup.Indicator, { className: s.RadioIndicator }) }) }), children] }) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RadioGroup.js","sourceRoot":"","sources":["../../../../src/components/RadioGroup/RadioGroup.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,UAAU,IAAI,eAAe,EAAE,MAAM,UAAU,CAAA;AACxD,OAAO,KAAK,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AACjE,OAAO,CAAC,MAAM,yBAAyB,CAAA;AASvC,MAAM,iBAAiB,GAAG,aAAa,CAAgC,IAAI,CAAC,CAAA;AAE5E,MAAM,oBAAoB,GAAG,GAAG,EAAE;IAChC,MAAM,OAAO,GAAG,GAAG,CAAC,iBAAiB,CAAC,CAAA;IAEtC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAA;IAC5E,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA;AAqBD,MAAM,CAAC,MAAM,UAAU,GAAG,CAAmB,EAC3C,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAS,GAAG,KAAK,EACjB,QAAQ,GAAG,KAAK,EAChB,GAAG,SAAS,EACO,EAAE,EAAE;IACvB,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC;QACL,QAAQ;QACR,SAAS;KACV,CAAC,EACF,CAAC,QAAQ,EAAE,SAAS,CAAC,CACtB,CAAA;IAED,OAAO,CACL,KAAC,iBAAiB,IAAC,KAAK,EAAE,KAAK,YAC7B,KAAC,eAAe,CAAC,IAAI,IACnB,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,UAAU,EAAE,SAAS,CAAC,oBACxB,SAAS,EACzB,aAAa,EAAE,QAAQ,EACvB,QAAQ,EAAE,QAAQ,KACd,SAAS,YAEZ,QAAQ,GACY,GACL,CACrB,CAAA;AACH,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"RadioGroup.js","sourceRoot":"","sources":["../../../../src/components/RadioGroup/RadioGroup.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\"\n\nimport clsx from \"clsx\"\nimport { RadioGroup as RadixRadioGroup } from \"radix-ui\"\nimport React, { createContext, use, useId, useMemo } from \"react\"\nimport s from \"./RadioGroup.module.css\"\n\ntype Direction = \"col\" | \"row\"\n\ntype RadioGroupContextValue = {\n disabled: boolean\n direction: Direction\n}\n\nconst RadioGroupContext = createContext<RadioGroupContextValue | null>(null)\n\nconst useRadioGroupContext = () => {\n const context = use(RadioGroupContext)\n\n if (!context) {\n throw new Error(\"RadioGroup components must be wrapped in <RadioGroup />\")\n }\n\n return context\n}\n\nexport type RadioGroupProps<T extends string> = {\n \"defaultValue\"?: T\n \"value\"?: T\n \"name\"?: string\n \"onChange\"?: (value: T) => void\n /** Accessible label for the radio options */\n \"aria-label\"?: string\n /** Determines the layout direction of the radio items\n * @default row\n */\n \"direction\"?: Direction\n /** Controls whether the entire radio group is disabled */\n \"disabled\"?: boolean\n /** Class applied to the radio group container */\n \"className\"?: string\n \"children\": React.ReactNode\n \"required\"?: boolean\n}\n\nexport const RadioGroup = <T extends string>({\n onChange,\n children,\n className,\n direction = \"row\",\n disabled = false,\n ...restProps\n}: RadioGroupProps<T>) => {\n const store = useMemo<RadioGroupContextValue>(\n () => ({\n disabled,\n direction,\n }),\n [disabled, direction],\n )\n\n return (\n <RadioGroupContext value={store}>\n <RadixRadioGroup.Root\n className={clsx(s.RadioGroup, className)}\n data-direction={direction}\n onValueChange={onChange}\n disabled={disabled}\n {...restProps}\n >\n {children}\n </RadixRadioGroup.Root>\n </RadioGroupContext>\n )\n}\n\nexport type RadioGroupItemProps<T extends string> = {\n value: T\n /** Determines if a given radio item is disabled */\n disabled?: boolean\n required?: boolean\n block?: boolean\n className?: string\n /**\n * The orientation of the radio indicator relative to the label.\n *\n * @default \"left\"\n */\n orientation?: \"left\" | \"right\"\n children: React.ReactNode\n}\n\nconst Item = <T extends string>({\n value,\n disabled: itemDisabled = false,\n required,\n children,\n className,\n block = false,\n orientation = \"left\",\n ...restProps\n}: RadioGroupItemProps<T>) => {\n const { disabled: groupDisabled } = useRadioGroupContext()\n const disabled = groupDisabled || itemDisabled\n\n const id = useId()\n const itemId = `${value}-${id}`\n\n return (\n // Providing an extra wrapper enables `label` to be inline-flex, avoiding clickable whitespace\n // when radio options are of varied lengths.\n // NOTE: Important that this is `flex` to prevent the `inline-flex` label from extra, unintentional whitespace.\n <div className={clsx(\"flex\", block && \"w-full\")} {...restProps}>\n <label\n htmlFor={itemId}\n className={clsx(s.RadioLabel, className)}\n data-disabled={disabled ? \"\" : undefined}\n data-block={block ? \"\" : undefined}\n data-orientation={orientation}\n onMouseDown={(event) => {\n if (!event.defaultPrevented && event.detail > 1) event.preventDefault()\n }}\n >\n <div className={s.RadioIndicatorWrapper}>\n <RadixRadioGroup.Item\n id={itemId}\n value={value}\n disabled={disabled}\n required={required}\n className={s.RadioItem}\n >\n <RadixRadioGroup.Indicator className={s.RadioIndicator} />\n </RadixRadioGroup.Item>\n </div>\n {children}\n </label>\n </div>\n )\n}\n\nRadioGroup.Item = Item\n"]}
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
font-size: var(--radio-group-item-font-size);
|
|
16
16
|
line-height: var(--radio-group-item-line-height);
|
|
17
17
|
}
|
|
18
|
+
.RadioLabel[data-orientation="right"]:where([data-block]) {
|
|
19
|
+
justify-content: space-between;
|
|
20
|
+
}
|
|
18
21
|
|
|
19
22
|
.RadioLabel[data-disabled] {
|
|
20
23
|
cursor: not-allowed;
|
|
@@ -22,6 +25,7 @@
|
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
.RadioLabel[data-block] {
|
|
28
|
+
display: flex;
|
|
25
29
|
width: 100%;
|
|
26
30
|
}/* Creates centered alignment with `line-height` of the label */.RadioIndicatorWrapper {
|
|
27
31
|
position: relative;
|
|
@@ -40,6 +44,10 @@
|
|
|
40
44
|
left: 0;
|
|
41
45
|
height: 1px !important;
|
|
42
46
|
transform: none !important;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.RadioLabel[data-orientation="right"] .RadioIndicatorWrapper {
|
|
50
|
+
order: 1;
|
|
43
51
|
}.RadioItem {
|
|
44
52
|
position: relative;
|
|
45
53
|
display: flex;
|
|
@@ -99,12 +99,14 @@
|
|
|
99
99
|
--input-outline-border-color-focus: var(--alpha-50);
|
|
100
100
|
--input-soft-background-color: var(--color-background-primary-soft-alpha);
|
|
101
101
|
--input-soft-border-color-focus: var(--alpha-20);
|
|
102
|
+
--input-border-color-invalid: var(--color-border-danger-outline);
|
|
102
103
|
|
|
103
104
|
/* =============================================
|
|
104
105
|
FieldError
|
|
105
106
|
============================================= */
|
|
107
|
+
--field-error-color: var(--color-text-danger-outline);
|
|
106
108
|
--field-error-font-size: 0.75rem;
|
|
107
|
-
--field-error-line-height:
|
|
109
|
+
--field-error-line-height: var(--font-text-xs-line-height);
|
|
108
110
|
--field-error-icon-size: 1rem;
|
|
109
111
|
--field-error-gap: calc(var(--spacing) * 2);
|
|
110
112
|
--field-error-margin-top: 0.5rem;
|
|
@@ -114,7 +116,8 @@
|
|
|
114
116
|
/* =============================================
|
|
115
117
|
Field
|
|
116
118
|
============================================= */
|
|
117
|
-
--field-
|
|
119
|
+
--field-horizontal-label-width: 120px;
|
|
120
|
+
--field-horizontal-control-width: 240px;
|
|
118
121
|
|
|
119
122
|
/* =============================================
|
|
120
123
|
FloatingLabelInput
|
|
@@ -123,9 +126,9 @@
|
|
|
123
126
|
--floating-input-gutter: 1.25rem;
|
|
124
127
|
--floating-input-border-radius: var(--radius-full);
|
|
125
128
|
--floating-input-background: var(--color-surface);
|
|
126
|
-
--floating-input-border-color-invalid: var(--
|
|
129
|
+
--floating-input-border-color-invalid: var(--color-border-danger-outline);
|
|
127
130
|
--floating-input-label-color: var(--color-text-tertiary);
|
|
128
|
-
--floating-input-label-color-invalid: var(--
|
|
131
|
+
--floating-input-label-color-invalid: var(--color-text-danger-outline);
|
|
129
132
|
--floating-input-transition-duration: 80ms;
|
|
130
133
|
--floating-input-label-scale: 0.88;
|
|
131
134
|
|
|
@@ -385,8 +388,6 @@
|
|
|
385
388
|
:where(:root), :where([data-theme="light"]) {
|
|
386
389
|
--avatar-image-border-color: var(--alpha-04);
|
|
387
390
|
--input-outline-border-color-hover: var(--alpha-25);
|
|
388
|
-
--input-border-color-invalid: var(--red-500);
|
|
389
|
-
--field-error-color: #d00e17;
|
|
390
391
|
--floating-input-border-color: rgb(0 0 0 / 15%);
|
|
391
392
|
--floating-input-border-color-hover: rgb(0 0 0 / 20%);
|
|
392
393
|
--floating-input-border-color-focus: #3e68ff;
|
|
@@ -424,8 +425,6 @@
|
|
|
424
425
|
:where([data-theme="dark"]) {
|
|
425
426
|
--avatar-image-border-color: var(--alpha-15);
|
|
426
427
|
--input-outline-border-color-hover: var(--alpha-30);
|
|
427
|
-
--input-border-color-invalid: var(--red-600);
|
|
428
|
-
--field-error-color: #ff6b6b;
|
|
429
428
|
--floating-input-border-color: rgb(255 255 255 / 16%);
|
|
430
429
|
--floating-input-border-color-hover: rgb(255 255 255 / 24%);
|
|
431
430
|
--floating-input-border-color-focus: #6f8dff;
|
|
@@ -30,5 +30,17 @@ export type CheckboxProps = {
|
|
|
30
30
|
* @default "left"
|
|
31
31
|
*/
|
|
32
32
|
orientation?: "left" | "right";
|
|
33
|
+
/**
|
|
34
|
+
* Determines if the checkbox should be a fully rounded pill shape.
|
|
35
|
+
* @default false
|
|
36
|
+
*/
|
|
37
|
+
pill?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Visual style variant for the checkbox indicator.
|
|
40
|
+
* - `"solid"` — filled background when checked (default)
|
|
41
|
+
* - `"ghost"` — no border or background, checkmark only
|
|
42
|
+
* @default "solid"
|
|
43
|
+
*/
|
|
44
|
+
variant?: "solid" | "ghost";
|
|
33
45
|
};
|
|
34
|
-
export declare const Checkbox: ({ className, label, id: propsId, disabled, orientation, ...restProps }: CheckboxProps) => import("react/jsx-runtime").JSX.Element;
|
|
46
|
+
export declare const Checkbox: ({ className, label, id: propsId, disabled, orientation, pill, variant, ...restProps }: CheckboxProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -3,6 +3,7 @@ export type FieldChildProps = {
|
|
|
3
3
|
id: string;
|
|
4
4
|
"aria-describedby"?: string;
|
|
5
5
|
"aria-invalid"?: boolean;
|
|
6
|
+
opticallyAlign?: "start" | "end";
|
|
6
7
|
};
|
|
7
8
|
export type FieldProps = {
|
|
8
9
|
/**
|
|
@@ -44,6 +45,12 @@ export type FieldProps = {
|
|
|
44
45
|
* the id set on the child control and the `htmlFor` on the label.
|
|
45
46
|
*/
|
|
46
47
|
id?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Applies a negative margin on the child control using its gutter to
|
|
50
|
+
* optically align the control's text with surrounding content.
|
|
51
|
+
* Passed through to the child control via cloneElement / render prop.
|
|
52
|
+
*/
|
|
53
|
+
opticallyAlign?: "start" | "end";
|
|
47
54
|
/**
|
|
48
55
|
* CSS class applied to the root wrapper
|
|
49
56
|
*/
|
|
@@ -55,4 +62,4 @@ export type FieldProps = {
|
|
|
55
62
|
*/
|
|
56
63
|
children: React.ReactElement | ((fieldProps: FieldChildProps) => React.ReactNode);
|
|
57
64
|
};
|
|
58
|
-
export declare function Field({ label, description, errorMessage, size, required, orientation, id: idProp, className, children, }: FieldProps): import("react/jsx-runtime").JSX.Element;
|
|
65
|
+
export declare function Field({ label, description, errorMessage, size, required, orientation, opticallyAlign, id: idProp, className, children, }: FieldProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -6,7 +6,7 @@ export type RadioGroupProps<T extends string> = {
|
|
|
6
6
|
"name"?: string;
|
|
7
7
|
"onChange"?: (value: T) => void;
|
|
8
8
|
/** Accessible label for the radio options */
|
|
9
|
-
"aria-label"
|
|
9
|
+
"aria-label"?: string;
|
|
10
10
|
/** Determines the layout direction of the radio items
|
|
11
11
|
* @default row
|
|
12
12
|
*/
|
|
@@ -20,7 +20,7 @@ export type RadioGroupProps<T extends string> = {
|
|
|
20
20
|
};
|
|
21
21
|
export declare const RadioGroup: {
|
|
22
22
|
<T extends string>({ onChange, children, className, direction, disabled, ...restProps }: RadioGroupProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
23
|
-
Item: <T extends string>({ value, disabled: itemDisabled, required, children, className, block, ...restProps }: RadioGroupItemProps<T>) => import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
Item: <T extends string>({ value, disabled: itemDisabled, required, children, className, block, orientation, ...restProps }: RadioGroupItemProps<T>) => import("react/jsx-runtime").JSX.Element;
|
|
24
24
|
};
|
|
25
25
|
export type RadioGroupItemProps<T extends string> = {
|
|
26
26
|
value: T;
|
|
@@ -29,6 +29,12 @@ export type RadioGroupItemProps<T extends string> = {
|
|
|
29
29
|
required?: boolean;
|
|
30
30
|
block?: boolean;
|
|
31
31
|
className?: string;
|
|
32
|
+
/**
|
|
33
|
+
* The orientation of the radio indicator relative to the label.
|
|
34
|
+
*
|
|
35
|
+
* @default "left"
|
|
36
|
+
*/
|
|
37
|
+
orientation?: "left" | "right";
|
|
32
38
|
children: React.ReactNode;
|
|
33
39
|
};
|
|
34
40
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plexui/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Modern design system for building high-quality applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"tailwind",
|
|
23
23
|
"plexui",
|
|
24
24
|
"radix",
|
|
25
|
-
"
|
|
25
|
+
"ui-kit"
|
|
26
26
|
],
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": ">=18"
|
|
@@ -33,6 +33,9 @@
|
|
|
33
33
|
],
|
|
34
34
|
"exports": {
|
|
35
35
|
"./css": "./dist/es/styles/index.css",
|
|
36
|
+
"./styles/variables-primitive.css": "./dist/es/styles/variables-primitive.css",
|
|
37
|
+
"./styles/variables-semantic.css": "./dist/es/styles/variables-semantic.css",
|
|
38
|
+
"./styles/variables-components.css": "./dist/es/styles/variables-components.css",
|
|
36
39
|
"./components/*": {
|
|
37
40
|
"types": "./dist/types/components/*/index.d.ts",
|
|
38
41
|
"default": "./dist/es/components/*/index.js"
|