tempest-react-sdk 0.50.0 → 0.51.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/dist/audio/sfx-pool.cjs +1 -1
- package/dist/audio/sfx-pool.cjs.map +1 -1
- package/dist/audio/sfx-pool.js +36 -36
- package/dist/audio/sfx-pool.js.map +1 -1
- package/dist/components/BarList/BarList.cjs +1 -1
- package/dist/components/BarList/BarList.cjs.map +1 -1
- package/dist/components/BarList/BarList.js +6 -1
- package/dist/components/BarList/BarList.js.map +1 -1
- package/dist/components/BarList/bar-list-model.cjs +1 -1
- package/dist/components/BarList/bar-list-model.cjs.map +1 -1
- package/dist/components/BarList/bar-list-model.js +8 -8
- package/dist/components/BarList/bar-list-model.js.map +1 -1
- package/dist/components/Markdown/markdown-parse.cjs +5 -5
- package/dist/components/Markdown/markdown-parse.cjs.map +1 -1
- package/dist/components/Markdown/markdown-parse.js +66 -63
- package/dist/components/Markdown/markdown-parse.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.cjs +1 -1
- package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
- package/dist/components/Sidebar/Sidebar.js +25 -45
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.module.cjs.map +1 -1
- package/dist/components/Sidebar/Sidebar.module.js.map +1 -1
- package/dist/components/Sidebar/SidebarEntry.cjs +2 -0
- package/dist/components/Sidebar/SidebarEntry.cjs.map +1 -0
- package/dist/components/Sidebar/SidebarEntry.js +40 -0
- package/dist/components/Sidebar/SidebarEntry.js.map +1 -0
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +84 -71
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/timeout.cjs +2 -0
- package/dist/http/timeout.cjs.map +1 -0
- package/dist/http/timeout.js +22 -0
- package/dist/http/timeout.js.map +1 -0
- package/dist/icons/material-symbols.cjs.map +1 -1
- package/dist/icons/material-symbols.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/tempest-react-sdk.d.ts +68 -6
- package/dist/utils/csv.cjs.map +1 -1
- package/dist/utils/csv.js.map +1 -1
- package/dist/vite/tempest-pwa-dev-sw.cjs +1 -1
- package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
- package/dist/vite/tempest-pwa-dev-sw.js +49 -41
- package/dist/vite/tempest-pwa-dev-sw.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,90 +1,93 @@
|
|
|
1
1
|
import { safeImageUrl as e, safeLinkUrl as t } from "./markdown-url.js";
|
|
2
2
|
//#region src/components/Markdown/markdown-parse.ts
|
|
3
|
-
var n = /^\s{0,3}(`{3,}|~{3,})\s*([\w+-]*)\s*$/, r = /^\s{0,3}(#{1,6})\s+(.*?)\s*#*\s*$/, i = /^\s{0,3}([-*_])\s*(?:\1\s*){2,}$/, a = /^\s{0,3}>\s?(.*)$/, o = /^(\s*)([-*+])\s+(.*)$/, s = /^(\s*)(\d{1,9})[.)]\s+(.*)$/, c = /^\s{0,3}\|[\s|:-]*$|^\s{0,3}:?-+:?(\s*\|\s*:?-+:?)+\s*\|?\s*$/;
|
|
4
|
-
function l(e) {
|
|
5
|
-
return u(e.replace(/\r\n?/g, "\n").split("\n"));
|
|
6
|
-
}
|
|
3
|
+
var n = /^\s{0,3}(`{3,}|~{3,})\s*([\w+-]*)\s*$/, r = /^\s{0,3}(#{1,6})\s+(.*?)\s*#*\s*$/, i = /^\s{0,3}([-*_])\s*(?:\1\s*){2,}$/, a = /^\s{0,3}>\s?(.*)$/, o = /^(\s*)([-*+])\s+(.*)$/, s = /^(\s*)(\d{1,9})[.)]\s+(.*)$/, c = / {2,}$/, l = /^\s{0,3}\|[\s|:-]*$|^\s{0,3}:?-+:?(\s*\|\s*:?-+:?)+\s*\|?\s*$/;
|
|
7
4
|
function u(e) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
return d(e.replace(/\r\n?/g, "\n").split("\n"));
|
|
6
|
+
}
|
|
7
|
+
function d(e) {
|
|
8
|
+
let t = [], u = 0;
|
|
9
|
+
for (; u < e.length;) {
|
|
10
|
+
let m = e[u];
|
|
11
|
+
if (m.trim() === "") {
|
|
12
|
+
u += 1;
|
|
13
13
|
continue;
|
|
14
14
|
}
|
|
15
|
-
let
|
|
16
|
-
if (
|
|
17
|
-
let n =
|
|
18
|
-
for (
|
|
19
|
-
|
|
15
|
+
let g = n.exec(m);
|
|
16
|
+
if (g) {
|
|
17
|
+
let n = g[1][0], r = [];
|
|
18
|
+
for (u += 1; u < e.length && !RegExp(`^\\s{0,3}${n}{3,}\\s*$`).test(e[u]);) r.push(e[u]), u += 1;
|
|
19
|
+
u += 1, t.push({
|
|
20
20
|
type: "code",
|
|
21
|
-
language:
|
|
21
|
+
language: g[2] || null,
|
|
22
22
|
value: r.join("\n")
|
|
23
23
|
});
|
|
24
24
|
continue;
|
|
25
25
|
}
|
|
26
|
-
let
|
|
27
|
-
if (
|
|
26
|
+
let v = r.exec(m);
|
|
27
|
+
if (v) {
|
|
28
28
|
t.push({
|
|
29
29
|
type: "heading",
|
|
30
|
-
level:
|
|
31
|
-
children:
|
|
32
|
-
}),
|
|
30
|
+
level: v[1].length,
|
|
31
|
+
children: _(v[2])
|
|
32
|
+
}), u += 1;
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
35
|
-
if (i.test(
|
|
36
|
-
t.push({ type: "rule" }),
|
|
35
|
+
if (i.test(m)) {
|
|
36
|
+
t.push({ type: "rule" }), u += 1;
|
|
37
37
|
continue;
|
|
38
38
|
}
|
|
39
|
-
if (a.test(
|
|
39
|
+
if (a.test(m)) {
|
|
40
40
|
let n = [];
|
|
41
|
-
for (;
|
|
42
|
-
let t = a.exec(e[
|
|
41
|
+
for (; u < e.length;) {
|
|
42
|
+
let t = a.exec(e[u]);
|
|
43
43
|
if (t) {
|
|
44
|
-
n.push(t[1]),
|
|
44
|
+
n.push(t[1]), u += 1;
|
|
45
45
|
continue;
|
|
46
46
|
}
|
|
47
|
-
if (e[
|
|
48
|
-
n.push(e[
|
|
47
|
+
if (e[u].trim() === "") break;
|
|
48
|
+
n.push(e[u]), u += 1;
|
|
49
49
|
}
|
|
50
50
|
t.push({
|
|
51
51
|
type: "quote",
|
|
52
|
-
children:
|
|
52
|
+
children: d(n)
|
|
53
53
|
});
|
|
54
54
|
continue;
|
|
55
55
|
}
|
|
56
|
-
if (o.test(
|
|
57
|
-
let [n, r] =
|
|
58
|
-
t.push(n),
|
|
56
|
+
if (o.test(m) || s.test(m)) {
|
|
57
|
+
let [n, r] = p(e, u);
|
|
58
|
+
t.push(n), u = r;
|
|
59
59
|
continue;
|
|
60
60
|
}
|
|
61
|
-
if (
|
|
62
|
-
let [n, r] =
|
|
63
|
-
t.push(n),
|
|
61
|
+
if (u + 1 < e.length && m.includes("|") && l.test(e[u + 1])) {
|
|
62
|
+
let [n, r] = h(e, u);
|
|
63
|
+
t.push(n), u = r;
|
|
64
64
|
continue;
|
|
65
65
|
}
|
|
66
|
-
let
|
|
67
|
-
for (;
|
|
68
|
-
let t = e[
|
|
66
|
+
let y = [], b = [];
|
|
67
|
+
for (; u < e.length && e[u].trim() !== "";) {
|
|
68
|
+
let t = e[u];
|
|
69
69
|
if (r.test(t) || i.test(t) || n.test(t) || a.test(t) || o.test(t) || s.test(t)) break;
|
|
70
|
-
|
|
70
|
+
y.push(t.trim()), b.push(c.test(t)), u += 1;
|
|
71
71
|
}
|
|
72
|
-
|
|
72
|
+
y.length > 0 && t.push({
|
|
73
73
|
type: "paragraph",
|
|
74
|
-
children:
|
|
74
|
+
children: _(f(y, b))
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
return t;
|
|
78
78
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
function f(e, t) {
|
|
80
|
+
return e.map((n, r) => t[r] && r < e.length - 1 ? `${n} ` : n).join("\n");
|
|
81
|
+
}
|
|
82
|
+
function p(e, t) {
|
|
83
|
+
let n = o.exec(e[t]), r = s.exec(e[t]), i = r !== null, a = ((r ?? n)?.[1] ?? "").length, c = i ? Number(r?.[2] ?? 1) : 1, l = [], u = [], f = t, p = () => {
|
|
84
|
+
u.length !== 0 && (l.push(d(u)), u = []);
|
|
82
85
|
};
|
|
83
86
|
for (; f < e.length;) {
|
|
84
87
|
let t = e[f];
|
|
85
88
|
if (t.trim() === "") {
|
|
86
89
|
if (f + 1 < e.length && e[f + 1].trim() !== "") {
|
|
87
|
-
|
|
90
|
+
u.push(""), f += 1;
|
|
88
91
|
continue;
|
|
89
92
|
}
|
|
90
93
|
break;
|
|
@@ -92,15 +95,15 @@ function d(e, t) {
|
|
|
92
95
|
let n = o.exec(t), r = s.exec(t), c = n ?? r;
|
|
93
96
|
if (c && c[1].length <= a) {
|
|
94
97
|
if (!(i ? r : n)) break;
|
|
95
|
-
p(),
|
|
98
|
+
p(), u.push(c[3]), f += 1;
|
|
96
99
|
continue;
|
|
97
100
|
}
|
|
98
101
|
if (c) {
|
|
99
|
-
|
|
102
|
+
u.push(t.slice(a)), f += 1;
|
|
100
103
|
continue;
|
|
101
104
|
}
|
|
102
105
|
if (t.search(/\S/) > a) {
|
|
103
|
-
|
|
106
|
+
u.push(t.trim()), f += 1;
|
|
104
107
|
continue;
|
|
105
108
|
}
|
|
106
109
|
break;
|
|
@@ -112,7 +115,7 @@ function d(e, t) {
|
|
|
112
115
|
items: l
|
|
113
116
|
}, f];
|
|
114
117
|
}
|
|
115
|
-
function
|
|
118
|
+
function m(e) {
|
|
116
119
|
let t = e.trim().replace(/^\|/, "").replace(/\|$/, ""), n = [], r = "";
|
|
117
120
|
for (let e = 0; e < t.length; e += 1) {
|
|
118
121
|
if (t[e] === "\\" && t[e + 1] === "|") {
|
|
@@ -127,20 +130,20 @@ function f(e) {
|
|
|
127
130
|
}
|
|
128
131
|
return n.push(r), n.map((e) => e.trim());
|
|
129
132
|
}
|
|
130
|
-
function
|
|
131
|
-
let n =
|
|
133
|
+
function h(e, t) {
|
|
134
|
+
let n = m(e[t]), r = m(e[t + 1]).map((e) => {
|
|
132
135
|
let t = e.startsWith(":"), n = e.endsWith(":");
|
|
133
136
|
return t && n ? "center" : n ? "right" : t ? "left" : null;
|
|
134
137
|
}), i = [], a = t + 2;
|
|
135
|
-
for (; a < e.length && e[a].trim() !== "" && e[a].includes("|");) i.push(
|
|
138
|
+
for (; a < e.length && e[a].trim() !== "" && e[a].includes("|");) i.push(m(e[a]).map(_)), a += 1;
|
|
136
139
|
return [{
|
|
137
140
|
type: "table",
|
|
138
141
|
align: r,
|
|
139
|
-
head: n.map(
|
|
142
|
+
head: n.map(_),
|
|
140
143
|
rows: i
|
|
141
144
|
}, a];
|
|
142
145
|
}
|
|
143
|
-
var
|
|
146
|
+
var g = [
|
|
144
147
|
{
|
|
145
148
|
pattern: /^!\[([^\]]*)\]\(((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?\)/,
|
|
146
149
|
build: (t) => {
|
|
@@ -158,7 +161,7 @@ var m = [
|
|
|
158
161
|
{
|
|
159
162
|
pattern: /^\[([^\]]*)\]\(((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?\)/,
|
|
160
163
|
build: (e) => {
|
|
161
|
-
let n = t(e[2]), r =
|
|
164
|
+
let n = t(e[2]), r = _(e[1]);
|
|
162
165
|
return n ? {
|
|
163
166
|
type: "link",
|
|
164
167
|
href: n,
|
|
@@ -180,35 +183,35 @@ var m = [
|
|
|
180
183
|
pattern: /^\*\*([\s\S]+?)\*\*/,
|
|
181
184
|
build: (e) => ({
|
|
182
185
|
type: "strong",
|
|
183
|
-
children:
|
|
186
|
+
children: _(e[1])
|
|
184
187
|
})
|
|
185
188
|
},
|
|
186
189
|
{
|
|
187
190
|
pattern: /^__([\s\S]+?)__/,
|
|
188
191
|
build: (e) => ({
|
|
189
192
|
type: "strong",
|
|
190
|
-
children:
|
|
193
|
+
children: _(e[1])
|
|
191
194
|
})
|
|
192
195
|
},
|
|
193
196
|
{
|
|
194
197
|
pattern: /^~~([\s\S]+?)~~/,
|
|
195
198
|
build: (e) => ({
|
|
196
199
|
type: "del",
|
|
197
|
-
children:
|
|
200
|
+
children: _(e[1])
|
|
198
201
|
})
|
|
199
202
|
},
|
|
200
203
|
{
|
|
201
204
|
pattern: /^\*([^*\n]+)\*/,
|
|
202
205
|
build: (e) => ({
|
|
203
206
|
type: "em",
|
|
204
|
-
children:
|
|
207
|
+
children: _(e[1])
|
|
205
208
|
})
|
|
206
209
|
},
|
|
207
210
|
{
|
|
208
211
|
pattern: /^_([^_\n]+)_/,
|
|
209
212
|
build: (e) => ({
|
|
210
213
|
type: "em",
|
|
211
|
-
children:
|
|
214
|
+
children: _(e[1])
|
|
212
215
|
})
|
|
213
216
|
},
|
|
214
217
|
{
|
|
@@ -237,7 +240,7 @@ var m = [
|
|
|
237
240
|
build: () => ({ type: "break" })
|
|
238
241
|
}
|
|
239
242
|
];
|
|
240
|
-
function
|
|
243
|
+
function _(e) {
|
|
241
244
|
let t = [], n = "", r = 0, i = () => {
|
|
242
245
|
n && t.push({
|
|
243
246
|
type: "text",
|
|
@@ -250,7 +253,7 @@ function h(e) {
|
|
|
250
253
|
continue;
|
|
251
254
|
}
|
|
252
255
|
let a = e.slice(r), o = !1;
|
|
253
|
-
for (let e of
|
|
256
|
+
for (let e of g) {
|
|
254
257
|
let n = e.pattern.exec(a);
|
|
255
258
|
if (!n) continue;
|
|
256
259
|
let s = e.build(n);
|
|
@@ -264,6 +267,6 @@ function h(e) {
|
|
|
264
267
|
return i(), t;
|
|
265
268
|
}
|
|
266
269
|
//#endregion
|
|
267
|
-
export {
|
|
270
|
+
export { _ as parseInline, u as parseMarkdown };
|
|
268
271
|
|
|
269
272
|
//# sourceMappingURL=markdown-parse.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown-parse.js","names":[],"sources":["../../../src/components/Markdown/markdown-parse.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a Markdown block parser is a state\n * machine over line kinds: paragraph, heading, fence, list, quote, table and\n * thematic break each need to see the lines around them to know where they end.\n * parseBlocks is that loop, and cutting it into per-kind functions would hand each\n * one the same lookahead cursor.\n */\n// A small Markdown parser for the subset an app actually renders from untrusted\n// text: comments, ticket bodies, release notes, a product description.\n//\n// It produces a node tree, never an HTML string, and the renderer turns that into\n// React elements. No `dangerouslySetInnerHTML` exists anywhere in this component —\n// which is the property that makes rendering somebody else's Markdown safe, and it\n// is structural rather than a promise about escaping.\n//\n// Raw HTML in the input is therefore not \"sanitized\", it is **text**: `<script>` in\n// a comment renders as the four characters somebody typed. That is the whole point,\n// and it is also the documented limit — this is not a CommonMark implementation and\n// does not try to be.\n\nimport { safeImageUrl, safeLinkUrl } from \"./markdown-url\";\n\n/** Inline content. */\nexport type MarkdownInline =\n | { type: \"text\"; value: string }\n | { type: \"strong\"; children: MarkdownInline[] }\n | { type: \"em\"; children: MarkdownInline[] }\n | { type: \"del\"; children: MarkdownInline[] }\n | { type: \"code\"; value: string }\n | { type: \"link\"; href: string; children: MarkdownInline[] }\n | { type: \"image\"; src: string; alt: string }\n | { type: \"break\" };\n\n/** Column alignment of a table, from the delimiter row. */\nexport type MarkdownAlign = \"left\" | \"center\" | \"right\" | null;\n\n/** Block content. */\nexport type MarkdownBlock =\n | { type: \"heading\"; level: number; children: MarkdownInline[] }\n | { type: \"paragraph\"; children: MarkdownInline[] }\n | { type: \"code\"; language: string | null; value: string }\n | { type: \"quote\"; children: MarkdownBlock[] }\n | { type: \"list\"; ordered: boolean; start: number; items: MarkdownBlock[][] }\n | {\n type: \"table\";\n align: MarkdownAlign[];\n head: MarkdownInline[][];\n rows: MarkdownInline[][][];\n }\n | { type: \"rule\" };\n\nconst FENCE = /^\\s{0,3}(`{3,}|~{3,})\\s*([\\w+-]*)\\s*$/;\nconst HEADING = /^\\s{0,3}(#{1,6})\\s+(.*?)\\s*#*\\s*$/;\nconst RULE = /^\\s{0,3}([-*_])\\s*(?:\\1\\s*){2,}$/;\nconst QUOTE = /^\\s{0,3}>\\s?(.*)$/;\nconst BULLET = /^(\\s*)([-*+])\\s+(.*)$/;\nconst ORDERED = /^(\\s*)(\\d{1,9})[.)]\\s+(.*)$/;\n/**\n * A table's delimiter row.\n *\n * The `|` is required somewhere in the line, and that is what tells a one-column\n * table (`| --- |`) apart from a thematic break (`---`) — a single column is a\n * legitimate table, and the first version of this regex demanded two.\n */\nconst TABLE_DELIMITER = /^\\s{0,3}\\|[\\s|:-]*$|^\\s{0,3}:?-+:?(\\s*\\|\\s*:?-+:?)+\\s*\\|?\\s*$/;\n\n/**\n * Parse a Markdown document into blocks.\n *\n * @param source - Raw Markdown.\n * @returns Block nodes in document order.\n */\nexport function parseMarkdown(source: string): MarkdownBlock[] {\n const lines = source.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n return parseBlocks(lines);\n}\n\n/** Parse a run of lines into blocks. */\nfunction parseBlocks(lines: string[]): MarkdownBlock[] {\n const blocks: MarkdownBlock[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n if (line.trim() === \"\") {\n i += 1;\n continue;\n }\n\n const fence = FENCE.exec(line);\n if (fence) {\n const marker = fence[1][0];\n const body: string[] = [];\n i += 1;\n while (i < lines.length && !new RegExp(`^\\\\s{0,3}${marker}{3,}\\\\s*$`).test(lines[i])) {\n body.push(lines[i]);\n i += 1;\n }\n // An unterminated fence still yields a code block: the alternative is to\n // render the rest of the document as prose full of backticks.\n i += 1;\n blocks.push({ type: \"code\", language: fence[2] || null, value: body.join(\"\\n\") });\n continue;\n }\n\n const heading = HEADING.exec(line);\n if (heading) {\n blocks.push({\n type: \"heading\",\n level: heading[1].length,\n children: parseInline(heading[2]),\n });\n i += 1;\n continue;\n }\n\n if (RULE.test(line)) {\n blocks.push({ type: \"rule\" });\n i += 1;\n continue;\n }\n\n if (QUOTE.test(line)) {\n const inner: string[] = [];\n while (i < lines.length) {\n const match = QUOTE.exec(lines[i]);\n if (match) {\n inner.push(match[1]);\n i += 1;\n continue;\n }\n // A blank line ends the quote; a plain line continues it (lazy\n // continuation), which is how people actually write quotes.\n if (lines[i].trim() === \"\") break;\n inner.push(lines[i]);\n i += 1;\n }\n blocks.push({ type: \"quote\", children: parseBlocks(inner) });\n continue;\n }\n\n if (BULLET.test(line) || ORDERED.test(line)) {\n const [list, next] = parseList(lines, i);\n blocks.push(list);\n i = next;\n continue;\n }\n\n if (i + 1 < lines.length && line.includes(\"|\") && TABLE_DELIMITER.test(lines[i + 1])) {\n const [table, next] = parseTable(lines, i);\n blocks.push(table);\n i = next;\n continue;\n }\n\n const paragraph: string[] = [];\n while (i < lines.length && lines[i].trim() !== \"\") {\n const current = lines[i];\n if (\n HEADING.test(current) ||\n RULE.test(current) ||\n FENCE.test(current) ||\n QUOTE.test(current) ||\n BULLET.test(current) ||\n ORDERED.test(current)\n ) {\n break;\n }\n paragraph.push(current.trim());\n i += 1;\n }\n if (paragraph.length > 0) {\n blocks.push({ type: \"paragraph\", children: parseInline(paragraph.join(\"\\n\")) });\n }\n }\n\n return blocks;\n}\n\n/**\n * Parse a list, including nested lists and multi-line items.\n *\n * Nesting is decided by indentation relative to the **first** item's marker, so a\n * list indented inside a quote or another list still reads correctly.\n *\n * @returns The list node and the index of the first line after it.\n */\nfunction parseList(lines: string[], start: number): [MarkdownBlock, number] {\n const bulletStart = BULLET.exec(lines[start]);\n const orderedStart = ORDERED.exec(lines[start]);\n // The two markers are mutually exclusive — `-*+` versus digits — so whichever\n // matched decides the list kind.\n const ordered = orderedStart !== null;\n const first = orderedStart ?? bulletStart;\n const baseIndent = (first?.[1] ?? \"\").length;\n const startNumber = ordered ? Number(orderedStart?.[2] ?? 1) : 1;\n\n const items: MarkdownBlock[][] = [];\n let buffer: string[] = [];\n let i = start;\n\n const flush = (): void => {\n if (buffer.length === 0) return;\n items.push(parseBlocks(buffer));\n buffer = [];\n };\n\n while (i < lines.length) {\n const line = lines[i];\n if (line.trim() === \"\") {\n // A single blank line inside a list is a loose item, not the end. Two in\n // a row end it, which is what a blank-then-paragraph document means.\n if (i + 1 < lines.length && lines[i + 1].trim() !== \"\") {\n buffer.push(\"\");\n i += 1;\n continue;\n }\n break;\n }\n const bullet = BULLET.exec(line);\n const numbered = ORDERED.exec(line);\n const match = bullet ?? numbered;\n\n if (match && match[1].length <= baseIndent) {\n const sameKind = ordered ? Boolean(numbered) : Boolean(bullet);\n if (!sameKind) break;\n flush();\n buffer.push(match[3]);\n i += 1;\n continue;\n }\n if (match) {\n // Deeper marker: keep the indentation so the recursive call sees a list.\n buffer.push(line.slice(baseIndent));\n i += 1;\n continue;\n }\n if (line.search(/\\S/) > baseIndent) {\n buffer.push(line.trim());\n i += 1;\n continue;\n }\n break;\n }\n flush();\n\n return [{ type: \"list\", ordered, start: startNumber, items }, i];\n}\n\n/** Split a table row on unescaped pipes. */\nfunction splitRow(line: string): string[] {\n const trimmed = line.trim().replace(/^\\|/, \"\").replace(/\\|$/, \"\");\n const cells: string[] = [];\n let current = \"\";\n for (let i = 0; i < trimmed.length; i += 1) {\n if (trimmed[i] === \"\\\\\" && trimmed[i + 1] === \"|\") {\n current += \"|\";\n i += 1;\n continue;\n }\n if (trimmed[i] === \"|\") {\n cells.push(current);\n current = \"\";\n continue;\n }\n current += trimmed[i];\n }\n cells.push(current);\n return cells.map((cell) => cell.trim());\n}\n\n/**\n * Parse a GFM pipe table.\n *\n * @returns The table node and the index of the first line after it.\n */\nfunction parseTable(lines: string[], start: number): [MarkdownBlock, number] {\n const head = splitRow(lines[start]);\n const align: MarkdownAlign[] = splitRow(lines[start + 1]).map((cell) => {\n const left = cell.startsWith(\":\");\n const right = cell.endsWith(\":\");\n if (left && right) return \"center\";\n if (right) return \"right\";\n if (left) return \"left\";\n return null;\n });\n\n const rows: MarkdownInline[][][] = [];\n let i = start + 2;\n while (i < lines.length && lines[i].trim() !== \"\" && lines[i].includes(\"|\")) {\n rows.push(splitRow(lines[i]).map(parseInline));\n i += 1;\n }\n\n return [{ type: \"table\", align, head: head.map(parseInline), rows }, i];\n}\n\n/** Inline delimiters, longest marker first so `**` wins over `*`. */\nconst INLINE_RULES: Array<{\n pattern: RegExp;\n build: (match: RegExpExecArray) => MarkdownInline | null;\n}> = [\n {\n pattern: /^!\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const src = safeImageUrl(match[2]);\n return src ? { type: \"image\", src, alt: match[1] } : { type: \"text\", value: match[1] };\n },\n },\n {\n pattern: /^\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const href = safeLinkUrl(match[2]);\n const children = parseInline(match[1]);\n // A rejected URL keeps the label as plain text: dropping the text too\n // would silently delete words somebody wrote.\n return href ? { type: \"link\", href, children } : { type: \"text\", value: match[1] };\n },\n },\n { pattern: /^`([^`]+)`/, build: (match) => ({ type: \"code\", value: match[1] }) },\n {\n pattern: /^\\*\\*([\\s\\S]+?)\\*\\*/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^__([\\s\\S]+?)__/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^~~([\\s\\S]+?)~~/,\n build: (match) => ({ type: \"del\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^\\*([^*\\n]+)\\*/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^_([^_\\n]+)_/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^<((?:https?:\\/\\/|mailto:)[^>\\s]+)>/,\n build: (match) => {\n const href = safeLinkUrl(match[1]);\n return href\n ? { type: \"link\", href, children: [{ type: \"text\", value: match[1] }] }\n : { type: \"text\", value: match[1] };\n },\n },\n { pattern: /^ {2,}\\n/, build: () => ({ type: \"break\" }) },\n { pattern: /^\\\\\\n/, build: () => ({ type: \"break\" }) },\n];\n\n/**\n * Parse inline Markdown.\n *\n * A backslash escapes the next character, so `\\*not italic\\*` renders with the\n * asterisks. Anything that matches no rule is text — an unclosed `**` is two\n * asterisks, not a bold run to the end of the paragraph.\n *\n * @param source - One block's text.\n * @returns Inline nodes.\n */\nexport function parseInline(source: string): MarkdownInline[] {\n const nodes: MarkdownInline[] = [];\n let text = \"\";\n let i = 0;\n\n const flush = (): void => {\n if (text) nodes.push({ type: \"text\", value: text });\n text = \"\";\n };\n\n while (i < source.length) {\n if (source[i] === \"\\\\\" && i + 1 < source.length && source[i + 1] !== \"\\n\") {\n text += source[i + 1];\n i += 2;\n continue;\n }\n\n const rest = source.slice(i);\n let matched = false;\n for (const rule of INLINE_RULES) {\n const match = rule.pattern.exec(rest);\n if (!match) continue;\n const node = rule.build(match);\n if (!node) continue;\n flush();\n nodes.push(node);\n i += match[0].length;\n matched = true;\n break;\n }\n if (matched) continue;\n\n text += source[i];\n i += 1;\n }\n\n flush();\n return nodes;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"markdown-parse.js","names":[],"sources":["../../../src/components/Markdown/markdown-parse.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a Markdown block parser is a state\n * machine over line kinds: paragraph, heading, fence, list, quote, table and\n * thematic break each need to see the lines around them to know where they end.\n * parseBlocks is that loop, and cutting it into per-kind functions would hand each\n * one the same lookahead cursor.\n */\n// A small Markdown parser for the subset an app actually renders from untrusted\n// text: comments, ticket bodies, release notes, a product description.\n//\n// It produces a node tree, never an HTML string, and the renderer turns that into\n// React elements. No `dangerouslySetInnerHTML` exists anywhere in this component —\n// which is the property that makes rendering somebody else's Markdown safe, and it\n// is structural rather than a promise about escaping.\n//\n// Raw HTML in the input is therefore not \"sanitized\", it is **text**: `<script>` in\n// a comment renders as the four characters somebody typed. That is the whole point,\n// and it is also the documented limit — this is not a CommonMark implementation and\n// does not try to be.\n\nimport { safeImageUrl, safeLinkUrl } from \"./markdown-url\";\n\n/** Inline content. */\nexport type MarkdownInline =\n | { type: \"text\"; value: string }\n | { type: \"strong\"; children: MarkdownInline[] }\n | { type: \"em\"; children: MarkdownInline[] }\n | { type: \"del\"; children: MarkdownInline[] }\n | { type: \"code\"; value: string }\n | { type: \"link\"; href: string; children: MarkdownInline[] }\n | { type: \"image\"; src: string; alt: string }\n | { type: \"break\" };\n\n/** Column alignment of a table, from the delimiter row. */\nexport type MarkdownAlign = \"left\" | \"center\" | \"right\" | null;\n\n/** Block content. */\nexport type MarkdownBlock =\n | { type: \"heading\"; level: number; children: MarkdownInline[] }\n | { type: \"paragraph\"; children: MarkdownInline[] }\n | { type: \"code\"; language: string | null; value: string }\n | { type: \"quote\"; children: MarkdownBlock[] }\n | { type: \"list\"; ordered: boolean; start: number; items: MarkdownBlock[][] }\n | {\n type: \"table\";\n align: MarkdownAlign[];\n head: MarkdownInline[][];\n rows: MarkdownInline[][][];\n }\n | { type: \"rule\" };\n\nconst FENCE = /^\\s{0,3}(`{3,}|~{3,})\\s*([\\w+-]*)\\s*$/;\nconst HEADING = /^\\s{0,3}(#{1,6})\\s+(.*?)\\s*#*\\s*$/;\nconst RULE = /^\\s{0,3}([-*_])\\s*(?:\\1\\s*){2,}$/;\nconst QUOTE = /^\\s{0,3}>\\s?(.*)$/;\nconst BULLET = /^(\\s*)([-*+])\\s+(.*)$/;\nconst ORDERED = /^(\\s*)(\\d{1,9})[.)]\\s+(.*)$/;\nconst HARD_BREAK = / {2,}$/;\n/**\n * A table's delimiter row.\n *\n * The `|` is required somewhere in the line, and that is what tells a one-column\n * table (`| --- |`) apart from a thematic break (`---`) — a single column is a\n * legitimate table, and the first version of this regex demanded two.\n */\nconst TABLE_DELIMITER = /^\\s{0,3}\\|[\\s|:-]*$|^\\s{0,3}:?-+:?(\\s*\\|\\s*:?-+:?)+\\s*\\|?\\s*$/;\n\n/**\n * Parse a Markdown document into blocks.\n *\n * @param source - Raw Markdown.\n * @returns Block nodes in document order.\n */\nexport function parseMarkdown(source: string): MarkdownBlock[] {\n const lines = source.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n return parseBlocks(lines);\n}\n\n/** Parse a run of lines into blocks. */\nfunction parseBlocks(lines: string[]): MarkdownBlock[] {\n const blocks: MarkdownBlock[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n if (line.trim() === \"\") {\n i += 1;\n continue;\n }\n\n const fence = FENCE.exec(line);\n if (fence) {\n const marker = fence[1][0];\n const body: string[] = [];\n i += 1;\n while (i < lines.length && !new RegExp(`^\\\\s{0,3}${marker}{3,}\\\\s*$`).test(lines[i])) {\n body.push(lines[i]);\n i += 1;\n }\n // An unterminated fence still yields a code block: the alternative is to\n // render the rest of the document as prose full of backticks.\n i += 1;\n blocks.push({ type: \"code\", language: fence[2] || null, value: body.join(\"\\n\") });\n continue;\n }\n\n const heading = HEADING.exec(line);\n if (heading) {\n blocks.push({\n type: \"heading\",\n level: heading[1].length,\n children: parseInline(heading[2]),\n });\n i += 1;\n continue;\n }\n\n if (RULE.test(line)) {\n blocks.push({ type: \"rule\" });\n i += 1;\n continue;\n }\n\n if (QUOTE.test(line)) {\n const inner: string[] = [];\n while (i < lines.length) {\n const match = QUOTE.exec(lines[i]);\n if (match) {\n inner.push(match[1]);\n i += 1;\n continue;\n }\n // A blank line ends the quote; a plain line continues it (lazy\n // continuation), which is how people actually write quotes.\n if (lines[i].trim() === \"\") break;\n inner.push(lines[i]);\n i += 1;\n }\n blocks.push({ type: \"quote\", children: parseBlocks(inner) });\n continue;\n }\n\n if (BULLET.test(line) || ORDERED.test(line)) {\n const [list, next] = parseList(lines, i);\n blocks.push(list);\n i = next;\n continue;\n }\n\n if (i + 1 < lines.length && line.includes(\"|\") && TABLE_DELIMITER.test(lines[i + 1])) {\n const [table, next] = parseTable(lines, i);\n blocks.push(table);\n i = next;\n continue;\n }\n\n const paragraph: string[] = [];\n const hardBreaks: boolean[] = [];\n while (i < lines.length && lines[i].trim() !== \"\") {\n const current = lines[i];\n if (\n HEADING.test(current) ||\n RULE.test(current) ||\n FENCE.test(current) ||\n QUOTE.test(current) ||\n BULLET.test(current) ||\n ORDERED.test(current)\n ) {\n break;\n }\n paragraph.push(current.trim());\n hardBreaks.push(HARD_BREAK.test(current));\n i += 1;\n }\n if (paragraph.length > 0) {\n blocks.push({\n type: \"paragraph\",\n children: parseInline(joinParagraph(paragraph, hardBreaks)),\n });\n }\n }\n\n return blocks;\n}\n\n/**\n * Re-join the lines of a paragraph, keeping the two-space hard break.\n *\n * Every line is trimmed before it is parsed, because leading indentation is not\n * content and a trailing space is invisible noise in almost every line. The one\n * exception is the CommonMark hard break: two or more trailing spaces mean\n * \"break the line here\", so trimming first would delete the only mark that says\n * so and the ` \\n` inline rule could never match. The flag is recorded before\n * the trim and the marker put back here, on every line but the last — a break\n * after the final line of a paragraph would break into nothing.\n *\n * @param lines - The trimmed lines, in order.\n * @param hardBreaks - Whether each line ended in two or more spaces.\n * @returns The paragraph source the inline parser reads.\n */\nfunction joinParagraph(lines: readonly string[], hardBreaks: readonly boolean[]): string {\n return lines\n .map((line, index) => (hardBreaks[index] && index < lines.length - 1 ? `${line} ` : line))\n .join(\"\\n\");\n}\n\n/**\n * Parse a list, including nested lists and multi-line items.\n *\n * Nesting is decided by indentation relative to the **first** item's marker, so a\n * list indented inside a quote or another list still reads correctly.\n *\n * @returns The list node and the index of the first line after it.\n */\nfunction parseList(lines: string[], start: number): [MarkdownBlock, number] {\n const bulletStart = BULLET.exec(lines[start]);\n const orderedStart = ORDERED.exec(lines[start]);\n // The two markers are mutually exclusive — `-*+` versus digits — so whichever\n // matched decides the list kind.\n const ordered = orderedStart !== null;\n const first = orderedStart ?? bulletStart;\n const baseIndent = (first?.[1] ?? \"\").length;\n const startNumber = ordered ? Number(orderedStart?.[2] ?? 1) : 1;\n\n const items: MarkdownBlock[][] = [];\n let buffer: string[] = [];\n let i = start;\n\n const flush = (): void => {\n if (buffer.length === 0) return;\n items.push(parseBlocks(buffer));\n buffer = [];\n };\n\n while (i < lines.length) {\n const line = lines[i];\n if (line.trim() === \"\") {\n // A single blank line inside a list is a loose item, not the end. Two in\n // a row end it, which is what a blank-then-paragraph document means.\n if (i + 1 < lines.length && lines[i + 1].trim() !== \"\") {\n buffer.push(\"\");\n i += 1;\n continue;\n }\n break;\n }\n const bullet = BULLET.exec(line);\n const numbered = ORDERED.exec(line);\n const match = bullet ?? numbered;\n\n if (match && match[1].length <= baseIndent) {\n const sameKind = ordered ? Boolean(numbered) : Boolean(bullet);\n if (!sameKind) break;\n flush();\n buffer.push(match[3]);\n i += 1;\n continue;\n }\n if (match) {\n // Deeper marker: keep the indentation so the recursive call sees a list.\n buffer.push(line.slice(baseIndent));\n i += 1;\n continue;\n }\n if (line.search(/\\S/) > baseIndent) {\n buffer.push(line.trim());\n i += 1;\n continue;\n }\n break;\n }\n flush();\n\n return [{ type: \"list\", ordered, start: startNumber, items }, i];\n}\n\n/** Split a table row on unescaped pipes. */\nfunction splitRow(line: string): string[] {\n const trimmed = line.trim().replace(/^\\|/, \"\").replace(/\\|$/, \"\");\n const cells: string[] = [];\n let current = \"\";\n for (let i = 0; i < trimmed.length; i += 1) {\n if (trimmed[i] === \"\\\\\" && trimmed[i + 1] === \"|\") {\n current += \"|\";\n i += 1;\n continue;\n }\n if (trimmed[i] === \"|\") {\n cells.push(current);\n current = \"\";\n continue;\n }\n current += trimmed[i];\n }\n cells.push(current);\n return cells.map((cell) => cell.trim());\n}\n\n/**\n * Parse a GFM pipe table.\n *\n * @returns The table node and the index of the first line after it.\n */\nfunction parseTable(lines: string[], start: number): [MarkdownBlock, number] {\n const head = splitRow(lines[start]);\n const align: MarkdownAlign[] = splitRow(lines[start + 1]).map((cell) => {\n const left = cell.startsWith(\":\");\n const right = cell.endsWith(\":\");\n if (left && right) return \"center\";\n if (right) return \"right\";\n if (left) return \"left\";\n return null;\n });\n\n const rows: MarkdownInline[][][] = [];\n let i = start + 2;\n while (i < lines.length && lines[i].trim() !== \"\" && lines[i].includes(\"|\")) {\n rows.push(splitRow(lines[i]).map(parseInline));\n i += 1;\n }\n\n return [{ type: \"table\", align, head: head.map(parseInline), rows }, i];\n}\n\n/** Inline delimiters, longest marker first so `**` wins over `*`. */\nconst INLINE_RULES: Array<{\n pattern: RegExp;\n build: (match: RegExpExecArray) => MarkdownInline | null;\n}> = [\n {\n pattern: /^!\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const src = safeImageUrl(match[2]);\n return src ? { type: \"image\", src, alt: match[1] } : { type: \"text\", value: match[1] };\n },\n },\n {\n pattern: /^\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const href = safeLinkUrl(match[2]);\n const children = parseInline(match[1]);\n // A rejected URL keeps the label as plain text: dropping the text too\n // would silently delete words somebody wrote.\n return href ? { type: \"link\", href, children } : { type: \"text\", value: match[1] };\n },\n },\n { pattern: /^`([^`]+)`/, build: (match) => ({ type: \"code\", value: match[1] }) },\n {\n pattern: /^\\*\\*([\\s\\S]+?)\\*\\*/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^__([\\s\\S]+?)__/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^~~([\\s\\S]+?)~~/,\n build: (match) => ({ type: \"del\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^\\*([^*\\n]+)\\*/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^_([^_\\n]+)_/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^<((?:https?:\\/\\/|mailto:)[^>\\s]+)>/,\n build: (match) => {\n const href = safeLinkUrl(match[1]);\n return href\n ? { type: \"link\", href, children: [{ type: \"text\", value: match[1] }] }\n : { type: \"text\", value: match[1] };\n },\n },\n { pattern: /^ {2,}\\n/, build: () => ({ type: \"break\" }) },\n { pattern: /^\\\\\\n/, build: () => ({ type: \"break\" }) },\n];\n\n/**\n * Parse inline Markdown.\n *\n * A backslash escapes the next character, so `\\*not italic\\*` renders with the\n * asterisks. Anything that matches no rule is text — an unclosed `**` is two\n * asterisks, not a bold run to the end of the paragraph.\n *\n * @param source - One block's text.\n * @returns Inline nodes.\n */\nexport function parseInline(source: string): MarkdownInline[] {\n const nodes: MarkdownInline[] = [];\n let text = \"\";\n let i = 0;\n\n const flush = (): void => {\n if (text) nodes.push({ type: \"text\", value: text });\n text = \"\";\n };\n\n while (i < source.length) {\n if (source[i] === \"\\\\\" && i + 1 < source.length && source[i + 1] !== \"\\n\") {\n text += source[i + 1];\n i += 2;\n continue;\n }\n\n const rest = source.slice(i);\n let matched = false;\n for (const rule of INLINE_RULES) {\n const match = rule.pattern.exec(rest);\n if (!match) continue;\n const node = rule.build(match);\n if (!node) continue;\n flush();\n nodes.push(node);\n i += match[0].length;\n matched = true;\n break;\n }\n if (matched) continue;\n\n text += source[i];\n i += 1;\n }\n\n flush();\n return nodes;\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../utils/cn.cjs"),t=require("./Sidebar.module.cjs");let
|
|
1
|
+
const e=require("../../utils/cn.cjs"),t=require("./Sidebar.module.cjs"),n=require("./SidebarEntry.cjs");let r=require("react"),i=require("react/jsx-runtime");function a(e){let t=[],n;for(let r of e){if(r.type===`separator`){t.push({kind:`separator`,key:r.key}),n=void 0;continue}if(r.type===`section`){n={kind:`items`,key:r.key,section:r,items:[]},t.push(n);continue}n||(n={kind:`items`,key:`loose-${r.key}`,items:[]},t.push(n)),n.items.push(r)}return t}function o({header:o,items:s,value:c,onChange:l,footer:u,collapsed:d=!1,width:f=240,collapsedWidth:p=64,className:m,style:h,...g}){let _=(0,r.useId)(),v=a(s),y=typeof(d?p:f)==`number`?`${d?p:f}px`:d?p:f;return(0,i.jsxs)(`aside`,{className:e.cn(t.default.sidebar,d&&t.default.collapsed,m),style:{width:y,...h},...g,children:[o&&(0,i.jsx)(`div`,{className:t.default.header,children:o}),(0,i.jsx)(`nav`,{className:t.default.nav,"aria-label":`Navegação lateral`,children:v.map(r=>{if(r.kind===`separator`)return(0,i.jsx)(`hr`,{className:t.default.separator},r.key);if(!r.section)return(0,i.jsx)(`div`,{className:t.default.group,children:r.items.map(e=>(0,i.jsx)(n.SidebarEntry,{item:e,active:e.key===c,collapsed:d,onSelect:l},e.key))},r.key);let a=`${_}-${r.key}`;return(0,i.jsxs)(`div`,{role:`group`,"aria-labelledby":a,className:t.default.group,children:[(0,i.jsx)(`div`,{id:a,role:`presentation`,className:e.cn(t.default.section,d&&t.default.sectionCollapsed),children:r.section.label}),r.items.map(e=>(0,i.jsx)(n.SidebarEntry,{item:e,active:e.key===c,collapsed:d,onSelect:l},e.key))]},r.key)})}),u&&(0,i.jsx)(`div`,{className:t.default.footer,children:u})]})}exports.Sidebar=o;
|
|
2
2
|
//# sourceMappingURL=Sidebar.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sidebar.cjs","names":[],"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — header, items, footer are the three regions,\n * value/onChange the selection, and collapsed/width/collapsedWidth the two layout\n * modes it switches between.\n */\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Sidebar.module.css\";\n\nexport interface SidebarItem {\n key: string;\n label: ReactNode;\n icon?: ReactNode;\n badge?: ReactNode;\n disabled?: boolean;\n /**\n * Renders the entry as a link to this URL instead of a button.\n *\n * `onChange` still fires on click, so an app that tracks the active key keeps\n * working — the difference is that the entry now behaves like navigation:\n * middle-click, ctrl-click and \"copy link address\" do what the user expects,\n * and a screen reader announces a link rather than a button.\n *\n * A `disabled` entry ignores this and stays a `<button disabled>`: there is no\n * disabled state for an anchor, and dropping the `href` to fake one leaves a\n * link that announces itself as actionable and is not.\n */\n href?: string;\n}\n\n/**\n * A section heading. Every item after it belongs to the section, until the next\n * section or separator.\n */\nexport interface SidebarSection {\n type: \"section\";\n key: string;\n label: ReactNode;\n}\n\n/** A plain divider, for splitting the list without naming the parts. */\nexport interface SidebarSeparator {\n type: \"separator\";\n key: string;\n}\n\n/**\n * One entry of the navigation list.\n *\n * An entry with no `type` is an item, which is what keeps a plain\n * `SidebarItem[]` a valid `SidebarEntry[]`: adding sections to this component\n * needed no change at any existing call site.\n */\nexport type SidebarEntry = ({ type?: \"item\" } & SidebarItem) | SidebarSection | SidebarSeparator;\n\nexport interface SidebarProps extends Omit<HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Top slot — typically the logo + brand. */\n header?: ReactNode;\n /** Navigation entries. An entry with no `type` is an item. */\n items: SidebarEntry[];\n /** Active item key. */\n value?: string;\n /** Fires when an item is clicked. Receives the item's `key`. */\n onChange?: (key: string) => void;\n /** Bottom slot — typically settings/profile/logout. */\n footer?: ReactNode;\n /** Collapsed mode — only icons visible. Default `false`. */\n collapsed?: boolean;\n /** Width when expanded, in pixels or any CSS length. Default `240px`. */\n width?: number | string;\n /** Width when collapsed, in pixels or any CSS length. Default `64px`. */\n collapsedWidth?: number | string;\n}\n\n/** A run of items, optionally under a section heading. */\ninterface ItemBlock {\n kind: \"items\";\n key: string;\n section?: SidebarSection;\n items: SidebarItem[];\n}\n\n/** A standalone divider between blocks. */\ninterface SeparatorBlock {\n kind: \"separator\";\n key: string;\n}\n\ntype Block = ItemBlock | SeparatorBlock;\n\n/**\n * Fold the flat entry list into the blocks that get rendered.\n *\n * A section opens a block that swallows every following item; a separator closes\n * whatever is open, so items after it are loose again. Items before the first\n * section are loose too — which is the whole existing behaviour, and why a list\n * with no sections comes out of here as one unnamed block.\n *\n * @param entries - The `items` prop, as given.\n * @returns Blocks in render order.\n */\nfunction toBlocks(entries: readonly SidebarEntry[]): Block[] {\n const blocks: Block[] = [];\n let open: ItemBlock | undefined;\n\n for (const entry of entries) {\n if (entry.type === \"separator\") {\n blocks.push({ kind: \"separator\", key: entry.key });\n open = undefined;\n continue;\n }\n if (entry.type === \"section\") {\n open = { kind: \"items\", key: entry.key, section: entry, items: [] };\n blocks.push(open);\n continue;\n }\n if (!open) {\n open = { kind: \"items\", key: `loose-${entry.key}`, items: [] };\n blocks.push(open);\n }\n open.items.push(entry);\n }\n return blocks;\n}\n\n/**\n * Desktop sidebar navigation. Pair with `<Show above=\"md\">` and a `Drawer`\n * for mobile.\n *\n * Sixteen screens in one flat list is a wall, so `items` also takes section\n * headings and separators. A section renders as a labelled `role=\"group\"`, which\n * is what tells a screen reader \"Monitoring, group, 3 items\" — the reason it is\n * not a styled `disabled` item, which would announce an unavailable button and\n * stay in the navigation order.\n *\n * @example\n * const [tab, setTab] = useState(\"home\");\n * <Show above=\"md\">\n * <Sidebar\n * header={<Brand />}\n * items={[{ key: \"home\", label: \"Home\", icon: <Home /> }]}\n * value={tab}\n * onChange={setTab}\n * />\n * </Show>\n *\n * @example\n * <Sidebar\n * items={[\n * { type: \"section\", key: \"monitoring\", label: \"Monitoring\" },\n * { key: \"overview\", label: \"Overview\", href: \"/overview\" },\n * { key: \"activity\", label: \"Activity\", href: \"/activity\" },\n * { type: \"section\", key: \"admin\", label: \"Administration\" },\n * { key: \"settings\", label: \"Settings\", href: \"/settings\" },\n * ]}\n * value={tab}\n * onChange={setTab}\n * />\n */\nexport function Sidebar({\n header,\n items,\n value,\n onChange,\n footer,\n collapsed = false,\n width = 240,\n collapsedWidth = 64,\n className,\n style,\n ...props\n}: SidebarProps) {\n const baseId = useId();\n const blocks = toBlocks(items);\n const finalWidth =\n typeof (collapsed ? collapsedWidth : width) === \"number\"\n ? `${collapsed ? collapsedWidth : width}px`\n : collapsed\n ? collapsedWidth\n : width;\n\n /**\n * Render one navigation entry.\n *\n * @param item - The entry to render.\n * @returns The button, or the anchor when the entry carries an `href`.\n */\n const renderItem = (item: SidebarItem): ReactNode => {\n const active = item.key === value;\n const title = collapsed && typeof item.label === \"string\" ? item.label : undefined;\n const body = (\n <>\n {item.icon && <span className={styles.icon}>{item.icon}</span>}\n {!collapsed && <span className={styles.label}>{item.label}</span>}\n {!collapsed && item.badge !== undefined && (\n <span className={styles.badge}>{item.badge}</span>\n )}\n </>\n );\n\n if (item.href && !item.disabled) {\n return (\n <a\n key={item.key}\n href={item.href}\n className={cn(styles.item, active && styles.active)}\n aria-current={active ? \"page\" : undefined}\n onClick={() => onChange?.(item.key)}\n title={title}\n >\n {body}\n </a>\n );\n }\n\n return (\n <button\n key={item.key}\n type=\"button\"\n className={cn(styles.item, active && styles.active)}\n aria-current={active ? \"page\" : undefined}\n disabled={item.disabled}\n onClick={() => onChange?.(item.key)}\n title={title}\n >\n {body}\n </button>\n );\n };\n\n return (\n <aside\n className={cn(styles.sidebar, collapsed && styles.collapsed, className)}\n style={{ width: finalWidth, ...style }}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <nav className={styles.nav} aria-label=\"Navegação lateral\">\n {blocks.map((block) => {\n if (block.kind === \"separator\") {\n return <hr key={block.key} className={styles.separator} />;\n }\n if (!block.section) {\n return (\n <div key={block.key} className={styles.group}>\n {block.items.map(renderItem)}\n </div>\n );\n }\n const labelId = `${baseId}-${block.key}`;\n return (\n <div\n key={block.key}\n role=\"group\"\n aria-labelledby={labelId}\n className={styles.group}\n >\n <div\n id={labelId}\n role=\"presentation\"\n className={cn(styles.section, collapsed && styles.sectionCollapsed)}\n >\n {block.section.label}\n </div>\n {block.items.map(renderItem)}\n </div>\n );\n })}\n </nav>\n {footer && <div className={styles.footer}>{footer}</div>}\n </aside>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Sidebar.cjs","names":[],"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — header, items, footer are the three regions,\n * value/onChange the selection, and collapsed/width/collapsedWidth the two layout\n * modes it switches between.\n */\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Sidebar.module.css\";\nimport { SidebarEntry } from \"./SidebarEntry\";\n\nexport interface SidebarItem {\n key: string;\n label: ReactNode;\n icon?: ReactNode;\n badge?: ReactNode;\n disabled?: boolean;\n /**\n * Renders the entry as a link to this URL instead of a button.\n *\n * `onChange` still fires on click, so an app that tracks the active key keeps\n * working — the difference is that the entry now behaves like navigation:\n * middle-click, ctrl-click and \"copy link address\" do what the user expects,\n * and a screen reader announces a link rather than a button.\n *\n * A `disabled` entry ignores this and stays a `<button disabled>`: there is no\n * disabled state for an anchor, and dropping the `href` to fake one leaves a\n * link that announces itself as actionable and is not.\n */\n href?: string;\n}\n\n/**\n * A section heading. Every item after it belongs to the section, until the next\n * section or separator.\n */\nexport interface SidebarSection {\n type: \"section\";\n key: string;\n label: ReactNode;\n}\n\n/** A plain divider, for splitting the list without naming the parts. */\nexport interface SidebarSeparator {\n type: \"separator\";\n key: string;\n}\n\n/**\n * One entry of the navigation list.\n *\n * An entry with no `type` is an item, which is what keeps a plain\n * `SidebarItem[]` a valid `SidebarEntry[]`: adding sections to this component\n * needed no change at any existing call site.\n */\nexport type SidebarEntry = ({ type?: \"item\" } & SidebarItem) | SidebarSection | SidebarSeparator;\n\nexport interface SidebarProps extends Omit<HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Top slot — typically the logo + brand. */\n header?: ReactNode;\n /** Navigation entries. An entry with no `type` is an item. */\n items: SidebarEntry[];\n /** Active item key. */\n value?: string;\n /** Fires when an item is clicked. Receives the item's `key`. */\n onChange?: (key: string) => void;\n /** Bottom slot — typically settings/profile/logout. */\n footer?: ReactNode;\n /** Collapsed mode — only icons visible. Default `false`. */\n collapsed?: boolean;\n /** Width when expanded, in pixels or any CSS length. Default `240px`. */\n width?: number | string;\n /** Width when collapsed, in pixels or any CSS length. Default `64px`. */\n collapsedWidth?: number | string;\n}\n\n/** A run of items, optionally under a section heading. */\ninterface ItemBlock {\n kind: \"items\";\n key: string;\n section?: SidebarSection;\n items: SidebarItem[];\n}\n\n/** A standalone divider between blocks. */\ninterface SeparatorBlock {\n kind: \"separator\";\n key: string;\n}\n\ntype Block = ItemBlock | SeparatorBlock;\n\n/**\n * Fold the flat entry list into the blocks that get rendered.\n *\n * A section opens a block that swallows every following item; a separator closes\n * whatever is open, so items after it are loose again. Items before the first\n * section are loose too — which is the whole existing behaviour, and why a list\n * with no sections comes out of here as one unnamed block.\n *\n * @param entries - The `items` prop, as given.\n * @returns Blocks in render order.\n */\nfunction toBlocks(entries: readonly SidebarEntry[]): Block[] {\n const blocks: Block[] = [];\n let open: ItemBlock | undefined;\n\n for (const entry of entries) {\n if (entry.type === \"separator\") {\n blocks.push({ kind: \"separator\", key: entry.key });\n open = undefined;\n continue;\n }\n if (entry.type === \"section\") {\n open = { kind: \"items\", key: entry.key, section: entry, items: [] };\n blocks.push(open);\n continue;\n }\n if (!open) {\n open = { kind: \"items\", key: `loose-${entry.key}`, items: [] };\n blocks.push(open);\n }\n open.items.push(entry);\n }\n return blocks;\n}\n\n/**\n * Desktop sidebar navigation. Pair with `<Show above=\"md\">` and a `Drawer`\n * for mobile.\n *\n * Sixteen screens in one flat list is a wall, so `items` also takes section\n * headings and separators. A section renders as a labelled `role=\"group\"`, which\n * is what tells a screen reader \"Monitoring, group, 3 items\" — the reason it is\n * not a styled `disabled` item, which would announce an unavailable button and\n * stay in the navigation order.\n *\n * @example\n * const [tab, setTab] = useState(\"home\");\n * <Show above=\"md\">\n * <Sidebar\n * header={<Brand />}\n * items={[{ key: \"home\", label: \"Home\", icon: <Home /> }]}\n * value={tab}\n * onChange={setTab}\n * />\n * </Show>\n *\n * @example\n * <Sidebar\n * items={[\n * { type: \"section\", key: \"monitoring\", label: \"Monitoring\" },\n * { key: \"overview\", label: \"Overview\", href: \"/overview\" },\n * { key: \"activity\", label: \"Activity\", href: \"/activity\" },\n * { type: \"section\", key: \"admin\", label: \"Administration\" },\n * { key: \"settings\", label: \"Settings\", href: \"/settings\" },\n * ]}\n * value={tab}\n * onChange={setTab}\n * />\n */\nexport function Sidebar({\n header,\n items,\n value,\n onChange,\n footer,\n collapsed = false,\n width = 240,\n collapsedWidth = 64,\n className,\n style,\n ...props\n}: SidebarProps) {\n const baseId = useId();\n const blocks = toBlocks(items);\n const finalWidth =\n typeof (collapsed ? collapsedWidth : width) === \"number\"\n ? `${collapsed ? collapsedWidth : width}px`\n : collapsed\n ? collapsedWidth\n : width;\n\n return (\n <aside\n className={cn(styles.sidebar, collapsed && styles.collapsed, className)}\n style={{ width: finalWidth, ...style }}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <nav className={styles.nav} aria-label=\"Navegação lateral\">\n {blocks.map((block) => {\n if (block.kind === \"separator\") {\n return <hr key={block.key} className={styles.separator} />;\n }\n if (!block.section) {\n return (\n <div key={block.key} className={styles.group}>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === value}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n }\n const labelId = `${baseId}-${block.key}`;\n return (\n <div\n key={block.key}\n role=\"group\"\n aria-labelledby={labelId}\n className={styles.group}\n >\n <div\n id={labelId}\n role=\"presentation\"\n className={cn(styles.section, collapsed && styles.sectionCollapsed)}\n >\n {block.section.label}\n </div>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === value}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n })}\n </nav>\n {footer && <div className={styles.footer}>{footer}</div>}\n </aside>\n );\n}\n"],"mappings":"8JAsGA,SAAS,EAAS,EAA2C,CACzD,IAAM,EAAkB,CAAC,EACrB,EAEJ,IAAK,IAAM,KAAS,EAAS,CACzB,GAAI,EAAM,OAAS,YAAa,CAC5B,EAAO,KAAK,CAAE,KAAM,YAAa,IAAK,EAAM,GAAI,CAAC,EACjD,EAAO,IAAA,GACP,QACJ,CACA,GAAI,EAAM,OAAS,UAAW,CAC1B,EAAO,CAAE,KAAM,QAAS,IAAK,EAAM,IAAK,QAAS,EAAO,MAAO,CAAC,CAAE,EAClE,EAAO,KAAK,CAAI,EAChB,QACJ,CACK,IACD,EAAO,CAAE,KAAM,QAAS,IAAK,SAAS,EAAM,MAAO,MAAO,CAAC,CAAE,EAC7D,EAAO,KAAK,CAAI,GAEpB,EAAK,MAAM,KAAK,CAAK,CACzB,CACA,OAAO,CACX,CAoCA,SAAgB,EAAQ,CACpB,SACA,QACA,QACA,WACA,SACA,YAAY,GACZ,QAAQ,IACR,iBAAiB,GACjB,YACA,QACA,GAAG,GACU,CACb,IAAM,GAAA,EAAS,EAAA,MAAA,CAAM,EACf,EAAS,EAAS,CAAK,EACvB,EACF,OAAQ,EAAY,EAAiB,IAAW,SAC1C,GAAG,EAAY,EAAiB,EAAM,IACtC,EACE,EACA,EAEZ,OACI,EAAA,EAAA,KAAA,CAAC,QAAD,CACI,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,GAAa,EAAA,QAAO,UAAW,CAAS,EACtE,MAAO,CAAE,MAAO,EAAY,GAAG,CAAM,EACrC,GAAI,EAHR,SAAA,CAKK,IAAU,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,OAAS,SAAA,CAAY,CAAA,GACvD,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,IAAK,aAAW,oBAClC,SAAA,EAAO,IAAK,GAAU,CACnB,GAAI,EAAM,OAAS,YACf,OAAO,EAAA,EAAA,IAAA,CAAC,KAAD,CAAoB,UAAW,EAAA,QAAO,SAAY,EAAzC,EAAM,GAAmC,EAE7D,GAAI,CAAC,EAAM,QACP,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAqB,UAAW,EAAA,QAAO,MAClC,SAAA,EAAM,MAAM,IAAK,IACd,EAAA,EAAA,IAAA,CAAC,EAAA,aAAD,CAEU,OACN,OAAQ,EAAK,MAAQ,EACV,YACX,SAAU,CACb,EALQ,EAAK,GAKb,CACJ,CACA,EAVK,EAAM,GAUX,EAGb,IAAM,EAAU,GAAG,EAAO,GAAG,EAAM,MACnC,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAEI,KAAK,QACL,kBAAiB,EACjB,UAAW,EAAA,QAAO,MAJtB,SAAA,EAMI,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,GAAI,EACJ,KAAK,eACL,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,GAAa,EAAA,QAAO,gBAAgB,EAEjE,SAAA,EAAM,QAAQ,KACd,CAAA,EACJ,EAAM,MAAM,IAAK,IACd,EAAA,EAAA,IAAA,CAAC,EAAA,aAAD,CAEU,OACN,OAAQ,EAAK,MAAQ,EACV,YACX,SAAU,CACb,EALQ,EAAK,GAKb,CACJ,CACA,CArBI,EAAA,EAAM,GAqBV,CAEb,CAAC,CACA,CAAA,EACJ,IAAU,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,OAAS,SAAA,CAAY,CAAA,CACpD,GAEf"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { cn as e } from "../../utils/cn.js";
|
|
2
2
|
import t from "./Sidebar.module.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { SidebarEntry as n } from "./SidebarEntry.js";
|
|
4
|
+
import { useId as r } from "react";
|
|
5
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
5
6
|
//#region src/components/Sidebar/Sidebar.tsx
|
|
6
7
|
function o(e) {
|
|
7
8
|
let t = [], n;
|
|
@@ -31,38 +32,7 @@ function o(e) {
|
|
|
31
32
|
return t;
|
|
32
33
|
}
|
|
33
34
|
function s({ header: s, items: c, value: l, onChange: u, footer: d, collapsed: f = !1, width: p = 240, collapsedWidth: m = 64, className: h, style: g, ..._ }) {
|
|
34
|
-
let v =
|
|
35
|
-
let o = n.key === l, s = f && typeof n.label == "string" ? n.label : void 0, c = /* @__PURE__ */ a(r, { children: [
|
|
36
|
-
n.icon && /* @__PURE__ */ i("span", {
|
|
37
|
-
className: t.icon,
|
|
38
|
-
children: n.icon
|
|
39
|
-
}),
|
|
40
|
-
!f && /* @__PURE__ */ i("span", {
|
|
41
|
-
className: t.label,
|
|
42
|
-
children: n.label
|
|
43
|
-
}),
|
|
44
|
-
!f && n.badge !== void 0 && /* @__PURE__ */ i("span", {
|
|
45
|
-
className: t.badge,
|
|
46
|
-
children: n.badge
|
|
47
|
-
})
|
|
48
|
-
] });
|
|
49
|
-
return n.href && !n.disabled ? /* @__PURE__ */ i("a", {
|
|
50
|
-
href: n.href,
|
|
51
|
-
className: e(t.item, o && t.active),
|
|
52
|
-
"aria-current": o ? "page" : void 0,
|
|
53
|
-
onClick: () => u?.(n.key),
|
|
54
|
-
title: s,
|
|
55
|
-
children: c
|
|
56
|
-
}, n.key) : /* @__PURE__ */ i("button", {
|
|
57
|
-
type: "button",
|
|
58
|
-
className: e(t.item, o && t.active),
|
|
59
|
-
"aria-current": o ? "page" : void 0,
|
|
60
|
-
disabled: n.disabled,
|
|
61
|
-
onClick: () => u?.(n.key),
|
|
62
|
-
title: s,
|
|
63
|
-
children: c
|
|
64
|
-
}, n.key);
|
|
65
|
-
};
|
|
35
|
+
let v = r(), y = o(c), b = typeof (f ? m : p) == "number" ? `${f ? m : p}px` : f ? m : p;
|
|
66
36
|
return /* @__PURE__ */ a("aside", {
|
|
67
37
|
className: e(t.sidebar, f && t.collapsed, h),
|
|
68
38
|
style: {
|
|
@@ -78,24 +48,34 @@ function s({ header: s, items: c, value: l, onChange: u, footer: d, collapsed: f
|
|
|
78
48
|
/* @__PURE__ */ i("nav", {
|
|
79
49
|
className: t.nav,
|
|
80
50
|
"aria-label": "Navegação lateral",
|
|
81
|
-
children: y.map((
|
|
82
|
-
if (
|
|
83
|
-
if (!
|
|
51
|
+
children: y.map((r) => {
|
|
52
|
+
if (r.kind === "separator") return /* @__PURE__ */ i("hr", { className: t.separator }, r.key);
|
|
53
|
+
if (!r.section) return /* @__PURE__ */ i("div", {
|
|
84
54
|
className: t.group,
|
|
85
|
-
children:
|
|
86
|
-
|
|
87
|
-
|
|
55
|
+
children: r.items.map((e) => /* @__PURE__ */ i(n, {
|
|
56
|
+
item: e,
|
|
57
|
+
active: e.key === l,
|
|
58
|
+
collapsed: f,
|
|
59
|
+
onSelect: u
|
|
60
|
+
}, e.key))
|
|
61
|
+
}, r.key);
|
|
62
|
+
let o = `${v}-${r.key}`;
|
|
88
63
|
return /* @__PURE__ */ a("div", {
|
|
89
64
|
role: "group",
|
|
90
|
-
"aria-labelledby":
|
|
65
|
+
"aria-labelledby": o,
|
|
91
66
|
className: t.group,
|
|
92
67
|
children: [/* @__PURE__ */ i("div", {
|
|
93
|
-
id:
|
|
68
|
+
id: o,
|
|
94
69
|
role: "presentation",
|
|
95
70
|
className: e(t.section, f && t.sectionCollapsed),
|
|
96
|
-
children:
|
|
97
|
-
}),
|
|
98
|
-
|
|
71
|
+
children: r.section.label
|
|
72
|
+
}), r.items.map((e) => /* @__PURE__ */ i(n, {
|
|
73
|
+
item: e,
|
|
74
|
+
active: e.key === l,
|
|
75
|
+
collapsed: f,
|
|
76
|
+
onSelect: u
|
|
77
|
+
}, e.key))]
|
|
78
|
+
}, r.key);
|
|
99
79
|
})
|
|
100
80
|
}),
|
|
101
81
|
d && /* @__PURE__ */ i("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sidebar.js","names":[],"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — header, items, footer are the three regions,\n * value/onChange the selection, and collapsed/width/collapsedWidth the two layout\n * modes it switches between.\n */\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Sidebar.module.css\";\n\nexport interface SidebarItem {\n key: string;\n label: ReactNode;\n icon?: ReactNode;\n badge?: ReactNode;\n disabled?: boolean;\n /**\n * Renders the entry as a link to this URL instead of a button.\n *\n * `onChange` still fires on click, so an app that tracks the active key keeps\n * working — the difference is that the entry now behaves like navigation:\n * middle-click, ctrl-click and \"copy link address\" do what the user expects,\n * and a screen reader announces a link rather than a button.\n *\n * A `disabled` entry ignores this and stays a `<button disabled>`: there is no\n * disabled state for an anchor, and dropping the `href` to fake one leaves a\n * link that announces itself as actionable and is not.\n */\n href?: string;\n}\n\n/**\n * A section heading. Every item after it belongs to the section, until the next\n * section or separator.\n */\nexport interface SidebarSection {\n type: \"section\";\n key: string;\n label: ReactNode;\n}\n\n/** A plain divider, for splitting the list without naming the parts. */\nexport interface SidebarSeparator {\n type: \"separator\";\n key: string;\n}\n\n/**\n * One entry of the navigation list.\n *\n * An entry with no `type` is an item, which is what keeps a plain\n * `SidebarItem[]` a valid `SidebarEntry[]`: adding sections to this component\n * needed no change at any existing call site.\n */\nexport type SidebarEntry = ({ type?: \"item\" } & SidebarItem) | SidebarSection | SidebarSeparator;\n\nexport interface SidebarProps extends Omit<HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Top slot — typically the logo + brand. */\n header?: ReactNode;\n /** Navigation entries. An entry with no `type` is an item. */\n items: SidebarEntry[];\n /** Active item key. */\n value?: string;\n /** Fires when an item is clicked. Receives the item's `key`. */\n onChange?: (key: string) => void;\n /** Bottom slot — typically settings/profile/logout. */\n footer?: ReactNode;\n /** Collapsed mode — only icons visible. Default `false`. */\n collapsed?: boolean;\n /** Width when expanded, in pixels or any CSS length. Default `240px`. */\n width?: number | string;\n /** Width when collapsed, in pixels or any CSS length. Default `64px`. */\n collapsedWidth?: number | string;\n}\n\n/** A run of items, optionally under a section heading. */\ninterface ItemBlock {\n kind: \"items\";\n key: string;\n section?: SidebarSection;\n items: SidebarItem[];\n}\n\n/** A standalone divider between blocks. */\ninterface SeparatorBlock {\n kind: \"separator\";\n key: string;\n}\n\ntype Block = ItemBlock | SeparatorBlock;\n\n/**\n * Fold the flat entry list into the blocks that get rendered.\n *\n * A section opens a block that swallows every following item; a separator closes\n * whatever is open, so items after it are loose again. Items before the first\n * section are loose too — which is the whole existing behaviour, and why a list\n * with no sections comes out of here as one unnamed block.\n *\n * @param entries - The `items` prop, as given.\n * @returns Blocks in render order.\n */\nfunction toBlocks(entries: readonly SidebarEntry[]): Block[] {\n const blocks: Block[] = [];\n let open: ItemBlock | undefined;\n\n for (const entry of entries) {\n if (entry.type === \"separator\") {\n blocks.push({ kind: \"separator\", key: entry.key });\n open = undefined;\n continue;\n }\n if (entry.type === \"section\") {\n open = { kind: \"items\", key: entry.key, section: entry, items: [] };\n blocks.push(open);\n continue;\n }\n if (!open) {\n open = { kind: \"items\", key: `loose-${entry.key}`, items: [] };\n blocks.push(open);\n }\n open.items.push(entry);\n }\n return blocks;\n}\n\n/**\n * Desktop sidebar navigation. Pair with `<Show above=\"md\">` and a `Drawer`\n * for mobile.\n *\n * Sixteen screens in one flat list is a wall, so `items` also takes section\n * headings and separators. A section renders as a labelled `role=\"group\"`, which\n * is what tells a screen reader \"Monitoring, group, 3 items\" — the reason it is\n * not a styled `disabled` item, which would announce an unavailable button and\n * stay in the navigation order.\n *\n * @example\n * const [tab, setTab] = useState(\"home\");\n * <Show above=\"md\">\n * <Sidebar\n * header={<Brand />}\n * items={[{ key: \"home\", label: \"Home\", icon: <Home /> }]}\n * value={tab}\n * onChange={setTab}\n * />\n * </Show>\n *\n * @example\n * <Sidebar\n * items={[\n * { type: \"section\", key: \"monitoring\", label: \"Monitoring\" },\n * { key: \"overview\", label: \"Overview\", href: \"/overview\" },\n * { key: \"activity\", label: \"Activity\", href: \"/activity\" },\n * { type: \"section\", key: \"admin\", label: \"Administration\" },\n * { key: \"settings\", label: \"Settings\", href: \"/settings\" },\n * ]}\n * value={tab}\n * onChange={setTab}\n * />\n */\nexport function Sidebar({\n header,\n items,\n value,\n onChange,\n footer,\n collapsed = false,\n width = 240,\n collapsedWidth = 64,\n className,\n style,\n ...props\n}: SidebarProps) {\n const baseId = useId();\n const blocks = toBlocks(items);\n const finalWidth =\n typeof (collapsed ? collapsedWidth : width) === \"number\"\n ? `${collapsed ? collapsedWidth : width}px`\n : collapsed\n ? collapsedWidth\n : width;\n\n /**\n * Render one navigation entry.\n *\n * @param item - The entry to render.\n * @returns The button, or the anchor when the entry carries an `href`.\n */\n const renderItem = (item: SidebarItem): ReactNode => {\n const active = item.key === value;\n const title = collapsed && typeof item.label === \"string\" ? item.label : undefined;\n const body = (\n <>\n {item.icon && <span className={styles.icon}>{item.icon}</span>}\n {!collapsed && <span className={styles.label}>{item.label}</span>}\n {!collapsed && item.badge !== undefined && (\n <span className={styles.badge}>{item.badge}</span>\n )}\n </>\n );\n\n if (item.href && !item.disabled) {\n return (\n <a\n key={item.key}\n href={item.href}\n className={cn(styles.item, active && styles.active)}\n aria-current={active ? \"page\" : undefined}\n onClick={() => onChange?.(item.key)}\n title={title}\n >\n {body}\n </a>\n );\n }\n\n return (\n <button\n key={item.key}\n type=\"button\"\n className={cn(styles.item, active && styles.active)}\n aria-current={active ? \"page\" : undefined}\n disabled={item.disabled}\n onClick={() => onChange?.(item.key)}\n title={title}\n >\n {body}\n </button>\n );\n };\n\n return (\n <aside\n className={cn(styles.sidebar, collapsed && styles.collapsed, className)}\n style={{ width: finalWidth, ...style }}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <nav className={styles.nav} aria-label=\"Navegação lateral\">\n {blocks.map((block) => {\n if (block.kind === \"separator\") {\n return <hr key={block.key} className={styles.separator} />;\n }\n if (!block.section) {\n return (\n <div key={block.key} className={styles.group}>\n {block.items.map(renderItem)}\n </div>\n );\n }\n const labelId = `${baseId}-${block.key}`;\n return (\n <div\n key={block.key}\n role=\"group\"\n aria-labelledby={labelId}\n className={styles.group}\n >\n <div\n id={labelId}\n role=\"presentation\"\n className={cn(styles.section, collapsed && styles.sectionCollapsed)}\n >\n {block.section.label}\n </div>\n {block.items.map(renderItem)}\n </div>\n );\n })}\n </nav>\n {footer && <div className={styles.footer}>{footer}</div>}\n </aside>\n );\n}\n"],"mappings":";;;;;AAqGA,SAAS,EAAS,GAA2C;CACzD,IAAM,IAAkB,CAAC,GACrB;CAEJ,KAAK,IAAM,KAAS,GAAS;EACzB,IAAI,EAAM,SAAS,aAAa;GAE5B,AADA,EAAO,KAAK;IAAE,MAAM;IAAa,KAAK,EAAM;GAAI,CAAC,GACjD,IAAO,KAAA;GACP;EACJ;EACA,IAAI,EAAM,SAAS,WAAW;GAE1B,AADA,IAAO;IAAE,MAAM;IAAS,KAAK,EAAM;IAAK,SAAS;IAAO,OAAO,CAAC;GAAE,GAClE,EAAO,KAAK,CAAI;GAChB;EACJ;EAKA,AAJK,MACD,IAAO;GAAE,MAAM;GAAS,KAAK,SAAS,EAAM;GAAO,OAAO,CAAC;EAAE,GAC7D,EAAO,KAAK,CAAI,IAEpB,EAAK,MAAM,KAAK,CAAK;CACzB;CACA,OAAO;AACX;AAoCA,SAAgB,EAAQ,EACpB,WACA,UACA,UACA,aACA,WACA,eAAY,IACZ,WAAQ,KACR,oBAAiB,IACjB,cACA,UACA,GAAG,KACU;CACb,IAAM,IAAS,EAAM,GACf,IAAS,EAAS,CAAK,GACvB,IACF,QAAQ,IAAY,IAAiB,MAAW,WAC1C,GAAG,IAAY,IAAiB,EAAM,MACtC,IACE,IACA,GAQN,KAAc,MAAiC;EACjD,IAAM,IAAS,EAAK,QAAQ,GACtB,IAAQ,KAAa,OAAO,EAAK,SAAU,WAAW,EAAK,QAAQ,KAAA,GACnE,IACF,kBAAA,GAAA,EAAA,UAAA;GACK,EAAK,QAAQ,kBAAC,QAAD;IAAM,WAAW,EAAO;IAAO,UAAA,EAAK;GAAW,CAAA;GAC5D,CAAC,KAAa,kBAAC,QAAD;IAAM,WAAW,EAAO;IAAQ,UAAA,EAAK;GAAY,CAAA;GAC/D,CAAC,KAAa,EAAK,UAAU,KAAA,KAC1B,kBAAC,QAAD;IAAM,WAAW,EAAO;IAAQ,UAAA,EAAK;GAAY,CAAA;EAEvD,EAAA,CAAA;EAkBN,OAfI,EAAK,QAAQ,CAAC,EAAK,WAEf,kBAAC,KAAD;GAEI,MAAM,EAAK;GACX,WAAW,EAAG,EAAO,MAAM,KAAU,EAAO,MAAM;GAClD,gBAAc,IAAS,SAAS,KAAA;GAChC,eAAe,IAAW,EAAK,GAAG;GAC3B;GAEN,UAAA;EACF,GARM,EAAK,GAQX,IAKP,kBAAC,UAAD;GAEI,MAAK;GACL,WAAW,EAAG,EAAO,MAAM,KAAU,EAAO,MAAM;GAClD,gBAAc,IAAS,SAAS,KAAA;GAChC,UAAU,EAAK;GACf,eAAe,IAAW,EAAK,GAAG;GAC3B;GAEN,UAAA;EACG,GATC,EAAK,GASN;CAEhB;CAEA,OACI,kBAAC,SAAD;EACI,WAAW,EAAG,EAAO,SAAS,KAAa,EAAO,WAAW,CAAS;EACtE,OAAO;GAAE,OAAO;GAAY,GAAG;EAAM;EACrC,GAAI;EAHR,UAAA;GAKK,KAAU,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAS,UAAA;GAAY,CAAA;GACvD,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAK,cAAW;IAClC,UAAA,EAAO,KAAK,MAAU;KACnB,IAAI,EAAM,SAAS,aACf,OAAO,kBAAC,MAAD,EAAoB,WAAW,EAAO,UAAY,GAAzC,EAAM,GAAmC;KAE7D,IAAI,CAAC,EAAM,SACP,OACI,kBAAC,OAAD;MAAqB,WAAW,EAAO;MAClC,UAAA,EAAM,MAAM,IAAI,CAAU;KAC1B,GAFK,EAAM,GAEX;KAGb,IAAM,IAAU,GAAG,EAAO,GAAG,EAAM;KACnC,OACI,kBAAC,OAAD;MAEI,MAAK;MACL,mBAAiB;MACjB,WAAW,EAAO;MAJtB,UAAA,CAMI,kBAAC,OAAD;OACI,IAAI;OACJ,MAAK;OACL,WAAW,EAAG,EAAO,SAAS,KAAa,EAAO,gBAAgB;OAEjE,UAAA,EAAM,QAAQ;MACd,CAAA,GACJ,EAAM,MAAM,IAAI,CAAU,CAC1B;KAbI,GAAA,EAAM,GAaV;IAEb,CAAC;GACA,CAAA;GACJ,KAAU,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAS,UAAA;GAAY,CAAA;EACpD;;AAEf"}
|
|
1
|
+
{"version":3,"file":"Sidebar.js","names":[],"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — header, items, footer are the three regions,\n * value/onChange the selection, and collapsed/width/collapsedWidth the two layout\n * modes it switches between.\n */\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Sidebar.module.css\";\nimport { SidebarEntry } from \"./SidebarEntry\";\n\nexport interface SidebarItem {\n key: string;\n label: ReactNode;\n icon?: ReactNode;\n badge?: ReactNode;\n disabled?: boolean;\n /**\n * Renders the entry as a link to this URL instead of a button.\n *\n * `onChange` still fires on click, so an app that tracks the active key keeps\n * working — the difference is that the entry now behaves like navigation:\n * middle-click, ctrl-click and \"copy link address\" do what the user expects,\n * and a screen reader announces a link rather than a button.\n *\n * A `disabled` entry ignores this and stays a `<button disabled>`: there is no\n * disabled state for an anchor, and dropping the `href` to fake one leaves a\n * link that announces itself as actionable and is not.\n */\n href?: string;\n}\n\n/**\n * A section heading. Every item after it belongs to the section, until the next\n * section or separator.\n */\nexport interface SidebarSection {\n type: \"section\";\n key: string;\n label: ReactNode;\n}\n\n/** A plain divider, for splitting the list without naming the parts. */\nexport interface SidebarSeparator {\n type: \"separator\";\n key: string;\n}\n\n/**\n * One entry of the navigation list.\n *\n * An entry with no `type` is an item, which is what keeps a plain\n * `SidebarItem[]` a valid `SidebarEntry[]`: adding sections to this component\n * needed no change at any existing call site.\n */\nexport type SidebarEntry = ({ type?: \"item\" } & SidebarItem) | SidebarSection | SidebarSeparator;\n\nexport interface SidebarProps extends Omit<HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Top slot — typically the logo + brand. */\n header?: ReactNode;\n /** Navigation entries. An entry with no `type` is an item. */\n items: SidebarEntry[];\n /** Active item key. */\n value?: string;\n /** Fires when an item is clicked. Receives the item's `key`. */\n onChange?: (key: string) => void;\n /** Bottom slot — typically settings/profile/logout. */\n footer?: ReactNode;\n /** Collapsed mode — only icons visible. Default `false`. */\n collapsed?: boolean;\n /** Width when expanded, in pixels or any CSS length. Default `240px`. */\n width?: number | string;\n /** Width when collapsed, in pixels or any CSS length. Default `64px`. */\n collapsedWidth?: number | string;\n}\n\n/** A run of items, optionally under a section heading. */\ninterface ItemBlock {\n kind: \"items\";\n key: string;\n section?: SidebarSection;\n items: SidebarItem[];\n}\n\n/** A standalone divider between blocks. */\ninterface SeparatorBlock {\n kind: \"separator\";\n key: string;\n}\n\ntype Block = ItemBlock | SeparatorBlock;\n\n/**\n * Fold the flat entry list into the blocks that get rendered.\n *\n * A section opens a block that swallows every following item; a separator closes\n * whatever is open, so items after it are loose again. Items before the first\n * section are loose too — which is the whole existing behaviour, and why a list\n * with no sections comes out of here as one unnamed block.\n *\n * @param entries - The `items` prop, as given.\n * @returns Blocks in render order.\n */\nfunction toBlocks(entries: readonly SidebarEntry[]): Block[] {\n const blocks: Block[] = [];\n let open: ItemBlock | undefined;\n\n for (const entry of entries) {\n if (entry.type === \"separator\") {\n blocks.push({ kind: \"separator\", key: entry.key });\n open = undefined;\n continue;\n }\n if (entry.type === \"section\") {\n open = { kind: \"items\", key: entry.key, section: entry, items: [] };\n blocks.push(open);\n continue;\n }\n if (!open) {\n open = { kind: \"items\", key: `loose-${entry.key}`, items: [] };\n blocks.push(open);\n }\n open.items.push(entry);\n }\n return blocks;\n}\n\n/**\n * Desktop sidebar navigation. Pair with `<Show above=\"md\">` and a `Drawer`\n * for mobile.\n *\n * Sixteen screens in one flat list is a wall, so `items` also takes section\n * headings and separators. A section renders as a labelled `role=\"group\"`, which\n * is what tells a screen reader \"Monitoring, group, 3 items\" — the reason it is\n * not a styled `disabled` item, which would announce an unavailable button and\n * stay in the navigation order.\n *\n * @example\n * const [tab, setTab] = useState(\"home\");\n * <Show above=\"md\">\n * <Sidebar\n * header={<Brand />}\n * items={[{ key: \"home\", label: \"Home\", icon: <Home /> }]}\n * value={tab}\n * onChange={setTab}\n * />\n * </Show>\n *\n * @example\n * <Sidebar\n * items={[\n * { type: \"section\", key: \"monitoring\", label: \"Monitoring\" },\n * { key: \"overview\", label: \"Overview\", href: \"/overview\" },\n * { key: \"activity\", label: \"Activity\", href: \"/activity\" },\n * { type: \"section\", key: \"admin\", label: \"Administration\" },\n * { key: \"settings\", label: \"Settings\", href: \"/settings\" },\n * ]}\n * value={tab}\n * onChange={setTab}\n * />\n */\nexport function Sidebar({\n header,\n items,\n value,\n onChange,\n footer,\n collapsed = false,\n width = 240,\n collapsedWidth = 64,\n className,\n style,\n ...props\n}: SidebarProps) {\n const baseId = useId();\n const blocks = toBlocks(items);\n const finalWidth =\n typeof (collapsed ? collapsedWidth : width) === \"number\"\n ? `${collapsed ? collapsedWidth : width}px`\n : collapsed\n ? collapsedWidth\n : width;\n\n return (\n <aside\n className={cn(styles.sidebar, collapsed && styles.collapsed, className)}\n style={{ width: finalWidth, ...style }}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <nav className={styles.nav} aria-label=\"Navegação lateral\">\n {blocks.map((block) => {\n if (block.kind === \"separator\") {\n return <hr key={block.key} className={styles.separator} />;\n }\n if (!block.section) {\n return (\n <div key={block.key} className={styles.group}>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === value}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n }\n const labelId = `${baseId}-${block.key}`;\n return (\n <div\n key={block.key}\n role=\"group\"\n aria-labelledby={labelId}\n className={styles.group}\n >\n <div\n id={labelId}\n role=\"presentation\"\n className={cn(styles.section, collapsed && styles.sectionCollapsed)}\n >\n {block.section.label}\n </div>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === value}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n })}\n </nav>\n {footer && <div className={styles.footer}>{footer}</div>}\n </aside>\n );\n}\n"],"mappings":";;;;;;AAsGA,SAAS,EAAS,GAA2C;CACzD,IAAM,IAAkB,CAAC,GACrB;CAEJ,KAAK,IAAM,KAAS,GAAS;EACzB,IAAI,EAAM,SAAS,aAAa;GAE5B,AADA,EAAO,KAAK;IAAE,MAAM;IAAa,KAAK,EAAM;GAAI,CAAC,GACjD,IAAO,KAAA;GACP;EACJ;EACA,IAAI,EAAM,SAAS,WAAW;GAE1B,AADA,IAAO;IAAE,MAAM;IAAS,KAAK,EAAM;IAAK,SAAS;IAAO,OAAO,CAAC;GAAE,GAClE,EAAO,KAAK,CAAI;GAChB;EACJ;EAKA,AAJK,MACD,IAAO;GAAE,MAAM;GAAS,KAAK,SAAS,EAAM;GAAO,OAAO,CAAC;EAAE,GAC7D,EAAO,KAAK,CAAI,IAEpB,EAAK,MAAM,KAAK,CAAK;CACzB;CACA,OAAO;AACX;AAoCA,SAAgB,EAAQ,EACpB,WACA,UACA,UACA,aACA,WACA,eAAY,IACZ,WAAQ,KACR,oBAAiB,IACjB,cACA,UACA,GAAG,KACU;CACb,IAAM,IAAS,EAAM,GACf,IAAS,EAAS,CAAK,GACvB,IACF,QAAQ,IAAY,IAAiB,MAAW,WAC1C,GAAG,IAAY,IAAiB,EAAM,MACtC,IACE,IACA;CAEZ,OACI,kBAAC,SAAD;EACI,WAAW,EAAG,EAAO,SAAS,KAAa,EAAO,WAAW,CAAS;EACtE,OAAO;GAAE,OAAO;GAAY,GAAG;EAAM;EACrC,GAAI;EAHR,UAAA;GAKK,KAAU,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAS,UAAA;GAAY,CAAA;GACvD,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAK,cAAW;IAClC,UAAA,EAAO,KAAK,MAAU;KACnB,IAAI,EAAM,SAAS,aACf,OAAO,kBAAC,MAAD,EAAoB,WAAW,EAAO,UAAY,GAAzC,EAAM,GAAmC;KAE7D,IAAI,CAAC,EAAM,SACP,OACI,kBAAC,OAAD;MAAqB,WAAW,EAAO;MAClC,UAAA,EAAM,MAAM,KAAK,MACd,kBAAC,GAAD;OAEU;OACN,QAAQ,EAAK,QAAQ;OACV;OACX,UAAU;MACb,GALQ,EAAK,GAKb,CACJ;KACA,GAVK,EAAM,GAUX;KAGb,IAAM,IAAU,GAAG,EAAO,GAAG,EAAM;KACnC,OACI,kBAAC,OAAD;MAEI,MAAK;MACL,mBAAiB;MACjB,WAAW,EAAO;MAJtB,UAAA,CAMI,kBAAC,OAAD;OACI,IAAI;OACJ,MAAK;OACL,WAAW,EAAG,EAAO,SAAS,KAAa,EAAO,gBAAgB;OAEjE,UAAA,EAAM,QAAQ;MACd,CAAA,GACJ,EAAM,MAAM,KAAK,MACd,kBAAC,GAAD;OAEU;OACN,QAAQ,EAAK,QAAQ;OACV;OACX,UAAU;MACb,GALQ,EAAK,GAKb,CACJ,CACA;KArBI,GAAA,EAAM,GAqBV;IAEb,CAAC;GACA,CAAA;GACJ,KAAU,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAS,UAAA;GAAY,CAAA;EACpD;;AAEf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sidebar.module.cjs","names":[],"sources":["../../../src/components/Sidebar/Sidebar.module.css"],"sourcesContent":[".sidebar {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--tempest-surface);\n border-right: 1px solid var(--tempest-border);\n font-family: var(--tempest-font-sans);\n transition: width var(--tempest-duration-base, 200ms) var(--tempest-ease-out, ease-out);\n overflow: hidden;\n}\n\n.header {\n padding: var(--tempest-space-3) var(--tempest-space-4);\n border-bottom: 1px solid var(--tempest-border);\n min-height: 56px;\n display: flex;\n align-items: center;\n}\n\n.nav {\n flex: 1 1 auto;\n overflow-y: auto;\n padding: var(--tempest-space-2);\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.group {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.group + .group {\n margin-top: var(--tempest-space-3);\n}\n\n.collapsed .group + .group {\n margin-top: var(--tempest-space-2);\n padding-top: var(--tempest-space-2);\n border-top: 1px solid var(--tempest-border);\n}\n\n.section {\n padding: var(--tempest-space-2) var(--tempest-space-3) var(--tempest-space-1);\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-semibold);\n letter-spacing: 0.04em;\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sectionCollapsed {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n\n.separator {\n flex: 0 0 auto;\n margin: var(--tempest-space-2) var(--tempest-space-1);\n border: 0;\n border-top: 1px solid var(--tempest-border);\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-3);\n padding: var(--tempest-space-2) var(--tempest-space-3);\n background: none;\n border: 0;\n border-radius: var(--tempest-radius-md);\n color: var(--tempest-text);\n font-size: var(--tempest-text-sm);\n text-align: left;\n cursor: pointer;\n transition: background var(--tempest-duration-fast, 120ms);\n min-height: 36px;\n width: 100%;\n}\n\n.collapsed .item {\n justify-content: center;\n padding-left: 0;\n padding-right: 0;\n}\n\n.item
|
|
1
|
+
{"version":3,"file":"Sidebar.module.cjs","names":[],"sources":["../../../src/components/Sidebar/Sidebar.module.css"],"sourcesContent":[".sidebar {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--tempest-surface);\n border-right: 1px solid var(--tempest-border);\n font-family: var(--tempest-font-sans);\n transition: width var(--tempest-duration-base, 200ms) var(--tempest-ease-out, ease-out);\n overflow: hidden;\n}\n\n.header {\n padding: var(--tempest-space-3) var(--tempest-space-4);\n border-bottom: 1px solid var(--tempest-border);\n min-height: 56px;\n display: flex;\n align-items: center;\n}\n\n.nav {\n flex: 1 1 auto;\n overflow-y: auto;\n padding: var(--tempest-space-2);\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.group {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.group + .group {\n margin-top: var(--tempest-space-3);\n}\n\n.collapsed .group + .group {\n margin-top: var(--tempest-space-2);\n padding-top: var(--tempest-space-2);\n border-top: 1px solid var(--tempest-border);\n}\n\n.section {\n padding: var(--tempest-space-2) var(--tempest-space-3) var(--tempest-space-1);\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-semibold);\n letter-spacing: 0.04em;\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sectionCollapsed {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n\n.separator {\n flex: 0 0 auto;\n margin: var(--tempest-space-2) var(--tempest-space-1);\n border: 0;\n border-top: 1px solid var(--tempest-border);\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-3);\n padding: var(--tempest-space-2) var(--tempest-space-3);\n background: none;\n border: 0;\n border-radius: var(--tempest-radius-md);\n color: var(--tempest-text);\n font-size: var(--tempest-text-sm);\n text-align: left;\n cursor: pointer;\n transition: background var(--tempest-duration-fast, 120ms);\n min-height: 36px;\n width: 100%;\n text-decoration: none;\n}\n\n.collapsed .item {\n justify-content: center;\n padding-left: 0;\n padding-right: 0;\n}\n\n.item:hover {\n background: var(--tempest-gray-100);\n}\n\n.item.active {\n background: var(--tempest-primary-100);\n color: var(--tempest-primary-700);\n font-weight: var(--tempest-weight-medium);\n}\n\n.item:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.icon {\n display: inline-flex;\n align-items: center;\n font-size: 18px;\n line-height: 1;\n flex: 0 0 auto;\n}\n\n.label {\n flex: 1 1 auto;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.badge {\n flex: 0 0 auto;\n min-width: 18px;\n height: 18px;\n padding: 0 6px;\n border-radius: var(--tempest-radius-full);\n background: var(--tempest-gray-200);\n color: var(--tempest-text);\n font-size: 10px;\n line-height: 18px;\n text-align: center;\n}\n\n.footer {\n padding: var(--tempest-space-3) var(--tempest-space-4);\n border-top: 1px solid var(--tempest-border);\n}\n"],"mappings":""}
|