@xonic0506/sonic-ui 0.1.0 → 0.3.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 (64) hide show
  1. package/AI-RULES.md +1 -1
  2. package/DESIGN.md +505 -16
  3. package/README.md +87 -38
  4. package/dist/components/accordion.d.ts +30 -0
  5. package/dist/components/accordion.js +44 -0
  6. package/dist/components/alert-dialog.d.ts +16 -0
  7. package/dist/components/alert-dialog.js +46 -0
  8. package/dist/components/badge.d.ts +8 -0
  9. package/dist/components/badge.js +77 -0
  10. package/dist/components/button.d.ts +11 -2
  11. package/dist/components/button.js +24 -14
  12. package/dist/components/checkbox.d.ts +30 -0
  13. package/dist/components/checkbox.js +48 -0
  14. package/dist/components/dialog.d.ts +11 -0
  15. package/dist/components/dialog.js +17 -14
  16. package/dist/components/empty-state.d.ts +11 -0
  17. package/dist/components/empty-state.js +17 -0
  18. package/dist/components/error-state.d.ts +12 -0
  19. package/dist/components/error-state.js +24 -0
  20. package/dist/components/field.d.ts +13 -0
  21. package/dist/components/field.js +33 -0
  22. package/dist/components/file-upload.d.ts +57 -0
  23. package/dist/components/file-upload.js +238 -0
  24. package/dist/components/input.d.ts +1 -0
  25. package/dist/components/input.js +5 -5
  26. package/dist/components/link.d.ts +9 -0
  27. package/dist/components/link.js +30 -0
  28. package/dist/components/menu.d.ts +33 -0
  29. package/dist/components/menu.js +44 -0
  30. package/dist/components/popover.d.ts +26 -0
  31. package/dist/components/popover.js +50 -0
  32. package/dist/components/popup-styles.d.ts +3 -0
  33. package/dist/components/popup-styles.js +5 -0
  34. package/dist/components/progress.d.ts +16 -0
  35. package/dist/components/progress.js +27 -0
  36. package/dist/components/radio-group.d.ts +20 -0
  37. package/dist/components/radio-group.js +40 -0
  38. package/dist/components/select.d.ts +30 -0
  39. package/dist/components/select.js +66 -0
  40. package/dist/components/separator.d.ts +7 -0
  41. package/dist/components/separator.js +14 -0
  42. package/dist/components/skeleton.d.ts +8 -0
  43. package/dist/components/skeleton.js +22 -0
  44. package/dist/components/state-layout.d.ts +11 -0
  45. package/dist/components/state-layout.js +31 -0
  46. package/dist/components/switch.d.ts +12 -0
  47. package/dist/components/switch.js +24 -0
  48. package/dist/components/table.d.ts +24 -0
  49. package/dist/components/table.js +46 -0
  50. package/dist/components/tabs.d.ts +31 -0
  51. package/dist/components/tabs.js +41 -0
  52. package/dist/components/textarea.d.ts +6 -0
  53. package/dist/components/textarea.js +20 -0
  54. package/dist/components/toast.d.ts +24 -0
  55. package/dist/components/toast.js +62 -0
  56. package/dist/components/toggle-group.d.ts +36 -0
  57. package/dist/components/toggle-group.js +44 -0
  58. package/dist/components/tooltip.d.ts +11 -0
  59. package/dist/components/tooltip.js +19 -0
  60. package/dist/index.d.ts +23 -0
  61. package/dist/index.js +28 -5
  62. package/dist/styles.css +2 -2
  63. package/dist/tailwind.css +15 -0
  64. package/package.json +9 -2
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { cn as e } from "../lib/cn.js";
3
+ import { popupClass as t } from "./popup-styles.js";
4
+ import * as n from "react";
5
+ import { jsx as r } from "react/jsx-runtime";
6
+ import { Popover as i } from "@base-ui/react/popover";
7
+ //#region src/components/popover.tsx
8
+ function a({ onOpenChange: e, ...t }) {
9
+ return /* @__PURE__ */ r(i.Root, {
10
+ ...t,
11
+ onOpenChange: e && ((t) => e(t))
12
+ });
13
+ }
14
+ var o = n.forwardRef(function(e, t) {
15
+ return /* @__PURE__ */ r(i.Trigger, {
16
+ ref: t,
17
+ ...e
18
+ });
19
+ }), s = n.forwardRef(function(e, t) {
20
+ return /* @__PURE__ */ r(i.Close, {
21
+ ref: t,
22
+ ...e
23
+ });
24
+ }), c = n.forwardRef(function({ side: n = "bottom", className: a, children: o }, s) {
25
+ return /* @__PURE__ */ r(i.Portal, { children: /* @__PURE__ */ r(i.Positioner, {
26
+ side: n,
27
+ sideOffset: 8,
28
+ align: "start",
29
+ className: "sn:z-50 sn:outline-none",
30
+ children: /* @__PURE__ */ r(i.Popup, {
31
+ ref: s,
32
+ className: e(t, "sn:flex sn:w-80 sn:max-w-[calc(100vw-2*var(--sn-space-4))] sn:min-w-0 sn:flex-col sn:gap-3 sn:p-4", a),
33
+ children: o
34
+ })
35
+ }) });
36
+ }), l = n.forwardRef(function({ className: t, ...n }, a) {
37
+ return /* @__PURE__ */ r(i.Title, {
38
+ ref: a,
39
+ className: e("sn:m-0 sn:text-body-strong sn:text-default", t),
40
+ ...n
41
+ });
42
+ }), u = n.forwardRef(function({ className: t, ...n }, a) {
43
+ return /* @__PURE__ */ r(i.Description, {
44
+ ref: a,
45
+ className: e("sn:-mt-2 sn:mb-0 sn:text-caption sn:text-muted", t),
46
+ ...n
47
+ });
48
+ });
49
+ //#endregion
50
+ export { a as Popover, s as PopoverClose, u as PopoverDescription, c as PopoverPopup, l as PopoverTitle, o as PopoverTrigger };
@@ -0,0 +1,3 @@
1
+ export declare const popupClass = "sn:box-border sn:min-w-(--anchor-width) sn:max-h-(--available-height) sn:overflow-y-auto sn:origin-(--transform-origin) sn:rounded-lg sn:border sn:border-default sn:bg-surface sn:p-1 sn:font-sans sn:text-body sn:text-default sn:outline-none sn:transition-[opacity,scale] sn:duration-(--sn-duration-normal) sn:ease-standard sn:data-starting-style:scale-98 sn:data-starting-style:opacity-0 sn:data-ending-style:scale-98 sn:data-ending-style:opacity-0 sn:motion-reduce:transition-none";
2
+ export declare const itemClass = "sn:group sn:flex sn:h-8 sn:cursor-default sn:items-center sn:gap-2 sn:rounded-md sn:px-2 sn:outline-none sn:select-none sn:data-highlighted:bg-subtle sn:data-disabled:opacity-(--sn-opacity-disabled) sn:[&_svg]:size-4 sn:[&_svg]:shrink-0";
3
+ export declare const boxClass = "sn:box-border sn:inline-flex sn:size-4 sn:shrink-0 sn:items-center sn:justify-center sn:rounded-sm sn:border sn:border-strong sn:bg-surface sn:text-on-primary sn:[&_svg]:size-3";
@@ -0,0 +1,5 @@
1
+ "use client";
2
+ //#region src/components/popup-styles.ts
3
+ var e = "sn:box-border sn:min-w-(--anchor-width) sn:max-h-(--available-height) sn:overflow-y-auto sn:origin-(--transform-origin) sn:rounded-lg sn:border sn:border-default sn:bg-surface sn:p-1 sn:font-sans sn:text-body sn:text-default sn:outline-none sn:transition-[opacity,scale] sn:duration-(--sn-duration-normal) sn:ease-standard sn:data-starting-style:scale-98 sn:data-starting-style:opacity-0 sn:data-ending-style:scale-98 sn:data-ending-style:opacity-0 sn:motion-reduce:transition-none", t = "sn:group sn:flex sn:h-8 sn:cursor-default sn:items-center sn:gap-2 sn:rounded-md sn:px-2 sn:outline-none sn:select-none sn:data-highlighted:bg-subtle sn:data-disabled:opacity-(--sn-opacity-disabled) sn:[&_svg]:size-4 sn:[&_svg]:shrink-0", n = "sn:box-border sn:inline-flex sn:size-4 sn:shrink-0 sn:items-center sn:justify-center sn:rounded-sm sn:border sn:border-strong sn:bg-surface sn:text-on-primary sn:[&_svg]:size-3";
4
+ //#endregion
5
+ export { n as boxClass, t as itemClass, e as popupClass };
@@ -0,0 +1,16 @@
1
+ import * as React from 'react';
2
+ interface ProgressBaseProps {
3
+ value: number;
4
+ /** 기본 100 */
5
+ max?: number;
6
+ className?: string;
7
+ }
8
+ /** 라벨이 없으면 읽을 이름이 없어서 aria-label이 필수예요 */
9
+ export type ProgressProps = ProgressBaseProps & ({
10
+ label: React.ReactNode;
11
+ } | {
12
+ label?: undefined;
13
+ 'aria-label': string;
14
+ });
15
+ export declare function Progress({ value, max, className, ...props }: ProgressProps): React.JSX.Element;
16
+ export {};
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { cn as e } from "../lib/cn.js";
3
+ import "react";
4
+ import { jsx as t, jsxs as n } from "react/jsx-runtime";
5
+ import { Progress as r } from "@base-ui/react/progress";
6
+ //#region src/components/progress.tsx
7
+ function i({ value: i, max: a = 100, className: o, ...s }) {
8
+ let c = s.label;
9
+ return /* @__PURE__ */ n(r.Root, {
10
+ value: i,
11
+ max: a,
12
+ "aria-label": "aria-label" in s ? s["aria-label"] : void 0,
13
+ className: e("sn:flex sn:flex-col sn:gap-2 sn:font-sans", o),
14
+ children: [c != null && /* @__PURE__ */ n("div", {
15
+ className: "sn:flex sn:items-center sn:gap-2",
16
+ children: [/* @__PURE__ */ t(r.Label, {
17
+ className: "sn:min-w-0 sn:flex-1 sn:text-label sn:text-default",
18
+ children: c
19
+ }), /* @__PURE__ */ t(r.Value, { className: "sn:font-mono sn:text-caption sn:text-muted" })]
20
+ }), /* @__PURE__ */ t(r.Track, {
21
+ className: "sn:h-1 sn:overflow-hidden sn:rounded-full sn:bg-subtle",
22
+ children: /* @__PURE__ */ t(r.Indicator, { className: "sn:h-full sn:rounded-full sn:bg-primary sn:transition-[width] sn:duration-(--sn-duration-normal) sn:ease-standard sn:motion-reduce:transition-none" })
23
+ })]
24
+ });
25
+ }
26
+ //#endregion
27
+ export { i as Progress };
@@ -0,0 +1,20 @@
1
+ import * as React from 'react';
2
+ export interface RadioGroupProps {
3
+ /** 그룹 이름 (fieldset legend) */
4
+ label: React.ReactNode;
5
+ value?: string;
6
+ defaultValue?: string;
7
+ onValueChange?: (value: string) => void;
8
+ disabled?: boolean;
9
+ name?: string;
10
+ className?: string;
11
+ children: React.ReactNode;
12
+ }
13
+ export declare function RadioGroup({ label, value, defaultValue, onValueChange, disabled, name, className, children }: RadioGroupProps): React.JSX.Element;
14
+ export interface RadioProps {
15
+ value: string;
16
+ disabled?: boolean;
17
+ className?: string;
18
+ children?: React.ReactNode;
19
+ }
20
+ export declare const Radio: React.ForwardRefExoticComponent<RadioProps & React.RefAttributes<HTMLElement>>;
@@ -0,0 +1,40 @@
1
+ "use client";
2
+ import { cn as e } from "../lib/cn.js";
3
+ import { choiceFocusClass as t, choiceLabelClass as n } from "./checkbox.js";
4
+ import * as r from "react";
5
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
6
+ import { Fieldset as o } from "@base-ui/react/fieldset";
7
+ import { Radio as s } from "@base-ui/react/radio";
8
+ import { RadioGroup as c } from "@base-ui/react/radio-group";
9
+ //#region src/components/radio-group.tsx
10
+ function l({ label: t, value: n, defaultValue: r, onValueChange: s, disabled: l, name: u, className: d, children: f }) {
11
+ return /* @__PURE__ */ a(o.Root, {
12
+ className: e("sn:m-0 sn:flex sn:min-w-0 sn:flex-col sn:gap-3 sn:border-0 sn:p-0 sn:font-sans", d),
13
+ children: [/* @__PURE__ */ i(o.Legend, {
14
+ className: "sn:p-0 sn:text-label sn:text-default",
15
+ children: t
16
+ }), /* @__PURE__ */ i(c, {
17
+ value: n,
18
+ defaultValue: r,
19
+ onValueChange: s && ((e) => s(e)),
20
+ disabled: l,
21
+ name: u,
22
+ className: "sn:flex sn:flex-col sn:gap-2",
23
+ children: f
24
+ })]
25
+ });
26
+ }
27
+ var u = r.forwardRef(function({ value: r, disabled: o, className: c, children: l }, u) {
28
+ return /* @__PURE__ */ a("label", {
29
+ className: e(n, o && "sn:cursor-not-allowed sn:opacity-(--sn-opacity-disabled)", c),
30
+ children: [/* @__PURE__ */ i(s.Root, {
31
+ ref: u,
32
+ value: r,
33
+ disabled: o,
34
+ className: e("sn:box-border sn:inline-flex sn:size-4 sn:shrink-0 sn:items-center sn:justify-center sn:rounded-full sn:border sn:border-strong sn:bg-surface sn:data-checked:border-transparent sn:data-checked:bg-primary", t),
35
+ children: /* @__PURE__ */ i(s.Indicator, { className: "sn:size-2 sn:rounded-full sn:bg-(--sn-color-text-on-primary)" })
36
+ }), l]
37
+ });
38
+ });
39
+ //#endregion
40
+ export { u as Radio, l as RadioGroup };
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+ export interface SelectOption {
3
+ value: string;
4
+ label: string;
5
+ disabled?: boolean;
6
+ }
7
+ interface SelectBaseProps {
8
+ options: SelectOption[];
9
+ placeholder?: string;
10
+ invalid?: boolean;
11
+ disabled?: boolean;
12
+ name?: string;
13
+ id?: string;
14
+ className?: string;
15
+ 'aria-label'?: string;
16
+ }
17
+ /** multiple이면 값이 배열이에요 */
18
+ export type SelectProps = SelectBaseProps & ({
19
+ multiple?: false;
20
+ value?: string | null;
21
+ defaultValue?: string | null;
22
+ onValueChange?: (value: string | null) => void;
23
+ } | {
24
+ multiple: true;
25
+ value?: string[];
26
+ defaultValue?: string[];
27
+ onValueChange?: (value: string[]) => void;
28
+ });
29
+ export declare function Select(props: SelectProps): React.JSX.Element;
30
+ export {};
@@ -0,0 +1,66 @@
1
+ "use client";
2
+ import { cn as e } from "../lib/cn.js";
3
+ import { inputClass as t } from "./input.js";
4
+ import { itemClass as n, popupClass as r } from "./popup-styles.js";
5
+ import "react";
6
+ import { Check as i, ChevronDown as a } from "lucide-react";
7
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
8
+ import { Select as c } from "@base-ui/react/select";
9
+ //#region src/components/select.tsx
10
+ function l(l) {
11
+ let { options: u, placeholder: d, invalid: f, disabled: p, name: m, id: h, className: g, multiple: _ } = l, v = (e) => u.find((t) => t.value === e)?.label ?? e;
12
+ return /* @__PURE__ */ s(c.Root, {
13
+ items: u,
14
+ multiple: _,
15
+ value: l.value,
16
+ defaultValue: l.defaultValue,
17
+ onValueChange: l.onValueChange && ((e) => l.onValueChange(e)),
18
+ disabled: p,
19
+ name: m,
20
+ children: [/* @__PURE__ */ s(c.Trigger, {
21
+ id: h,
22
+ "aria-label": l["aria-label"],
23
+ "aria-invalid": f || void 0,
24
+ className: e(t, "sn:group sn:flex sn:h-8 sn:items-center sn:gap-2 sn:text-left sn:cursor-default sn:data-popup-open:border-focus", g),
25
+ children: [/* @__PURE__ */ o(c.Value, {
26
+ placeholder: d,
27
+ className: "sn:min-w-0 sn:flex-1 sn:truncate sn:data-placeholder:text-muted",
28
+ children: _ ? (e) => e.length ? e.map(v).join(", ") : d : void 0
29
+ }), /* @__PURE__ */ o(c.Icon, {
30
+ className: "sn:flex sn:text-muted sn:transition-transform sn:duration-(--sn-duration-fast) sn:group-data-popup-open:rotate-180 sn:motion-reduce:transition-none sn:[&_svg]:size-4",
31
+ children: /* @__PURE__ */ o(a, {})
32
+ })]
33
+ }), /* @__PURE__ */ o(c.Portal, { children: /* @__PURE__ */ o(c.Positioner, {
34
+ sideOffset: 4,
35
+ alignItemWithTrigger: !1,
36
+ className: "sn:z-50 sn:outline-none",
37
+ children: /* @__PURE__ */ o(c.Popup, {
38
+ className: r,
39
+ children: /* @__PURE__ */ o(c.List, { children: u.map((t) => /* @__PURE__ */ s(c.Item, {
40
+ value: t.value,
41
+ disabled: t.disabled,
42
+ className: n,
43
+ children: [
44
+ _ && /* @__PURE__ */ o("span", {
45
+ className: e("sn:box-border sn:inline-flex sn:size-4 sn:shrink-0 sn:items-center sn:justify-center sn:rounded-sm sn:border sn:border-strong sn:bg-surface sn:text-on-primary sn:[&_svg]:size-3", "sn:group-data-selected:border-transparent sn:group-data-selected:bg-primary"),
46
+ children: /* @__PURE__ */ o(c.ItemIndicator, {
47
+ className: "sn:flex",
48
+ children: /* @__PURE__ */ o(i, {})
49
+ })
50
+ }),
51
+ /* @__PURE__ */ o(c.ItemText, {
52
+ className: "sn:min-w-0 sn:flex-1 sn:truncate",
53
+ children: t.label
54
+ }),
55
+ !_ && /* @__PURE__ */ o(c.ItemIndicator, {
56
+ className: "sn:flex",
57
+ children: /* @__PURE__ */ o(i, {})
58
+ })
59
+ ]
60
+ }, t.value)) })
61
+ })
62
+ }) })]
63
+ });
64
+ }
65
+ //#endregion
66
+ export { l as Select };
@@ -0,0 +1,7 @@
1
+ import * as React from 'react';
2
+ export interface SeparatorProps {
3
+ orientation?: 'horizontal' | 'vertical';
4
+ className?: string;
5
+ }
6
+ /** 관련 없는 묶음 사이를 나눌 때만 써요. 1px border/default */
7
+ export declare function Separator({ orientation, className }: SeparatorProps): React.JSX.Element;
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ import { cn as e } from "../lib/cn.js";
3
+ import "react";
4
+ import { jsx as t } from "react/jsx-runtime";
5
+ import { Separator as n } from "@base-ui/react/separator";
6
+ //#region src/components/separator.tsx
7
+ function r({ orientation: r = "horizontal", className: i }) {
8
+ return /* @__PURE__ */ t(n, {
9
+ orientation: r,
10
+ className: e("sn:shrink-0 sn:border-0 sn:bg-(--sn-color-border-default)", r === "horizontal" ? "sn:h-px sn:w-full" : "sn:w-px sn:self-stretch", i)
11
+ });
12
+ }
13
+ //#endregion
14
+ export { r as Separator };
@@ -0,0 +1,8 @@
1
+ import * as React from 'react';
2
+ export interface SkeletonProps {
3
+ shape?: 'text' | 'rect' | 'circle';
4
+ /** 크기는 쓰는 곳에서 정해요 (예: className="w-40") */
5
+ className?: string;
6
+ }
7
+ /** 로딩 자리 표시. 스크린 리더에는 숨겨요 — 로딩 영역 전체에 aria-busy를 두세요 */
8
+ export declare function Skeleton({ shape, className }: SkeletonProps): React.JSX.Element;
@@ -0,0 +1,22 @@
1
+ "use client";
2
+ import { cn as e } from "../lib/cn.js";
3
+ import "react";
4
+ import { cva as t } from "class-variance-authority";
5
+ import { jsx as n } from "react/jsx-runtime";
6
+ //#region src/components/skeleton.tsx
7
+ var r = t("sn:block sn:shrink-0 sn:bg-subtle sn:animate-[sn-pulse_var(--sn-duration-loop)_ease-in-out_infinite] sn:motion-reduce:animate-none", {
8
+ variants: { shape: {
9
+ text: "sn:h-3 sn:w-full sn:rounded-sm",
10
+ rect: "sn:h-16 sn:w-full sn:rounded-md",
11
+ circle: "sn:size-8 sn:rounded-full"
12
+ } },
13
+ defaultVariants: { shape: "text" }
14
+ });
15
+ function i({ shape: t, className: i }) {
16
+ return /* @__PURE__ */ n("span", {
17
+ "aria-hidden": !0,
18
+ className: e(r({ shape: t }), i)
19
+ });
20
+ }
21
+ //#endregion
22
+ export { i as Skeleton };
@@ -0,0 +1,11 @@
1
+ import * as React from 'react';
2
+ export interface StateLayoutProps {
3
+ icon?: React.ReactNode;
4
+ iconClassName: string;
5
+ title: React.ReactNode;
6
+ description?: React.ReactNode;
7
+ className?: string;
8
+ role?: 'alert';
9
+ children?: React.ReactNode;
10
+ }
11
+ export declare function StateLayout({ icon, iconClassName, title, description, className, role, children }: StateLayoutProps): React.JSX.Element;
@@ -0,0 +1,31 @@
1
+ "use client";
2
+ import { cn as e } from "../lib/cn.js";
3
+ import "react";
4
+ import { jsx as t, jsxs as n } from "react/jsx-runtime";
5
+ //#region src/components/state-layout.tsx
6
+ function r({ icon: r, iconClassName: i, title: a, description: o, className: s, role: c, children: l }) {
7
+ return /* @__PURE__ */ n("div", {
8
+ role: c,
9
+ className: e("sn:box-border sn:flex sn:flex-col sn:items-center sn:gap-4 sn:px-6 sn:py-12 sn:text-center sn:font-sans", s),
10
+ children: [
11
+ r && /* @__PURE__ */ t("span", {
12
+ "aria-hidden": !0,
13
+ className: e("sn:inline-flex sn:size-12 sn:items-center sn:justify-center sn:rounded-full sn:bg-subtle sn:[&_svg]:size-6", i),
14
+ children: r
15
+ }),
16
+ /* @__PURE__ */ n("div", {
17
+ className: "sn:flex sn:flex-col sn:gap-1",
18
+ children: [/* @__PURE__ */ t("p", {
19
+ className: "sn:m-0 sn:text-body-strong sn:text-default",
20
+ children: a
21
+ }), o && /* @__PURE__ */ t("p", {
22
+ className: "sn:m-0 sn:text-body sn:text-muted",
23
+ children: o
24
+ })]
25
+ }),
26
+ l
27
+ ]
28
+ });
29
+ }
30
+ //#endregion
31
+ export { r as StateLayout };
@@ -0,0 +1,12 @@
1
+ import * as React from 'react';
2
+ export interface SwitchProps {
3
+ checked?: boolean;
4
+ defaultChecked?: boolean;
5
+ onCheckedChange?: (checked: boolean) => void;
6
+ disabled?: boolean;
7
+ name?: string;
8
+ className?: string;
9
+ children?: React.ReactNode;
10
+ }
11
+ /** 누르는 즉시 적용되는 설정에만 써요 */
12
+ export declare const Switch: React.ForwardRefExoticComponent<SwitchProps & React.RefAttributes<HTMLElement>>;
@@ -0,0 +1,24 @@
1
+ "use client";
2
+ import { cn as e } from "../lib/cn.js";
3
+ import { choiceFocusClass as t, choiceLabelClass as n } from "./checkbox.js";
4
+ import * as r from "react";
5
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
6
+ import { Switch as o } from "@base-ui/react/switch";
7
+ //#region src/components/switch.tsx
8
+ var s = r.forwardRef(function({ checked: r, defaultChecked: s, onCheckedChange: c, disabled: l, name: u, className: d, children: f }, p) {
9
+ return /* @__PURE__ */ a("label", {
10
+ className: e(n, l && "sn:cursor-not-allowed sn:opacity-(--sn-opacity-disabled)", d),
11
+ children: [/* @__PURE__ */ i(o.Root, {
12
+ ref: p,
13
+ checked: r,
14
+ defaultChecked: s,
15
+ onCheckedChange: c && ((e) => c(e)),
16
+ disabled: l,
17
+ name: u,
18
+ className: e("sn:box-border sn:inline-flex sn:h-4 sn:w-7 sn:shrink-0 sn:items-center sn:rounded-full sn:border sn:border-strong sn:bg-subtle sn:p-px sn:transition-colors sn:duration-(--sn-duration-fast) sn:ease-standard sn:data-checked:border-transparent sn:data-checked:bg-primary sn:motion-reduce:transition-none", t),
19
+ children: /* @__PURE__ */ i(o.Thumb, { className: "sn:block sn:size-3 sn:rounded-full sn:bg-surface sn:transition-transform sn:duration-(--sn-duration-fast) sn:ease-standard sn:data-checked:translate-x-3 sn:data-checked:bg-(--sn-color-text-on-primary) sn:motion-reduce:transition-none" })
20
+ }), f]
21
+ });
22
+ });
23
+ //#endregion
24
+ export { s as Switch };
@@ -0,0 +1,24 @@
1
+ import * as React from 'react';
2
+ /** 넓으면 가로로 스크롤해요 */
3
+ export declare const Table: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLTableElement>, "className"> & {
4
+ className?: string;
5
+ } & React.RefAttributes<HTMLTableElement>>;
6
+ export declare const TableHeader: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLTableSectionElement>, "className"> & {
7
+ className?: string;
8
+ } & React.RefAttributes<HTMLTableSectionElement>>;
9
+ export declare const TableBody: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLTableSectionElement>, "className"> & {
10
+ className?: string;
11
+ } & React.RefAttributes<HTMLTableSectionElement>>;
12
+ export declare const TableRow: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLTableRowElement>, "className"> & {
13
+ className?: string;
14
+ } & React.RefAttributes<HTMLTableRowElement>>;
15
+ export declare const TableHead: React.ForwardRefExoticComponent<Omit<React.TdHTMLAttributes<HTMLTableCellElement>, "align" | "className"> & {
16
+ /** 숫자 열은 end */
17
+ align?: 'start' | 'end';
18
+ className?: string;
19
+ } & React.RefAttributes<HTMLTableCellElement>>;
20
+ export declare const TableCell: React.ForwardRefExoticComponent<Omit<React.TdHTMLAttributes<HTMLTableCellElement>, "align" | "className"> & {
21
+ /** 숫자 열은 end */
22
+ align?: 'start' | 'end';
23
+ className?: string;
24
+ } & React.RefAttributes<HTMLTableCellElement>>;
@@ -0,0 +1,46 @@
1
+ "use client";
2
+ import { cn as e } from "../lib/cn.js";
3
+ import * as t from "react";
4
+ import { jsx as n } from "react/jsx-runtime";
5
+ //#region src/components/table.tsx
6
+ var r = t.forwardRef(function({ className: t, ...r }, i) {
7
+ return /* @__PURE__ */ n("div", {
8
+ className: "sn:w-full sn:overflow-x-auto",
9
+ children: /* @__PURE__ */ n("table", {
10
+ ref: i,
11
+ className: e("sn:w-full sn:border-separate sn:border-spacing-0 sn:font-sans sn:text-body sn:text-default", t),
12
+ ...r
13
+ })
14
+ });
15
+ }), i = t.forwardRef(function(e, t) {
16
+ return /* @__PURE__ */ n("thead", {
17
+ ref: t,
18
+ ...e
19
+ });
20
+ }), a = t.forwardRef(function(e, t) {
21
+ return /* @__PURE__ */ n("tbody", {
22
+ ref: t,
23
+ ...e
24
+ });
25
+ }), o = t.forwardRef(function({ className: e, ...t }, r) {
26
+ return /* @__PURE__ */ n("tr", {
27
+ ref: r,
28
+ className: e,
29
+ ...t
30
+ });
31
+ }), s = "sn:box-border sn:h-10 sn:border-b sn:border-default sn:px-3 sn:py-0 sn:align-middle", c = t.forwardRef(function({ align: t = "start", className: r, ...i }, a) {
32
+ return /* @__PURE__ */ n("th", {
33
+ ref: a,
34
+ scope: "col",
35
+ className: e(s, "sn:text-label sn:text-muted", t === "end" ? "sn:text-right" : "sn:text-left", r),
36
+ ...i
37
+ });
38
+ }), l = t.forwardRef(function({ align: t = "start", className: r, ...i }, a) {
39
+ return /* @__PURE__ */ n("td", {
40
+ ref: a,
41
+ className: e(s, t === "end" ? "sn:text-right sn:font-mono" : "sn:text-left", r),
42
+ ...i
43
+ });
44
+ });
45
+ //#endregion
46
+ export { r as Table, a as TableBody, l as TableCell, c as TableHead, i as TableHeader, o as TableRow };
@@ -0,0 +1,31 @@
1
+ import * as React from 'react';
2
+ export interface TabsProps {
3
+ value?: string;
4
+ defaultValue?: string;
5
+ onValueChange?: (value: string) => void;
6
+ className?: string;
7
+ children?: React.ReactNode;
8
+ }
9
+ /** 같은 대상의 다른 보기를 오갈 때 써요. 페이지 이동 링크에는 쓰지 않아요 */
10
+ export declare function Tabs({ value, defaultValue, onValueChange, className, children }: TabsProps): React.JSX.Element;
11
+ export interface TabsListProps {
12
+ /** 화살표 키로 이동하면 바로 탭을 바꿀지 (기본 true). false면 화살표로 이동하고 Enter·Space로 바꿔요 */
13
+ activateOnFocus?: boolean;
14
+ /** 끝에서 화살표를 누르면 처음으로 돌아갈지 (기본 true) */
15
+ loopFocus?: boolean;
16
+ className?: string;
17
+ children?: React.ReactNode;
18
+ }
19
+ export declare function TabsList({ activateOnFocus, loopFocus, className, children }: TabsListProps): React.JSX.Element;
20
+ export interface TabProps {
21
+ value: string;
22
+ disabled?: boolean;
23
+ className?: string;
24
+ children?: React.ReactNode;
25
+ }
26
+ export declare const Tab: React.ForwardRefExoticComponent<TabProps & React.RefAttributes<HTMLButtonElement>>;
27
+ export declare function TabsPanel({ value, className, children }: {
28
+ value: string;
29
+ className?: string;
30
+ children?: React.ReactNode;
31
+ }): React.JSX.Element;
@@ -0,0 +1,41 @@
1
+ "use client";
2
+ import { cn as e } from "../lib/cn.js";
3
+ import * as t from "react";
4
+ import { jsx as n, jsxs as r } from "react/jsx-runtime";
5
+ import { Tabs as i } from "@base-ui/react/tabs";
6
+ //#region src/components/tabs.tsx
7
+ function a({ value: t, defaultValue: r, onValueChange: a, className: o, children: s }) {
8
+ return /* @__PURE__ */ n(i.Root, {
9
+ value: t,
10
+ defaultValue: r,
11
+ onValueChange: a && ((e) => a(e)),
12
+ className: e("sn:flex sn:flex-col sn:gap-4 sn:font-sans", o),
13
+ children: s
14
+ });
15
+ }
16
+ function o({ activateOnFocus: t = !0, loopFocus: a = !0, className: o, children: s }) {
17
+ return /* @__PURE__ */ r(i.List, {
18
+ activateOnFocus: t,
19
+ loopFocus: a,
20
+ className: e("sn:relative sn:flex sn:border-b sn:border-default", o),
21
+ children: [s, /* @__PURE__ */ n(i.Indicator, { className: "sn:absolute sn:bottom-[-1px] sn:left-0 sn:h-0.5 sn:w-(--active-tab-width) sn:translate-x-(--active-tab-left) sn:bg-primary sn:transition-[translate,width] sn:duration-(--sn-duration-normal) sn:ease-standard sn:motion-reduce:transition-none" })]
22
+ });
23
+ }
24
+ var s = t.forwardRef(function({ value: t, disabled: r, className: a, children: o }, s) {
25
+ return /* @__PURE__ */ n(i.Tab, {
26
+ ref: s,
27
+ value: t,
28
+ disabled: r,
29
+ className: e("sn:box-border sn:flex sn:h-8 sn:items-center sn:border-0 sn:bg-transparent sn:px-3 sn:font-sans sn:text-body-strong sn:text-muted sn:cursor-pointer sn:focus-visible:outline-2 sn:focus-visible:-outline-offset-2 sn:focus-visible:outline-focus sn:transition-colors sn:duration-(--sn-duration-fast) sn:hover:not-data-disabled:text-default sn:data-active:text-default sn:data-disabled:cursor-not-allowed sn:data-disabled:opacity-(--sn-opacity-disabled)", a),
30
+ children: o
31
+ });
32
+ });
33
+ function c({ value: t, className: r, children: a }) {
34
+ return /* @__PURE__ */ n(i.Panel, {
35
+ value: t,
36
+ className: e("sn:text-body sn:text-default sn:focus-visible:outline-2 sn:focus-visible:outline-offset-2 sn:focus-visible:outline-focus", r),
37
+ children: a
38
+ });
39
+ }
40
+ //#endregion
41
+ export { s as Tab, a as Tabs, o as TabsList, c as TabsPanel };
@@ -0,0 +1,6 @@
1
+ import * as React from 'react';
2
+ export interface TextareaProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'className'> {
3
+ invalid?: boolean;
4
+ className?: string;
5
+ }
6
+ export declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ import { cn as e } from "../lib/cn.js";
3
+ import { inputClass as t } from "./input.js";
4
+ import * as n from "react";
5
+ import { jsx as r } from "react/jsx-runtime";
6
+ import { Input as i } from "@base-ui/react/input";
7
+ //#region src/components/textarea.tsx
8
+ var a = n.forwardRef(function({ invalid: n, className: a, rows: o = 3, ...s }, c) {
9
+ return /* @__PURE__ */ r(i, {
10
+ render: /* @__PURE__ */ r("textarea", {
11
+ ref: c,
12
+ rows: o
13
+ }),
14
+ "aria-invalid": n || void 0,
15
+ className: e(t, "sn:min-h-20 sn:resize-y sn:py-2", a),
16
+ ...s
17
+ });
18
+ });
19
+ //#endregion
20
+ export { a as Textarea };
@@ -0,0 +1,24 @@
1
+ import * as React from 'react';
2
+ export type ToastTone = 'neutral' | 'success' | 'warning' | 'danger';
3
+ export interface ToastOptions {
4
+ /** 결과 한 줄 */
5
+ title: React.ReactNode;
6
+ description?: React.ReactNode;
7
+ tone?: ToastTone;
8
+ /** 사라지기까지 ms. 0이면 직접 닫을 때까지 남아요 (기본 5000) */
9
+ timeout?: number;
10
+ }
11
+ /** ToastProvider 안의 클라이언트 컴포넌트에서만 써요 */
12
+ export declare function useToast(): {
13
+ /** 알림을 띄우고 id를 돌려줘요 */
14
+ add: ({ tone, ...options }: ToastOptions) => string;
15
+ /** id가 없으면 모두 닫아요 */
16
+ close: (id?: string) => void;
17
+ };
18
+ export interface ToastProviderProps {
19
+ /** 기본 사라지는 시간 ms (기본 5000) */
20
+ timeout?: number;
21
+ children?: React.ReactNode;
22
+ }
23
+ /** 앱 최상단에 한 번 둬요. 알림은 화면 오른쪽 아래에 쌓여요 */
24
+ export declare function ToastProvider({ timeout, children }: ToastProviderProps): React.JSX.Element;