@ashwa-io/ui 0.0.3 → 0.0.4

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.
Files changed (36) hide show
  1. package/dist/components/atomic/Checkbox/Checkbox.d.ts +11 -0
  2. package/dist/components/atomic/Checkbox/Checkbox.js +32 -0
  3. package/dist/components/atomic/Checkbox/index.d.ts +2 -0
  4. package/dist/components/atomic/ColorInput/ColorInput.d.ts +15 -0
  5. package/dist/components/atomic/ColorInput/ColorInput.js +44 -0
  6. package/dist/components/atomic/ColorInput/colorHex.js +8 -0
  7. package/dist/components/atomic/ColorInput/index.d.ts +2 -0
  8. package/dist/components/atomic/Select/Select.d.ts +18 -0
  9. package/dist/components/atomic/Select/Select.js +47 -0
  10. package/dist/components/atomic/Select/index.d.ts +2 -0
  11. package/dist/components/atomic/Switch/Switch.d.ts +11 -0
  12. package/dist/components/atomic/Switch/Switch.js +32 -0
  13. package/dist/components/atomic/Switch/index.d.ts +2 -0
  14. package/dist/components/atomic/TextArea/TextArea.d.ts +14 -0
  15. package/dist/components/atomic/TextArea/TextArea.js +35 -0
  16. package/dist/components/atomic/TextArea/index.d.ts +2 -0
  17. package/dist/components/atomic/TextInput/TextInput.d.ts +18 -0
  18. package/dist/components/atomic/TextInput/TextInput.js +42 -0
  19. package/dist/components/atomic/TextInput/index.d.ts +2 -0
  20. package/dist/components/layout/FormLayout/FormLayout.d.ts +9 -0
  21. package/dist/components/layout/FormLayout/FormLayout.js +15 -0
  22. package/dist/components/layout/FormLayout/index.d.ts +2 -0
  23. package/dist/components/molecule/FormField/FormField.d.ts +16 -0
  24. package/dist/components/molecule/FormField/FormField.js +47 -0
  25. package/dist/components/molecule/FormField/index.d.ts +2 -0
  26. package/dist/components/ui/checkbox.js +18 -0
  27. package/dist/components/ui/field.js +65 -0
  28. package/dist/components/ui/input.js +14 -0
  29. package/dist/components/ui/label.js +12 -0
  30. package/dist/components/ui/select.js +75 -0
  31. package/dist/components/ui/switch.js +18 -0
  32. package/dist/components/ui/textarea.js +12 -0
  33. package/dist/index.d.ts +18 -2
  34. package/dist/index.js +9 -1
  35. package/dist/lib/types.d.ts +15 -1
  36. package/package.json +1 -1
