@nphuonha/ui-kit 0.0.0-stage → 0.2.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.
Files changed (105) hide show
  1. package/CONVENTIONS.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +136 -2
  4. package/dist/components/ui/_shared/icon-provider.d.ts +18 -0
  5. package/dist/components/ui/_shared/icon-provider.js +26 -0
  6. package/dist/components/ui/alert/alert-variants.d.ts +5 -0
  7. package/dist/components/ui/alert/alert-variants.js +13 -0
  8. package/dist/components/ui/alert/alert.d.ts +9 -0
  9. package/dist/components/ui/alert/alert.js +13 -0
  10. package/dist/components/ui/alert/index.d.ts +2 -0
  11. package/dist/components/ui/alert/index.js +2 -0
  12. package/dist/components/ui/badge/badge-variants.d.ts +5 -0
  13. package/dist/components/ui/badge/badge-variants.js +16 -0
  14. package/dist/components/ui/badge/badge.d.ts +6 -0
  15. package/dist/components/ui/badge/badge.js +6 -0
  16. package/dist/components/ui/badge/index.d.ts +2 -0
  17. package/dist/components/ui/badge/index.js +2 -0
  18. package/dist/components/ui/button/button-variants.d.ts +7 -0
  19. package/dist/components/ui/button/button-variants.js +29 -0
  20. package/dist/components/ui/button/button.d.ts +10 -0
  21. package/dist/components/ui/button/button.js +12 -0
  22. package/dist/components/ui/button/index.d.ts +2 -0
  23. package/dist/components/ui/button/index.js +2 -0
  24. package/dist/components/ui/card/card.d.ts +7 -0
  25. package/dist/components/ui/card/card.js +20 -0
  26. package/dist/components/ui/card/index.d.ts +1 -0
  27. package/dist/components/ui/card/index.js +1 -0
  28. package/dist/components/ui/checkbox/checkbox.d.ts +8 -0
  29. package/dist/components/ui/checkbox/checkbox.js +18 -0
  30. package/dist/components/ui/checkbox/index.d.ts +1 -0
  31. package/dist/components/ui/checkbox/index.js +1 -0
  32. package/dist/components/ui/dialog/dialog.d.ts +15 -0
  33. package/dist/components/ui/dialog/dialog.js +10 -0
  34. package/dist/components/ui/dialog/index.d.ts +1 -0
  35. package/dist/components/ui/dialog/index.js +1 -0
  36. package/dist/components/ui/field/field.d.ts +11 -0
  37. package/dist/components/ui/field/field.js +22 -0
  38. package/dist/components/ui/field/index.d.ts +1 -0
  39. package/dist/components/ui/field/index.js +1 -0
  40. package/dist/components/ui/icon-button/icon-button.d.ts +7 -0
  41. package/dist/components/ui/icon-button/icon-button.js +7 -0
  42. package/dist/components/ui/icon-button/index.d.ts +1 -0
  43. package/dist/components/ui/icon-button/index.js +1 -0
  44. package/dist/components/ui/input/index.d.ts +1 -0
  45. package/dist/components/ui/input/index.js +1 -0
  46. package/dist/components/ui/input/input.d.ts +5 -0
  47. package/dist/components/ui/input/input.js +6 -0
  48. package/dist/components/ui/label/index.d.ts +1 -0
  49. package/dist/components/ui/label/index.js +1 -0
  50. package/dist/components/ui/label/label.d.ts +5 -0
  51. package/dist/components/ui/label/label.js +6 -0
  52. package/dist/components/ui/popover/index.d.ts +1 -0
  53. package/dist/components/ui/popover/index.js +1 -0
  54. package/dist/components/ui/popover/popover.d.ts +14 -0
  55. package/dist/components/ui/popover/popover.js +7 -0
  56. package/dist/components/ui/radio-group/index.d.ts +1 -0
  57. package/dist/components/ui/radio-group/index.js +1 -0
  58. package/dist/components/ui/radio-group/radio-group.d.ts +17 -0
  59. package/dist/components/ui/radio-group/radio-group.js +17 -0
  60. package/dist/components/ui/select/index.d.ts +1 -0
  61. package/dist/components/ui/select/index.js +1 -0
  62. package/dist/components/ui/select/select.d.ts +21 -0
  63. package/dist/components/ui/select/select.js +10 -0
  64. package/dist/components/ui/separator/index.d.ts +1 -0
  65. package/dist/components/ui/separator/index.js +1 -0
  66. package/dist/components/ui/separator/separator.d.ts +6 -0
  67. package/dist/components/ui/separator/separator.js +5 -0
  68. package/dist/components/ui/sheet/index.d.ts +1 -0
  69. package/dist/components/ui/sheet/index.js +1 -0
  70. package/dist/components/ui/sheet/sheet.d.ts +15 -0
  71. package/dist/components/ui/sheet/sheet.js +15 -0
  72. package/dist/components/ui/spinner/index.d.ts +1 -0
  73. package/dist/components/ui/spinner/index.js +1 -0
  74. package/dist/components/ui/spinner/spinner.d.ts +5 -0
  75. package/dist/components/ui/spinner/spinner.js +8 -0
  76. package/dist/components/ui/switch/index.d.ts +1 -0
  77. package/dist/components/ui/switch/index.js +1 -0
  78. package/dist/components/ui/switch/switch.d.ts +7 -0
  79. package/dist/components/ui/switch/switch.js +15 -0
  80. package/dist/components/ui/tabs/index.d.ts +1 -0
  81. package/dist/components/ui/tabs/index.js +1 -0
  82. package/dist/components/ui/tabs/tabs.d.ts +14 -0
  83. package/dist/components/ui/tabs/tabs.js +7 -0
  84. package/dist/components/ui/textarea/index.d.ts +1 -0
  85. package/dist/components/ui/textarea/index.js +1 -0
  86. package/dist/components/ui/textarea/textarea.d.ts +5 -0
  87. package/dist/components/ui/textarea/textarea.js +6 -0
  88. package/dist/components/ui/toast/index.d.ts +1 -0
  89. package/dist/components/ui/toast/index.js +1 -0
  90. package/dist/components/ui/toast/toast.d.ts +19 -0
  91. package/dist/components/ui/toast/toast.js +20 -0
  92. package/dist/components/ui/tooltip/index.d.ts +1 -0
  93. package/dist/components/ui/tooltip/index.js +1 -0
  94. package/dist/components/ui/tooltip/tooltip.d.ts +12 -0
  95. package/dist/components/ui/tooltip/tooltip.js +9 -0
  96. package/dist/index.d.ts +27 -0
  97. package/dist/index.js +29 -0
  98. package/dist/lib/color.d.ts +16 -0
  99. package/dist/lib/color.js +118 -0
  100. package/dist/lib/utils.d.ts +7 -0
  101. package/dist/lib/utils.js +5 -0
  102. package/dist/styles/base.css +212 -0
  103. package/dist/styles/theme.css +121 -0
  104. package/dist/styles.css +5 -0
  105. package/package.json +184 -4
