@pylerai/design-system 2.3.1-rc.1 → 2.5.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.
- package/README.md +2 -1
- package/dist/components/ui/badge.d.ts +3 -0
- package/dist/components/ui/badge.js +28 -20
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/components/ui/button.js +38 -25
- package/dist/components/ui/dropdown.js +89 -86
- package/dist/components/ui/input.js +30 -30
- package/dist/components/ui/label.d.ts +1 -1
- package/dist/components/ui/label.js +11 -10
- package/dist/components/ui/media-slider.d.ts +20 -0
- package/dist/components/ui/media-slider.js +90 -0
- package/dist/components/ui/progress-bar.d.ts +21 -0
- package/dist/components/ui/progress-bar.js +45 -0
- package/dist/components/ui/search-field.js +45 -43
- package/dist/components/ui/table.js +33 -33
- package/dist/components/ui/tabs.js +76 -69
- package/dist/components/ui/timeline-tracks.d.ts +66 -0
- package/dist/components/ui/timeline-tracks.js +80 -0
- package/dist/components/ui/timeline.d.ts +31 -0
- package/dist/components/ui/timeline.js +119 -0
- package/dist/components/ui/toast.js +5 -5
- package/dist/components/ui/video-player.d.ts +29 -0
- package/dist/components/ui/video-player.js +174 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +38 -32
- package/dist/lib/field-box.d.ts +13 -0
- package/dist/lib/field-box.js +11 -0
- package/dist/lib/format-time.d.ts +5 -0
- package/dist/lib/format-time.js +8 -0
- package/dist/lib/icons.d.ts +6 -0
- package/dist/lib/icons.js +68 -44
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +14 -7
- package/dist/style.css +1 -1
- package/dist/tokens/pyler-light.d.ts +12 -0
- package/dist/tokens/pyler-light.js +27 -23
- package/dist/tokens/shadow-radius.d.ts +3 -3
- package/dist/tokens/shadow-radius.js +3 -3
- package/dist/tokens/typography.d.ts +8 -2
- package/dist/tokens/typography.js +6 -0
- package/package.json +1 -1
- package/pyler-tailwind-colors.cjs +4 -0
- package/pyler-typography.cjs +7 -0
|
@@ -7,27 +7,27 @@ import { Checkbox as C } from "./checkbox.js";
|
|
|
7
7
|
const S = {
|
|
8
8
|
borderRight: "3px solid var(--ds-line-normal)"
|
|
9
9
|
}, j = f.forwardRef(
|
|
10
|
-
({ className:
|
|
10
|
+
({ className: i, ...r }, n) => /* @__PURE__ */ a(
|
|
11
11
|
"div",
|
|
12
12
|
{
|
|
13
13
|
ref: n,
|
|
14
14
|
role: "table",
|
|
15
|
-
className: g("w-full overflow-x-auto",
|
|
15
|
+
className: g("w-full overflow-x-auto", i),
|
|
16
16
|
...r
|
|
17
17
|
}
|
|
18
18
|
)
|
|
19
19
|
);
|
|
20
20
|
j.displayName = "TableRoot";
|
|
21
21
|
const L = f.forwardRef(
|
|
22
|
-
(
|
|
22
|
+
(i, r) => /* @__PURE__ */ a("div", { ref: r, role: "rowgroup", ...i })
|
|
23
23
|
);
|
|
24
24
|
L.displayName = "TableHead";
|
|
25
25
|
const R = f.forwardRef(
|
|
26
|
-
(
|
|
26
|
+
(i, r) => /* @__PURE__ */ a("div", { ref: r, role: "rowgroup", ...i })
|
|
27
27
|
);
|
|
28
28
|
R.displayName = "TableBody";
|
|
29
29
|
const N = f.forwardRef(
|
|
30
|
-
({ className:
|
|
30
|
+
({ className: i, hoverable: r = !0, selected: n, ...l }, s) => /* @__PURE__ */ a(
|
|
31
31
|
"div",
|
|
32
32
|
{
|
|
33
33
|
ref: s,
|
|
@@ -37,43 +37,43 @@ const N = f.forwardRef(
|
|
|
37
37
|
"flex items-stretch w-full",
|
|
38
38
|
r && "hover:bg-ds-surface-strong",
|
|
39
39
|
n && "bg-ds-surface-normal",
|
|
40
|
-
|
|
40
|
+
i
|
|
41
41
|
),
|
|
42
|
-
...
|
|
42
|
+
...l
|
|
43
43
|
}
|
|
44
44
|
)
|
|
45
45
|
);
|
|
46
46
|
N.displayName = "TableRow";
|
|
47
47
|
const k = f.forwardRef(
|
|
48
48
|
({
|
|
49
|
-
className:
|
|
49
|
+
className: i,
|
|
50
50
|
children: r,
|
|
51
51
|
sortable: n,
|
|
52
|
-
sortDirection:
|
|
52
|
+
sortDirection: l,
|
|
53
53
|
onSort: s,
|
|
54
54
|
info: d,
|
|
55
55
|
fixedLine: m,
|
|
56
56
|
align: h = "left",
|
|
57
|
-
width:
|
|
58
|
-
style:
|
|
57
|
+
width: b,
|
|
58
|
+
style: p,
|
|
59
59
|
...y
|
|
60
60
|
}, c) => {
|
|
61
|
-
const v = n &&
|
|
61
|
+
const v = n && l != null;
|
|
62
62
|
return /* @__PURE__ */ x(
|
|
63
63
|
"div",
|
|
64
64
|
{
|
|
65
65
|
ref: c,
|
|
66
66
|
role: "columnheader",
|
|
67
67
|
tabIndex: n ? 0 : void 0,
|
|
68
|
-
"aria-sort": n ?
|
|
68
|
+
"aria-sort": n ? l === "asc" ? "ascending" : l === "desc" ? "descending" : "none" : void 0,
|
|
69
69
|
className: g(
|
|
70
|
-
"flex h-10 items-center gap-1 border-
|
|
71
|
-
|
|
70
|
+
"flex h-10 items-center gap-1 border-b border-ds-line-normal bg-ds-surface-alternative px-3 py-2.5",
|
|
71
|
+
b ? "shrink-0" : "flex-1 min-w-0",
|
|
72
72
|
h === "right" && "justify-end",
|
|
73
73
|
n && "cursor-pointer select-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ds-primary-blue-normal",
|
|
74
|
-
|
|
74
|
+
i
|
|
75
75
|
),
|
|
76
|
-
style: { width:
|
|
76
|
+
style: { width: b, ...m ? S : {}, ...p },
|
|
77
77
|
onClick: n ? s : void 0,
|
|
78
78
|
onKeyDown: n ? (u) => {
|
|
79
79
|
(u.key === "Enter" || u.key === " ") && (u.preventDefault(), s?.());
|
|
@@ -88,7 +88,7 @@ const k = f.forwardRef(
|
|
|
88
88
|
className: g(
|
|
89
89
|
"shrink-0 transition-transform duration-150",
|
|
90
90
|
v ? "text-ds-text-strong" : "text-ds-text-disabled",
|
|
91
|
-
|
|
91
|
+
l === "asc" && "rotate-180"
|
|
92
92
|
)
|
|
93
93
|
}
|
|
94
94
|
)
|
|
@@ -99,18 +99,18 @@ const k = f.forwardRef(
|
|
|
99
99
|
);
|
|
100
100
|
k.displayName = "TableHeaderCell";
|
|
101
101
|
const T = f.forwardRef(
|
|
102
|
-
({ className:
|
|
102
|
+
({ className: i, align: r = "left", fixedLine: n, width: l, style: s, children: d, ...m }, h) => /* @__PURE__ */ a(
|
|
103
103
|
"div",
|
|
104
104
|
{
|
|
105
105
|
ref: h,
|
|
106
106
|
role: "cell",
|
|
107
107
|
className: g(
|
|
108
108
|
"flex items-center border-b border-ds-line-normal px-3 py-2",
|
|
109
|
-
|
|
109
|
+
l ? "shrink-0" : "flex-1 min-w-0",
|
|
110
110
|
r === "right" && "justify-end",
|
|
111
|
-
|
|
111
|
+
i
|
|
112
112
|
),
|
|
113
|
-
style: { width:
|
|
113
|
+
style: { width: l, ...n ? S : {}, ...s },
|
|
114
114
|
...m,
|
|
115
115
|
children: d
|
|
116
116
|
}
|
|
@@ -118,21 +118,21 @@ const T = f.forwardRef(
|
|
|
118
118
|
);
|
|
119
119
|
T.displayName = "TableCell";
|
|
120
120
|
function Y({
|
|
121
|
-
columns:
|
|
121
|
+
columns: i,
|
|
122
122
|
data: r,
|
|
123
123
|
sortKey: n,
|
|
124
|
-
sortDirection:
|
|
124
|
+
sortDirection: l,
|
|
125
125
|
onSort: s,
|
|
126
126
|
selectedRows: d,
|
|
127
127
|
onRowSelect: m,
|
|
128
128
|
onSelectAll: h,
|
|
129
|
-
getRowKey:
|
|
130
|
-
onRowClick:
|
|
129
|
+
getRowKey: b,
|
|
130
|
+
onRowClick: p,
|
|
131
131
|
className: y
|
|
132
132
|
}) {
|
|
133
133
|
const c = d !== void 0, v = c && r.length > 0 && d.length === r.length, u = c && d.length > 0 && d.length < r.length, E = (e) => {
|
|
134
134
|
if (!s) return;
|
|
135
|
-
s(e, n === e &&
|
|
135
|
+
s(e, n === e && l === "asc" ? "desc" : "asc");
|
|
136
136
|
};
|
|
137
137
|
return /* @__PURE__ */ x(j, { className: y, children: [
|
|
138
138
|
/* @__PURE__ */ a(L, { children: /* @__PURE__ */ x(N, { hoverable: !1, children: [
|
|
@@ -145,11 +145,11 @@ function Y({
|
|
|
145
145
|
onChange: (e) => h?.(e)
|
|
146
146
|
}
|
|
147
147
|
) }),
|
|
148
|
-
|
|
148
|
+
i.map((e) => /* @__PURE__ */ a(
|
|
149
149
|
k,
|
|
150
150
|
{
|
|
151
151
|
sortable: e.sortable,
|
|
152
|
-
sortDirection: e.sortable && n === e.key ?
|
|
152
|
+
sortDirection: e.sortable && n === e.key ? l ?? null : null,
|
|
153
153
|
onSort: () => E(e.key),
|
|
154
154
|
info: e.info,
|
|
155
155
|
fixedLine: e.fixedLine,
|
|
@@ -161,13 +161,13 @@ function Y({
|
|
|
161
161
|
))
|
|
162
162
|
] }) }),
|
|
163
163
|
/* @__PURE__ */ a(R, { children: r.map((e, o) => {
|
|
164
|
-
const H =
|
|
164
|
+
const H = b ? b(e, o) : o, w = c && d.includes(o);
|
|
165
165
|
return /* @__PURE__ */ x(
|
|
166
166
|
N,
|
|
167
167
|
{
|
|
168
168
|
selected: w,
|
|
169
|
-
onClick:
|
|
170
|
-
className:
|
|
169
|
+
onClick: p ? () => p(e, o) : void 0,
|
|
170
|
+
className: p ? "cursor-pointer" : void 0,
|
|
171
171
|
children: [
|
|
172
172
|
c && /* @__PURE__ */ a(T, { width: 40, className: "justify-center px-1.5", children: /* @__PURE__ */ a(
|
|
173
173
|
C,
|
|
@@ -178,7 +178,7 @@ function Y({
|
|
|
178
178
|
onClick: (t) => t.stopPropagation()
|
|
179
179
|
}
|
|
180
180
|
) }),
|
|
181
|
-
|
|
181
|
+
i.map((t) => /* @__PURE__ */ a(
|
|
182
182
|
T,
|
|
183
183
|
{
|
|
184
184
|
align: t.align,
|
|
@@ -1,55 +1,54 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as h } from "react/jsx-runtime";
|
|
3
3
|
import { cn as T } from "../../lib/utils.js";
|
|
4
|
-
import * as
|
|
5
|
-
const $ =
|
|
4
|
+
import * as s from "react";
|
|
5
|
+
const $ = s.createContext(null);
|
|
6
6
|
function C() {
|
|
7
|
-
const e =
|
|
7
|
+
const e = s.useContext($);
|
|
8
8
|
if (!e) throw new Error("TabsTrigger must be used within Tabs");
|
|
9
9
|
return e;
|
|
10
10
|
}
|
|
11
|
-
const A =
|
|
11
|
+
const A = s.forwardRef(
|
|
12
12
|
({
|
|
13
13
|
value: e,
|
|
14
14
|
defaultValue: t = "",
|
|
15
|
-
onValueChange:
|
|
16
|
-
variant:
|
|
15
|
+
onValueChange: d,
|
|
16
|
+
variant: l = "underline",
|
|
17
17
|
children: f,
|
|
18
|
-
className:
|
|
19
|
-
},
|
|
20
|
-
const [u, p] =
|
|
21
|
-
if (
|
|
22
|
-
const
|
|
23
|
-
return
|
|
24
|
-
}), () =>
|
|
25
|
-
if (!
|
|
26
|
-
const
|
|
27
|
-
return
|
|
18
|
+
className: b
|
|
19
|
+
}, x) => {
|
|
20
|
+
const [u, p] = s.useState(t), g = e !== void 0, i = g ? e : u, m = s.useId(), [w, r] = s.useState(() => /* @__PURE__ */ new Set()), y = s.useCallback((c) => (r((a) => {
|
|
21
|
+
if (a.has(c)) return a;
|
|
22
|
+
const n = new Set(a);
|
|
23
|
+
return n.add(c), n;
|
|
24
|
+
}), () => r((a) => {
|
|
25
|
+
if (!a.has(c)) return a;
|
|
26
|
+
const n = new Set(a);
|
|
27
|
+
return n.delete(c), n;
|
|
28
28
|
})), []), k = (c) => {
|
|
29
|
-
g || p(c),
|
|
30
|
-
},
|
|
31
|
-
() => ({ value: i, onValueChange: k, variant:
|
|
29
|
+
g || p(c), d?.(c);
|
|
30
|
+
}, o = s.useMemo(
|
|
31
|
+
() => ({ value: i, onValueChange: k, variant: l, baseId: m, panels: w, registerPanel: y }),
|
|
32
32
|
// handleChange 는 렌더마다 새로 만들어지지만 참조하는 값이 아래 의존성에 모두 들어 있다
|
|
33
33
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
-
[i, g,
|
|
34
|
+
[i, g, d, l, m, w, y]
|
|
35
35
|
);
|
|
36
|
-
return /* @__PURE__ */ h($.Provider, { value:
|
|
36
|
+
return /* @__PURE__ */ h($.Provider, { value: o, children: /* @__PURE__ */ h("div", { ref: x, className: T("flex flex-col", b), children: f }) });
|
|
37
37
|
}
|
|
38
38
|
);
|
|
39
39
|
A.displayName = "Tabs";
|
|
40
|
-
const R =
|
|
41
|
-
({ children: e, className: t },
|
|
42
|
-
const { variant:
|
|
40
|
+
const R = s.forwardRef(
|
|
41
|
+
({ children: e, className: t }, d) => {
|
|
42
|
+
const { variant: l } = C();
|
|
43
43
|
return /* @__PURE__ */ h(
|
|
44
44
|
"div",
|
|
45
45
|
{
|
|
46
|
-
ref:
|
|
46
|
+
ref: d,
|
|
47
47
|
role: "tablist",
|
|
48
48
|
className: T(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
a === "round" && "bg-ds-surface-strong p-1 rounded-ds-md",
|
|
49
|
+
// 탭 아이템 간격은 모든 변형 공통 32px
|
|
50
|
+
"flex items-center gap-8",
|
|
51
|
+
l === "round" && "bg-ds-surface-strong p-1 rounded-ds-md",
|
|
53
52
|
t
|
|
54
53
|
),
|
|
55
54
|
children: e
|
|
@@ -58,17 +57,17 @@ const R = o.forwardRef(
|
|
|
58
57
|
}
|
|
59
58
|
);
|
|
60
59
|
R.displayName = "TabsList";
|
|
61
|
-
const P =
|
|
62
|
-
({ value: e, disabled: t, children:
|
|
63
|
-
const { value: p, onValueChange: g, variant: i, baseId: m, panels: w } = C(),
|
|
64
|
-
if (
|
|
65
|
-
const
|
|
66
|
-
|
|
60
|
+
const P = s.forwardRef(
|
|
61
|
+
({ value: e, disabled: t, children: d, className: l, onClick: f, onKeyDown: b, ...x }, u) => {
|
|
62
|
+
const { value: p, onValueChange: g, variant: i, baseId: m, panels: w } = C(), r = p === e, y = w.has(e), k = (o) => {
|
|
63
|
+
if (b?.(o), o.defaultPrevented || !["ArrowRight", "ArrowLeft", "Home", "End"].includes(o.key)) return;
|
|
64
|
+
const a = o.currentTarget.closest('[role="tablist"]'), n = Array.from(
|
|
65
|
+
a?.querySelectorAll('[role="tab"]:not([disabled])') ?? []
|
|
67
66
|
);
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
const I =
|
|
71
|
-
|
|
67
|
+
if (n.length === 0) return;
|
|
68
|
+
o.preventDefault();
|
|
69
|
+
const I = n.indexOf(o.currentTarget), N = o.key === "Home" ? 0 : o.key === "End" ? n.length - 1 : o.key === "ArrowRight" ? (I + 1 + n.length) % n.length : (I - 1 + n.length) % n.length;
|
|
70
|
+
n[N].focus(), n[N].click();
|
|
72
71
|
};
|
|
73
72
|
return /* @__PURE__ */ h(
|
|
74
73
|
"button",
|
|
@@ -77,64 +76,72 @@ const P = o.forwardRef(
|
|
|
77
76
|
role: "tab",
|
|
78
77
|
type: "button",
|
|
79
78
|
id: `${m}-tab-${e}`,
|
|
80
|
-
"aria-selected":
|
|
79
|
+
"aria-selected": r,
|
|
81
80
|
"aria-controls": y ? `${m}-panel-${e}` : void 0,
|
|
82
|
-
tabIndex:
|
|
81
|
+
tabIndex: r ? 0 : -1,
|
|
83
82
|
disabled: t,
|
|
84
|
-
onClick: (
|
|
85
|
-
t || g(e), f?.(
|
|
83
|
+
onClick: (o) => {
|
|
84
|
+
t || g(e), f?.(o);
|
|
86
85
|
},
|
|
87
86
|
onKeyDown: k,
|
|
88
87
|
className: T(
|
|
89
|
-
"inline-flex items-center justify-center whitespace-nowrap shrink-0 transition-
|
|
88
|
+
"inline-flex items-center justify-center whitespace-nowrap shrink-0 transition-[color,background-color,border-color,box-shadow] focus-visible:outline-none",
|
|
89
|
+
// 선택되지 않은 탭(비활성 포함)은 모든 변형 공통 14 Regular. 선택된 탭의 글씨는 변형별로 아래에서 정한다
|
|
90
|
+
!r && "text-body-2-r",
|
|
90
91
|
i === "underline" && [
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
// 밑줄 자리(2px)를 모든 탭에 투명하게 깔아 둔다. 선택 탭에만 테두리가 있으면
|
|
93
|
+
// 같은 h-11 안에서 글자가 1px 위로 밀려 선택 탭만 떠 보인다
|
|
94
|
+
"h-11 border-b-2 border-transparent",
|
|
95
|
+
r && "text-body-2-sb",
|
|
96
|
+
r && !t && "border-ds-text-strong text-ds-text-strong",
|
|
97
|
+
!r && !t && "text-ds-text-normal",
|
|
94
98
|
t && "text-ds-text-disabled cursor-not-allowed"
|
|
95
99
|
],
|
|
96
100
|
i === "box" && [
|
|
97
|
-
"h-10 px-3 rounded-ds-md
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
"h-10 px-3 rounded-ds-md",
|
|
102
|
+
r && "text-body-3-m",
|
|
103
|
+
r && !t && "bg-ds-surface-normal text-ds-text-strong",
|
|
104
|
+
!r && !t && "bg-ds-background-default text-ds-text-normal",
|
|
100
105
|
t && "bg-ds-background-default text-ds-text-disabled cursor-not-allowed"
|
|
101
106
|
],
|
|
102
107
|
i === "outline" && [
|
|
103
|
-
"h-8 px-3 rounded-full
|
|
104
|
-
|
|
105
|
-
|
|
108
|
+
"h-8 px-3 rounded-full",
|
|
109
|
+
r && "text-body-4-m",
|
|
110
|
+
r && !t && "bg-ds-surface-normal text-ds-text-strong",
|
|
111
|
+
!r && !t && "bg-ds-background-default border border-ds-line-normal text-ds-text-normal",
|
|
106
112
|
t && "bg-ds-background-default border border-ds-line-normal text-ds-text-disabled cursor-not-allowed"
|
|
107
113
|
],
|
|
108
114
|
i === "round" && [
|
|
109
|
-
"h-7 px-2.5 rounded
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
"h-7 px-2.5 rounded",
|
|
116
|
+
r && "text-body-4-m",
|
|
117
|
+
r && !t && "bg-ds-surface-alternative text-ds-text-strong shadow-ds-default",
|
|
118
|
+
!r && !t && "bg-ds-surface-strong text-ds-text-normal",
|
|
112
119
|
t && "bg-ds-surface-strong text-ds-text-disabled cursor-not-allowed"
|
|
113
120
|
],
|
|
114
|
-
|
|
121
|
+
l
|
|
115
122
|
),
|
|
116
|
-
...
|
|
117
|
-
children:
|
|
123
|
+
...x,
|
|
124
|
+
children: d
|
|
118
125
|
}
|
|
119
126
|
);
|
|
120
127
|
}
|
|
121
128
|
);
|
|
122
129
|
P.displayName = "TabsTrigger";
|
|
123
|
-
const S =
|
|
124
|
-
({ value: e, children: t, className:
|
|
125
|
-
const { value: f, baseId:
|
|
126
|
-
return
|
|
130
|
+
const S = s.forwardRef(
|
|
131
|
+
({ value: e, children: t, className: d }, l) => {
|
|
132
|
+
const { value: f, baseId: b, registerPanel: x } = C(), u = f === e;
|
|
133
|
+
return s.useEffect(() => {
|
|
127
134
|
if (u)
|
|
128
|
-
return
|
|
129
|
-
}, [u, e,
|
|
135
|
+
return x(e);
|
|
136
|
+
}, [u, e, x]), u ? /* @__PURE__ */ h(
|
|
130
137
|
"div",
|
|
131
138
|
{
|
|
132
|
-
ref:
|
|
139
|
+
ref: l,
|
|
133
140
|
role: "tabpanel",
|
|
134
|
-
id: `${
|
|
135
|
-
"aria-labelledby": `${
|
|
141
|
+
id: `${b}-panel-${e}`,
|
|
142
|
+
"aria-labelledby": `${b}-tab-${e}`,
|
|
136
143
|
tabIndex: 0,
|
|
137
|
-
className: T("mt-4",
|
|
144
|
+
className: T("mt-4", d),
|
|
138
145
|
children: t
|
|
139
146
|
}
|
|
140
147
|
) : null;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** Timeline 트랙 타입 · 색 팔레트 · 트랙 행 렌더러. Timeline 내부용 — 타입만 index.tsx 로 공개한다. */
|
|
3
|
+
export interface TimelineTrackBase {
|
|
4
|
+
id: string;
|
|
5
|
+
label: React.ReactNode;
|
|
6
|
+
/**
|
|
7
|
+
* 트랙 색 강제 지정 (CSS color · `var(--ds-*)`). 보통은 비워 둔다 — 비우면
|
|
8
|
+
* `TRACK_PALETTE` 에서 서로 잘 구분되는 색이 트랙 순서대로 자동 배정된다.
|
|
9
|
+
*/
|
|
10
|
+
color?: string;
|
|
11
|
+
/** 표 아래 범례에 쓰는 설명 (예: `Probability (darker = higher)`). 없으면 범례에서 빠진다. */
|
|
12
|
+
description?: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
/** 구간별 강도 — `values` 를 전체 길이에 균등 분할해 진할수록 높게 칠한다. */
|
|
15
|
+
export interface TimelineHeatmapTrack extends TimelineTrackBase {
|
|
16
|
+
type: "heatmap";
|
|
17
|
+
/**
|
|
18
|
+
* 0~1. 값 하나가 구간 하나(div)라 화면 폭보다 많으면 보이지도 않고 DOM 만 늘어난다 —
|
|
19
|
+
* 소비처에서 구간 평균으로 줄여 수백 개 이하로 넘긴다.
|
|
20
|
+
*/
|
|
21
|
+
values: number[];
|
|
22
|
+
}
|
|
23
|
+
/** 시간에 따른 점수 — 균등 간격 샘플을 영역 그래프로 그린다. */
|
|
24
|
+
export interface TimelineLineTrack extends TimelineTrackBase {
|
|
25
|
+
type: "line";
|
|
26
|
+
values: number[];
|
|
27
|
+
/** `values` 의 최댓값 기준. 기본 100 */
|
|
28
|
+
max?: number;
|
|
29
|
+
}
|
|
30
|
+
/** 이벤트 구간 — 시작·끝(초)에 막대를 놓는다. */
|
|
31
|
+
export interface TimelineEventsTrack extends TimelineTrackBase {
|
|
32
|
+
type: "events";
|
|
33
|
+
events: {
|
|
34
|
+
start: number;
|
|
35
|
+
end: number;
|
|
36
|
+
label?: string;
|
|
37
|
+
}[];
|
|
38
|
+
}
|
|
39
|
+
export type TimelineTrack = TimelineHeatmapTrack | TimelineLineTrack | TimelineEventsTrack;
|
|
40
|
+
/**
|
|
41
|
+
* 자동 배정 순서 — `category` 토큰 11색을 CIEDE2000 기준 farthest-point 로 정렬했다.
|
|
42
|
+
* 각 색은 앞선 색들과 플레이헤드 빨강(`primary-red-normal`)에서 가장 먼 것을 고른 결과이고,
|
|
43
|
+
* 괄호는 앞선 색들·플레이헤드와의 최소 ΔE00 이다. 앞 6색은 서로 ΔE00 ≥ 21.8 로 한눈에 구분되고,
|
|
44
|
+
* 7번째부터는 차이가 줄어 트랙이 많으면 비슷한 색이 섞일 수 있다.
|
|
45
|
+
* 토큰 값이 바뀌면 순서를 다시 계산해야 한다 — `npm run check:tokens` 가 순서와 최솟값을 대조한다.
|
|
46
|
+
*/
|
|
47
|
+
export declare const TRACK_PALETTE: readonly ["var(--ds-category-politics)", "var(--ds-category-talk)", "var(--ds-category-religion)", "var(--ds-category-overseas)", "var(--ds-category-controversy)", "var(--ds-category-custom)", "var(--ds-category-sensitive)", "var(--ds-category-hate)", "var(--ds-category-issue)", "var(--ds-category-violence)", "var(--ds-category-sexual)"];
|
|
48
|
+
export declare const ROW_HEIGHT: Record<TimelineTrack["type"], string>;
|
|
49
|
+
export declare function TrackSwatch({ track, color }: {
|
|
50
|
+
track: TimelineTrack;
|
|
51
|
+
color: string;
|
|
52
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
53
|
+
export declare const HeatmapRow: React.NamedExoticComponent<{
|
|
54
|
+
track: TimelineHeatmapTrack;
|
|
55
|
+
color: string;
|
|
56
|
+
duration: number;
|
|
57
|
+
}>;
|
|
58
|
+
export declare const LineRow: React.NamedExoticComponent<{
|
|
59
|
+
track: TimelineLineTrack;
|
|
60
|
+
color: string;
|
|
61
|
+
}>;
|
|
62
|
+
export declare const EventsRow: React.NamedExoticComponent<{
|
|
63
|
+
track: TimelineEventsTrack;
|
|
64
|
+
color: string;
|
|
65
|
+
duration: number;
|
|
66
|
+
}>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as n, jsxs as g, Fragment as x } from "react/jsx-runtime";
|
|
3
|
+
import { formatTime as c } from "../../lib/format-time.js";
|
|
4
|
+
import { clamp as l } from "../../lib/utils.js";
|
|
5
|
+
import * as u from "react";
|
|
6
|
+
const b = [
|
|
7
|
+
"var(--ds-category-politics)",
|
|
8
|
+
// 초록 (70.6)
|
|
9
|
+
"var(--ds-category-talk)",
|
|
10
|
+
// 파랑 (46.4)
|
|
11
|
+
"var(--ds-category-religion)",
|
|
12
|
+
// 노랑 (36.2)
|
|
13
|
+
"var(--ds-category-overseas)",
|
|
14
|
+
// 분홍 (34.2)
|
|
15
|
+
"var(--ds-category-controversy)",
|
|
16
|
+
// 청록 (24.5)
|
|
17
|
+
"var(--ds-category-custom)",
|
|
18
|
+
// 올리브 (21.9)
|
|
19
|
+
"var(--ds-category-sensitive)",
|
|
20
|
+
// 자주 (21.6)
|
|
21
|
+
"var(--ds-category-hate)",
|
|
22
|
+
// 보라 (20.2)
|
|
23
|
+
"var(--ds-category-issue)",
|
|
24
|
+
// 하늘 (19.4)
|
|
25
|
+
"var(--ds-category-violence)",
|
|
26
|
+
// 살구 (17.4)
|
|
27
|
+
"var(--ds-category-sexual)"
|
|
28
|
+
// 빨강 (5.6)
|
|
29
|
+
], d = (o, e) => `color-mix(in srgb, ${o} ${Math.round(l(e, 0, 1) * 100)}%, var(--ds-background-default))`, v = (o) => 0.15 + 0.85 * l(o, 0, 1), R = {
|
|
30
|
+
heatmap: "h-10",
|
|
31
|
+
line: "h-[60px]",
|
|
32
|
+
events: "h-10"
|
|
33
|
+
};
|
|
34
|
+
function k({ track: o, color: e }) {
|
|
35
|
+
return o.type === "heatmap" ? /* @__PURE__ */ n("span", { "aria-hidden": !0, className: "flex h-2 w-2 shrink-0", children: [0, 0.5, 1].map((s) => /* @__PURE__ */ n("span", { className: "h-full flex-1", style: { background: d(e, v(s)) } }, s)) }) : /* @__PURE__ */ n("span", { "aria-hidden": !0, className: "size-2 shrink-0", style: { background: e } });
|
|
36
|
+
}
|
|
37
|
+
const N = u.memo(function({ track: e, color: s, duration: r }) {
|
|
38
|
+
const t = e.values.length;
|
|
39
|
+
return /* @__PURE__ */ n("div", { className: "flex h-3 w-full", children: e.values.map((i, a) => /* @__PURE__ */ n(
|
|
40
|
+
"div",
|
|
41
|
+
{
|
|
42
|
+
title: `${c(a / t * r)}–${c((a + 1) / t * r)} · ${Math.round(l(i, 0, 1) * 100)}%`,
|
|
43
|
+
className: "h-full min-w-px flex-1 hover:outline hover:outline-1 hover:-outline-offset-1 hover:outline-ds-primitive-black",
|
|
44
|
+
style: { background: d(s, v(i)) }
|
|
45
|
+
},
|
|
46
|
+
a
|
|
47
|
+
)) });
|
|
48
|
+
}), E = u.memo(function({ track: e, color: s }) {
|
|
49
|
+
const r = e.max && e.max > 0 ? e.max : 100, t = e.values.length;
|
|
50
|
+
if (t === 0) return null;
|
|
51
|
+
const a = `M${e.values.map((m, h) => {
|
|
52
|
+
const p = t === 1 ? 50 : h / (t - 1) * 100, f = 100 - l(m / r, 0, 1) * 100;
|
|
53
|
+
return `${p},${f}`;
|
|
54
|
+
}).join(" L")}`;
|
|
55
|
+
return /* @__PURE__ */ g("svg", { "aria-hidden": !0, viewBox: "0 0 100 100", preserveAspectRatio: "none", className: "absolute inset-x-0 bottom-0 top-3 h-[calc(100%-12px)] w-full overflow-visible", children: [
|
|
56
|
+
/* @__PURE__ */ n("path", { d: `${a} L100,100 L0,100 Z`, fill: s, fillOpacity: 0.12 }),
|
|
57
|
+
/* @__PURE__ */ n("path", { d: a, fill: "none", stroke: s, strokeWidth: 1, vectorEffect: "non-scaling-stroke" })
|
|
58
|
+
] });
|
|
59
|
+
}), L = u.memo(function({ track: e, color: s, duration: r }) {
|
|
60
|
+
return /* @__PURE__ */ n(x, { children: e.events.map((t, i) => {
|
|
61
|
+
const a = l(t.start / r, 0, 1), m = l(t.end / r, a, 1);
|
|
62
|
+
return /* @__PURE__ */ n(
|
|
63
|
+
"div",
|
|
64
|
+
{
|
|
65
|
+
title: `${t.label ? `${t.label} · ` : ""}${c(t.start)}–${c(t.end)}`,
|
|
66
|
+
className: "absolute top-1/2 h-3 min-w-[2px] -translate-y-1/2",
|
|
67
|
+
style: { left: `${a * 100}%`, width: `${(m - a) * 100}%`, background: s }
|
|
68
|
+
},
|
|
69
|
+
i
|
|
70
|
+
);
|
|
71
|
+
}) });
|
|
72
|
+
});
|
|
73
|
+
export {
|
|
74
|
+
L as EventsRow,
|
|
75
|
+
N as HeatmapRow,
|
|
76
|
+
E as LineRow,
|
|
77
|
+
R as ROW_HEIGHT,
|
|
78
|
+
b as TRACK_PALETTE,
|
|
79
|
+
k as TrackSwatch
|
|
80
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { TimelineTrack } from './timeline-tracks';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
export type { TimelineTrack, TimelineHeatmapTrack, TimelineLineTrack, TimelineEventsTrack, } from './timeline-tracks';
|
|
4
|
+
export interface TimelineProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onSeek"> {
|
|
5
|
+
/** 전체 길이(초) */
|
|
6
|
+
duration: number;
|
|
7
|
+
/**
|
|
8
|
+
* 트랙 행은 `React.memo` 라 플레이헤드가 움직여도 다시 그리지 않는다 — 단, 트랙 객체 참조가 같을 때만.
|
|
9
|
+
* `currentTime` 을 가진 컴포넌트에서 렌더마다 `.map` 으로 새로 만들지 말고 `useMemo` 등으로 고정해 넘긴다.
|
|
10
|
+
*/
|
|
11
|
+
tracks: TimelineTrack[];
|
|
12
|
+
/** 재생 위치(초) — 주면 빨간 플레이헤드를 그린다. `onSeek` 을 쓰면 키보드 이동 기준이므로 함께 넘긴다 */
|
|
13
|
+
currentTime?: number;
|
|
14
|
+
/** 시간 영역 클릭·키보드(←→)로 위치를 고르면 초 단위로 호출된다 */
|
|
15
|
+
onSeek?: (time: number) => void;
|
|
16
|
+
/**
|
|
17
|
+
* 눈금 간격(초, 1 이상). 비우면 길이·폭에 맞춰 눈금이 최대 8개가 되도록 고른다 (60초 영상 → 10초).
|
|
18
|
+
* 주어도 1초 미만이거나 눈금이 폭에 비해 너무 많아지면(눈금당 64px 미만) 자동 간격을 쓴다.
|
|
19
|
+
*/
|
|
20
|
+
tickInterval?: number;
|
|
21
|
+
/** 접근성 라벨 — `onSeek` 이 있으면 시크 슬라이더(기본 "타임라인 재생 위치"), 없으면 루트에 붙는다 */
|
|
22
|
+
"aria-label"?: string;
|
|
23
|
+
/** 트랙 이름 열 폭(px). 기본 240 (Figma). 좁은 화면에서는 줄여서 넘긴다. */
|
|
24
|
+
labelWidth?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Figma TimeLine (Instant Case). 영상 길이를 가로축으로 히트맵·점수 그래프·이벤트 구간 트랙을
|
|
28
|
+
* 쌓고, 재생 위치를 빨간 플레이헤드로 보여준다. 트랙 색은 `TRACK_PALETTE` 에서 자동 배정된다.
|
|
29
|
+
*/
|
|
30
|
+
declare const Timeline: React.ForwardRefExoticComponent<TimelineProps & React.RefAttributes<HTMLDivElement>>;
|
|
31
|
+
export { Timeline };
|