@@ -0,0 +1,11 @@
1
+ import { FieldBaseProps } from "../../../lib/types.js";
2
+ import React from "react";
3
+ export interface CheckboxProps extends FieldBaseProps {
4
+ value: boolean;
5
+ onChange: (value: boolean) => void;
6
+ onBlur?: () => void;
7
+ name?: string;
8
+ readOnly?: boolean;
9
+ ref?: React.Ref<HTMLElement>;
10
+ }
11
+ export declare const Checkbox: React.FC<CheckboxProps>;
@@ -0,0 +1,32 @@
1
+ import { FormField } from "../../molecule/FormField/FormField.js";
2
+ import { Checkbox as Checkbox$1 } from "../../ui/checkbox.js";
3
+ import { jsx } from "react/jsx-runtime";
4
+ // src/components/atomic/Checkbox/Checkbox.tsx
5
+ const Checkbox = (props) => {
6
+ const { label, hideLabel, description, error, required, disabled, id, "data-testid": testId, value, onChange, onBlur, name, readOnly = false, ref } = props;
7
+ return /* @__PURE__ */ jsx(FormField, {
8
+ orientation: "horizontal",
9
+ label,
10
+ hideLabel,
11
+ description,
12
+ error,
13
+ required,
14
+ disabled,
15
+ id,
16
+ "data-testid": testId,
17
+ children: (control) => /* @__PURE__ */ jsx(Checkbox$1, {
18
+ ref,
19
+ id: control.id,
20
+ "aria-describedby": control["aria-describedby"],
21
+ "aria-invalid": control["aria-invalid"],
22
+ "aria-required": control["aria-required"],
23
+ disabled: control.disabled,
24
+ checked: value,
25
+ onCheckedChange: (checked) => onChange(checked),
26
+ onBlur,
27
+ name,
28
+ readOnly
29
+ })
30
+ });
31
+ };
32
+ export { Checkbox };
@@ -0,0 +1,2 @@
1
+ import { Checkbox, CheckboxProps } from "./Checkbox.js";
2
+ export { Checkbox, type CheckboxProps };
@@ -0,0 +1,15 @@
1
+ import { FieldBaseProps } from "../../../lib/types.js";
2
+ import React from "react";
3
+ export interface ColorInputProps extends FieldBaseProps {
4
+ /** 6-digit hex, e.g. '#1a73e8' (orgs.primaryColor), or the text being typed. */
5
+ value: string;
6
+ /**
7
+ * A valid 6-digit hex arrives normalized ('#1a73e8'); anything else arrives as typed, so the
8
+ * form's validation can flag it (e.g. `validate: v => /^#[0-9a-f]{6}$/.test(v) || '…'`).
9
+ */
10
+ onChange: (value: string) => void;
11
+ onBlur?: () => void;
12
+ name?: string;
13
+ ref?: React.Ref<HTMLInputElement>;
14
+ }
15
+ export declare const ColorInput: React.FC<ColorInputProps>;
@@ -0,0 +1,44 @@
1
+ import { FormField } from "../../molecule/FormField/FormField.js";
2
+ import { Input } from "../../ui/input.js";
3
+ import { normalizeHex } from "./colorHex.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ // src/components/atomic/ColorInput/ColorInput.tsx
6
+ const ColorInput = (props) => {
7
+ const { label, hideLabel, description, error, required, disabled, id, "data-testid": testId, value, onChange, onBlur, name, ref } = props;
8
+ const handleText = (next) => onChange(normalizeHex(next) ?? next);
9
+ return /* @__PURE__ */ jsx(FormField, {
10
+ label,
11
+ hideLabel,
12
+ description,
13
+ error,
14
+ required,
15
+ disabled,
16
+ id,
17
+ "data-testid": testId,
18
+ children: (control) => /* @__PURE__ */ jsxs("div", {
19
+ className: "flex items-center gap-2",
20
+ children: [/* @__PURE__ */ jsx("input", {
21
+ type: "color",
22
+ "aria-label": `${label} picker`,
23
+ value: normalizeHex(value) ?? "#000000",
24
+ disabled: control.disabled,
25
+ onChange: (event) => handleText(event.target.value),
26
+ className: "border-input h-8 w-10 shrink-0 cursor-pointer rounded-lg border bg-transparent p-0.5 disabled:cursor-not-allowed disabled:opacity-50"
27
+ }), /* @__PURE__ */ jsx(Input, {
28
+ ref,
29
+ id: control.id,
30
+ "aria-describedby": control["aria-describedby"],
31
+ "aria-invalid": control["aria-invalid"],
32
+ "aria-required": control["aria-required"],
33
+ disabled: control.disabled,
34
+ name,
35
+ value,
36
+ spellCheck: false,
37
+ autoComplete: "off",
38
+ onChange: (event) => handleText(event.target.value),
39
+ onBlur
40
+ })]
41
+ })
42
+ });
43
+ };
44
+ export { ColorInput };
@@ -0,0 +1,8 @@
1
+ // src/components/atomic/ColorInput/colorHex.ts
2
+ const HEX6 = /^#?([0-9a-f]{6})$/i;
3
+ /** '#1A73E8' | '1a73e8' → '#1a73e8'; anything that is not 6 hex digits → null. */
4
+ const normalizeHex = (input) => {
5
+ const match = HEX6.exec(input.trim());
6
+ return match ? `#${match[1].toLowerCase()}` : null;
7
+ };
8
+ export { normalizeHex };
@@ -0,0 +1,2 @@
1
+ import { ColorInput, ColorInputProps } from "./ColorInput.js";
2
+ export { ColorInput, type ColorInputProps };
@@ -0,0 +1,18 @@
1
+ import { FieldBaseProps } from "../../../lib/types.js";
2
+ import React from "react";
3
+ export interface SelectOption {
4
+ value: string;
5
+ label: string;
6
+ disabled?: boolean;
7
+ }
8
+ export interface SelectProps extends FieldBaseProps {
9
+ options: SelectOption[];
10
+ /** null = nothing picked; the placeholder shows. */
11
+ value: string | null;
12
+ onChange: (value: string) => void;
13
+ onBlur?: () => void;
14
+ name?: string;
15
+ placeholder?: string;
16
+ ref?: React.Ref<HTMLButtonElement>;
17
+ }
18
+ export declare const Select: React.FC<SelectProps>;
@@ -0,0 +1,47 @@
1
+ import { FormField } from "../../molecule/FormField/FormField.js";
2
+ import { Select as Select$1, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../ui/select.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ // src/components/atomic/Select/Select.tsx
5
+ const LEFT_FIELD_REASONS = /* @__PURE__ */ new Set(["outside-press", "focus-out"]);
6
+ const Select = (props) => {
7
+ const { label, hideLabel, description, error, required, disabled, id, "data-testid": testId, options, value, onChange, onBlur, name, placeholder, ref } = props;
8
+ return /* @__PURE__ */ jsx(FormField, {
9
+ label,
10
+ hideLabel,
11
+ description,
12
+ error,
13
+ required,
14
+ disabled,
15
+ id,
16
+ "data-testid": testId,
17
+ children: (control) => /* @__PURE__ */ jsxs(Select$1, {
18
+ items: options,
19
+ value,
20
+ onValueChange: (next) => {
21
+ if (next !== null) onChange(next);
22
+ },
23
+ onOpenChange: (open, details) => {
24
+ if (!open && LEFT_FIELD_REASONS.has(details.reason)) onBlur?.();
25
+ },
26
+ name,
27
+ disabled: control.disabled,
28
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
29
+ ref,
30
+ id: control.id,
31
+ "aria-describedby": control["aria-describedby"],
32
+ "aria-invalid": control["aria-invalid"],
33
+ "aria-required": control["aria-required"],
34
+ onBlur: (event) => {
35
+ if (!event.relatedTarget?.closest("[data-slot=\"select-content\"]")) onBlur?.();
36
+ },
37
+ className: "w-full",
38
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder })
39
+ }), /* @__PURE__ */ jsx(SelectContent, { children: options.map((option) => /* @__PURE__ */ jsx(SelectItem, {
40
+ value: option.value,
41
+ disabled: option.disabled,
42
+ children: option.label
43
+ }, option.value)) })]
44
+ })
45
+ });
46
+ };
47
+ export { Select };
@@ -0,0 +1,2 @@
1
+ import { Select, SelectOption, SelectProps } from "./Select.js";
2
+ export { Select, type SelectOption, type SelectProps };
@@ -0,0 +1,11 @@
1
+ import { FieldBaseProps } from "../../../lib/types.js";
2
+ import React from "react";
3
+ export interface SwitchProps extends FieldBaseProps {
4
+ value: boolean;
5
+ onChange: (value: boolean) => void;
6
+ onBlur?: () => void;
7
+ name?: string;
8
+ readOnly?: boolean;
9
+ ref?: React.Ref<HTMLElement>;
10
+ }
11
+ export declare const Switch: React.FC<SwitchProps>;
@@ -0,0 +1,32 @@
1
+ import { FormField } from "../../molecule/FormField/FormField.js";
2
+ import { Switch as Switch$1 } from "../../ui/switch.js";
3
+ import { jsx } from "react/jsx-runtime";
4
+ // src/components/atomic/Switch/Switch.tsx
5
+ const Switch = (props) => {
6
+ const { label, hideLabel, description, error, required, disabled, id, "data-testid": testId, value, onChange, onBlur, name, readOnly = false, ref } = props;
7
+ return /* @__PURE__ */ jsx(FormField, {
8
+ orientation: "horizontal",
9
+ label,
10
+ hideLabel,
11
+ description,
12
+ error,
13
+ required,
14
+ disabled,
15
+ id,
16
+ "data-testid": testId,
17
+ children: (control) => /* @__PURE__ */ jsx(Switch$1, {
18
+ ref,
19
+ id: control.id,
20
+ "aria-describedby": control["aria-describedby"],
21
+ "aria-invalid": control["aria-invalid"],
22
+ "aria-required": control["aria-required"],
23
+ disabled: control.disabled,
24
+ checked: value,
25
+ onCheckedChange: (checked) => onChange(checked),
26
+ onBlur,
27
+ name,
28
+ readOnly
29
+ })
30
+ });
31
+ };
32
+ export { Switch };
@@ -0,0 +1,2 @@
1
+ import { Switch, SwitchProps } from "./Switch.js";
2
+ export { Switch, type SwitchProps };
@@ -0,0 +1,14 @@
1
+ import { FieldBaseProps } from "../../../lib/types.js";
2
+ import React from "react";
3
+ export interface TextAreaProps extends FieldBaseProps {
4
+ value: string;
5
+ onChange: (value: string) => void;
6
+ onBlur?: () => void;
7
+ name?: string;
8
+ placeholder?: string;
9
+ rows?: number;
10
+ maxLength?: number;
11
+ readOnly?: boolean;
12
+ ref?: React.Ref<HTMLTextAreaElement>;
13
+ }
14
+ export declare const TextArea: React.FC<TextAreaProps>;
@@ -0,0 +1,35 @@
1
+ import { FormField } from "../../molecule/FormField/FormField.js";
2
+ import { Textarea } from "../../ui/textarea.js";
3
+ import { jsx } from "react/jsx-runtime";
4
+ // src/components/atomic/TextArea/TextArea.tsx
5
+ const TextArea = (props) => {
6
+ const { label, hideLabel, description, error, required, disabled, id, "data-testid": testId, value, onChange, onBlur, name, placeholder, rows = 3, maxLength, readOnly = false, ref } = props;
7
+ return /* @__PURE__ */ jsx(FormField, {
8
+ label,
9
+ hideLabel,
10
+ description,
11
+ error,
12
+ required,
13
+ disabled,
14
+ id,
15
+ "data-testid": testId,
16
+ children: (control) => /* @__PURE__ */ jsx(Textarea, {
17
+ ref,
18
+ id: control.id,
19
+ "aria-describedby": control["aria-describedby"],
20
+ "aria-invalid": control["aria-invalid"],
21
+ "aria-required": control["aria-required"],
22
+ disabled: control.disabled,
23
+ name,
24
+ value,
25
+ placeholder,
26
+ rows,
27
+ maxLength,
28
+ readOnly,
29
+ onChange: (event) => onChange(event.target.value),
30
+ onBlur,
31
+ className: "field-sizing-fixed"
32
+ })
33
+ });
34
+ };
35
+ export { TextArea };
@@ -0,0 +1,2 @@
1
+ import { TextArea, TextAreaProps } from "./TextArea.js";
2
+ export { TextArea, type TextAreaProps };
@@ -0,0 +1,18 @@
1
+ import { FieldBaseProps, IconComponent } from "../../../lib/types.js";
2
+ import React from "react";
3
+ export type TextInputType = 'text' | 'email' | 'password' | 'search' | 'tel' | 'url';
4
+ export interface TextInputProps extends FieldBaseProps {
5
+ value: string;
6
+ onChange: (value: string) => void;
7
+ onBlur?: () => void;
8
+ name?: string;
9
+ placeholder?: string;
10
+ type?: TextInputType;
11
+ /** Leading icon, decorative (aria-hidden). */
12
+ icon?: IconComponent;
13
+ readOnly?: boolean;
14
+ /** e.g. 'username', 'current-password' on login forms. */
15
+ autoComplete?: string;
16
+ ref?: React.Ref<HTMLInputElement>;
17
+ }
18
+ export declare const TextInput: React.FC<TextInputProps>;
@@ -0,0 +1,42 @@
1
+ import { FormField } from "../../molecule/FormField/FormField.js";
2
+ import { Input } from "../../ui/input.js";
3
+ import { cn } from "../../../lib/utils.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ // src/components/atomic/TextInput/TextInput.tsx
6
+ const TextInput = (props) => {
7
+ const { label, hideLabel, description, error, required, disabled, id, "data-testid": testId, value, onChange, onBlur, name, placeholder, type = "text", icon: Icon, readOnly = false, autoComplete, ref } = props;
8
+ return /* @__PURE__ */ jsx(FormField, {
9
+ label,
10
+ hideLabel,
11
+ description,
12
+ error,
13
+ required,
14
+ disabled,
15
+ id,
16
+ "data-testid": testId,
17
+ children: (control) => /* @__PURE__ */ jsxs("div", {
18
+ className: "relative",
19
+ children: [Icon && /* @__PURE__ */ jsx(Icon, {
20
+ "aria-hidden": true,
21
+ className: "text-muted-foreground pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2"
22
+ }), /* @__PURE__ */ jsx(Input, {
23
+ ref,
24
+ id: control.id,
25
+ "aria-describedby": control["aria-describedby"],
26
+ "aria-invalid": control["aria-invalid"],
27
+ "aria-required": control["aria-required"],
28
+ disabled: control.disabled,
29
+ type,
30
+ name,
31
+ value,
32
+ placeholder,
33
+ readOnly,
34
+ autoComplete,
35
+ onChange: (event) => onChange(event.target.value),
36
+ onBlur,
37
+ className: cn(Icon && "pl-8")
38
+ })]
39
+ })
40
+ });
41
+ };
42
+ export { TextInput };
@@ -0,0 +1,2 @@
1
+ import { TextInput, TextInputProps, TextInputType } from "./TextInput.js";
2
+ export { TextInput, type TextInputProps, type TextInputType };
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ export type FormLayoutDirection = 'vertical' | 'horizontal';
3
+ export interface FormLayoutProps {
4
+ children: React.ReactNode;
5
+ /** horizontal: fields side by side in equal columns from sm up, stacked on phones. */
6
+ direction?: FormLayoutDirection;
7
+ 'data-testid'?: string;
8
+ }
9
+ export declare const FormLayout: React.FC<FormLayoutProps>;
@@ -0,0 +1,15 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ // src/components/layout/FormLayout/FormLayout.tsx
3
+ const DIRECTION = {
4
+ vertical: "flex flex-col gap-5",
5
+ horizontal: "grid gap-5 sm:auto-cols-fr sm:grid-flow-col"
6
+ };
7
+ const FormLayout = (props) => {
8
+ const { children, direction = "vertical", "data-testid": testId } = props;
9
+ return /* @__PURE__ */ jsx("div", {
10
+ "data-testid": testId,
11
+ className: DIRECTION[direction],
12
+ children
13
+ });
14
+ };
15
+ export { FormLayout };
@@ -0,0 +1,2 @@
1
+ import { FormLayout, FormLayoutDirection, FormLayoutProps } from "./FormLayout.js";
2
+ export { FormLayout, type FormLayoutDirection, type FormLayoutProps };
@@ -0,0 +1,16 @@
1
+ import { FieldBaseProps } from "../../../lib/types.js";
2
+ import React from "react";
3
+ /** Wiring FormField hands to its control; pass each prop by name. */
4
+ export interface FormFieldControlProps {
5
+ id: string;
6
+ 'aria-describedby'?: string;
7
+ 'aria-invalid'?: true;
8
+ 'aria-required'?: true;
9
+ disabled?: boolean;
10
+ }
11
+ export interface FormFieldProps extends FieldBaseProps {
12
+ children: (control: FormFieldControlProps) => React.ReactNode;
13
+ /** horizontal puts the label beside the control (checkbox, switch). */
14
+ orientation?: 'vertical' | 'horizontal';
15
+ }
16
+ export declare const FormField: React.FC<FormFieldProps>;
@@ -0,0 +1,47 @@
1
+ import { Field, FieldContent, FieldDescription, FieldError, FieldLabel } from "../../ui/field.js";
2
+ import { cn } from "../../../lib/utils.js";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ import { useId } from "react";
5
+ // src/components/molecule/FormField/FormField.tsx
6
+ const FormField = (props) => {
7
+ const { children, label, hideLabel = false, description, error, required = false, disabled = false, orientation = "vertical", id: idProp, "data-testid": testId } = props;
8
+ const generatedId = useId();
9
+ const id = idProp ?? generatedId;
10
+ const descriptionId = description ? `${id}-description` : void 0;
11
+ const errorId = error ? `${id}-error` : void 0;
12
+ const control = children({
13
+ id,
14
+ "aria-describedby": [descriptionId, errorId].filter(Boolean).join(" ") || void 0,
15
+ "aria-invalid": error ? true : void 0,
16
+ "aria-required": required ? true : void 0,
17
+ disabled
18
+ });
19
+ const labelNode = /* @__PURE__ */ jsxs(FieldLabel, {
20
+ htmlFor: id,
21
+ className: cn(hideLabel && "sr-only"),
22
+ children: [label, required && /* @__PURE__ */ jsx("span", {
23
+ "aria-hidden": true,
24
+ className: "text-destructive",
25
+ children: "*"
26
+ })]
27
+ });
28
+ const helpNodes = /* @__PURE__ */ jsxs(Fragment, { children: [description && /* @__PURE__ */ jsx(FieldDescription, {
29
+ id: descriptionId,
30
+ children: description
31
+ }), error && /* @__PURE__ */ jsx(FieldError, {
32
+ id: errorId,
33
+ children: error
34
+ })] });
35
+ return /* @__PURE__ */ jsx(Field, {
36
+ orientation,
37
+ "data-invalid": error ? true : void 0,
38
+ "data-disabled": disabled || void 0,
39
+ "data-testid": testId,
40
+ children: orientation === "horizontal" ? /* @__PURE__ */ jsxs(Fragment, { children: [control, /* @__PURE__ */ jsxs(FieldContent, { children: [labelNode, helpNodes] })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
41
+ labelNode,
42
+ control,
43
+ helpNodes
44
+ ] })
45
+ });
46
+ };
47
+ export { FormField };
@@ -0,0 +1,2 @@
1
+ import { FormField, FormFieldControlProps, FormFieldProps } from "./FormField.js";
2
+ export { FormField, type FormFieldControlProps, type FormFieldProps };
@@ -0,0 +1,18 @@
1
+ import { cn } from "cn";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { CheckIcon } from "lucide-react";
4
+ import { Checkbox } from "@base-ui/react/checkbox";
5
+ // src/components/ui/checkbox.tsx
6
+ const Checkbox$1 = ({ className, ...props }) => {
7
+ return /* @__PURE__ */ jsx(Checkbox.Root, {
8
+ "data-slot": "checkbox",
9
+ className: cn("peer border-input group-has-[:focus-visible]/field-label:not-data-checked:border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground group-has-[:focus-visible]/field-label:data-checked:border-primary dark:data-checked:bg-primary relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border transition-colors outline-none group-has-disabled/field:opacity-50 group-has-[:focus-visible]/field-label:ring-0 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-3 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3", className),
10
+ ...props,
11
+ children: /* @__PURE__ */ jsx(Checkbox.Indicator, {
12
+ "data-slot": "checkbox-indicator",
13
+ className: "grid place-content-center text-current transition-none [&>svg]:size-3.5",
14
+ children: /* @__PURE__ */ jsx(CheckIcon, {})
15
+ })
16
+ });
17
+ };
18
+ export { Checkbox$1 as Checkbox };
@@ -0,0 +1,65 @@
1
+ import { Label } from "./label.js";
2
+ import { cva } from "class-variance-authority";
3
+ import { cn } from "cn";
4
+ import { jsx } from "react/jsx-runtime";
5
+ import { useMemo } from "react";
6
+ // src/components/ui/field.tsx
7
+ const fieldVariants = cva("group/field flex w-full gap-2 data-[invalid=true]:text-destructive", {
8
+ variants: { orientation: {
9
+ vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
10
+ horizontal: "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
11
+ responsive: "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
12
+ } },
13
+ defaultVariants: { orientation: "vertical" }
14
+ });
15
+ const Field = ({ className, orientation = "vertical", ...props }) => {
16
+ return /* @__PURE__ */ jsx("div", {
17
+ role: "group",
18
+ "data-slot": "field",
19
+ "data-orientation": orientation,
20
+ className: cn(fieldVariants({ orientation }), className),
21
+ ...props
22
+ });
23
+ };
24
+ const FieldContent = ({ className, ...props }) => {
25
+ return /* @__PURE__ */ jsx("div", {
26
+ "data-slot": "field-content",
27
+ className: cn("group/field-content flex flex-1 flex-col gap-0.5 leading-snug", className),
28
+ ...props
29
+ });
30
+ };
31
+ const FieldLabel = ({ className, ...props }) => {
32
+ return /* @__PURE__ */ jsx(Label, {
33
+ "data-slot": "field-label",
34
+ className: cn("group/field-label peer/field-label has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-muted/50 has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10 flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 *:data-[slot=field]:p-2.5", "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col", className),
35
+ ...props
36
+ });
37
+ };
38
+ const FieldDescription = ({ className, ...props }) => {
39
+ return /* @__PURE__ */ jsx("p", {
40
+ "data-slot": "field-description",
41
+ className: cn("text-muted-foreground text-left text-sm leading-normal font-normal group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5", "last:mt-0 nth-last-2:-mt-1", "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4", className),
42
+ ...props
43
+ });
44
+ };
45
+ const FieldError = ({ className, children, errors, ...props }) => {
46
+ const content = useMemo(() => {
47
+ if (children) return children;
48
+ if (!errors?.length) return null;
49
+ const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()];
50
+ if (uniqueErrors?.length == 1) return uniqueErrors[0]?.message;
51
+ return /* @__PURE__ */ jsx("ul", {
52
+ className: "ml-4 flex list-disc flex-col gap-1",
53
+ children: uniqueErrors.map((error, index) => error?.message && /* @__PURE__ */ jsx("li", { children: error.message }, index))
54
+ });
55
+ }, [children, errors]);
56
+ if (!content) return null;
57
+ return /* @__PURE__ */ jsx("div", {
58
+ role: "alert",
59
+ "data-slot": "field-error",
60
+ className: cn("text-destructive text-sm font-normal", className),
61
+ ...props,
62
+ children: content
63
+ });
64
+ };
65
+ export { Field, FieldContent, FieldDescription, FieldError, FieldLabel };
@@ -0,0 +1,14 @@
1
+ import { cn } from "cn";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import "react";
4
+ import { Input } from "@base-ui/react/input";
5
+ // src/components/ui/input.tsx
6
+ const Input$1 = ({ className, type, ...props }) => {
7
+ return /* @__PURE__ */ jsx(Input, {
8
+ type,
9
+ "data-slot": "input",
10
+ className: cn("border-input file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 disabled:bg-input/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 h-8 w-full min-w-0 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-3 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3 md:text-sm", className),
11
+ ...props
12
+ });
13
+ };
14
+ export { Input$1 as Input };
@@ -0,0 +1,12 @@
1
+ import { cn } from "cn";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import "react";
4
+ // src/components/ui/label.tsx
5
+ const Label = ({ className, ...props }) => {
6
+ return /* @__PURE__ */ jsx("label", {
7
+ "data-slot": "label",
8
+ className: cn("flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", className),
9
+ ...props
10
+ });
11
+ };
12
+ export { Label };
@@ -0,0 +1,75 @@
1
+ import { cn } from "cn";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
4
+ import "react";
5
+ import { Select } from "@base-ui/react/select";
6
+ // src/components/ui/select.tsx
7
+ const Select$1 = Select.Root;
8
+ const SelectValue = ({ className, ...props }) => {
9
+ return /* @__PURE__ */ jsx(Select.Value, {
10
+ "data-slot": "select-value",
11
+ className: cn("flex flex-1 text-left", className),
12
+ ...props
13
+ });
14
+ };
15
+ const SelectTrigger = ({ className, size = "default", children, ...props }) => {
16
+ return /* @__PURE__ */ jsxs(Select.Trigger, {
17
+ "data-slot": "select-trigger",
18
+ "data-size": size,
19
+ className: cn("border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 flex w-fit items-center justify-between gap-1.5 rounded-lg border bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:ring-3 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
20
+ ...props,
21
+ children: [children, /* @__PURE__ */ jsx(Select.Icon, { render: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "text-muted-foreground pointer-events-none size-4" }) })]
22
+ });
23
+ };
24
+ const SelectContent = ({ className, children, side = "bottom", sideOffset = 4, align = "center", alignOffset = 0, alignItemWithTrigger = true, ...props }) => {
25
+ return /* @__PURE__ */ jsx(Select.Portal, { children: /* @__PURE__ */ jsx(Select.Positioner, {
26
+ side,
27
+ sideOffset,
28
+ align,
29
+ alignOffset,
30
+ alignItemWithTrigger,
31
+ className: "isolate z-50",
32
+ children: /* @__PURE__ */ jsxs(Select.Popup, {
33
+ "data-slot": "select-content",
34
+ "data-align-trigger": alignItemWithTrigger,
35
+ className: cn("bg-popover text-popover-foreground ring-foreground/10 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg shadow-md ring-1 duration-100 data-[align-trigger=true]:animate-none", className),
36
+ ...props,
37
+ children: [
38
+ /* @__PURE__ */ jsx(SelectScrollUpButton, {}),
39
+ /* @__PURE__ */ jsx(Select.List, { children }),
40
+ /* @__PURE__ */ jsx(SelectScrollDownButton, {})
41
+ ]
42
+ })
43
+ }) });
44
+ };
45
+ const SelectItem = ({ className, children, ...props }) => {
46
+ return /* @__PURE__ */ jsxs(Select.Item, {
47
+ "data-slot": "select-item",
48
+ className: cn("focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", className),
49
+ ...props,
50
+ children: [/* @__PURE__ */ jsx(Select.ItemText, {
51
+ className: "flex flex-1 shrink-0 gap-2 whitespace-nowrap",
52
+ children
53
+ }), /* @__PURE__ */ jsx(Select.ItemIndicator, {
54
+ render: /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute right-2 flex size-4 items-center justify-center" }),
55
+ children: /* @__PURE__ */ jsx(CheckIcon, { className: "pointer-events-none" })
56
+ })]
57
+ });
58
+ };
59
+ const SelectScrollUpButton = ({ className, ...props }) => {
60
+ return /* @__PURE__ */ jsx(Select.ScrollUpArrow, {
61
+ "data-slot": "select-scroll-up-button",
62
+ className: cn("bg-popover top-0 z-10 flex w-full cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4", className),
63
+ ...props,
64
+ children: /* @__PURE__ */ jsx(ChevronUpIcon, {})
65
+ });
66
+ };
67
+ const SelectScrollDownButton = ({ className, ...props }) => {
68
+ return /* @__PURE__ */ jsx(Select.ScrollDownArrow, {
69
+ "data-slot": "select-scroll-down-button",
70
+ className: cn("bg-popover bottom-0 z-10 flex w-full cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4", className),
71
+ ...props,
72
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, {})
73
+ });
74
+ };
75
+ export { Select$1 as Select, SelectContent, SelectItem, SelectScrollDownButton, SelectScrollUpButton, SelectTrigger, SelectValue };
@@ -0,0 +1,18 @@
1
+ "use client";
2
+ import { cn } from "cn";
3
+ import { jsx } from "react/jsx-runtime";
4
+ import { Switch } from "@base-ui/react/switch";
5
+ // src/components/ui/switch.tsx
6
+ const Switch$1 = ({ className, size = "default", ...props }) => {
7
+ return /* @__PURE__ */ jsx(Switch.Root, {
8
+ "data-slot": "switch",
9
+ "data-size": size,
10
+ className: cn("peer group/switch focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none group-has-[:focus-visible]/field-label:border-transparent group-has-[:focus-visible]/field-label:ring-0 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-3 aria-invalid:ring-3 data-disabled:cursor-not-allowed data-disabled:opacity-50 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px]", className),
11
+ ...props,
12
+ children: /* @__PURE__ */ jsx(Switch.Thumb, {
13
+ "data-slot": "switch-thumb",
14
+ className: "bg-background dark:data-checked:bg-primary-foreground dark:data-unchecked:bg-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0"
15
+ })
16
+ });
17
+ };
18
+ export { Switch$1 as Switch };
@@ -0,0 +1,12 @@
1
+ import { cn } from "cn";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import "react";
4
+ // src/components/ui/textarea.tsx
5
+ const Textarea = ({ className, ...props }) => {
6
+ return /* @__PURE__ */ jsx("textarea", {
7
+ "data-slot": "textarea",
8
+ className: cn("border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 disabled:bg-input/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 flex field-sizing-content min-h-16 w-full rounded-lg border bg-transparent px-2.5 py-2 text-base transition-colors outline-none focus-visible:ring-3 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3 md:text-sm", className),
9
+ ...props
10
+ });
11
+ };
12
+ export { Textarea };
package/dist/index.d.ts CHANGED
@@ -3,19 +3,35 @@ import { Badge, BadgeProps } from "./components/atomic/Badge/Badge.js";
3
3
  import "./components/atomic/Button/index.js";