@@ -0,0 +1,6 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../../lib/utils.js";
5
+ export const Input = React.forwardRef(({ className, type = "text", error, ...props }, ref) => (_jsx("input", { ref: ref, type: type, "aria-invalid": error || undefined, className: cn("surface-inset flex h-10 w-full rounded-field bg-field px-3.5 py-2 text-sm text-field-foreground transition-[background-color,box-shadow] duration-[var(--duration-ui)] ease-[var(--ease-out-ui)] placeholder:text-muted-foreground/70 hover:bg-field-hover focus-visible:bg-field focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-45", error && "bg-destructive/10 focus-visible:ring-destructive/30", className), ...props })));
6
+ Input.displayName = "Input";
@@ -0,0 +1 @@
1
+ export { Label, type LabelProps } from "./label.js";
@@ -0,0 +1 @@
1
+ export { Label } from "./label.js";
@@ -0,0 +1,5 @@
1
+ import * as React from "react";
2
+ export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
3
+ requiredMark?: boolean;
4
+ }
5
+ export declare const Label: React.ForwardRefExoticComponent<LabelProps & React.RefAttributes<HTMLLabelElement>>;
@@ -0,0 +1,6 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../../lib/utils.js";
5
+ export const Label = React.forwardRef(({ className, requiredMark, children, ...props }, ref) => (_jsxs("label", { ref: ref, className: cn("text-sm font-medium leading-none text-foreground", className), ...props, children: [children, requiredMark ? (_jsx("span", { "aria-hidden": "true", className: "ml-0.5 text-destructive", children: "*" })) : null] })));
6
+ Label.displayName = "Label";
@@ -0,0 +1 @@
1
+ export { Popover, type PopoverProps } from "./popover.js";
@@ -0,0 +1 @@
1
+ export { Popover } from "./popover.js";
@@ -0,0 +1,14 @@
1
+ import * as React from "react";
2
+ export interface PopoverProps {
3
+ trigger: React.ReactElement;
4
+ children: React.ReactNode;
5
+ title?: React.ReactNode;
6
+ description?: React.ReactNode;
7
+ side?: "top" | "bottom" | "left" | "right";
8
+ align?: "start" | "center" | "end";
9
+ open?: boolean;
10
+ defaultOpen?: boolean;
11
+ onOpenChange?: (open: boolean) => void;
12
+ className?: string;
13
+ }
14
+ export declare function Popover({ trigger, children, title, description, side, align, open, defaultOpen, onOpenChange, className, }: PopoverProps): React.JSX.Element;
@@ -0,0 +1,7 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
4
+ import { cn } from "../../../lib/utils.js";
5
+ export function Popover({ trigger, children, title, description, side = "bottom", align = "center", open, defaultOpen, onOpenChange, className, }) {
6
+ return (_jsxs(PopoverPrimitive.Root, { open: open, defaultOpen: defaultOpen, onOpenChange: onOpenChange, children: [_jsx(PopoverPrimitive.Trigger, { render: trigger }), _jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Positioner, { side: side, align: align, sideOffset: 8, children: _jsxs(PopoverPrimitive.Popup, { className: cn("surface-curved relative z-50 w-72 rounded-overlay bg-popover p-4 text-popover-foreground shadow-[var(--shadow-overlay)] outline-none", "origin-[var(--transform-origin)] transition-[opacity,transform] duration-[var(--duration-ui)] ease-[var(--ease-out-ui)]", "data-[starting-style]:opacity-0 data-[starting-style]:scale-[0.97] data-[ending-style]:opacity-0 data-[ending-style]:scale-[0.97]", className), children: [title ? _jsx(PopoverPrimitive.Title, { className: "text-sm font-semibold leading-none", children: title }) : null, description ? _jsx(PopoverPrimitive.Description, { className: "mt-1 text-sm text-muted-foreground", children: description }) : null, _jsx("div", { className: cn((title || description) && "mt-3"), children: children })] }) }) })] }));
7
+ }
@@ -0,0 +1 @@
1
+ export { RadioGroup, type RadioGroupProps, type RadioOption } from "./radio-group.js";
@@ -0,0 +1 @@
1
+ export { RadioGroup } from "./radio-group.js";
@@ -0,0 +1,17 @@
1
+ import * as React from "react";
2
+ import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
3
+ export interface RadioOption {
4
+ value: string;
5
+ label: React.ReactNode;
6
+ description?: React.ReactNode;
7
+ disabled?: boolean;
8
+ }
9
+ export interface RadioGroupProps extends Omit<React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive>, "children"> {
10
+ options?: RadioOption[];
11
+ label?: React.ReactNode;
12
+ description?: React.ReactNode;
13
+ error?: React.ReactNode;
14
+ orientation?: "vertical" | "horizontal";
15
+ children?: React.ReactNode;
16
+ }
17
+ export declare function RadioGroup({ className, options, label, description, error, orientation, children, ...props }: RadioGroupProps): React.JSX.Element;
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
5
+ import { Radio } from "@base-ui/react/radio";
6
+ import { cn } from "../../../lib/utils.js";
7
+ export function RadioGroup({ className, options, label, description, error, orientation = "vertical", children, ...props }) {
8
+ const groupId = React.useId();
9
+ const labelId = `${groupId}-label`;
10
+ const descriptionId = `${groupId}-description`;
11
+ return (_jsxs("div", { className: cn("flex flex-col gap-2", className), children: [label ? (_jsx("div", { id: labelId, className: "text-sm font-medium text-foreground", children: label })) : null, _jsx(RadioGroupPrimitive, { "aria-labelledby": label ? labelId : undefined, "aria-describedby": description ? descriptionId : undefined, className: cn("flex gap-3", orientation === "vertical" ? "flex-col" : "flex-row flex-wrap"), ...props, children: options
12
+ ? options.map((opt, index) => {
13
+ const optionId = `${groupId}-option-${index}`;
14
+ return (_jsxs("label", { className: cn("flex items-start gap-2.5 text-sm", opt.disabled && "opacity-45"), children: [_jsx(Radio.Root, { value: opt.value, disabled: opt.disabled, "aria-labelledby": `${optionId}-label`, "aria-describedby": opt.description ? `${optionId}-description` : undefined, className: "surface-inset mt-0.5 inline-flex size-5 shrink-0 items-center justify-center rounded-full bg-field outline-none transition-[background-color,box-shadow] duration-[var(--duration-ui)] focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[checked]:bg-primary", children: _jsx(Radio.Indicator, { className: "size-2 rounded-full bg-primary-foreground data-[unchecked]:hidden" }) }), _jsxs("span", { className: "grid gap-0.5", children: [_jsx("span", { id: `${optionId}-label`, className: "font-medium leading-none text-foreground", children: opt.label }), opt.description ? (_jsx("span", { id: `${optionId}-description`, className: "text-xs text-muted-foreground", children: opt.description })) : null] })] }, opt.value));
15
+ })
16
+ : children }), description ? (_jsx("p", { id: descriptionId, className: "text-xs text-muted-foreground", children: description })) : null, error ? _jsx("p", { role: "alert", className: "text-xs font-medium text-destructive", children: error }) : null] }));
17
+ }
@@ -0,0 +1 @@
1
+ export { Select, type SelectProps, type SelectOption } from "./select.js";
@@ -0,0 +1 @@
1
+ export { Select } from "./select.js";
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+ export interface SelectOption {
3
+ value: string;
4
+ label: React.ReactNode;
5
+ disabled?: boolean;
6
+ }
7
+ export interface SelectProps {
8
+ options: SelectOption[];
9
+ value?: string | null;
10
+ defaultValue?: string | null;
11
+ onValueChange?: (value: string | null) => void;
12
+ placeholder?: string;
13
+ disabled?: boolean;
14
+ error?: boolean;
15
+ className?: string;
16
+ name?: string;
17
+ id?: string;
18
+ "aria-label"?: string;
19
+ "aria-labelledby"?: string;
20
+ }
21
+ export declare function Select({ options, value, defaultValue, onValueChange, placeholder, disabled, error, className, name, id, ...aria }: SelectProps): React.JSX.Element;
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Select as SelectPrimitive } from "@base-ui/react/select";
4
+ import { cn } from "../../../lib/utils.js";
5
+ import { useKitIcon } from "../_shared/icon-provider.js";
6
+ export function Select({ options, value, defaultValue, onValueChange, placeholder = "Select…", disabled, error, className, name, id, ...aria }) {
7
+ const ChevronIcon = useKitIcon("chevron");
8
+ const CheckIcon = useKitIcon("check");
9
+ return (_jsxs(SelectPrimitive.Root, { value: value, defaultValue: defaultValue, onValueChange: onValueChange, disabled: disabled, name: name, children: [_jsxs(SelectPrimitive.Trigger, { id: id, "aria-invalid": error || undefined, className: cn("surface-inset inline-flex h-10 w-full items-center justify-between gap-2 rounded-field bg-field px-3.5 text-sm text-field-foreground outline-none transition-[background-color,box-shadow] duration-[var(--duration-ui)] hover:bg-field-hover focus-visible:bg-field focus-visible:ring-2 focus-visible:ring-ring/35 focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[disabled]:cursor-not-allowed data-[disabled]:opacity-45", error && "bg-destructive/10 focus-visible:ring-destructive/30", className), ...aria, children: [_jsx(SelectPrimitive.Value, { children: (val) => options.find((o) => o.value === val)?.label ?? _jsx("span", { className: "text-muted-foreground/70", children: placeholder }) }), _jsx(SelectPrimitive.Icon, { className: "shrink-0 text-muted-foreground", children: _jsx(ChevronIcon, { size: 14 }) })] }), _jsx(SelectPrimitive.Portal, { children: _jsx(SelectPrimitive.Positioner, { sideOffset: 6, className: "z-50 outline-none", children: _jsx(SelectPrimitive.Popup, { className: cn("surface-curved relative min-w-[var(--anchor-width)] rounded-overlay bg-popover p-1.5 text-popover-foreground shadow-[var(--shadow-overlay)] outline-none", "origin-[var(--transform-origin)] transition-[opacity,transform] duration-[var(--duration-ui)] data-[starting-style]:opacity-0 data-[starting-style]:scale-[0.97] data-[ending-style]:opacity-0 data-[ending-style]:scale-[0.97]"), children: _jsx(SelectPrimitive.List, { children: options.map((opt) => (_jsxs(SelectPrimitive.Item, { value: opt.value, disabled: opt.disabled, className: "flex cursor-default select-none items-center gap-2 rounded-control px-2.5 py-2 text-sm outline-none data-[highlighted]:bg-surface data-[selected]:bg-primary-soft data-[selected]:font-medium data-[selected]:text-primary-soft-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-45", children: [_jsx(SelectPrimitive.ItemIndicator, { className: "inline-flex w-4 shrink-0 items-center justify-center text-primary", children: _jsx(CheckIcon, { size: 14 }) }), _jsx(SelectPrimitive.ItemText, { children: opt.label })] }, opt.value))) }) }) }) })] }));
10
+ }
@@ -0,0 +1 @@
1
+ export { Separator, type SeparatorProps } from "./separator.js";
@@ -0,0 +1 @@
1
+ export { Separator } from "./separator.js";
@@ -0,0 +1,6 @@
1
+ import * as React from "react";
2
+ export interface SeparatorProps extends React.HTMLAttributes<HTMLDivElement> {
3
+ orientation?: "horizontal" | "vertical";
4
+ decorative?: boolean;
5
+ }
6
+ export declare function Separator({ className, orientation, decorative, ...props }: SeparatorProps): React.JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../../../lib/utils.js";
3
+ export function Separator({ className, orientation = "horizontal", decorative = true, ...props }) {
4
+ return (_jsx("div", { role: decorative ? "none" : "separator", "aria-orientation": decorative ? undefined : orientation, className: cn("shrink-0 bg-border", orientation === "horizontal" ? "h-px w-full" : "h-full w-px", className), ...props }));
5
+ }
@@ -0,0 +1 @@
1
+ export { Sheet, type SheetProps } from "./sheet.js";
@@ -0,0 +1 @@
1
+ export { Sheet } from "./sheet.js";
@@ -0,0 +1,15 @@
1
+ import * as React from "react";
2
+ export interface SheetProps {
3
+ open?: boolean;
4
+ defaultOpen?: boolean;
5
+ onOpenChange?: (open: boolean) => void;
6
+ trigger?: React.ReactElement;
7
+ title?: React.ReactNode;
8
+ description?: React.ReactNode;
9
+ children: React.ReactNode;
10
+ footer?: React.ReactNode;
11
+ side?: "right" | "left" | "bottom";
12
+ className?: string;
13
+ showClose?: boolean;
14
+ }
15
+ export declare function Sheet({ open, defaultOpen, onOpenChange, trigger, title, description, children, footer, side, className, showClose, }: SheetProps): React.JSX.Element;
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
4
+ import { cn } from "../../../lib/utils.js";
5
+ import { IconButton } from "../icon-button/index.js";
6
+ import { useKitIcon } from "../_shared/icon-provider.js";
7
+ const sideStyles = {
8
+ right: "inset-y-2 right-2 h-[calc(100%-1rem)] w-[calc(100%-1rem)] max-w-md rounded-overlay data-[starting-style]:translate-x-[calc(100%+0.5rem)] data-[ending-style]:translate-x-[calc(100%+0.5rem)]",
9
+ left: "inset-y-2 left-2 h-[calc(100%-1rem)] w-[calc(100%-1rem)] max-w-md rounded-overlay data-[starting-style]:-translate-x-[calc(100%+0.5rem)] data-[ending-style]:-translate-x-[calc(100%+0.5rem)]",
10
+ bottom: "inset-x-2 bottom-2 max-h-[85vh] w-[calc(100%-1rem)] rounded-overlay data-[starting-style]:translate-y-[calc(100%+0.5rem)] data-[ending-style]:translate-y-[calc(100%+0.5rem)]",
11
+ };
12
+ export function Sheet({ open, defaultOpen, onOpenChange, trigger, title, description, children, footer, side = "right", className, showClose = true, }) {
13
+ const CloseIcon = useKitIcon("close");
14
+ return (_jsxs(DialogPrimitive.Root, { open: open, defaultOpen: defaultOpen, onOpenChange: onOpenChange, children: [trigger ? _jsx(DialogPrimitive.Trigger, { render: trigger }) : null, _jsxs(DialogPrimitive.Portal, { children: [_jsx(DialogPrimitive.Backdrop, { className: "fixed inset-0 z-50 bg-overlay backdrop-blur-[2px] transition-opacity duration-[var(--duration-ui-slow)] data-[starting-style]:opacity-0 data-[ending-style]:opacity-0" }), _jsxs(DialogPrimitive.Popup, { className: cn("surface-curved fixed z-50 flex flex-col gap-5 bg-popover p-6 text-popover-foreground shadow-[var(--shadow-overlay)] outline-none", "transition-transform duration-[var(--duration-ui-slow)] ease-[var(--ease-out-ui)]", sideStyles[side], className), children: [(title || description || showClose) && (_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "grid gap-1", children: [title ? _jsx(DialogPrimitive.Title, { className: "text-lg font-semibold tracking-[-0.02em] leading-tight", children: title }) : null, description ? _jsx(DialogPrimitive.Description, { className: "text-sm leading-relaxed text-muted-foreground", children: description }) : null] }), showClose ? (_jsx(DialogPrimitive.Close, { render: _jsx(IconButton, { "aria-label": "Close", variant: "ghost", size: "icon-sm", className: "-mr-2 -mt-2 shrink-0", children: _jsx(CloseIcon, { size: 16 }) }) })) : null] })), _jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: children }), footer ? _jsx("div", { className: "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", children: footer }) : null] })] })] }));
15
+ }
@@ -0,0 +1 @@
1
+ export { Spinner, type SpinnerProps } from "./spinner.js";
@@ -0,0 +1 @@
1
+ export { Spinner } from "./spinner.js";
@@ -0,0 +1,5 @@
1
+ import * as React from "react";
2
+ export interface SpinnerProps extends React.HTMLAttributes<SVGSVGElement> {
3
+ size?: number;
4
+ }
5
+ export declare function Spinner({ size, className, ...props }: SpinnerProps): React.JSX.Element;
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { cn } from "../../../lib/utils.js";
4
+ import { useKitIcon } from "../_shared/icon-provider.js";
5
+ export function Spinner({ size = 18, className, ...props }) {
6
+ const Icon = useKitIcon("spinner");
7
+ return (_jsx(Icon, { size: size, className: cn("animate-spin text-current", className), "aria-hidden": "true", ...props }));
8
+ }
@@ -0,0 +1 @@
1
+ export { Switch, type SwitchProps } from "./switch.js";
@@ -0,0 +1 @@
1
+ export { Switch } from "./switch.js";
@@ -0,0 +1,7 @@
1
+ import * as React from "react";
2
+ import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
3
+ export interface SwitchProps extends Omit<React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>, "children"> {
4
+ label?: React.ReactNode;
5
+ description?: React.ReactNode;
6
+ }
7
+ export declare const Switch: React.ForwardRefExoticComponent<SwitchProps & React.RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
5
+ import { cn } from "../../../lib/utils.js";
6
+ export const Switch = React.forwardRef(({ className, label, description, id, ...props }, ref) => {
7
+ const autoId = React.useId();
8
+ const controlId = id ?? autoId;
9
+ const descriptionId = description ? `${controlId}-description` : undefined;
10
+ const control = (_jsx(SwitchPrimitive.Root, { ref: ref, id: controlId, "aria-describedby": descriptionId, className: cn("surface-curved-soft relative peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-pill bg-muted-foreground/30 p-0.5 transition-[background-color] duration-[var(--duration-ui)] ease-[var(--ease-out-ui)] outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[checked]:bg-primary disabled:cursor-not-allowed disabled:opacity-45", className), ...props, children: _jsx(SwitchPrimitive.Thumb, { className: "surface-curved-pill relative pointer-events-none block h-5 w-7 rounded-pill bg-white shadow-[0_1px_3px_rgb(0_0_0_/_0.2)] transition-[transform,width] duration-[var(--duration-ui)] ease-[var(--ease-out-ui)] data-[checked]:translate-x-3 data-[unchecked]:translate-x-0 motion-safe:active:w-8 data-[checked]:motion-safe:active:translate-x-2" }) }));
11
+ if (!label && !description)
12
+ return control;
13
+ return (_jsxs("div", { className: "flex items-start gap-3", children: [control, _jsxs("div", { className: "grid gap-0.5 pt-0.5", children: [label ? _jsx("label", { htmlFor: controlId, className: "cursor-pointer text-sm font-medium leading-none text-foreground", children: label }) : null, description ? _jsx("p", { id: descriptionId, className: "text-xs text-muted-foreground", children: description }) : null] })] }));
14
+ });
15
+ Switch.displayName = "Switch";
@@ -0,0 +1 @@
1
+ export { Tabs, type TabsProps, type TabItem } from "./tabs.js";
@@ -0,0 +1 @@
1
+ export { Tabs } from "./tabs.js";
@@ -0,0 +1,14 @@
1
+ import * as React from "react";
2
+ import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
3
+ export interface TabItem {
4
+ value: string;
5
+ label: React.ReactNode;
6
+ content: React.ReactNode;
7
+ disabled?: boolean;
8
+ }
9
+ export interface TabsProps extends Omit<React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>, "children"> {
10
+ items: TabItem[];
11
+ listClassName?: string;
12
+ panelClassName?: string;
13
+ }
14
+ export declare function Tabs({ items, className, listClassName, panelClassName, ...props }: TabsProps): React.JSX.Element;
@@ -0,0 +1,7 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
4
+ import { cn } from "../../../lib/utils.js";
5
+ export function Tabs({ items, className, listClassName, panelClassName, ...props }) {
6
+ return (_jsxs(TabsPrimitive.Root, { className: cn("w-full", className), ...props, children: [_jsxs(TabsPrimitive.List, { className: cn("surface-curved-soft relative inline-flex h-10 items-center gap-0.5 rounded-pill bg-surface p-1", listClassName), children: [_jsx(TabsPrimitive.Indicator, { className: "surface-curved pointer-events-none absolute left-0 top-0 z-0 h-[var(--active-tab-height)] w-[var(--active-tab-width)] translate-x-[var(--active-tab-left)] translate-y-[var(--active-tab-top)] rounded-pill bg-card shadow-sm transition-[translate,width] duration-[var(--duration-ui-slow)] ease-[var(--ease-out-ui)]" }), items.map((item) => (_jsx(TabsPrimitive.Tab, { value: item.value, disabled: item.disabled, className: "relative z-10 inline-flex h-8 items-center justify-center whitespace-nowrap rounded-pill px-3.5 text-sm font-medium text-muted-foreground transition-[color] duration-[var(--duration-ui)] outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/35 disabled:pointer-events-none disabled:opacity-45 data-[active]:font-semibold data-[active]:text-foreground", children: item.label }, item.value)))] }), items.map((item) => (_jsx(TabsPrimitive.Panel, { value: item.value, className: cn("mt-4 rounded-control outline-none transition-opacity duration-[var(--duration-ui)] focus-visible:ring-2 focus-visible:ring-ring/20 data-[hidden]:hidden", panelClassName), children: item.content }, item.value)))] }));
7
+ }
@@ -0,0 +1 @@
1
+ export { Textarea, type TextareaProps } from "./textarea.js";
@@ -0,0 +1 @@
1
+ export { Textarea } from "./textarea.js";
@@ -0,0 +1,5 @@
1
+ import * as React from "react";
2
+ export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
3
+ error?: boolean;
4
+ }
5
+ export declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
@@ -0,0 +1,6 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../../lib/utils.js";
5
+ export const Textarea = React.forwardRef(({ className, error, ...props }, ref) => (_jsx("textarea", { ref: ref, "aria-invalid": error || undefined, className: cn("surface-inset flex min-h-[96px] w-full resize-y rounded-field bg-field px-3.5 py-2.5 text-sm text-field-foreground transition-[background-color,box-shadow] duration-[var(--duration-ui)] ease-[var(--ease-out-ui)] placeholder:text-muted-foreground/70 hover:bg-field-hover focus-visible:bg-field focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-45", error && "bg-destructive/10 focus-visible:ring-destructive/30", className), ...props })));
6
+ Textarea.displayName = "Textarea";
@@ -0,0 +1 @@
1
+ export { Toaster, type ToasterIcons, type ToasterProps, toast } from "./toast.js";
@@ -0,0 +1 @@
1
+ export { Toaster, toast } from "./toast.js";
@@ -0,0 +1,19 @@
1
+ import type * as React from "react";
2
+ import { toast } from "sonner";
3
+ export interface ToasterIcons {
4
+ success?: React.ReactNode;
5
+ info?: React.ReactNode;
6
+ warning?: React.ReactNode;
7
+ error?: React.ReactNode;
8
+ loading?: React.ReactNode;
9
+ close?: React.ReactNode;
10
+ }
11
+ export interface ToasterProps {
12
+ position?: "top-left" | "top-right" | "bottom-left" | "bottom-right" | "top-center" | "bottom-center";
13
+ theme?: "light" | "dark" | "system";
14
+ richColors?: boolean;
15
+ closeButton?: boolean;
16
+ icons?: ToasterIcons;
17
+ }
18
+ export declare function Toaster({ position, theme, richColors, closeButton, icons, }: ToasterProps): React.JSX.Element;
19
+ export { toast };
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Toaster as SonnerToaster, toast } from "sonner";
4
+ export function Toaster({ position = "bottom-right", theme = "light", richColors = false, closeButton = true, icons, }) {
5
+ return (_jsx(SonnerToaster, { position: position, theme: theme, richColors: richColors, closeButton: closeButton, icons: icons, toastOptions: {
6
+ classNames: {
7
+ toast: "surface-curved relative group rounded-overlay bg-popover text-popover-foreground shadow-[var(--shadow-overlay)]",
8
+ title: "text-sm font-medium",
9
+ description: "text-sm text-muted-foreground",
10
+ actionButton: "rounded-control bg-primary text-primary-foreground text-xs font-medium",
11
+ cancelButton: "rounded-control bg-surface text-foreground text-xs font-medium",
12
+ closeButton: "border-border bg-card text-muted-foreground hover:text-foreground",
13
+ success: "!border-success/25",
14
+ error: "!border-destructive/25",
15
+ warning: "!border-warning/25",
16
+ info: "!border-info/25",
17
+ },
18
+ } }));
19
+ }
20
+ export { toast };
@@ -0,0 +1 @@
1
+ export { Tooltip, type TooltipProps } from "./tooltip.js";
@@ -0,0 +1 @@
1
+ export { Tooltip } from "./tooltip.js";
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ export interface TooltipProps {
3
+ content: React.ReactNode;
4
+ children: React.ReactElement;
5
+ side?: "top" | "bottom" | "left" | "right";
6
+ align?: "start" | "center" | "end";
7
+ delay?: number;
8
+ open?: boolean;
9
+ defaultOpen?: boolean;
10
+ onOpenChange?: (open: boolean) => void;
11
+ }
12
+ export declare function Tooltip({ content, children, side, align, delay, open, defaultOpen, onOpenChange, }: TooltipProps): React.JSX.Element;
@@ -0,0 +1,9 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
4
+ import { cn } from "../../../lib/utils.js";
5
+ export function Tooltip({ content, children, side = "top", align = "center", delay = 300, open, defaultOpen, onOpenChange, }) {
6
+ if (content == null || content === "")
7
+ return children;
8
+ return (_jsx(TooltipPrimitive.Provider, { delay: delay, children: _jsxs(TooltipPrimitive.Root, { open: open, defaultOpen: defaultOpen, onOpenChange: onOpenChange, children: [_jsx(TooltipPrimitive.Trigger, { render: children }), _jsx(TooltipPrimitive.Portal, { children: _jsx(TooltipPrimitive.Positioner, { side: side, align: align, sideOffset: 6, children: _jsx(TooltipPrimitive.Popup, { role: "tooltip", className: cn("z-50 max-w-xs rounded-[10px] bg-foreground px-2.5 py-1.5 text-xs font-medium text-background shadow-[var(--shadow-overlay)]", "origin-[var(--transform-origin)] transition-[opacity,transform] duration-[var(--duration-ui)] ease-[var(--ease-out-ui)]", "data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95"), children: content }) }) })] }) }));
9
+ }
@@ -0,0 +1,27 @@
1
+ export { cn } from "./lib/utils.js";
2
+ export type { IconComponent } from "./lib/utils.js";
3
+ export { generateAccentVars, accentStyle } from "./lib/color.js";
4
+ export type { AccentVars, ThemeMode } from "./lib/color.js";
5
+ export { IconProvider, useKitIcon } from "./components/ui/_shared/icon-provider.js";
6
+ export type { IconProviderProps, KitIcon, KitIconName, } from "./components/ui/_shared/icon-provider.js";
7
+ export * from "./components/ui/alert/index.js";
8
+ export * from "./components/ui/badge/index.js";
9
+ export * from "./components/ui/button/index.js";
10
+ export * from "./components/ui/card/index.js";
11
+ export * from "./components/ui/checkbox/index.js";
12
+ export * from "./components/ui/dialog/index.js";
13
+ export * from "./components/ui/field/index.js";
14
+ export * from "./components/ui/icon-button/index.js";
15
+ export * from "./components/ui/input/index.js";
16
+ export * from "./components/ui/label/index.js";
17
+ export * from "./components/ui/popover/index.js";
18
+ export * from "./components/ui/radio-group/index.js";
19
+ export * from "./components/ui/select/index.js";
20
+ export * from "./components/ui/separator/index.js";
21
+ export * from "./components/ui/sheet/index.js";
22
+ export * from "./components/ui/spinner/index.js";
23
+ export * from "./components/ui/switch/index.js";
24
+ export * from "./components/ui/tabs/index.js";
25
+ export * from "./components/ui/textarea/index.js";
26
+ export * from "./components/ui/toast/index.js";
27
+ export * from "./components/ui/tooltip/index.js";
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ // @nphuonha/ui-kit — solid, clean, flat, modern.
2
+ // Usage:
3
+ // import "@nphuonha/ui-kit/styles.css";
4
+ // import { Button } from "@nphuonha/ui-kit";
5
+ // Scope with <div data-ui="kit"> (put on <html> for portalled overlays).
6
+ export { cn } from "./lib/utils.js";
7
+ export { generateAccentVars, accentStyle } from "./lib/color.js";
8
+ export { IconProvider, useKitIcon } from "./components/ui/_shared/icon-provider.js";
9
+ export * from "./components/ui/alert/index.js";
10
+ export * from "./components/ui/badge/index.js";
11
+ export * from "./components/ui/button/index.js";
12
+ export * from "./components/ui/card/index.js";
13
+ export * from "./components/ui/checkbox/index.js";
14
+ export * from "./components/ui/dialog/index.js";
15
+ export * from "./components/ui/field/index.js";
16
+ export * from "./components/ui/icon-button/index.js";
17
+ export * from "./components/ui/input/index.js";
18
+ export * from "./components/ui/label/index.js";
19
+ export * from "./components/ui/popover/index.js";
20
+ export * from "./components/ui/radio-group/index.js";
21
+ export * from "./components/ui/select/index.js";
22
+ export * from "./components/ui/separator/index.js";
23
+ export * from "./components/ui/sheet/index.js";
24
+ export * from "./components/ui/spinner/index.js";
25
+ export * from "./components/ui/switch/index.js";
26
+ export * from "./components/ui/tabs/index.js";
27
+ export * from "./components/ui/textarea/index.js";
28
+ export * from "./components/ui/toast/index.js";
29
+ export * from "./components/ui/tooltip/index.js";
@@ -0,0 +1,16 @@
1
+ export type ThemeMode = "light" | "dark";
2
+ export interface AccentVars {
3
+ "--primary": string;
4
+ "--primary-foreground": string;
5
+ "--primary-hover": string;
6
+ "--primary-active": string;
7
+ "--primary-soft": string;
8
+ "--primary-soft-foreground": string;
9
+ "--ring": string;
10
+ }
11
+ /**
12
+ * Generate accent/primary CSS variables from a single color.
13
+ * Apply the returned vars on `[data-ui="kit"]` (or a wrapper) to change accent.
14
+ */
15
+ export declare function generateAccentVars(accent: string, mode?: ThemeMode): AccentVars;
16
+ export declare function accentStyle(accent: string, mode?: ThemeMode): React.CSSProperties;
@@ -0,0 +1,118 @@
1
+ function clamp(n, min = 0, max = 1) {
2
+ return Math.min(max, Math.max(min, n));
3
+ }
4
+ function hexToRgb(hex) {
5
+ const h = hex.replace("#", "").trim();
6
+ const v = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
7
+ if (!/^[0-9a-fA-F]{6}$/.test(v))
8
+ throw new Error(`Invalid hex color: ${hex}`);
9
+ const n = parseInt(v, 16);
10
+ return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
11
+ }
12
+ function rgbToHex(r, g, b) {
13
+ const to = (x) => Math.round(x).toString(16).padStart(2, "0");
14
+ return `#${to(r)}${to(g)}${to(b)}`;
15
+ }
16
+ function rgbToHsl(r, g, b) {
17
+ r /= 255;
18
+ g /= 255;
19
+ b /= 255;
20
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
21
+ let h = 0, s = 0;
22
+ const l = (max + min) / 2;
23
+ if (max !== min) {
24
+ const d = max - min;
25
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
26
+ switch (max) {
27
+ case r:
28
+ h = (g - b) / d + (g < b ? 6 : 0);
29
+ break;
30
+ case g:
31
+ h = (b - r) / d + 2;
32
+ break;
33
+ case b:
34
+ h = (r - g) / d + 4;
35
+ break;
36
+ }
37
+ h /= 6;
38
+ }
39
+ return { h: h * 360, s, l };
40
+ }
41
+ function hslToRgb(h, s, l) {
42
+ h = ((h % 360) + 360) % 360;
43
+ h /= 360;
44
+ let r, g, b;
45
+ if (s === 0) {
46
+ r = g = b = l;
47
+ }
48
+ else {
49
+ const hue2rgb = (p, q, t) => {
50
+ if (t < 0)
51
+ t += 1;
52
+ if (t > 1)
53
+ t -= 1;
54
+ if (t < 1 / 6)
55
+ return p + (q - p) * 6 * t;
56
+ if (t < 1 / 2)
57
+ return q;
58
+ if (t < 2 / 3)
59
+ return p + (q - p) * (2 / 3 - t) * 6;
60
+ return p;
61
+ };
62
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
63
+ const p = 2 * l - q;
64
+ r = hue2rgb(p, q, h + 1 / 3);
65
+ g = hue2rgb(p, q, h);
66
+ b = hue2rgb(p, q, h - 1 / 3);
67
+ }
68
+ return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) };
69
+ }
70
+ function hslToHex(h, s, l) {
71
+ const { r, g, b } = hslToRgb(h, s, l);
72
+ return rgbToHex(r, g, b);
73
+ }
74
+ function relativeLuminance(hex) {
75
+ const { r, g, b } = hexToRgb(hex);
76
+ const f = (c) => {
77
+ const s = c / 255;
78
+ return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
79
+ };
80
+ return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b);
81
+ }
82
+ function contrastText(bg) {
83
+ return relativeLuminance(bg) > 0.45 ? "#111827" : "#ffffff";
84
+ }
85
+ /**
86
+ * Generate accent/primary CSS variables from a single color.
87
+ * Apply the returned vars on `[data-ui="kit"]` (or a wrapper) to change accent.
88
+ */
89
+ export function generateAccentVars(accent, mode = "light") {
90
+ const { r, g, b } = hexToRgb(accent);
91
+ const { h, s } = rgbToHsl(r, g, b);
92
+ const sat = clamp(s, 0.35, 1);
93
+ if (mode === "dark") {
94
+ const primary = hslToHex(h, sat, 0.62);
95
+ return {
96
+ "--primary": primary,
97
+ "--primary-foreground": contrastText(primary),
98
+ "--primary-hover": hslToHex(h, sat, 0.68),
99
+ "--primary-active": hslToHex(h, sat, 0.56),
100
+ "--primary-soft": hslToHex(h, clamp(sat - 0.15, 0.2, 1), 0.22),
101
+ "--primary-soft-foreground": hslToHex(h, clamp(sat - 0.05, 0.2, 1), 0.82),
102
+ "--ring": hslToHex(h, sat, 0.62),
103
+ };
104
+ }
105
+ const primary = hslToHex(h, sat, 0.48);
106
+ return {
107
+ "--primary": primary,
108
+ "--primary-foreground": contrastText(primary),
109
+ "--primary-hover": hslToHex(h, sat, 0.42),
110
+ "--primary-active": hslToHex(h, sat, 0.38),
111
+ "--primary-soft": hslToHex(h, clamp(sat - 0.1, 0.15, 1), 0.94),
112
+ "--primary-soft-foreground": hslToHex(h, sat, 0.3),
113
+ "--ring": hslToHex(h, sat, 0.48),
114
+ };
115
+ }
116
+ export function accentStyle(accent, mode = "light") {
117
+ return generateAccentVars(accent, mode);
118
+ }
@@ -0,0 +1,7 @@
1
+ import type * as React from "react";
2
+ import { type ClassValue } from "clsx";
3
+ export declare function cn(...inputs: ClassValue[]): string;
4
+ export type IconComponent = React.ComponentType<{
5
+ size?: number;
6
+ className?: string;
7
+ }>;