@neasg/design-system 0.4.9 → 0.4.10
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 +10 -10
- package/dist/checkbox.js +8 -2
- package/dist/field.d.ts +5 -1
- package/dist/field.js +4 -1
- package/dist/radio-group.d.ts +6 -0
- package/dist/radio-group.js +15 -6
- package/dist/styles.css +8 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Shared NEA UI primitives, theme tokens, and Storybook documentation.
|
|
4
4
|
|
|
5
|
+
Package: [@neasg/design-system on npm](https://www.npmjs.com/package/@neasg/design-system)
|
|
6
|
+
|
|
5
7
|
## Local Development
|
|
6
8
|
|
|
7
9
|
Use Node 22 for local development:
|
|
@@ -174,16 +176,14 @@ Release flow:
|
|
|
174
176
|
|
|
175
177
|
## Storybook Deployment
|
|
176
178
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
- Build command: `npm run build-storybook`
|
|
181
|
-
- Output directory: `storybook-static`
|
|
179
|
+
Storybook is published to Chromatic from GitHub Actions. CI builds Storybook
|
|
180
|
+
with `npm run build-storybook`, then uploads `storybook-static` with
|
|
181
|
+
`npm run chromatic`.
|
|
182
182
|
|
|
183
|
-
One-time
|
|
183
|
+
One-time setup:
|
|
184
184
|
|
|
185
|
-
1.
|
|
186
|
-
2.
|
|
187
|
-
3. Keep the build settings from `vercel.json`.
|
|
185
|
+
1. Create or connect the Chromatic project for this repo.
|
|
186
|
+
2. Add a GitHub Actions secret named `CHROMATIC_PROJECT_TOKEN`.
|
|
188
187
|
|
|
189
|
-
After that,
|
|
188
|
+
After that, pull requests and pushes to `main` publish a Chromatic build. The
|
|
189
|
+
GitHub Actions log includes the published Storybook URL.
|
package/dist/checkbox.js
CHANGED
|
@@ -8,10 +8,16 @@ const sizeClasses = {
|
|
|
8
8
|
md: "size-4",
|
|
9
9
|
lg: "size-5",
|
|
10
10
|
};
|
|
11
|
+
const startAlignedOffsetClasses = {
|
|
12
|
+
sm: "mt-[3px]",
|
|
13
|
+
md: "mt-[3px]",
|
|
14
|
+
lg: "mt-0",
|
|
15
|
+
};
|
|
11
16
|
const Checkbox = React.forwardRef(({ className, indeterminate = false, size = "md", checked, disabled, label, description, labelAlign = "center", id, ...props }, ref) => {
|
|
12
17
|
const innerRef = React.useRef(null);
|
|
13
18
|
const generatedId = React.useId();
|
|
14
19
|
const inputId = id !== null && id !== void 0 ? id : `checkbox-${generatedId}`;
|
|
20
|
+
const alignToStart = labelAlign === "start" || Boolean(description);
|
|
15
21
|
React.useImperativeHandle(ref, () => innerRef.current);
|
|
16
22
|
React.useEffect(() => {
|
|
17
23
|
if (innerRef.current) {
|
|
@@ -20,10 +26,10 @@ const Checkbox = React.forwardRef(({ className, indeterminate = false, size = "m
|
|
|
20
26
|
}, [indeterminate]);
|
|
21
27
|
const box = (_jsxs("span", { className: cn("relative inline-block shrink-0 rounded-sm border bg-background align-middle text-primary-foreground transition-colors", sizeClasses[size], (checked || indeterminate) && !disabled
|
|
22
28
|
? "border-primary bg-primary"
|
|
23
|
-
: "border-input", disabled && "cursor-not-allowed opacity-50", !label && className), children: [_jsx("input", { ref: innerRef, type: "checkbox", id: inputId, checked: checked, disabled: disabled, className: "absolute inset-0 h-full w-full cursor-pointer appearance-none opacity-0 disabled:cursor-not-allowed", ...props }), indeterminate ? (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", className: "pointer-events-none absolute inset-0 m-auto h-3 w-3", "aria-hidden": "true", children: _jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" }) })) : checked ? (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", className: "pointer-events-none absolute inset-0 m-auto h-3 w-3", "aria-hidden": "true", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) })) : null] }));
|
|
29
|
+
: "border-input", disabled && "cursor-not-allowed opacity-50", label && alignToStart && startAlignedOffsetClasses[size], !label && className), children: [_jsx("input", { ref: innerRef, type: "checkbox", id: inputId, checked: checked, disabled: disabled, className: "absolute inset-0 h-full w-full cursor-pointer appearance-none opacity-0 disabled:cursor-not-allowed", ...props }), indeterminate ? (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", className: "pointer-events-none absolute inset-0 m-auto h-3 w-3", "aria-hidden": "true", children: _jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" }) })) : checked ? (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", className: "pointer-events-none absolute inset-0 m-auto h-3 w-3", "aria-hidden": "true", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) })) : null] }));
|
|
24
30
|
if (!label)
|
|
25
31
|
return box;
|
|
26
|
-
return (_jsxs("label", { htmlFor: inputId, className: cn("inline-flex cursor-pointer
|
|
32
|
+
return (_jsxs("label", { htmlFor: inputId, className: cn("inline-flex cursor-pointer gap-2 text-sm", alignToStart ? "items-start" : "items-center", disabled && "cursor-not-allowed opacity-50", className), children: [box, _jsxs("div", { className: "flex min-w-0 flex-col gap-0.5 select-none", children: [_jsx(Typography, { as: "span", variant: "bodySm", children: label }), description ? (_jsx(Typography, { as: "p", variant: "caption", color: "muted", children: description })) : null] })] }));
|
|
27
33
|
});
|
|
28
34
|
Checkbox.displayName = "Checkbox";
|
|
29
35
|
export { Checkbox };
|
package/dist/field.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ interface FieldLabelProps extends React.ComponentProps<"label"> {
|
|
|
4
4
|
required?: boolean;
|
|
5
5
|
}
|
|
6
6
|
declare function FieldLabel({ className, required, children, ...props }: FieldLabelProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
interface FieldLegendProps extends React.ComponentProps<"legend"> {
|
|
8
|
+
required?: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare function FieldLegend({ className, required, children, ...props }: FieldLegendProps): import("react/jsx-runtime").JSX.Element;
|
|
7
11
|
declare function FieldDescription({ className, ...props }: Omit<React.ComponentProps<"p">, "color">): import("react/jsx-runtime").JSX.Element;
|
|
8
12
|
declare function FieldError({ className, ...props }: Omit<React.ComponentProps<"p">, "color">): import("react/jsx-runtime").JSX.Element | null;
|
|
9
|
-
export { Field, FieldDescription, FieldError, FieldLabel };
|
|
13
|
+
export { Field, FieldDescription, FieldError, FieldLabel, FieldLegend };
|
package/dist/field.js
CHANGED
|
@@ -7,6 +7,9 @@ function Field({ className, ...props }) {
|
|
|
7
7
|
function FieldLabel({ className, required, children, ...props }) {
|
|
8
8
|
return (_jsx(Typography, { asChild: true, variant: "label", className: cn("group-data-[disabled=true]/field:cursor-not-allowed group-data-[disabled=true]/field:opacity-50 group-data-[invalid=true]/field:text-destructive", className), children: _jsxs("label", { "data-slot": "field-label", ...props, children: [children, required ? (_jsx("span", { "aria-hidden": "true", className: "ml-0.5 text-destructive", children: "*" })) : null] }) }));
|
|
9
9
|
}
|
|
10
|
+
function FieldLegend({ className, required, children, ...props }) {
|
|
11
|
+
return (_jsx(Typography, { asChild: true, variant: "label", className: cn("group-data-[disabled=true]/field:cursor-not-allowed group-data-[disabled=true]/field:opacity-50 group-data-[invalid=true]/field:text-destructive", className), children: _jsxs("legend", { "data-slot": "field-legend", ...props, children: [children, required ? (_jsx("span", { "aria-hidden": "true", className: "ml-0.5 text-destructive", children: "*" })) : null] }) }));
|
|
12
|
+
}
|
|
10
13
|
function FieldDescription({ className, ...props }) {
|
|
11
14
|
return (_jsx(Typography, { as: "p", variant: "bodySm", color: "muted", "data-slot": "field-description", className: className, ...props }));
|
|
12
15
|
}
|
|
@@ -16,4 +19,4 @@ function FieldError({ className, ...props }) {
|
|
|
16
19
|
}
|
|
17
20
|
return (_jsx(Typography, { as: "p", variant: "bodySm", role: "alert", "data-slot": "field-error", className: cn("text-destructive", className), ...props }));
|
|
18
21
|
}
|
|
19
|
-
export { Field, FieldDescription, FieldError, FieldLabel };
|
|
22
|
+
export { Field, FieldDescription, FieldError, FieldLabel, FieldLegend };
|
package/dist/radio-group.d.ts
CHANGED
|
@@ -14,8 +14,14 @@ export interface RadioGroupProps extends Omit<React.FieldsetHTMLAttributes<HTMLF
|
|
|
14
14
|
label?: React.ReactNode;
|
|
15
15
|
hideLabel?: boolean;
|
|
16
16
|
description?: React.ReactNode;
|
|
17
|
+
error?: React.ReactNode;
|
|
18
|
+
invalid?: boolean;
|
|
19
|
+
required?: boolean;
|
|
17
20
|
orientation?: "horizontal" | "vertical";
|
|
18
21
|
groupClassName?: string;
|
|
19
22
|
optionClassName?: string;
|
|
23
|
+
labelClassName?: string;
|
|
24
|
+
descriptionClassName?: string;
|
|
25
|
+
errorClassName?: string;
|
|
20
26
|
}
|
|
21
27
|
export declare const RadioGroup: React.ForwardRefExoticComponent<RadioGroupProps & React.RefAttributes<HTMLFieldSetElement>>;
|
package/dist/radio-group.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { FieldDescription, FieldError, FieldLegend } from "./field";
|
|
4
5
|
import { cn } from "./lib/utils";
|
|
5
6
|
import { Typography } from "./typography";
|
|
6
7
|
function useControllableValue({ value, defaultValue, onValueChange, }) {
|
|
@@ -15,27 +16,35 @@ function useControllableValue({ value, defaultValue, onValueChange, }) {
|
|
|
15
16
|
}, [isControlled, onValueChange]);
|
|
16
17
|
return [currentValue, setValue];
|
|
17
18
|
}
|
|
18
|
-
export const RadioGroup = React.forwardRef(({ className, optionClassName, options, value, defaultValue, onValueChange, name, label, hideLabel = false, description, orientation = "horizontal", groupClassName, disabled, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, ...props }, ref) => {
|
|
19
|
+
export const RadioGroup = React.forwardRef(({ className, optionClassName, options, value, defaultValue, onValueChange, name, label, hideLabel = false, description, error, invalid = false, required = false, orientation = "horizontal", groupClassName, disabled, labelClassName, descriptionClassName, errorClassName, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, ...props }, ref) => {
|
|
19
20
|
const generatedId = React.useId();
|
|
20
21
|
const groupName = name !== null && name !== void 0 ? name : `radio-group-${generatedId.replace(/:/g, "")}`;
|
|
21
22
|
const labelId = label ? `${groupName}-label` : undefined;
|
|
22
|
-
const
|
|
23
|
+
const errorId = error ? `${groupName}-error` : undefined;
|
|
24
|
+
const showDescription = Boolean(description) && !error;
|
|
25
|
+
const descriptionId = showDescription ? `${groupName}-description` : undefined;
|
|
26
|
+
const isInvalid = invalid ||
|
|
27
|
+
ariaInvalid === true ||
|
|
28
|
+
ariaInvalid === "true" ||
|
|
29
|
+
Boolean(error);
|
|
30
|
+
const describedBy = [ariaDescribedBy, descriptionId, errorId].filter(Boolean).join(" ") ||
|
|
31
|
+
undefined;
|
|
23
32
|
const [currentValue, setCurrentValue] = useControllableValue({
|
|
24
33
|
value,
|
|
25
34
|
defaultValue,
|
|
26
35
|
onValueChange,
|
|
27
36
|
});
|
|
28
|
-
return (_jsxs("fieldset", { ref: ref, disabled: disabled, className: cn("min-w-0 border-0 p-0", className),
|
|
37
|
+
return (_jsxs("fieldset", { ref: ref, disabled: disabled, "aria-invalid": isInvalid ? "true" : undefined, className: cn("group/field flex w-full min-w-0 flex-col gap-2 border-0 p-0", className), "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, ...props, children: [label ? (_jsx(FieldLegend, { id: labelId, required: required, className: cn("mb-2", hideLabel && "sr-only", labelClassName), children: label })) : null, _jsx("div", { role: "radiogroup", "aria-orientation": orientation, "aria-labelledby": labelId !== null && labelId !== void 0 ? labelId : ariaLabelledBy, "aria-describedby": describedBy, "aria-invalid": isInvalid ? "true" : undefined, className: cn(orientation === "horizontal"
|
|
29
38
|
? "flex flex-wrap items-center gap-4"
|
|
30
39
|
: "grid gap-2", groupClassName), children: options.map((option, optionIndex) => {
|
|
31
40
|
const optionId = `${groupName}-option-${optionIndex}`;
|
|
32
41
|
const checked = currentValue === option.value;
|
|
33
42
|
const optionDisabled = disabled || option.disabled;
|
|
34
|
-
return (_jsxs("label", { htmlFor: optionId, className: cn("inline-flex min-w-0 cursor-pointer items-start gap-2 text-sm text-foreground", optionDisabled && "cursor-not-allowed opacity-50", optionClassName), children: [_jsxs("span", { className: cn("relative mt-
|
|
43
|
+
return (_jsxs("label", { htmlFor: optionId, className: cn("inline-flex min-w-0 cursor-pointer items-start gap-2 text-sm text-foreground", optionDisabled && "cursor-not-allowed opacity-50", optionClassName), children: [_jsxs("span", { className: cn("relative mt-[3px] inline-flex size-3.5 shrink-0 items-center justify-center rounded-full border bg-background transition-colors focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2", checked ? "border-primary" : "border-input"), children: [_jsx("span", { className: cn("size-2 rounded-full bg-primary transition-transform", checked ? "scale-100" : "scale-0") }), _jsx("input", { id: optionId, type: "radio", name: groupName, value: option.value, checked: checked, required: required, disabled: optionDisabled, onChange: (event) => {
|
|
35
44
|
if (event.target.checked) {
|
|
36
45
|
setCurrentValue(option.value);
|
|
37
46
|
}
|
|
38
|
-
}, className: "absolute inset-0 cursor-pointer opacity-0 disabled:cursor-not-allowed" })] }), _jsxs("span", { className: "min-w-0", children: [_jsx(Typography, { as: "span", variant: "bodySm", children: option.label }), option.description ? (_jsx(Typography, { as: "p", variant: "caption", color: "muted", children: option.description })) : null] })] }, option.value));
|
|
39
|
-
}) })] }));
|
|
47
|
+
}, className: "absolute inset-0 cursor-pointer opacity-0 disabled:cursor-not-allowed" })] }), _jsxs("span", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx(Typography, { as: "span", variant: "bodySm", children: option.label }), option.description ? (_jsx(Typography, { as: "p", variant: "caption", color: "muted", children: option.description })) : null] })] }, option.value));
|
|
48
|
+
}) }), showDescription ? (_jsx(FieldDescription, { id: descriptionId, className: descriptionClassName, children: description })) : null, _jsx(FieldError, { id: errorId, className: errorClassName, children: error })] }));
|
|
40
49
|
});
|
|
41
50
|
RadioGroup.displayName = "RadioGroup";
|
package/dist/styles.css
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neasg/design-system",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
4
4
|
"description": "NEA shared design system primitives, theme tokens, and Storybook docs.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"storybook": "storybook dev -p 6007 -c .storybook",
|
|
23
23
|
"build-storybook": "storybook build -c .storybook",
|
|
24
24
|
"test": "vitest run",
|
|
25
|
-
"prepublishOnly": "npm test && npm run build"
|
|
25
|
+
"prepublishOnly": "npm test && npm run build",
|
|
26
|
+
"chromatic": "chromatic --storybook-build-dir storybook-static --exit-once-uploaded"
|
|
26
27
|
},
|
|
27
28
|
"publishConfig": {
|
|
28
29
|
"access": "public",
|
|
@@ -85,6 +86,7 @@
|
|
|
85
86
|
"@types/react-dom": "^18.2.18",
|
|
86
87
|
"@vitejs/plugin-react": "^6.0.1",
|
|
87
88
|
"autoprefixer": "^10.4.16",
|
|
89
|
+
"chromatic": "^16.10.1",
|
|
88
90
|
"jsdom": "^29.0.0",
|
|
89
91
|
"postcss": "^8.4.31",
|
|
90
92
|
"react": "^18.2.0",
|