4
4
  import { ButtonVariant } from "./components/atomic/Button/buttonVariant.js";
5
5
  import { Button, ButtonProps } from "./components/atomic/Button/Button.js";
6
+ import "./components/atomic/Checkbox/index.js";
7
+ import { Checkbox, CheckboxProps } from "./components/atomic/Checkbox/Checkbox.js";
8
+ import "./components/atomic/ColorInput/index.js";
9
+ import { ColorInput, ColorInputProps } from "./components/atomic/ColorInput/ColorInput.js";
6
10
  import "./components/atomic/Divider/index.js";
7
11
  import { Divider, DividerProps } from "./components/atomic/Divider/Divider.js";
8
12
  import "./components/atomic/Heading/index.js";
9
13
  import { Heading, HeadingLevel, HeadingProps } from "./components/atomic/Heading/Heading.js";
14
+ import "./components/atomic/Switch/index.js";
15
+ import { Switch, SwitchProps } from "./components/atomic/Switch/Switch.js";
10
16
  import "./components/atomic/Text/index.js";
11
17
  import { Text, TextProps, TextSize, TextTone, TextWeight } from "./components/atomic/Text/Text.js";
18
+ import "./components/atomic/TextArea/index.js";
19
+ import { TextArea, TextAreaProps } from "./components/atomic/TextArea/TextArea.js";
20
+ import "./components/atomic/TextInput/index.js";
21
+ import { TextInput, TextInputProps, TextInputType } from "./components/atomic/TextInput/TextInput.js";
12
22
  import "./components/atomic/IconButton/index.js";
