@xonic0506/sonic-ui 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 (64) hide show
  1. package/AI-RULES.md +18 -0
  2. package/DESIGN.md +652 -0
  3. package/LICENSE +21 -0
  4. package/README.md +124 -2
  5. package/dist/components/alert-dialog.d.ts +16 -0
  6. package/dist/components/alert-dialog.js +46 -0
  7. package/dist/components/avatar.d.ts +10 -0
  8. package/dist/components/avatar.js +27 -0
  9. package/dist/components/badge.d.ts +8 -0
  10. package/dist/components/badge.js +77 -0
  11. package/dist/components/button.d.ts +18 -0
  12. package/dist/components/button.js +49 -0
  13. package/dist/components/card.d.ts +24 -0
  14. package/dist/components/card.js +15 -0
  15. package/dist/components/checkbox.d.ts +30 -0
  16. package/dist/components/checkbox.js +48 -0
  17. package/dist/components/dialog.d.ts +39 -0
  18. package/dist/components/dialog.js +52 -0
  19. package/dist/components/empty-state.d.ts +11 -0
  20. package/dist/components/empty-state.js +17 -0
  21. package/dist/components/error-state.d.ts +12 -0
  22. package/dist/components/error-state.js +24 -0
  23. package/dist/components/field.d.ts +13 -0
  24. package/dist/components/field.js +33 -0
  25. package/dist/components/input.d.ts +8 -0
  26. package/dist/components/input.js +16 -0
  27. package/dist/components/menu.d.ts +33 -0
  28. package/dist/components/menu.js +44 -0
  29. package/dist/components/popover.d.ts +26 -0
  30. package/dist/components/popover.js +50 -0
  31. package/dist/components/popup-styles.d.ts +3 -0
  32. package/dist/components/popup-styles.js +5 -0
  33. package/dist/components/progress.d.ts +16 -0
  34. package/dist/components/progress.js +27 -0
  35. package/dist/components/radio-group.d.ts +20 -0
  36. package/dist/components/radio-group.js +40 -0
  37. package/dist/components/select.d.ts +30 -0
  38. package/dist/components/select.js +66 -0
  39. package/dist/components/separator.d.ts +7 -0
  40. package/dist/components/separator.js +14 -0
  41. package/dist/components/skeleton.d.ts +8 -0
  42. package/dist/components/skeleton.js +22 -0
  43. package/dist/components/state-layout.d.ts +11 -0
  44. package/dist/components/state-layout.js +31 -0
  45. package/dist/components/switch.d.ts +12 -0
  46. package/dist/components/switch.js +24 -0
  47. package/dist/components/tabs.d.ts +31 -0
  48. package/dist/components/tabs.js +41 -0
  49. package/dist/components/text.d.ts +10 -0
  50. package/dist/components/text.js +39 -0
  51. package/dist/components/textarea.d.ts +6 -0
  52. package/dist/components/textarea.js +20 -0
  53. package/dist/components/toast.d.ts +24 -0
  54. package/dist/components/toast.js +62 -0
  55. package/dist/components/tooltip.d.ts +11 -0
  56. package/dist/components/tooltip.js +19 -0
  57. package/dist/index.d.ts +24 -0
  58. package/dist/index.js +26 -0
  59. package/dist/lib/cn.d.ts +1 -0
  60. package/dist/lib/cn.js +19 -0
  61. package/dist/styles.css +3 -0
  62. package/dist/tailwind.css +72 -0
  63. package/lint/design-rules.js +25 -0
  64. package/package.json +62 -4
