@plexui/ui 0.5.0 → 0.7.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 CHANGED
@@ -1,17 +1,20 @@
1
1
  # @plexui/ui
2
2
 
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.
3
+ A modern React component library with 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
 
9
11
  ## Highlights
10
12
 
11
- - **Battle-tested at ChatGPT scale** — components validated in a product used by hundreds of millions.
13
+ - **Production-grade** — components designed and tested for real products at scale.
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;AAmCrC,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EACvB,SAAS,EACT,KAAK,EACL,EAAE,EAAE,OAAO,EACX,QAAQ,EACR,WAAW,GAAG,MAAM,EACpB,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,IAAC,SAAS,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,QAAQ,KAAM,SAAS,YAClF,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\nexport const Checkbox = ({\n className,\n label,\n id: propsId,\n disabled,\n orientation = \"left\",\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 className={s.Checkbox} id={id} disabled={disabled} {...restProps}>\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"]}
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: 18px;
29
- max-width: 18px;
30
- height: 18px;
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
- [data-has-label] .Checkbox {
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
- .Checkbox[data-disabled], :where([data-theme="light"]) .Checkbox[data-disabled] {
75
- border-color: var(--gray-150);
76
- background: var(--gray-25);
77
- }
82
+ /* =============================================
83
+ Pill
84
+ ============================================= */
85
+ .Checkbox[data-pill] {
86
+ border-radius: var(--radius-full);
87
+ }
78
88
 
79
- :where([data-theme="dark"]) .Checkbox[data-disabled] {
80
- border-color: var(--gray-300);
81
- background: var(--gray-200);
82
- }
89
+ /* =============================================
90
+ Ghost variant
91
+ ============================================= */
92
+ .Checkbox[data-variant="ghost"] {
93
+ border-color: transparent;
94
+ background-color: transparent;
83
95
 
84
- .Checkbox[data-disabled][data-state="checked"], :where([data-theme="light"]) .Checkbox[data-disabled][data-state="checked"] {
85
- border-color: var(--gray-300);
86
- background-color: var(--gray-300);
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
- :where([data-theme="dark"]) .Checkbox[data-disabled][data-state="checked"] {
90
- border-color: var(--gray-200);
91
- background-color: var(--gray-200);
92
- }.CheckMark {
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
- /* pure black checkbox is not low enough contrast to look disabled */
135
- :where([data-theme="dark"]) .CheckMark[data-disabled]::before,
136
- :where([data-theme="dark"]) .CheckMark[data-disabled]::after {
137
- background: var(--gray-100);
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: flex;
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: 8px;
190
+ padding-left: calc(var(--spacing) * 2);
177
191
  }
178
192
 
179
193
  [data-orientation="right"] .Label {
180
- padding-right: 8px;
194
+ padding-right: calc(var(--spacing) * 3);
181
195
  }
182
196
  }
@@ -5,18 +5,31 @@
5
5
  background-color: var(--codeblock-background-color);
6
6
  }.CopyButtonContainer {
7
7
  position: absolute;
8
- top: 0;
9
- right: 0;
10
- padding: 12px 8px 0 0;
11
- border-radius: inherit;
12
- color: #24292e;
8
+ top: 8px;
9
+ right: 8px;
10
+ box-sizing: border-box;
11
+ display: grid;
12
+ place-items: center;
13
+ width: 32px;
14
+ height: 32px;
15
+ border-radius: 8px;
16
+ background-color: var(--color-surface-elevated-secondary);
17
+ color: var(--color-text-secondary);
13
18
  }/* Force the CopyButton to inherit the container's color on all states.
14
19
  Ghost hover background (::before) is preserved — it's the correct behavior. */.CopyButtonContainer button {
15
20
  color: inherit !important;
16
- }@media (prefers-color-scheme: dark) {
17
- .CopyButtonContainer {
18
- color: #d0d0d0;
19
- }
21
+ padding: 0 !important;
22
+ -webkit-tap-highlight-color: transparent;
23
+ }.CopyButtonContainer button::before {
24
+ opacity: 0 !important;
25
+ transform: none !important;
26
+ background-color: transparent !important;
27
+ box-shadow: none !important;
28
+ }.CopyButtonContainer button:focus,
29
+ .CopyButtonContainer button:focus-visible {
30
+ outline: none !important;
31
+ }.CopyButtonContainer button:focus-visible::after {
32
+ outline: none !important;
20
33
  }.SyntaxHighlighter {
21
34
  --syntax1: var(--codeblock-syntax-1);
22
35
  --syntax2: var(--codeblock-syntax-2);
@@ -35,7 +48,7 @@
35
48
  /* 14px to a 16px baseline */
36
49
  font-weight: var(--font-weight-normal);
37
50
  line-height: 1.714em;
38
- /* 24px at 14px — matches OpenAI reference */
51
+ /* 24px at 14px */
39
52
 
40
53
  /* stylelint-disable selector-class-pattern */
41
54
 
@@ -115,4 +128,4 @@
115
128
  align-items: center;
116
129
  justify-content: center;
117
130
  }
118
- }
131
+ }
@@ -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;AA4DlC,MAAM,UAAU,KAAK,CAAC,EACpB,KAAK,EACL,WAAW,EACX,YAAY,EACZ,IAAI,GAAG,IAAI,EACX,QAAQ,GAAG,KAAK,EAChB,WAAW,GAAG,UAAU,EACxB,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;KACrC,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,aAE7B,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,EACD,cAAK,SAAS,EAAE,CAAC,CAAC,OAAO,YAAG,gBAAgB,GAAO,EAClD,YAAY,IAAI,CACf,KAAC,UAAU,IAAC,EAAE,EAAE,OAAO,YAAG,YAAY,GAAc,CACrD,IACG,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}\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 * 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 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 }\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 >\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 className={s.Control}>{resolvedChildren}</div>\n {errorMessage && (\n <FieldError id={errorId}>{errorMessage}</FieldError>\n )}\n </div>\n )\n}\n"]}
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
- Orientation
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"]) .Label {
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
- min-width: var(--field-label-horizontal-min-width, 120px);
19
- }.Field:where([data-orientation="horizontal"]) .Control {
20
- flex: 1;
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.5);
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
- --field-label-horizontal-offset: 0.25rem;
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
- --field-label-horizontal-offset: 0.375rem;
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
- --field-description-margin-top: calc(-1 * calc(var(--spacing) * 1));
65
- --field-label-horizontal-offset: 0.875rem;
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 {
@@ -18,6 +18,5 @@
18
18
  height: var(--field-error-icon-size);
19
19
  display: block;
20
20
  align-self: center;
21
- margin-top: -0.1em;
22
21
  }