13
23
  import { IconButton, IconButtonProps, IconButtonVariant } from "./components/atomic/IconButton/IconButton.js";
24
+ import "./components/atomic/Select/index.js";
25
+ import { Select, SelectOption, SelectProps } from "./components/atomic/Select/Select.js";
14
26
  import "./components/atomic/Skeleton/index.js";
15
27
  import { Skeleton, SkeletonProps, SkeletonShape } from "./components/atomic/Skeleton/Skeleton.js";
16
28
  import "./components/atomic/Spinner/index.js";
17
29
  import { Spinner, SpinnerProps } from "./components/atomic/Spinner/Spinner.js";
30
+ import "./components/layout/FormLayout/index.js";
31
+ import { FormLayout, FormLayoutDirection, FormLayoutProps } from "./components/layout/FormLayout/FormLayout.js";
32
+ import "./components/molecule/FormField/index.js";
33
+ import { FormField, FormFieldControlProps, FormFieldProps } from "./components/molecule/FormField/FormField.js";
18
34
  import "./components/layout/Stack/index.js";
19
35
  import { HStack, HStackProps, Stack, StackAlign, StackGap, StackJustify, StackProps, VStack, VStackProps } from "./components/layout/Stack/Stack.js";
20
- import { IconComponent, Size, Tone } from "./lib/types.js";
21
- export { Badge, type BadgeProps, Button, type ButtonProps, type ButtonVariant, Divider, type DividerProps, HStack, type HStackProps, Heading, type HeadingLevel, type HeadingProps, IconButton, type IconButtonProps, type IconButtonVariant, type IconComponent, type Size, Skeleton, type SkeletonProps, type SkeletonShape, Spinner, type SpinnerProps, Stack, type StackAlign, type StackGap, type StackJustify, type StackProps, Text, type TextProps, type TextSize, type TextTone, type TextWeight, type Tone, VStack, type VStackProps };
36
+ import { FieldBaseProps, IconComponent, Size, Tone } from "./lib/types.js";
37
+ export { Badge, type BadgeProps, Button, type ButtonProps, type ButtonVariant, Checkbox, type CheckboxProps, ColorInput, type ColorInputProps, Divider, type DividerProps, type FieldBaseProps, FormField, type FormFieldControlProps, type FormFieldProps, FormLayout, type FormLayoutDirection, type FormLayoutProps, HStack, type HStackProps, Heading, type HeadingLevel, type HeadingProps, IconButton, type IconButtonProps, type IconButtonVariant, type IconComponent, Select, type SelectOption, type SelectProps, type Size, Skeleton, type SkeletonProps, type SkeletonShape, Spinner, type SpinnerProps, Stack, type StackAlign, type StackGap, type StackJustify, type StackProps, Switch, type SwitchProps, Text, TextArea, type TextAreaProps, TextInput, type TextInputProps, type TextInputType, type TextProps, type TextSize, type TextTone, type TextWeight, type Tone, VStack, type VStackProps };
package/dist/index.js CHANGED
@@ -1,10 +1,18 @@
1
1
  import { Badge } from "./components/atomic/Badge/Badge.js";