@@ -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,10 @@
1
+ import * as React from 'react';
2
+ export type TextVariant = 'display' | 'title' | 'body' | 'body-strong' | 'label' | 'caption' | 'mono' | 'mono-caption';
3
+ export interface TextProps extends Omit<React.HTMLAttributes<HTMLElement>, 'className'>, Pick<React.LabelHTMLAttributes<HTMLLabelElement>, 'htmlFor'> {
4
+ variant?: TextVariant;
5
+ tone?: 'default' | 'muted' | 'danger';
6
+ /** 렌더할 태그. 기본값 span */
7
+ as?: 'span' | 'p' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' | 'strong' | 'code';
8
+ className?: string;
9
+ }
10
+ export declare const Text: React.ForwardRefExoticComponent<TextProps & React.RefAttributes<HTMLElement>>;
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ import { cn as e } from "../lib/cn.js";
3
+ import * as t from "react";
4
+ import { cva as n } from "class-variance-authority";
5
+ //#region src/components/text.tsx
6
+ var r = n("sn:m-0 sn:font-sans", {
7
+ variants: {
8
+ variant: {
9
+ display: "sn:text-display",
10
+ title: "sn:text-title",
11
+ body: "sn:text-body",
12
+ "body-strong": "sn:text-body-strong",
13
+ label: "sn:text-label",
14
+ caption: "sn:text-caption",
15
+ mono: "sn:font-mono sn:text-body sn:tabular-nums",
16
+ "mono-caption": "sn:font-mono sn:text-caption sn:tabular-nums"
17
+ },
18
+ tone: {
19
+ default: "sn:text-default",
20
+ muted: "sn:text-muted",
21
+ danger: "sn:text-danger"
22
+ }
23
+ },
24
+ defaultVariants: {
25
+ variant: "body",
26
+ tone: "default"
27
+ }
28
+ }), i = t.forwardRef(function({ variant: n, tone: i, as: a = "span", className: o, ...s }, c) {
29
+ return t.createElement(a, {
30
+ ref: c,
31
+ className: e(r({
32
+ variant: n,
33
+ tone: i
34
+ }), o),
35
+ ...s
36
+ });
37
+ });
38
+ //#endregion
39
+ export { i as Text };
@@ -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;
@@ -0,0 +1,62 @@
1
+ "use client";
2
+ import { cn as e } from "../lib/cn.js";
3
+ import { Button as t } from "./button.js";
4
+ import * as n from "react";
5
+ import { CircleAlert as r, CircleCheck as i, Info as a, TriangleAlert as o, X as s } from "lucide-react";
6
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
7
+ import { Toast as u } from "@base-ui/react/toast";
8
+ //#region src/components/toast.tsx
9
+ var d = {
10
+ neutral: /* @__PURE__ */ c(a, { className: "sn:text-muted" }),
11
+ success: /* @__PURE__ */ c(i, { className: "sn:text-success" }),
12
+ warning: /* @__PURE__ */ c(o, { className: "sn:text-warning" }),
13
+ danger: /* @__PURE__ */ c(r, { className: "sn:text-danger" })
14
+ };
15
+ function f() {
16
+ let { add: e, close: t } = u.useToastManager();
17
+ return n.useMemo(() => ({
18
+ add: ({ tone: t = "neutral", ...n }) => e({
19
+ ...n,
20
+ type: t,
21
+ priority: t === "danger" ? "high" : "low"
22
+ }),
23
+ close: (e) => t(e)
24
+ }), [e, t]);
25
+ }
26
+ function p({ timeout: e, children: t }) {
27
+ return /* @__PURE__ */ l(u.Provider, {
28
+ timeout: e,
29
+ children: [t, /* @__PURE__ */ c(u.Portal, { children: /* @__PURE__ */ c(u.Viewport, {
30
+ "aria-label": "알림",
31
+ className: "sn:fixed sn:right-4 sn:bottom-4 sn:z-50 sn:flex sn:w-90 sn:max-w-[calc(100vw-2*var(--sn-space-4))] sn:flex-col-reverse sn:gap-2 sn:outline-none",
32
+ children: /* @__PURE__ */ c(m, {})
33
+ }) })]
34
+ });
35
+ }
36
+ function m() {
37
+ let { toasts: n } = u.useToastManager();
38
+ return n.map((n) => /* @__PURE__ */ l(u.Root, {
39
+ toast: n,
40
+ className: "sn:box-border sn:flex sn:items-start sn:gap-3 sn:rounded-lg sn:border sn:border-default sn:bg-surface sn:p-3 sn:font-sans sn:text-default sn:transition-[opacity,translate] sn:duration-(--sn-duration-normal) sn:ease-standard sn:focus-visible:outline-2 sn:focus-visible:outline-offset-2 sn:focus-visible:outline-focus sn:data-starting-style:translate-y-2 sn:data-starting-style:opacity-0 sn:data-ending-style:translate-y-2 sn:data-ending-style:opacity-0 sn:data-limited:hidden sn:motion-reduce:transition-none",
41
+ children: [
42
+ /* @__PURE__ */ c("span", {
43
+ className: "sn:flex sn:h-7 sn:shrink-0 sn:items-center sn:[&_svg]:size-4",
44
+ children: d[n.type ?? "neutral"]
45
+ }),
46
+ /* @__PURE__ */ l("div", {
47
+ className: "sn:flex sn:min-w-0 sn:flex-1 sn:flex-col sn:gap-1 sn:pt-1",
48
+ children: [/* @__PURE__ */ c(u.Title, { className: "sn:m-0 sn:text-body-strong sn:text-default" }), /* @__PURE__ */ c(u.Description, { className: e("sn:m-0 sn:text-caption sn:text-muted", !n.description && "sn:hidden") })]
49
+ }),
50
+ /* @__PURE__ */ c(u.Close, {
51
+ render: /* @__PURE__ */ c(t, {
52
+ size: "icon-sm",
53
+ variant: "ghost",
54
+ "aria-label": "알림 닫기"
55
+ }),
56
+ children: /* @__PURE__ */ c(s, {})
57
+ })
58
+ ]
59
+ }, n.id));
60
+ }
61
+ //#endregion
62
+ export { p as ToastProvider, f as useToast };
@@ -0,0 +1,11 @@
1
+ import * as React from 'react';
2
+ export interface TooltipProps {
3
+ /** 보여 줄 짧은 이름. 중요한 정보나 클릭할 요소는 넣지 않아요 */
4
+ content: React.ReactNode;
5
+ side?: 'top' | 'bottom' | 'left' | 'right';
6
+ className?: string;
7
+ /** 대상 요소 하나 (보통 아이콘 버튼) */
8
+ children: React.ReactElement;
9
+ }
10
+ /** 마우스는 600ms 뒤, 키보드 포커스는 바로 보여요 */
11
+ export declare function Tooltip({ content, side, className, children }: TooltipProps): React.JSX.Element;
@@ -0,0 +1,19 @@
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 { Tooltip as r } from "@base-ui/react/tooltip";
6
+ //#region src/components/tooltip.tsx
7
+ function i({ content: i, side: a = "top", className: o, children: s }) {
8
+ return /* @__PURE__ */ n(r.Root, { children: [/* @__PURE__ */ t(r.Trigger, { render: s }), /* @__PURE__ */ t(r.Portal, { children: /* @__PURE__ */ t(r.Positioner, {
9
+ side: a,
10
+ sideOffset: 4,
11
+ className: "sn:z-50",
12
+ children: /* @__PURE__ */ t(r.Popup, {
13
+ className: e("sn:origin-(--transform-origin) sn:rounded-md sn:bg-inverse sn:px-2 sn:py-1 sn:font-sans sn:text-caption sn:text-inverse sn:transition-[opacity,scale] sn:duration-(--sn-duration-fast) 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", o),
14
+ children: i
15
+ })
16
+ }) })] });
17
+ }
18
+ //#endregion
19
+ export { i as Tooltip };
@@ -0,0 +1,24 @@
1
+ export { Button, type ButtonProps } from './components/button';
2
+ export { Input, type InputProps } from './components/input';
3
+ export { Textarea, type TextareaProps } from './components/textarea';
4
+ export { Checkbox, CheckboxGroup, type CheckboxProps, type CheckboxGroupProps } from './components/checkbox';
5
+ export { RadioGroup, Radio, type RadioGroupProps, type RadioProps } from './components/radio-group';
6
+ export { Switch, type SwitchProps } from './components/switch';
7
+ export { Select, type SelectProps, type SelectOption } from './components/select';
8
+ export { Text, type TextProps, type TextVariant } from './components/text';
9
+ export { Card, CardHeader, CardTitle, CardDescription, CardBody, CardFooter, type CardProps } from './components/card';
10
+ export { Avatar, type AvatarProps } from './components/avatar';
11
+ export { Field, type FieldProps } from './components/field';
12
+ export { Skeleton, type SkeletonProps } from './components/skeleton';
13
+ export { EmptyState, type EmptyStateProps } from './components/empty-state';
14
+ export { ErrorState, type ErrorStateProps } from './components/error-state';
15
+ export { Dialog, DialogTrigger, DialogPopup, DialogTitle, DialogDescription, DialogClose, DialogFooter, type DialogProps, type DialogButtonProps, type DialogPopupProps, } from './components/dialog';
16
+ export { AlertDialog, AlertDialogTrigger, AlertDialogPopup, AlertDialogTitle, AlertDialogDescription, AlertDialogClose, AlertDialogFooter, } from './components/alert-dialog';
17
+ export { Menu, MenuTrigger, MenuPopup, MenuItem, MenuSeparator, type MenuProps, type MenuTriggerProps, type MenuPopupProps, type MenuItemProps } from './components/menu';
18
+ export { Popover, PopoverTrigger, PopoverPopup, PopoverTitle, PopoverDescription, PopoverClose, type PopoverProps, type PopoverPopupProps } from './components/popover';
19
+ export { Tooltip, type TooltipProps } from './components/tooltip';
20
+ export { Tabs, TabsList, Tab, TabsPanel, type TabsProps, type TabsListProps, type TabProps } from './components/tabs';
21
+ export { Separator, type SeparatorProps } from './components/separator';
22
+ export { ToastProvider, useToast, type ToastProviderProps, type ToastOptions, type ToastTone } from './components/toast';
23
+ export { Badge, type BadgeProps } from './components/badge';
24
+ export { Progress, type ProgressProps } from './components/progress';
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ "use client";
2
+ import { Button as e } from "./components/button.js";
3
+ import { Input as t } from "./components/input.js";
4
+ import { Textarea as n } from "./components/textarea.js";
5
+ import { Checkbox as r, CheckboxGroup as i } from "./components/checkbox.js";
6
+ import { Radio as a, RadioGroup as o } from "./components/radio-group.js";
7
+ import { Switch as s } from "./components/switch.js";
8
+ import { Select as c } from "./components/select.js";
9
+ import { Text as l } from "./components/text.js";
10
+ import { Card as u, CardBody as d, CardDescription as f, CardFooter as p, CardHeader as m, CardTitle as h } from "./components/card.js";
11
+ import { Avatar as g } from "./components/avatar.js";
12
+ import { Field as _ } from "./components/field.js";
13
+ import { Skeleton as v } from "./components/skeleton.js";
14
+ import { EmptyState as y } from "./components/empty-state.js";
15
+ import { ErrorState as b } from "./components/error-state.js";
16
+ import { Dialog as x, DialogClose as S, DialogDescription as C, DialogFooter as w, DialogPopup as T, DialogTitle as E, DialogTrigger as D } from "./components/dialog.js";
17
+ import { AlertDialog as O, AlertDialogClose as k, AlertDialogDescription as A, AlertDialogFooter as j, AlertDialogPopup as M, AlertDialogTitle as N, AlertDialogTrigger as P } from "./components/alert-dialog.js";
18
+ import { Menu as F, MenuItem as I, MenuPopup as L, MenuSeparator as R, MenuTrigger as z } from "./components/menu.js";
19
+ import { Popover as B, PopoverClose as V, PopoverDescription as H, PopoverPopup as U, PopoverTitle as W, PopoverTrigger as G } from "./components/popover.js";
20
+ import { Tooltip as K } from "./components/tooltip.js";
21
+ import { Tab as q, Tabs as J, TabsList as Y, TabsPanel as X } from "./components/tabs.js";
22
+ import { Separator as Z } from "./components/separator.js";
23
+ import { ToastProvider as Q, useToast as $ } from "./components/toast.js";
24
+ import { Badge as ee } from "./components/badge.js";
25
+ import { Progress as te } from "./components/progress.js";
26
+ export { O as AlertDialog, k as AlertDialogClose, A as AlertDialogDescription, j as AlertDialogFooter, M as AlertDialogPopup, N as AlertDialogTitle, P as AlertDialogTrigger, g as Avatar, ee as Badge, e as Button, u as Card, d as CardBody, f as CardDescription, p as CardFooter, m as CardHeader, h as CardTitle, r as Checkbox, i as CheckboxGroup, x as Dialog, S as DialogClose, C as DialogDescription, w as DialogFooter, T as DialogPopup, E as DialogTitle, D as DialogTrigger, y as EmptyState, b as ErrorState, _ as Field, t as Input, F as Menu, I as MenuItem, L as MenuPopup, R as MenuSeparator, z as MenuTrigger, B as Popover, V as PopoverClose, H as PopoverDescription, U as PopoverPopup, W as PopoverTitle, G as PopoverTrigger, te as Progress, a as Radio, o as RadioGroup, c as Select, Z as Separator, v as Skeleton, s as Switch, q as Tab, J as Tabs, Y as TabsList, X as TabsPanel, l as Text, n as Textarea, Q as ToastProvider, K as Tooltip, $ as useToast };
@@ -0,0 +1 @@
1
+ export declare function cn(...inputs: Array<string | false | null | undefined>): string;
package/dist/lib/cn.js ADDED
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import { extendTailwindMerge as e } from "tailwind-merge";
3
+ //#region src/lib/cn.ts
4
+ var t = e({
5
+ prefix: "sn",
6
+ extend: { classGroups: { "font-size": [{ text: [
7
+ "display",
8
+ "title",
9
+ "body",
10
+ "body-strong",
11
+ "label",
12
+ "caption"
13
+ ] }] } }
14
+ });
15
+ function n(...e) {
16
+ return t(e.filter(Boolean).join(" "));
17
+ }
18
+ //#endregion
19
+ export { n as cn };
@@ -0,0 +1,3 @@
1
+ /*! sonic-ui | 전체가 @layer sonic 안에 있어요. 쓰는 쪽: @layer theme, base, sonic, components, utilities; */
2
+ @property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@layer sonic{/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
3
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-duration:initial;--tw-ease:initial;--tw-outline-style:solid;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme;@layer utilities{.sn\:absolute{position:absolute}.sn\:fixed{position:fixed}.sn\:relative{position:relative}.sn\:inset-0{inset:0}.sn\:top-1\/2{top:50%}.sn\:right-4{right:calc(var(--sn-space-1) * 4)}.sn\:bottom-4{bottom:calc(var(--sn-space-1) * 4)}.sn\:bottom-\[-1px\]{bottom:-1px}.sn\:left-0{left:0}.sn\:left-1\/2{left:50%}.sn\:z-50{z-index:50}.sn\:m-0{margin:0}.sn\:my-1{margin-block:var(--sn-space-1)}.sn\:-mt-2{margin-top:calc(var(--sn-space-1) * -2)}.sn\:-mt-3{margin-top:calc(var(--sn-space-1) * -3)}.sn\:mt-2{margin-top:calc(var(--sn-space-1) * 2)}.sn\:mb-0{margin-bottom:0}.sn\:box-border{box-sizing:border-box}.sn\:block{display:block}.sn\:flex{display:flex}.sn\:hidden{display:none}.sn\:inline-flex{display:inline-flex}.sn\:size-2{width:calc(var(--sn-space-1) * 2);height:calc(var(--sn-space-1) * 2)}.sn\:size-3{width:calc(var(--sn-space-1) * 3);height:calc(var(--sn-space-1) * 3)}.sn\:size-4{width:calc(var(--sn-space-1) * 4);height:calc(var(--sn-space-1) * 4)}.sn\:size-6{width:calc(var(--sn-space-1) * 6);height:calc(var(--sn-space-1) * 6)}.sn\:size-7{width:calc(var(--sn-space-1) * 7);height:calc(var(--sn-space-1) * 7)}.sn\:size-8{width:calc(var(--sn-space-1) * 8);height:calc(var(--sn-space-1) * 8)}.sn\:size-10{width:calc(var(--sn-space-1) * 10);height:calc(var(--sn-space-1) * 10)}.sn\:size-12{width:calc(var(--sn-space-1) * 12);height:calc(var(--sn-space-1) * 12)}.sn\:size-full{width:100%;height:100%}.sn\:h-0\.5{height:calc(var(--sn-space-1) * .5)}.sn\:h-1{height:var(--sn-space-1)}.sn\:h-3{height:calc(var(--sn-space-1) * 3)}.sn\:h-4{height:calc(var(--sn-space-1) * 4)}.sn\:h-5{height:calc(var(--sn-space-1) * 5)}.sn\:h-7{height:calc(var(--sn-space-1) * 7)}.sn\:h-8{height:calc(var(--sn-space-1) * 8)}.sn\:h-10{height:calc(var(--sn-space-1) * 10)}.sn\:h-16{height:calc(var(--sn-space-1) * 16)}.sn\:h-full{height:100%}.sn\:h-px{height:1px}.sn\:max-h-\(--available-height\){max-height:var(--available-height)}.sn\:min-h-20{min-height:calc(var(--sn-space-1) * 20)}.sn\:w-\(--active-tab-width\){width:var(--active-tab-width)}.sn\:w-7{width:calc(var(--sn-space-1) * 7)}.sn\:w-80{width:calc(var(--sn-space-1) * 80)}.sn\:w-90{width:calc(var(--sn-space-1) * 90)}.sn\:w-110{width:calc(var(--sn-space-1) * 110)}.sn\:w-full{width:100%}.sn\:w-px{width:1px}.sn\:max-w-\[calc\(100vw-2\*var\(--sn-space-4\)\)\]{max-width:calc(100vw - 2 * var(--sn-space-4))}.sn\:min-w-\(--anchor-width\){min-width:var(--anchor-width)}.sn\:min-w-0{min-width:0}.sn\:min-w-48{min-width:calc(var(--sn-space-1) * 48)}.sn\:flex-1{flex:1}.sn\:shrink-0{flex-shrink:0}.sn\:origin-\(--transform-origin\){transform-origin:var(--transform-origin)}.sn\:-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:translate-x-\(--active-tab-left\){--tw-translate-x:var(--active-tab-left);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:animate-\[sn-pulse_var\(--sn-duration-loop\)_ease-in-out_infinite\]{animation:sn-pulse var(--sn-duration-loop) ease-in-out infinite}.sn\:animate-\[sn-spin_var\(--sn-duration-loop\)_linear_infinite\]{animation:sn-spin var(--sn-duration-loop) linear infinite}.sn\:cursor-default{cursor:default}.sn\:cursor-not-allowed{cursor:not-allowed}.sn\:cursor-pointer{cursor:pointer}.sn\:resize-y{resize:vertical}.sn\:flex-col{flex-direction:column}.sn\:flex-col-reverse{flex-direction:column-reverse}.sn\:flex-wrap{flex-wrap:wrap}.sn\:items-center{align-items:center}.sn\:items-start{align-items:flex-start}.sn\:justify-center{justify-content:center}.sn\:justify-end{justify-content:flex-end}.sn\:gap-1{gap:var(--sn-space-1)}.sn\:gap-2{gap:calc(var(--sn-space-1) * 2)}.sn\:gap-3{gap:calc(var(--sn-space-1) * 3)}.sn\:gap-4{gap:calc(var(--sn-space-1) * 4)}.sn\:gap-6{gap:calc(var(--sn-space-1) * 6)}.sn\:gap-10{gap:calc(var(--sn-space-1) * 10)}.sn\:self-stretch{align-self:stretch}.sn\:truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.sn\:overflow-hidden{overflow:hidden}.sn\:overflow-y-auto{overflow-y:auto}.sn\:rounded-full{border-radius:var(--sn-radius-full)}.sn\:rounded-lg{border-radius:var(--sn-radius-lg)}.sn\:rounded-md{border-radius:var(--sn-radius-md)}.sn\:rounded-sm{border-radius:var(--sn-radius-sm)}.sn\:border{border-style:var(--tw-border-style);border-width:1px}.sn\:border-0{border-style:var(--tw-border-style);border-width:0}.sn\:border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.sn\:border-default{border-color:var(--sn-color-border-default)}.sn\:border-strong{border-color:var(--sn-color-border-strong)}.sn\:border-transparent{border-color:#0000}.sn\:bg-\(--sn-color-border-default\){background-color:var(--sn-color-border-default)}.sn\:bg-\(--sn-color-text-on-primary\){background-color:var(--sn-color-text-on-primary)}.sn\:bg-canvas{background-color:var(--sn-color-bg-canvas)}.sn\:bg-danger{background-color:var(--sn-color-bg-danger)}.sn\:bg-danger-subtle{background-color:var(--sn-color-bg-danger-subtle)}.sn\:bg-inverse{background-color:var(--sn-color-bg-inverse)}.sn\:bg-overlay{background-color:var(--sn-color-bg-overlay)}.sn\:bg-primary{background-color:var(--sn-color-bg-primary)}.sn\:bg-subtle{background-color:var(--sn-color-bg-subtle)}.sn\:bg-success{background-color:var(--sn-color-bg-success)}.sn\:bg-success-subtle{background-color:var(--sn-color-bg-success-subtle)}.sn\:bg-surface{background-color:var(--sn-color-bg-surface)}.sn\:bg-transparent{background-color:#0000}.sn\:bg-warning{background-color:var(--sn-color-bg-warning)}.sn\:bg-warning-subtle{background-color:var(--sn-color-bg-warning-subtle)}.sn\:object-cover{object-fit:cover}.sn\:p-0{padding:0}.sn\:p-1{padding:var(--sn-space-1)}.sn\:p-3{padding:calc(var(--sn-space-1) * 3)}.sn\:p-4{padding:calc(var(--sn-space-1) * 4)}.sn\:p-5{padding:calc(var(--sn-space-1) * 5)}.sn\:p-6{padding:calc(var(--sn-space-1) * 6)}.sn\:p-px{padding:1px}.sn\:px-2{padding-inline:calc(var(--sn-space-1) * 2)}.sn\:px-3{padding-inline:calc(var(--sn-space-1) * 3)}.sn\:px-4{padding-inline:calc(var(--sn-space-1) * 4)}.sn\:px-6{padding-inline:calc(var(--sn-space-1) * 6)}.sn\:py-1{padding-block:var(--sn-space-1)}.sn\:py-2{padding-block:calc(var(--sn-space-1) * 2)}.sn\:py-12{padding-block:calc(var(--sn-space-1) * 12)}.sn\:pt-1{padding-top:var(--sn-space-1)}.sn\:text-center{text-align:center}.sn\:text-left{text-align:left}.sn\:align-middle{vertical-align:middle}.sn\:font-mono{font-family:var(--sn-font-mono)}.sn\:font-sans{font-family:var(--sn-font-sans)}.sn\:text-body{font-size:var(--sn-text-body);line-height:var(--tw-leading,var(--sn-text-body--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-body--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-body--font-weight))}.sn\:text-body-strong{font-size:var(--sn-text-body-strong);line-height:var(--tw-leading,var(--sn-text-body-strong--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-body-strong--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-body-strong--font-weight))}.sn\:text-caption{font-size:var(--sn-text-caption);line-height:var(--tw-leading,var(--sn-text-caption--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-caption--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-caption--font-weight))}.sn\:text-display{font-size:var(--sn-text-display);line-height:var(--tw-leading,var(--sn-text-display--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-display--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-display--font-weight))}.sn\:text-label{font-size:var(--sn-text-label);line-height:var(--tw-leading,var(--sn-text-label--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-label--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-label--font-weight))}.sn\:text-title{font-size:var(--sn-text-title);line-height:var(--tw-leading,var(--sn-text-title--line-height));letter-spacing:var(--tw-tracking,var(--sn-text-title--letter-spacing));font-weight:var(--tw-font-weight,var(--sn-text-title--font-weight))}.sn\:whitespace-nowrap{white-space:nowrap}.sn\:text-danger{color:var(--sn-color-text-danger)}.sn\:text-default{color:var(--sn-color-text-default)}.sn\:text-inverse{color:var(--sn-color-text-inverse)}.sn\:text-muted{color:var(--sn-color-text-muted)}.sn\:text-on-danger{color:var(--sn-color-text-on-danger)}.sn\:text-on-primary{color:var(--sn-color-text-on-primary)}.sn\:text-on-success{color:var(--sn-color-text-on-success)}.sn\:text-on-warning{color:var(--sn-color-text-on-warning)}.sn\:text-success{color:var(--sn-color-text-success)}.sn\:text-warning{color:var(--sn-color-text-warning)}.sn\:tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.sn\:opacity-\(--sn-opacity-disabled\){opacity:var(--sn-opacity-disabled)}.sn\:transition-\[opacity\,scale\]{transition-property:opacity,scale;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-\[opacity\,translate\]{transition-property:opacity,translate;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-\[translate\,width\]{transition-property:translate,width;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.sn\:duration-\(--sn-duration-fast\){--tw-duration:var(--sn-duration-fast);transition-duration:var(--sn-duration-fast)}.sn\:duration-\(--sn-duration-normal\){--tw-duration:var(--sn-duration-normal);transition-duration:var(--sn-duration-normal)}.sn\:ease-standard{--tw-ease:var(--sn-easing-standard);transition-timing-function:var(--sn-easing-standard)}.sn\:outline-none{--tw-outline-style:none;outline-style:none}.sn\:select-none{-webkit-user-select:none;user-select:none}.sn\:group-data-popup-open\:rotate-180:is(:where(.sn\:group)[data-popup-open] *){rotate:180deg}.sn\:group-data-selected\:border-transparent:is(:where(.sn\:group)[data-selected] *){border-color:#0000}.sn\:group-data-selected\:bg-primary:is(:where(.sn\:group)[data-selected] *){background-color:var(--sn-color-bg-primary)}.sn\:placeholder\:text-muted::placeholder{color:var(--sn-color-text-muted)}@media (hover:hover){.sn\:hover\:not-data-disabled\:border-strong:hover:not([data-disabled]){border-color:var(--sn-color-border-strong)}.sn\:hover\:not-data-disabled\:bg-primary-hover:hover:not([data-disabled]){background-color:var(--sn-color-bg-primary-hover)}.sn\:hover\:not-data-disabled\:bg-subtle:hover:not([data-disabled]){background-color:var(--sn-color-bg-subtle)}.sn\:hover\:not-data-disabled\:text-default:hover:not([data-disabled]){color:var(--sn-color-text-default)}}.sn\:focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}.sn\:focus-visible\:-outline-offset-2:focus-visible{outline-offset:calc(2px * -1)}.sn\:focus-visible\:outline-offset-2:focus-visible{outline-offset:2px}.sn\:focus-visible\:outline-focus:focus-visible{outline-color:var(--sn-color-border-focus)}.sn\:aria-invalid\:border-danger[aria-invalid=true]{border-color:var(--sn-color-border-danger)}@media (hover:hover){.sn\:hover\:aria-invalid\:border-danger:hover[aria-invalid=true]{border-color:var(--sn-color-border-danger)}}.sn\:aria-invalid\:focus-visible\:outline-danger[aria-invalid=true]:focus-visible{outline-color:var(--sn-color-border-danger)}.sn\:data-active\:text-default[data-active]{color:var(--sn-color-text-default)}.sn\:data-checked\:translate-x-3[data-checked]{--tw-translate-x:calc(var(--sn-space-1) * 3);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:data-checked\:border-transparent[data-checked]{border-color:#0000}.sn\:data-checked\:bg-\(--sn-color-text-on-primary\)[data-checked]{background-color:var(--sn-color-text-on-primary)}.sn\:data-checked\:bg-primary[data-checked]{background-color:var(--sn-color-bg-primary)}.sn\:data-disabled\:cursor-not-allowed[data-disabled]{cursor:not-allowed}.sn\:data-disabled\:opacity-\(--sn-opacity-disabled\)[data-disabled]{opacity:var(--sn-opacity-disabled)}.sn\:data-disabled\:not-data-loading\:cursor-not-allowed[data-disabled]:not([data-loading]){cursor:not-allowed}.sn\:data-disabled\:not-data-loading\:opacity-\(--sn-opacity-disabled\)[data-disabled]:not([data-loading]){opacity:var(--sn-opacity-disabled)}.sn\:data-ending-style\:translate-y-2[data-ending-style]{--tw-translate-y:calc(var(--sn-space-1) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:data-ending-style\:scale-98[data-ending-style]{--tw-scale-x:98%;--tw-scale-y:98%;--tw-scale-z:98%;scale:var(--tw-scale-x) var(--tw-scale-y)}.sn\:data-ending-style\:opacity-0[data-ending-style]{opacity:0}.sn\:data-highlighted\:bg-subtle[data-highlighted]{background-color:var(--sn-color-bg-subtle)}.sn\:data-indeterminate\:border-transparent[data-indeterminate]{border-color:#0000}.sn\:data-indeterminate\:bg-primary[data-indeterminate]{background-color:var(--sn-color-bg-primary)}.sn\:data-invalid\:border-danger[data-invalid]{border-color:var(--sn-color-border-danger)}.sn\:data-invalid\:focus-visible\:outline-danger[data-invalid]:focus-visible{outline-color:var(--sn-color-border-danger)}.sn\:data-limited\:hidden[data-limited]{display:none}.sn\:data-loading\:cursor-progress[data-loading]{cursor:progress}.sn\:data-placeholder\:text-muted[data-placeholder]{color:var(--sn-color-text-muted)}.sn\:data-popup-open\:border-focus[data-popup-open]{border-color:var(--sn-color-border-focus)}.sn\:data-starting-style\:translate-y-2[data-starting-style]{--tw-translate-y:calc(var(--sn-space-1) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.sn\:data-starting-style\:scale-98[data-starting-style]{--tw-scale-x:98%;--tw-scale-y:98%;--tw-scale-z:98%;scale:var(--tw-scale-x) var(--tw-scale-y)}.sn\:data-starting-style\:opacity-0[data-starting-style]{opacity:0}@media (prefers-reduced-motion:reduce){.sn\:motion-reduce\:animate-none{animation:none}.sn\:motion-reduce\:transition-none{transition-property:none}}.sn\:\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.sn\:\[\&_svg\]\:size-3 svg{width:calc(var(--sn-space-1) * 3);height:calc(var(--sn-space-1) * 3)}.sn\:\[\&_svg\]\:size-4 svg{width:calc(var(--sn-space-1) * 4);height:calc(var(--sn-space-1) * 4)}.sn\:\[\&_svg\]\:size-5 svg{width:calc(var(--sn-space-1) * 5);height:calc(var(--sn-space-1) * 5)}.sn\:\[\&_svg\]\:size-6 svg{width:calc(var(--sn-space-1) * 6);height:calc(var(--sn-space-1) * 6)}.sn\:\[\&_svg\]\:shrink-0 svg{flex-shrink:0}}:root{--sn-space-0:0px;--sn-space-1:4px;--sn-space-2:8px;--sn-space-3:12px;--sn-space-4:16px;--sn-space-5:20px;--sn-space-6:24px;--sn-space-7:28px;--sn-space-8:32px;--sn-space-10:40px;--sn-space-12:48px;--sn-space-16:64px;--sn-radius-sm:2px;--sn-radius-md:4px;--sn-radius-lg:6px;--sn-radius-full:9999px;--sn-stroke-default:1px;--sn-stroke-focus:2px;--sn-duration-fast:.12s;--sn-duration-normal:.2s;--sn-easing-standard:cubic-bezier(.2, 0, 0, 1);--sn-duration-loop:1s;--sn-opacity-disabled:.45;--sn-font-sans:"IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;--sn-font-mono:"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;--sn-text-display:32px;--sn-text-display--line-height:40px;--sn-text-display--font-weight:600;--sn-text-display--letter-spacing:-.02em;--sn-text-title:20px;--sn-text-title--line-height:28px;--sn-text-title--font-weight:600;--sn-text-title--letter-spacing:-.01em;--sn-text-body:14px;--sn-text-body--line-height:20px;--sn-text-body--font-weight:400;--sn-text-body--letter-spacing:0em;--sn-text-body-strong:14px;--sn-text-body-strong--line-height:20px;--sn-text-body-strong--font-weight:500;--sn-text-body-strong--letter-spacing:0em;--sn-text-label:13px;--sn-text-label--line-height:16px;--sn-text-label--font-weight:500;--sn-text-label--letter-spacing:0em;--sn-text-caption:12px;--sn-text-caption--line-height:16px;--sn-text-caption--font-weight:400;--sn-text-caption--letter-spacing:0em;--sn-text-mono:14px;--sn-text-mono--line-height:20px;--sn-text-mono--font-weight:400;--sn-text-mono--letter-spacing:0em;--sn-text-mono-caption:12px;--sn-text-mono-caption--line-height:16px;--sn-text-mono-caption--font-weight:400;--sn-text-mono-caption--letter-spacing:0em}:root,[data-theme=light]{color-scheme:light;--sn-color-bg-canvas:#f4f6f8;--sn-color-bg-surface:#fff;--sn-color-bg-subtle:#e6eaee;--sn-color-bg-primary:#2340d0;--sn-color-bg-primary-hover:#1b33ae;--sn-color-bg-danger:#c8312b;--sn-color-bg-overlay:#0e121680;--sn-color-text-default:#0e1216;--sn-color-text-muted:#5b6672;--sn-color-text-on-primary:#fff;--sn-color-text-on-danger:#fff;--sn-color-text-danger:#c8312b;--sn-color-border-default:#d5dbe1;--sn-color-border-strong:#8d99a6;--sn-color-border-focus:#2340d0;--sn-color-border-danger:#c8312b;--sn-color-bg-inverse:#0e1216;--sn-color-text-inverse:#fff;--sn-color-bg-success:#16804f;--sn-color-text-on-success:#fff;--sn-color-bg-success-subtle:#eaf7f0;--sn-color-text-success:#0f6640;--sn-color-bg-warning:#e3a43a;--sn-color-text-on-warning:#0e1216;--sn-color-bg-warning-subtle:#fcf5e6;--sn-color-text-warning:#85520a;--sn-color-bg-danger-subtle:#fcedec;--sn-color-border-success:#16804f;--sn-color-border-warning:#a86c12}[data-theme=dark]{color-scheme:dark;--sn-color-bg-canvas:#0e1216;--sn-color-bg-surface:#161b21;--sn-color-bg-subtle:#1e252d;--sn-color-bg-primary:#6e86ff;--sn-color-bg-primary-hover:#8c9fff;--sn-color-bg-danger:#f06a5f;--sn-color-bg-overlay:#0e121680;--sn-color-text-default:#e6eaee;--sn-color-text-muted:#8d99a6;--sn-color-text-on-primary:#0e1216;--sn-color-text-on-danger:#0e1216;--sn-color-text-danger:#f06a5f;--sn-color-border-default:#2a323b;--sn-color-border-strong:#5b6672;--sn-color-border-focus:#6e86ff;--sn-color-border-danger:#f06a5f;--sn-color-bg-inverse:#e6eaee;--sn-color-text-inverse:#0e1216;--sn-color-bg-success:#3db882;--sn-color-text-on-success:#0e1216;--sn-color-bg-success-subtle:#0b2218;--sn-color-text-success:#6bd1a0;--sn-color-bg-warning:#e3a43a;--sn-color-text-on-warning:#0e1216;--sn-color-bg-warning-subtle:#24190a;--sn-color-text-warning:#eec46a;--sn-color-bg-danger-subtle:#2b1311;--sn-color-border-success:#3db882;--sn-color-border-warning:#e3a43a}@keyframes sn-pulse{50%{opacity:.5}}@keyframes sn-spin{to{transform:rotate(360deg)}}}
@@ -0,0 +1,72 @@
1
+ /* 생성물: scripts/build-tokens.mjs. 손으로 수정 금지 (원본: Figma → scripts/figma-tokens.json) */
2
+ /* Tailwind v4 @theme 매핑. sn 토큰을 Tailwind 클래스 이름으로 연결해요 (bg-primary, text-muted, rounded-md, text-body …).
3
+ inline reference: 클래스가 var(--sn-*)를 직접 참조하고, Tailwind 테마 변수는 따로 출력하지 않음 */
4
+ @theme inline reference {
5
+ --spacing: var(--sn-space-1);
6
+ --radius-sm: var(--sn-radius-sm);
7
+ --radius-md: var(--sn-radius-md);
8
+ --radius-lg: var(--sn-radius-lg);
9
+ --radius-full: var(--sn-radius-full);
10
+ --ease-standard: var(--sn-easing-standard);
11
+ --font-sans: var(--sn-font-sans);
12
+ --font-mono: var(--sn-font-mono);
13
+ --text-display: var(--sn-text-display);
14
+ --text-display--line-height: var(--sn-text-display--line-height);
15
+ --text-display--font-weight: var(--sn-text-display--font-weight);
16
+ --text-display--letter-spacing: var(--sn-text-display--letter-spacing);
17
+ --text-title: var(--sn-text-title);
18
+ --text-title--line-height: var(--sn-text-title--line-height);
19
+ --text-title--font-weight: var(--sn-text-title--font-weight);
20
+ --text-title--letter-spacing: var(--sn-text-title--letter-spacing);
21
+ --text-body: var(--sn-text-body);
22
+ --text-body--line-height: var(--sn-text-body--line-height);
23
+ --text-body--font-weight: var(--sn-text-body--font-weight);
24
+ --text-body--letter-spacing: var(--sn-text-body--letter-spacing);
25
+ --text-body-strong: var(--sn-text-body-strong);
26
+ --text-body-strong--line-height: var(--sn-text-body-strong--line-height);
27
+ --text-body-strong--font-weight: var(--sn-text-body-strong--font-weight);
28
+ --text-body-strong--letter-spacing: var(--sn-text-body-strong--letter-spacing);
29
+ --text-label: var(--sn-text-label);
30
+ --text-label--line-height: var(--sn-text-label--line-height);
31
+ --text-label--font-weight: var(--sn-text-label--font-weight);
32
+ --text-label--letter-spacing: var(--sn-text-label--letter-spacing);
33
+ --text-caption: var(--sn-text-caption);
34
+ --text-caption--line-height: var(--sn-text-caption--line-height);
35
+ --text-caption--font-weight: var(--sn-text-caption--font-weight);
36
+ --text-caption--letter-spacing: var(--sn-text-caption--letter-spacing);
37
+ --background-color-canvas: var(--sn-color-bg-canvas);
38
+ --background-color-surface: var(--sn-color-bg-surface);
39
+ --background-color-subtle: var(--sn-color-bg-subtle);
40
+ --background-color-primary: var(--sn-color-bg-primary);
41
+ --background-color-primary-hover: var(--sn-color-bg-primary-hover);
42
+ --background-color-danger: var(--sn-color-bg-danger);
43
+ --background-color-overlay: var(--sn-color-bg-overlay);
44
+ --text-color-default: var(--sn-color-text-default);
45
+ --text-color-muted: var(--sn-color-text-muted);
46
+ --text-color-on-primary: var(--sn-color-text-on-primary);
47
+ --text-color-on-danger: var(--sn-color-text-on-danger);
48
+ --text-color-danger: var(--sn-color-text-danger);
49
+ --border-color-default: var(--sn-color-border-default);
50
+ --outline-color-default: var(--sn-color-border-default);
51
+ --border-color-strong: var(--sn-color-border-strong);
52
+ --outline-color-strong: var(--sn-color-border-strong);
53
+ --border-color-focus: var(--sn-color-border-focus);
54
+ --outline-color-focus: var(--sn-color-border-focus);
55
+ --border-color-danger: var(--sn-color-border-danger);
56
+ --outline-color-danger: var(--sn-color-border-danger);
57
+ --background-color-inverse: var(--sn-color-bg-inverse);
58
+ --text-color-inverse: var(--sn-color-text-inverse);
59
+ --background-color-success: var(--sn-color-bg-success);
60
+ --text-color-on-success: var(--sn-color-text-on-success);
61
+ --background-color-success-subtle: var(--sn-color-bg-success-subtle);
62
+ --text-color-success: var(--sn-color-text-success);
63
+ --background-color-warning: var(--sn-color-bg-warning);
64
+ --text-color-on-warning: var(--sn-color-text-on-warning);
65
+ --background-color-warning-subtle: var(--sn-color-bg-warning-subtle);
66
+ --text-color-warning: var(--sn-color-text-warning);
67
+ --background-color-danger-subtle: var(--sn-color-bg-danger-subtle);
68
+ --border-color-success: var(--sn-color-border-success);
69
+ --outline-color-success: var(--sn-color-border-success);
70
+ --border-color-warning: var(--sn-color-border-warning);
71
+ --outline-color-warning: var(--sn-color-border-warning);
72
+ }
@@ -0,0 +1,25 @@
1
+ // DESIGN.md를 지키게 하는 ESLint 규칙 (flat config 조각). 이 저장소에서는 켜지 않고 제공만 함.
2
+ // 쓰는 쪽: import sonicDesign from '@xonic0506/sonic-ui/eslint-design-rules'; export default [..., sonicDesign];
3
+ const className = "JSXAttribute[name.name='className']";
4
+
5
+ export default {
6
+ name: 'sonic-ui/design-rules',
7
+ files: ['**/*.{jsx,tsx}'],
8
+ rules: {
9
+ 'no-restricted-syntax': [
10
+ 'error',
11
+ {
12
+ selector: `${className} Literal[value=/(^|\\s)[\\w:-]*-\\[[^\\]]+\\]/], ${className} TemplateElement[value.raw=/(^|\\s)[\\w:-]*-\\[[^\\]]+\\]/]`,
13
+ message: 'DESIGN.md: 임의의 값 클래스(p-[7px] 등) 대신 토큰 클래스를 쓰세요.',
14
+ },
15
+ {
16
+ selector: `${className} Literal[value=/(^|\\s)[\\w:-]*(bg|text|border|outline|ring|fill|stroke)-(red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|slate|gray|zinc|neutral|stone)-\\d+/]`,
17
+ message: 'DESIGN.md: Tailwind 기본 팔레트 대신 sn 토큰 색(bg-primary, text-muted …)을 쓰세요.',
18
+ },
19
+ {
20
+ selector: 'Literal[value=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/]',
21
+ message: 'DESIGN.md: hex 색을 직접 쓰지 말고 var(--sn-color-…) 토큰을 쓰세요.',
22
+ },
23
+ ],
24
+ },
25
+ };
package/package.json CHANGED
@@ -1,6 +1,64 @@
1
1
  {
2
2
  "name": "@xonic0506/sonic-ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Figma-first personal design system (Base UI + Tailwind v4, prebuilt CSS)",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": [
8
+ "**/*.css"
9
+ ],
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./styles.css": "./dist/styles.css",
16
+ "./tailwind.css": "./dist/tailwind.css",
17
+ "./DESIGN.md": "./DESIGN.md",
18
+ "./AI-RULES.md": "./AI-RULES.md",
19
+ "./eslint-design-rules": "./lint/design-rules.js"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "lint",
24
+ "DESIGN.md",
25
+ "AI-RULES.md"
26
+ ],
27
+ "scripts": {
28
+ "tokens": "node scripts/build-tokens.mjs",
29
+ "build:css": "node scripts/build-css.mjs",
30
+ "build:js": "vite build && tsc -p tsconfig.build.json",
31
+ "build": "rm -rf dist && npm run tokens && npm run build:js && npm run build:css",
32
+ "typecheck": "tsc --noEmit",
33
+ "test": "vitest run",
34
+ "playground": "vite --config playground/vite.config.ts",
35
+ "prepublishOnly": "npm run typecheck && npm test && npm run check:contrast && npm run build",
36
+ "check:contrast": "node scripts/check-contrast.mjs"
37
+ },
38
+ "peerDependencies": {
39
+ "react": "^18.2.0 || ^19.0.0",
40
+ "react-dom": "^18.2.0 || ^19.0.0"
41
+ },
42
+ "dependencies": {
43
+ "@base-ui/react": "^1.9.0",
44
+ "class-variance-authority": "^0.7.1",
45
+ "lucide-react": "^1.59.0",
46
+ "tailwind-merge": "^3.7.0"
47
+ },
48
+ "devDependencies": {
49
+ "@tailwindcss/cli": "^4.3.3",
50
+ "@testing-library/jest-dom": "^7.0.1",
51
+ "@testing-library/react": "^16.3.3",
52
+ "@testing-library/user-event": "^14.6.8",
53
+ "@types/react": "^19.3.0",
54
+ "@types/react-dom": "^19.3.0",
55
+ "@vitejs/plugin-react": "^6.1.2",
56
+ "jsdom": "^30.1.3",
57
+ "react": "^19.3.1",
58
+ "react-dom": "^19.3.1",
59
+ "tailwindcss": "^4.3.3",
60
+ "typescript": "^7.0.2",
61
+ "vite": "^8.3.4",
62
+ "vitest": "^5.0.5"
63
+ }
64
+ }