23
22
  }
@@ -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":"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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 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 ...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=\"flex\" {...restProps}>\n <label\n htmlFor={itemId}\n className={clsx(s.RadioLabel, className)}\n data-disabled={disabled ? \"\" : undefined}\n data-block={block ? \"\" : undefined}\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"]}
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;
@@ -1,6 +1,5 @@
1
1
  @layer components {/* =============================================
2
2
  Sidebar Component Styles
3
- Based on OpenAI Platform styling
4
3
  ============================================= *//* Layout container (wraps sidebar + content) */.SidebarLayout {
5
4
  display: flex;
6
5
  width: 100%;
@@ -4,9 +4,10 @@ import clsx from "clsx";
4
4
  import { Switch as RadixSwitch } from "radix-ui";
5
5
  import { useId } from "react";
6
6
  import s from "./Switch.module.css";
7
- export const Switch = ({ className, label, id: propsId, disabled, labelPosition = "end", ...restProps }) => {
7
+ export const Switch = ({ className, label, description, id: propsId, disabled, labelPosition = "end", ...restProps }) => {
8
8
  const reactId = useId();
9
9
  const id = propsId ?? reactId;
10
- return (_jsxs("div", { className: clsx(s.Container, className), "data-disabled": disabled ? "" : undefined, "data-has-label": label ? "" : undefined, "data-label-position": labelPosition, children: [_jsx(RadixSwitch.Root, { id: id, className: s.Track, disabled: disabled, ...restProps, children: _jsx(RadixSwitch.Thumb, { className: s.Thumb }) }), label && (_jsx("label", { htmlFor: id, className: s.Label, children: label }))] }));
10
+ const descriptionId = description ? `${id}-description` : undefined;
11
+ return (_jsxs("div", { className: clsx(s.Container, className), "data-disabled": disabled ? "" : undefined, "data-has-label": label ? "" : undefined, "data-label-position": labelPosition, children: [_jsx(RadixSwitch.Root, { id: id, className: s.Track, disabled: disabled, "aria-describedby": descriptionId, ...restProps, children: _jsx(RadixSwitch.Thumb, { className: s.Thumb }) }), label && (description ? (_jsxs("div", { className: s.LabelGroup, children: [_jsx("label", { htmlFor: id, className: s.Label, children: label }), _jsx("span", { id: descriptionId, className: s.Description, children: description })] })) : (_jsx("label", { htmlFor: id, className: s.Label, children: label })))] }));
11
12
  };
12
13
  //# sourceMappingURL=Switch.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Switch.js","sourceRoot":"","sources":["../../../../src/components/Switch/Switch.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,UAAU,CAAA;AAChD,OAAO,EAA0C,KAAK,EAAE,MAAM,OAAO,CAAA;AACrE,OAAO,CAAC,MAAM,qBAAqB,CAAA;AAkCnC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,SAAS,EACT,KAAK,EACL,EAAE,EAAE,OAAO,EACX,QAAQ,EACR,aAAa,GAAG,KAAK,EACrB,GAAG,SAAS,EACA,EAAE,EAAE;IAChB,MAAM,OAAO,GAAG,KAAK,EAAE,CAAA;IACvB,MAAM,EAAE,GAAG,OAAO,IAAI,OAAO,CAAA;IAE7B,OAAO,CACL,eACE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,mBACxB,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,oBACxB,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,yBACjB,aAAa,aAElC,KAAC,WAAW,CAAC,IAAI,IAAC,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,KAAM,SAAS,YAC7E,KAAC,WAAW,CAAC,KAAK,IAAC,SAAS,EAAE,CAAC,CAAC,KAAK,GAAI,GACxB,EAElB,KAAK,IAAI,CACR,gBAAO,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,KAAK,YACnC,KAAK,GACA,CACT,IACG,CACP,CAAA;AACH,CAAC,CAAA","sourcesContent":["\"use client\"\n\nimport clsx from \"clsx\"\nimport { Switch as RadixSwitch } from \"radix-ui\"\nimport { type FocusEventHandler, type ReactNode, useId } from \"react\"\nimport s from \"./Switch.module.css\"\n\nexport type SwitchProps = {\n /** The `id` of the switch. */\n id?: string\n /** The state of the switch when it is initially rendered. Use when you do not need to control its state. */\n defaultChecked?: boolean\n /** The controlled state of the switch. Must be used in conjunction with `onCheckedChange`. */\n checked?: boolean\n /** Optional accessible label rendered to the right of the checkbox. */\n label?: ReactNode\n /** Event handler called when the state of the switch 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 switch. */\n disabled?: boolean\n /** When `true`, indicates that the user must check the switch before the owning form can be submitted. */\n required?: boolean\n /** The name of the switch. 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 position of the label relative to the switch.\n * @default end\n */\n labelPosition?: \"start\" | \"end\"\n}\n\nexport const Switch = ({\n className,\n label,\n id: propsId,\n disabled,\n labelPosition = \"end\",\n ...restProps\n}: SwitchProps) => {\n const reactId = useId()\n const id = propsId ?? reactId\n\n return (\n <div\n className={clsx(s.Container, className)}\n data-disabled={disabled ? \"\" : undefined}\n data-has-label={label ? \"\" : undefined}\n data-label-position={labelPosition}\n >\n <RadixSwitch.Root id={id} className={s.Track} disabled={disabled} {...restProps}>\n <RadixSwitch.Thumb className={s.Thumb} />\n </RadixSwitch.Root>\n\n {label && (\n <label htmlFor={id} className={s.Label}>\n {label}\n </label>\n )}\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"Switch.js","sourceRoot":"","sources":["../../../../src/components/Switch/Switch.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,UAAU,CAAA;AAChD,OAAO,EAA0C,KAAK,EAAE,MAAM,OAAO,CAAA;AACrE,OAAO,CAAC,MAAM,qBAAqB,CAAA;AAoCnC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,SAAS,EACT,KAAK,EACL,WAAW,EACX,EAAE,EAAE,OAAO,EACX,QAAQ,EACR,aAAa,GAAG,KAAK,EACrB,GAAG,SAAS,EACA,EAAE,EAAE;IAChB,MAAM,OAAO,GAAG,KAAK,EAAE,CAAA;IACvB,MAAM,EAAE,GAAG,OAAO,IAAI,OAAO,CAAA;IAC7B,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,SAAS,CAAA;IAEnE,OAAO,CACL,eACE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,mBACxB,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,oBACxB,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,yBACjB,aAAa,aAElC,KAAC,WAAW,CAAC,IAAI,IACf,EAAE,EAAE,EAAE,EACN,SAAS,EAAE,CAAC,CAAC,KAAK,EAClB,QAAQ,EAAE,QAAQ,sBACA,aAAa,KAC3B,SAAS,YAEb,KAAC,WAAW,CAAC,KAAK,IAAC,SAAS,EAAE,CAAC,CAAC,KAAK,GAAI,GACxB,EAElB,KAAK,IAAI,CACR,WAAW,CAAC,CAAC,CAAC,CACZ,eAAK,SAAS,EAAE,CAAC,CAAC,UAAU,aAC1B,gBAAO,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,KAAK,YACnC,KAAK,GACA,EACR,eAAM,EAAE,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC,CAAC,WAAW,YAC9C,WAAW,GACP,IACH,CACP,CAAC,CAAC,CAAC,CACF,gBAAO,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,KAAK,YACnC,KAAK,GACA,CACT,CACF,IACG,CACP,CAAA;AACH,CAAC,CAAA","sourcesContent":["\"use client\"\n\nimport clsx from \"clsx\"\nimport { Switch as RadixSwitch } from \"radix-ui\"\nimport { type FocusEventHandler, type ReactNode, useId } from \"react\"\nimport s from \"./Switch.module.css\"\n\nexport type SwitchProps = {\n /** The `id` of the switch. */\n id?: string\n /** The state of the switch when it is initially rendered. Use when you do not need to control its state. */\n defaultChecked?: boolean\n /** The controlled state of the switch. Must be used in conjunction with `onCheckedChange`. */\n checked?: boolean\n /** Optional accessible label rendered next to the switch. */\n label?: ReactNode\n /** Optional description rendered below the label. Linked via `aria-describedby`. */\n description?: ReactNode\n /** Event handler called when the state of the switch 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 switch. */\n disabled?: boolean\n /** When `true`, indicates that the user must check the switch before the owning form can be submitted. */\n required?: boolean\n /** The name of the switch. 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 position of the label relative to the switch.\n * @default end\n */\n labelPosition?: \"start\" | \"end\"\n}\n\nexport const Switch = ({\n className,\n label,\n description,\n id: propsId,\n disabled,\n labelPosition = \"end\",\n ...restProps\n}: SwitchProps) => {\n const reactId = useId()\n const id = propsId ?? reactId\n const descriptionId = description ? `${id}-description` : undefined\n\n return (\n <div\n className={clsx(s.Container, className)}\n data-disabled={disabled ? \"\" : undefined}\n data-has-label={label ? \"\" : undefined}\n data-label-position={labelPosition}\n >\n <RadixSwitch.Root\n id={id}\n className={s.Track}\n disabled={disabled}\n aria-describedby={descriptionId}\n {...restProps}\n >\n <RadixSwitch.Thumb className={s.Thumb} />\n </RadixSwitch.Root>\n\n {label && (\n description ? (\n <div className={s.LabelGroup}>\n <label htmlFor={id} className={s.Label}>\n {label}\n </label>\n <span id={descriptionId} className={s.Description}>\n {description}\n </span>\n </div>\n ) : (\n <label htmlFor={id} className={s.Label}>\n {label}\n </label>\n )\n )}\n </div>\n )\n}\n"]}
@@ -75,20 +75,46 @@
75
75
  .Thumb[data-disabled] {
76
76
  background: var(--switch-thumb-color-disabled);
77
77
  box-shadow: none;
78
+ }.LabelGroup {
79
+ display: flex;
80
+ flex-direction: column;
81
+ gap: 4px;
82
+ }
83
+
84
+ /* Use padding so that the spacing is intrinsic to the group, remaining clickable. */
85
+ [data-label-position="end"] .LabelGroup {
86
+ padding-left: var(--switch-label-gap);
87
+ }
88
+
89
+ [data-label-position="start"] .LabelGroup {
90
+ padding-right: var(--switch-label-gap);
91
+ }
92
+
93
+ [data-disabled] .LabelGroup {
94
+ cursor: not-allowed;
78
95
  }.Label {
79
96
  cursor: pointer;
80
97
  }
81
98
 
82
99
  /* Use padding so that the spacing is intrinsic to the label, remaining clickable. */
83
- [data-label-position="end"] .Label {
100
+ [data-label-position="end"] > .Label {
84
101
  padding-left: var(--switch-label-gap);
85
102
  }
86
103
 
87
- [data-label-position="start"] .Label {
104
+ [data-label-position="start"] > .Label {
88
105
  padding-right: var(--switch-label-gap);
89
106
  }
90
107
 
91
108
  [data-disabled] .Label {
92
109
  cursor: not-allowed;
110
+ }.Description {
111
+ color: var(--color-text-secondary);
112
+ font-size: var(--font-text-xs-size);
113
+ line-height: var(--font-text-xs-line-height);
114
+ cursor: pointer;
115
+ }
116
+
117
+ [data-disabled] .Description {
118
+ cursor: not-allowed;
93
119
  }
94
120
  }
@@ -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: 1.4;
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-label-horizontal-min-width: 120px;
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(--field-error-color);
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(--field-error-color);
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": string;
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 {};
@@ -6,8 +6,10 @@ export type SwitchProps = {
6
6
  defaultChecked?: boolean;
7
7
  /** The controlled state of the switch. Must be used in conjunction with `onCheckedChange`. */
8
8
  checked?: boolean;
9
- /** Optional accessible label rendered to the right of the checkbox. */
9
+ /** Optional accessible label rendered next to the switch. */
10
10
  label?: ReactNode;
11
+ /** Optional description rendered below the label. Linked via `aria-describedby`. */
12
+ description?: ReactNode;
11
13
  /** Event handler called when the state of the switch changes. */
12
14
  onCheckedChange?: (nextState: boolean) => void;
13
15
  /** Event handler called when the checkbox looses focus. */
@@ -30,4 +32,4 @@ export type SwitchProps = {
30
32
  */
31
33
  labelPosition?: "start" | "end";
32
34
  };
33
- export declare const Switch: ({ className, label, id: propsId, disabled, labelPosition, ...restProps }: SwitchProps) => import("react/jsx-runtime").JSX.Element;
35
+ export declare const Switch: ({ className, label, description, id: propsId, disabled, labelPosition, ...restProps }: SwitchProps) => import("react/jsx-runtime").JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plexui/ui",
3
- "version": "0.5.0",
3
+ "version": "0.7.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
- "chatgpt"
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"