2
2
  import { Button } from "./components/atomic/Button/Button.js";
3
+ import { Checkbox } from "./components/atomic/Checkbox/Checkbox.js";
4
+ import { ColorInput } from "./components/atomic/ColorInput/ColorInput.js";
3
5
  import { Divider } from "./components/atomic/Divider/Divider.js";
4
6
  import { Heading } from "./components/atomic/Heading/Heading.js";
7
+ import { Switch } from "./components/atomic/Switch/Switch.js";
5
8
  import { Text } from "./components/atomic/Text/Text.js";
9
+ import { TextArea } from "./components/atomic/TextArea/TextArea.js";
10
+ import { TextInput } from "./components/atomic/TextInput/TextInput.js";
6
11
  import { IconButton } from "./components/atomic/IconButton/IconButton.js";
12
+ import { Select } from "./components/atomic/Select/Select.js";
7
13
  import { Skeleton } from "./components/atomic/Skeleton/Skeleton.js";
8
14
  import { Spinner } from "./components/atomic/Spinner/Spinner.js";
15
+ import { FormLayout } from "./components/layout/FormLayout/FormLayout.js";
16
+ import { FormField } from "./components/molecule/FormField/FormField.js";
9
17
  import { HStack, Stack, VStack } from "./components/layout/Stack/Stack.js";
10
- export { Badge, Button, Divider, HStack, Heading, IconButton, Skeleton, Spinner, Stack, Text, VStack };
18
+ export { Badge, Button, Checkbox, ColorInput, Divider, FormField, FormLayout, HStack, Heading, IconButton, Select, Skeleton, Spinner, Stack, Switch, Text, TextArea, TextInput, VStack };
@@ -7,4 +7,18 @@ export type IconComponent = React.ComponentType<{
7
7
  className?: string;
8
8
  'aria-hidden'?: boolean;
9
9
  'data-icon'?: string;
10
- }>;
10
+ }>;
11
+ /** Label, help and error props every form input shares (rendered by FormField). */
12
+ export interface FieldBaseProps {
13
+ /** Accessible name; always required, even when hidden. */
14
+ label: string;
15
+ /** Hides the label visually; screen readers still announce it. */
16
+ hideLabel?: boolean;
17
+ description?: string;
18
+ /** Message shown under the field; also sets aria-invalid. */
19
+ error?: string;
20
+ required?: boolean;
21
+ disabled?: boolean;
22
+ id?: string;
23
+ 'data-testid'?: string;
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ashwa-io/ui",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Ashwa design system: locked React components on shadcn/ui (Base UI) and Tailwind v4, with per-org brand theming.",
5
5
  "license": "MIT",
6
6
  "type": "module",