dishui 0.0.82 → 0.0.83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/components/Button/index.d.ts +1 -0
  2. package/dist/components/Button/index.js.map +1 -1
  3. package/dist/components/ButtonMenu/index.d.ts +28 -0
  4. package/dist/components/ButtonMenu/index.js +197 -0
  5. package/dist/components/ButtonMenu/index.js.map +1 -0
  6. package/dist/components/Demo/index.d.ts +5 -0
  7. package/dist/components/Demo/index.js +131 -56
  8. package/dist/components/Demo/index.js.map +1 -1
  9. package/dist/components/Demo/source.d.ts +9 -0
  10. package/dist/components/Demo/source.js +32 -0
  11. package/dist/components/Demo/source.js.map +1 -0
  12. package/dist/components/Field/index.d.ts +8 -0
  13. package/dist/components/Field/index.js +43 -0
  14. package/dist/components/Field/index.js.map +1 -0
  15. package/dist/components/LayoutSuite/DashboardSuite.d.ts +35 -0
  16. package/dist/components/LayoutSuite/DashboardSuite.js +61 -0
  17. package/dist/components/LayoutSuite/DashboardSuite.js.map +1 -0
  18. package/dist/components/LayoutSuite/index.d.ts +56 -0
  19. package/dist/components/LayoutSuite/index.js +101 -0
  20. package/dist/components/LayoutSuite/index.js.map +1 -0
  21. package/dist/components/Modal/focusScope.d.ts +1 -0
  22. package/dist/components/Modal/focusScope.js +81 -0
  23. package/dist/components/Modal/focusScope.js.map +1 -0
  24. package/dist/components/Modal/index.d.ts +4 -1
  25. package/dist/components/Modal/index.js +48 -43
  26. package/dist/components/Modal/index.js.map +1 -1
  27. package/dist/components/Page/Float.d.ts +14 -0
  28. package/dist/components/Page/Float2.js +154 -0
  29. package/dist/components/Page/Float2.js.map +1 -0
  30. package/dist/components/Page/index.d.ts +18 -5
  31. package/dist/components/Page/index.js +103 -34
  32. package/dist/components/Page/index.js.map +1 -1
  33. package/dist/components/Page/resize.d.ts +28 -0
  34. package/dist/components/Page/resize2.js +95 -0
  35. package/dist/components/Page/resize2.js.map +1 -0
  36. package/dist/components/PageHeader/index.d.ts +10 -3
  37. package/dist/components/PageHeader/index.js +46 -25
  38. package/dist/components/PageHeader/index.js.map +1 -1
  39. package/dist/components/PagePanel/index.d.ts +29 -0
  40. package/dist/components/PagePanel/index.js +99 -0
  41. package/dist/components/PagePanel/index.js.map +1 -0
  42. package/dist/components/Resizable/ResizeGrip.d.ts +5 -0
  43. package/dist/components/Resizable/ResizeGrip.js +14 -0
  44. package/dist/components/Resizable/ResizeGrip.js.map +1 -0
  45. package/dist/components/Resizable/index.js +57 -59
  46. package/dist/components/Resizable/index.js.map +1 -1
  47. package/dist/components/Resizable/useResize.d.ts +6 -0
  48. package/dist/components/Resizable/useResize.js +29 -28
  49. package/dist/components/Resizable/useResize.js.map +1 -1
  50. package/dist/components/SidebarToggleButton/index.d.ts +5 -3
  51. package/dist/components/SidebarToggleButton/index.js +24 -21
  52. package/dist/components/SidebarToggleButton/index.js.map +1 -1
  53. package/dist/components/Table/index.d.ts +10 -12
  54. package/dist/components/Table/index.js +25 -20
  55. package/dist/components/Table/index.js.map +1 -1
  56. package/dist/components/Text/index.d.ts +1 -1
  57. package/dist/components/Text/index.js +11 -10
  58. package/dist/components/Text/index.js.map +1 -1
  59. package/dist/components/Textarea/index.d.ts +14 -0
  60. package/dist/components/Textarea/index.js +44 -0
  61. package/dist/components/Textarea/index.js.map +1 -0
  62. package/dist/components/Tree/Tree.d.ts +2 -0
  63. package/dist/components/Tree/Tree.js +49 -40
  64. package/dist/components/Tree/Tree.js.map +1 -1
  65. package/dist/dishui.css +1 -1
  66. package/dist/index-nostyled.d.ts +9 -0
  67. package/dist/index.d.ts +11 -2
  68. package/dist/index.js +119 -113
  69. package/dist/layouts/Sidebar.d.ts +12 -1
  70. package/dist/layouts/Sidebar.js +121 -97
  71. package/dist/layouts/Sidebar.js.map +1 -1
  72. package/dist/lite.d.ts +10 -2
  73. package/dist/lite.js +17 -12
  74. package/dist/utils/menu.js +14 -16
  75. package/dist/utils/menu.js.map +1 -1
  76. package/package.json +3 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/Demo/index.tsx"],"sourcesContent":["import React, { useId, useState } from 'react';\nimport { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';\nimport { coldarkDark } from 'react-syntax-highlighter/dist/esm/styles/prism';\nimport { useCopyToClipboard } from '../../hooks/useCopyToClipboard';\n\nexport interface CodeBlockProps {\n children: string;\n language?: string;\n}\n\nconst CodeBlock: React.FC<CodeBlockProps> = ({ children, language = 'jsx' }) => {\n const { copied, error, copy } = useCopyToClipboard();\n\n return (\n <div className=\"min-w-0 overflow-hidden rounded-lg border border-base-300\">\n <div className=\"flex items-center justify-between gap-3 bg-base-200 px-4 py-2\">\n <span className=\"font-mono text-xs opacity-70\">{language}</span>\n <div className=\"flex items-center gap-2\">\n <span role=\"status\" className=\"text-xs\">\n {error ? 'Copy failed / 复制失败,请手动复制' : copied ? 'Copied / 已复制' : ''}\n </span>\n <button\n type=\"button\"\n className=\"btn btn-ghost btn-xs\"\n onClick={() => void copy(children)}\n aria-label=\"Copy source code / 复制源代码\"\n >\n Copy / 复制\n </button>\n </div>\n </div>\n <SyntaxHighlighter\n language={language}\n style={coldarkDark}\n tabIndex={0}\n aria-label=\"Source code / 源代码\"\n customStyle={{\n margin: 0,\n borderRadius: 0,\n fontSize: '0.8125rem',\n lineHeight: 1.7,\n padding: '1rem',\n maxHeight: '32rem',\n overflow: 'auto',\n }}\n >\n {children}\n </SyntaxHighlighter>\n </div>\n );\n};\n\nexport interface DemoProps {\n title: string;\n description?: string;\n children: React.ReactNode;\n code: string;\n language?: string;\n}\n\nconst Demo: React.FC<DemoProps> = ({\n title,\n description,\n children,\n code,\n language = 'jsx',\n}) => {\n const [showCode, setShowCode] = useState(false);\n const id = useId();\n const titleId = `${id}-title`;\n const codeId = `${id}-code`;\n\n return (\n <section aria-labelledby={titleId} className=\"mb-8 min-w-0 rounded-xl border border-base-300 bg-base-100 shadow-sm\">\n <div className=\"border-b border-base-300 px-5 py-4\">\n <h3 id={titleId} className=\"text-lg font-semibold\">{title}</h3>\n {description && <p className=\"mt-1 text-sm leading-relaxed opacity-70\">{description}</p>}\n </div>\n\n <div className=\"flex min-w-0 flex-wrap items-center gap-4 px-5 py-6\">\n {children}\n </div>\n\n <div className=\"border-t border-base-300\">\n <button\n type=\"button\"\n className=\"btn btn-ghost flex w-full items-center justify-between rounded-none rounded-b-xl px-5 text-sm font-medium\"\n aria-expanded={showCode}\n aria-controls={codeId}\n onClick={() => setShowCode(value => !value)}\n >\n <span>Source Code / 源代码</span>\n <svg\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={`h-4 w-4 transition-transform motion-reduce:transition-none ${showCode ? 'rotate-180' : ''}`}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"2\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n </button>\n <div id={codeId} hidden={!showCode} className=\"min-w-0 border-t border-base-300 p-4\">\n {showCode && <CodeBlock language={language}>{code}</CodeBlock>}\n </div>\n </div>\n </section>\n );\n};\n\nexport default Demo;\n"],"mappings":";;;;;;AAUA,IAAM,KAAuC,EAAE,aAAU,cAAW,YAAY;CAC9E,IAAM,EAAE,WAAQ,UAAO,YAAS,EAAmB;CAEnD,OACE,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,kBAAC,QAAD;IAAM,WAAU;IAAgC,UAAA;GAAe,CAAA,GAC/D,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,QAAD;KAAM,MAAK;KAAS,WAAU;KAC3B,UAAA,IAAQ,6BAA6B,IAAS,iBAAiB;IAC5D,CAAA,GACN,kBAAC,UAAD;KACE,MAAK;KACL,WAAU;KACV,eAAe,KAAK,EAAK,CAAQ;KACjC,cAAW;KACZ,UAAA;IAEO,CAAA,CACL;GACF,CAAA,CAAA;EACL,CAAA,GAAA,kBAAC,GAAD;GACY;GACV,OAAO;GACP,UAAU;GACV,cAAW;GACX,aAAa;IACX,QAAQ;IACR,cAAc;IACd,UAAU;IACV,YAAY;IACZ,SAAS;IACT,WAAW;IACX,UAAU;GACZ;GAEC;EACgB,CAAA,CAChB;;AAET,GAUM,KAA6B,EACjC,UACA,gBACA,aACA,SACA,cAAW,YACP;CACJ,IAAM,CAAC,GAAU,KAAe,EAAS,EAAK,GACxC,IAAK,EAAM,GACX,IAAU,GAAG,EAAG,SAChB,IAAS,GAAG,EAAG;CAErB,OACE,kBAAC,WAAD;EAAS,mBAAiB;EAAS,WAAU;EAA7C,UAAA;GACE,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,MAAD;KAAI,IAAI;KAAS,WAAU;KAAyB,UAAA;IAAU,CAAA,GAC7D,KAAe,kBAAC,KAAD;KAAG,WAAU;KAA2C,UAAA;IAAe,CAAA,CACpF;;GAEL,kBAAC,OAAD;IAAK,WAAU;IACZ;GACE,CAAA;GAEL,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,UAAD;KACE,MAAK;KACL,WAAU;KACV,iBAAe;KACf,iBAAe;KACf,eAAe,GAAY,MAAS,CAAC,CAAK;KAL5C,UAAA,CAOE,kBAAC,QAAD,EAAA,UAAM,oBAAuB,CAAA,GAC7B,kBAAC,OAAD;MACE,eAAY;MACZ,OAAM;MACN,WAAW,8DAA8D,IAAW,eAAe;MACnG,MAAK;MACL,SAAQ;MACR,QAAO;MAEP,UAAA,kBAAC,QAAD;OAAM,eAAc;OAAQ,gBAAe;OAAQ,aAAY;OAAI,GAAE;MAAkB,CAAA;KACpF,CAAA,CACC;IACR,CAAA,GAAA,kBAAC,OAAD;KAAK,IAAI;KAAQ,QAAQ,CAAC;KAAU,WAAU;KAC3C,UAAA,KAAY,kBAAC,GAAD;MAAqB;MAAW,UAAA;KAAgB,CAAA;IAC1D,CAAA,CACF;;EACE;;AAEb"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/Demo/index.tsx"],"sourcesContent":["import React, { useEffect, useId, useRef, useState } from 'react';\nimport { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';\nimport { coldarkDark } from 'react-syntax-highlighter/dist/esm/styles/prism';\nimport { useCopyToClipboard } from '../../hooks/useCopyToClipboard';\nimport { formatDemoSource, type DemoSource } from './source';\nimport Button from '../Button';\nimport Text from '../Text';\n\nexport type { DemoSource } from './source';\n\nexport interface CodeBlockProps {\n children: string;\n language?: string;\n}\n\nconst CodeBlock: React.FC<CodeBlockProps> = ({ children, language = 'jsx' }) => {\n const { copied, error, copy } = useCopyToClipboard();\n\n return (\n <div className=\"min-w-0 overflow-hidden rounded-lg border border-base-300\">\n <div className=\"flex items-center justify-between gap-3 bg-base-200 px-4 py-2\">\n <Text size=\"xs\" mono color=\"secondary\">{language}</Text>\n <div className=\"flex items-center gap-2\">\n <Text role=\"status\" size=\"xs\">\n {error ? 'Copy failed / 复制失败,请手动复制' : copied ? 'Copied / 已复制' : ''}\n </Text>\n <Button\n type=\"button\"\n variant=\"ghost\" size=\"xs\"\n onClick={() => void copy(children)}\n aria-label=\"Copy source code / 复制源代码\"\n >\n Copy / 复制\n </Button>\n </div>\n </div>\n <SyntaxHighlighter\n language={language}\n style={coldarkDark}\n tabIndex={0}\n aria-label=\"Source code / 源代码\"\n customStyle={{\n margin: 0,\n borderRadius: 0,\n fontSize: '0.8125rem',\n lineHeight: 1.7,\n padding: '1rem',\n maxHeight: '32rem',\n overflow: 'auto',\n }}\n >\n {children}\n </SyntaxHighlighter>\n </div>\n );\n};\n\ntype DemoSourceLoader = () => Promise<DemoSource>;\n\ntype SourceLoadResult = {\n loader: DemoSourceLoader;\n attempt: number;\n} & ({ status: 'success'; data: DemoSource } | { status: 'error' });\n\nexport interface DemoProps {\n title: string;\n description?: string;\n children: React.ReactNode;\n code: string;\n language?: string;\n source?: DemoSource | DemoSourceLoader;\n sourceContext?: Record<string, unknown>;\n}\n\nconst Demo: React.FC<DemoProps> = ({\n title,\n description,\n children,\n code,\n language = 'jsx',\n source,\n sourceContext,\n}) => {\n const [showCode, setShowCode] = useState(false);\n const [attempt, setAttempt] = useState(0);\n const [loadResult, setLoadResult] = useState<SourceLoadResult | null>(null);\n const request = useRef<{\n loader: DemoSourceLoader;\n attempt: number;\n promise: Promise<DemoSource>;\n } | null>(null);\n\n useEffect(() => {\n if (!showCode || typeof source !== 'function') return;\n\n let active = true;\n // Reuse the request across collapse/reopen and StrictMode effect replays.\n if (request.current?.loader !== source || request.current.attempt !== attempt) {\n request.current = {\n loader: source,\n attempt,\n promise: Promise.resolve().then(() => source()),\n };\n }\n void request.current.promise.then(\n data => {\n if (active) setLoadResult({ loader: source, attempt, status: 'success', data });\n },\n () => {\n if (active) setLoadResult({ loader: source, attempt, status: 'error' });\n },\n );\n return () => { active = false; };\n }, [source, showCode, attempt]);\n\n // Match the loader before rendering so switching props cannot flash stale source.\n const currentResult = typeof source === 'function'\n && loadResult?.loader === source && loadResult.attempt === attempt\n ? loadResult : null;\n const resolvedSource = typeof source === 'function'\n ? currentResult?.status === 'success' ? currentResult.data : undefined\n : source;\n const loading = typeof source === 'function' && !currentResult;\n const id = useId();\n const titleId = `${id}-title`;\n const codeId = `${id}-code`;\n\n return (\n <section aria-labelledby={titleId} className=\"dishui-demo mb-4 min-w-0 rounded-lg border border-hairline bg-page\">\n <div className=\"border-b border-hairline bg-subtle px-4 py-3 rounded-t-lg\">\n <Text as=\"h3\" id={titleId} size=\"sm\" weight=\"semibold\">{title}</Text>\n {description && <Text as=\"p\" size=\"sm\" color=\"secondary\" className=\"mt-1 leading-relaxed\">{description}</Text>}\n </div>\n\n <div className=\"flex min-w-0 flex-wrap items-center gap-4 p-4\">\n {children}\n </div>\n\n <div className=\"border-t border-base-300\">\n <Button\n type=\"button\" variant=\"ghost\"\n className=\"flex w-full items-center justify-between rounded-none rounded-b-lg px-4 text-xs font-medium\"\n aria-expanded={showCode}\n aria-controls={codeId}\n onClick={() => setShowCode(value => !value)}\n >\n <Text size=\"xs\" weight=\"medium\" color=\"inherit\">Source Code / 源代码</Text>\n <svg\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={`h-4 w-4 transition-transform motion-reduce:transition-none ${showCode ? 'rotate-180' : ''}`}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"2\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n </Button>\n <div id={codeId} hidden={!showCode} className=\"min-w-0 border-t border-base-300 p-4\">\n {showCode && (\n <div className=\"min-w-0 space-y-3\">\n <div className=\"min-w-0\" aria-busy={loading}>\n {loading ? (\n <Text as=\"p\" role=\"status\" size=\"sm\">Loading source code… / 正在加载源码…</Text>\n ) : currentResult?.status === 'error' ? (\n <div className=\"space-y-2\">\n <Text as=\"p\" role=\"alert\" size=\"sm\">\n Could not load source code. Please retry. / 源码加载失败,请重试。\n </Text>\n <Button\n type=\"button\" variant=\"ghost\" size=\"sm\"\n onClick={() => setAttempt(value => value + 1)}\n >\n Retry loading source / 重新加载源码\n </Button>\n </div>\n ) : resolvedSource ? (\n <CodeBlock language=\"tsx\">\n {formatDemoSource(resolvedSource, sourceContext)}\n </CodeBlock>\n ) : !source ? (\n <CodeBlock language={language}>{code}</CodeBlock>\n ) : null}\n </div>\n </div>\n )}\n </div>\n </div>\n </section>\n );\n};\n\nexport default Demo;\n"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export interface DemoSource {
2
+ imports: string;
3
+ setup: string;
4
+ preview: string;
5
+ context: string[];
6
+ path?: string;
7
+ line?: number;
8
+ }
9
+ export declare function formatDemoSource(source: DemoSource, sourceContext?: Record<string, unknown>): string;
@@ -0,0 +1,32 @@
1
+ //#region src/components/Demo/source.ts
2
+ function e(e, t = !1) {
3
+ let n = e.replace(/\r\n/g, "\n").split("\n");
4
+ for (; n.length && !n[0].trim();) n.shift();
5
+ for (; n.length && !n[n.length - 1].trim();) n.pop();
6
+ if (e.includes("`")) return n.join("\n");
7
+ let r = t && n[0]?.match(/^\S/) ? n.slice(1) : n, i = Math.min(...r.filter((e) => e.trim()).map((e) => e.match(/^[\t ]*/)?.[0].length ?? 0));
8
+ return n.map((e, n) => t && n === 0 && /^\S/.test(e) ? e : e.slice(i)).join("\n");
9
+ }
10
+ function t(t, n = {}) {
11
+ let r = [];
12
+ for (let e of new Set(t.context)) {
13
+ if (!Object.prototype.hasOwnProperty.call(n, e)) continue;
14
+ let t = n[e];
15
+ try {
16
+ let n = t === void 0 ? "undefined" : JSON.stringify(t, null, 2);
17
+ n !== void 0 && r.push(`const ${e} = ${n};`);
18
+ } catch {
19
+ continue;
20
+ }
21
+ }
22
+ return [
23
+ e(t.imports),
24
+ r.join("\n"),
25
+ e(t.setup),
26
+ e(t.preview, !0)
27
+ ].filter(Boolean).join("\n\n");
28
+ }
29
+ //#endregion
30
+ export { t as formatDemoSource };
31
+
32
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","names":[],"sources":["../../../src/components/Demo/source.ts"],"sourcesContent":["export interface DemoSource {\n imports: string;\n setup: string;\n preview: string;\n context: string[];\n path?: string;\n line?: number;\n}\n\nfunction normalizeSection(code: string, trimmedFirstLine = false): string {\n const lines = code.replace(/\\r\\n/g, '\\n').split('\\n');\n while (lines.length && !lines[0].trim()) lines.shift();\n while (lines.length && !lines[lines.length - 1].trim()) lines.pop();\n // The extracted JSX starts at its first token; subsequent lines still carry the\n // page's indentation. Do not reindent template literals, whose spaces are data.\n if (code.includes('`')) return lines.join('\\n');\n const indentedLines = trimmedFirstLine && lines[0]?.match(/^\\S/) ? lines.slice(1) : lines;\n const indentation = Math.min(...indentedLines.filter(line => line.trim()).map(line => line.match(/^[\\t ]*/)?.[0].length ?? 0));\n return lines.map((line, index) => trimmedFirstLine && index === 0 && /^\\S/.test(line) ? line : line.slice(indentation)).join('\\n');\n}\n\nexport function formatDemoSource(\n source: DemoSource,\n sourceContext: Record<string, unknown> = {},\n): string {\n const declarations: string[] = [];\n for (const name of new Set(source.context)) {\n if (!Object.prototype.hasOwnProperty.call(sourceContext, name)) continue;\n const value = sourceContext[name];\n // Runtime functions and circular values cannot be represented as JSON source.\n try {\n const literal = value === undefined ? 'undefined' : JSON.stringify(value, null, 2);\n if (literal !== undefined) declarations.push(`const ${name} = ${literal};`);\n } catch {\n continue;\n }\n }\n\n return [\n normalizeSection(source.imports),\n declarations.join('\\n'),\n normalizeSection(source.setup),\n normalizeSection(source.preview, true),\n ].filter(Boolean).join('\\n\\n');\n}\n"],"mappings":";AASA,SAAS,EAAiB,GAAc,IAAmB,IAAe;CACxE,IAAM,IAAQ,EAAK,QAAQ,SAAS,IAAI,CAAC,CAAC,MAAM,IAAI;CACpD,OAAO,EAAM,UAAU,CAAC,EAAM,EAAE,CAAC,KAAK,IAAG,EAAM,MAAM;CACrD,OAAO,EAAM,UAAU,CAAC,EAAM,EAAM,SAAS,EAAE,CAAC,KAAK,IAAG,EAAM,IAAI;CAGlE,IAAI,EAAK,SAAS,GAAG,GAAG,OAAO,EAAM,KAAK,IAAI;CAC9C,IAAM,IAAgB,KAAoB,EAAM,EAAE,EAAE,MAAM,KAAK,IAAI,EAAM,MAAM,CAAC,IAAI,GAC9E,IAAc,KAAK,IAAI,GAAG,EAAc,QAAO,MAAQ,EAAK,KAAK,CAAC,CAAC,CAAC,KAAI,MAAQ,EAAK,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC;CAC7H,OAAO,EAAM,KAAK,GAAM,MAAU,KAAoB,MAAU,KAAK,MAAM,KAAK,CAAI,IAAI,IAAO,EAAK,MAAM,CAAW,CAAC,CAAC,CAAC,KAAK,IAAI;AACnI;AAEA,SAAgB,EACd,GACA,IAAyC,CAAC,GAClC;CACR,IAAM,IAAyB,CAAC;CAChC,KAAK,IAAM,KAAQ,IAAI,IAAI,EAAO,OAAO,GAAG;EAC1C,IAAI,CAAC,OAAO,UAAU,eAAe,KAAK,GAAe,CAAI,GAAG;EAChE,IAAM,IAAQ,EAAc;EAE5B,IAAI;GACF,IAAM,IAAU,MAAU,KAAA,IAAY,cAAc,KAAK,UAAU,GAAO,MAAM,CAAC;GACjF,AAAI,MAAY,KAAA,KAAW,EAAa,KAAK,SAAS,EAAK,KAAK,EAAQ,EAAE;EAC5E,QAAQ;GACN;EACF;CACF;CAEA,OAAO;EACL,EAAiB,EAAO,OAAO;EAC/B,EAAa,KAAK,IAAI;EACtB,EAAiB,EAAO,KAAK;EAC7B,EAAiB,EAAO,SAAS,EAAI;CACvC,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,MAAM;AAC/B"}
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export interface FieldProps extends React.FieldsetHTMLAttributes<HTMLFieldSetElement> {
3
+ legend?: React.ReactNode;
4
+ description?: React.ReactNode;
5
+ error?: React.ReactNode;
6
+ }
7
+ declare const Field: React.ForwardRefExoticComponent<FieldProps & React.RefAttributes<HTMLFieldSetElement>>;
8
+ export default Field;
@@ -0,0 +1,43 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ /* empty css */
3
+ import { forwardRef as t, useId as n } from "react";
4
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
5
+ //#region src/components/Field/index.tsx
6
+ var a = (e) => e != null && typeof e != "boolean" && e !== "", o = t(function({ legend: t, description: o, error: s, children: c, className: l, "aria-describedby": u, "aria-invalid": d, ...f }, p) {
7
+ let m = n(), h = `${m}-description`, g = `${m}-error`, _ = a(o), v = a(s), y = [
8
+ u,
9
+ _ ? h : void 0,
10
+ v ? g : void 0
11
+ ].filter(Boolean).join(" ") || void 0;
12
+ return /* @__PURE__ */ i("fieldset", {
13
+ ...f,
14
+ ref: p,
15
+ className: e("dishui-field", l),
16
+ "aria-describedby": y,
17
+ "aria-invalid": d ?? (v ? !0 : void 0),
18
+ children: [a(t) && /* @__PURE__ */ r("legend", {
19
+ className: "dishui-field-legend",
20
+ children: t
21
+ }), /* @__PURE__ */ i("div", {
22
+ className: "dishui-field-content",
23
+ children: [
24
+ _ && /* @__PURE__ */ r("div", {
25
+ id: h,
26
+ className: "dishui-field-description",
27
+ children: o
28
+ }),
29
+ c,
30
+ v && /* @__PURE__ */ r("div", {
31
+ id: g,
32
+ className: "dishui-field-error",
33
+ role: "alert",
34
+ children: s
35
+ })
36
+ ]
37
+ })]
38
+ });
39
+ });
40
+ //#endregion
41
+ export { o as default };
42
+
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/Field/index.tsx"],"sourcesContent":["import React, { forwardRef, useId } from 'react';\nimport { cn } from '../../utils/cn';\nimport './style.css';\n\nexport interface FieldProps extends React.FieldsetHTMLAttributes<HTMLFieldSetElement> {\n legend?: React.ReactNode;\n description?: React.ReactNode;\n error?: React.ReactNode;\n}\n\nconst hasContent = (node: React.ReactNode) => node !== null && node !== undefined && typeof node !== 'boolean' && node !== '';\n\nconst Field = forwardRef<HTMLFieldSetElement, FieldProps>(function Field({\n legend,\n description,\n error,\n children,\n className,\n 'aria-describedby': describedBy,\n 'aria-invalid': invalid,\n ...props\n}, ref) {\n const id = useId();\n const descriptionId = `${id}-description`;\n const errorId = `${id}-error`;\n const showDescription = hasContent(description);\n const showError = hasContent(error);\n const ids = [describedBy, showDescription ? descriptionId : undefined, showError ? errorId : undefined]\n .filter(Boolean).join(' ') || undefined;\n\n return (\n <fieldset\n {...props}\n ref={ref}\n className={cn('dishui-field', className)}\n aria-describedby={ids}\n aria-invalid={invalid ?? (showError ? true : undefined)}\n >\n {hasContent(legend) && <legend className=\"dishui-field-legend\">{legend}</legend>}\n <div className=\"dishui-field-content\">\n {showDescription && <div id={descriptionId} className=\"dishui-field-description\">{description}</div>}\n {children}\n {showError && <div id={errorId} className=\"dishui-field-error\" role=\"alert\">{error}</div>}\n </div>\n </fieldset>\n );\n});\n\nexport default Field;\n"],"mappings":";;;;;AAUA,IAAM,KAAc,MAA0B,KAAS,QAA8B,OAAO,KAAS,aAAa,MAAS,IAErH,IAAQ,EAA4C,SAAe,EACvE,WACA,gBACA,UACA,aACA,cACA,oBAAoB,GACpB,gBAAgB,GAChB,GAAG,KACF,GAAK;CACN,IAAM,IAAK,EAAM,GACX,IAAgB,GAAG,EAAG,eACtB,IAAU,GAAG,EAAG,SAChB,IAAkB,EAAW,CAAW,GACxC,IAAY,EAAW,CAAK,GAC5B,IAAM;EAAC;EAAa,IAAkB,IAAgB,KAAA;EAAW,IAAY,IAAU,KAAA;CAAS,CAAC,CACpG,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,KAAK,KAAA;CAEhC,OACE,kBAAC,YAAD;EACE,GAAI;EACC;EACL,WAAW,EAAG,gBAAgB,CAAS;EACvC,oBAAkB;EAClB,gBAAc,MAAY,IAAY,KAAO,KAAA;EAL/C,UAAA,CAOG,EAAW,CAAM,KAAK,kBAAC,UAAD;GAAQ,WAAU;GAAuB,UAAA;EAAe,CAAA,GAC/E,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACG,KAAmB,kBAAC,OAAD;KAAK,IAAI;KAAe,WAAU;KAA4B,UAAA;IAAiB,CAAA;IAClG;IACA,KAAa,kBAAC,OAAD;KAAK,IAAI;KAAS,WAAU;KAAqB,MAAK;KAAS,UAAA;IAAW,CAAA;GACrF;EACG,CAAA,CAAA;;AAEd,CAAC"}
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import { type PageProps, type PageBodyProps, type PageMainProps, type PageAsideProps } from '../Page';
3
+ import { type CardMetricProps } from '../Card';
4
+ import { type NavigationListProps } from '../NavigationList';
5
+ import { type LayoutGridProps } from '../Layout';
6
+ export type DashboardSuiteProps = PageProps & {
7
+ header?: React.ReactNode;
8
+ overview?: React.ReactNode;
9
+ /** Persistent partial/stale/error feedback; data semantics remain with the caller. */
10
+ notice?: React.ReactNode;
11
+ metrics?: {
12
+ 'aria-label': string;
13
+ items: readonly (CardMetricProps & {
14
+ key: React.Key;
15
+ })[];
16
+ loading?: boolean;
17
+ grid?: Omit<LayoutGridProps, 'children'>;
18
+ };
19
+ /** Cross-dashboard filters, above navigation and main content. */
20
+ filters?: React.ReactNode;
21
+ navigation?: NavigationListProps;
22
+ /** Explicit children (including null) override the built-in NavigationList. */
23
+ left?: PageAsideProps;
24
+ body?: Omit<PageBodyProps, 'children'>;
25
+ main?: PageMainProps;
26
+ toolbar?: React.ReactNode;
27
+ contentHeader?: React.ReactNode;
28
+ footer?: React.ReactNode;
29
+ };
30
+ /** Controlled dashboard composition; no fetching, filtering or table dependency. */
31
+ export declare const DashboardSuite: ({ header, overview, notice, metrics, filters, navigation, left, body, main, toolbar, contentHeader, footer, children, fullHeight, className, ...page }: DashboardSuiteProps) => React.JSX.Element;
32
+ export declare const LayoutSuite: {
33
+ DashboardSuite: typeof DashboardSuite;
34
+ };
35
+ export default DashboardSuite;
@@ -0,0 +1,61 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import t from "../NavigationList/index.js";
3
+ import n from "../Card/index.js";
4
+ import r from "../Page/index.js";
5
+ import { Grid as i, Group as a } from "../Layout/index.js";
6
+ import "react";
7
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
8
+ //#region src/components/LayoutSuite/DashboardSuite.tsx
9
+ var c = ({ header: c, overview: l, notice: u, metrics: d, filters: f, navigation: p, left: m, body: h, main: g, toolbar: _, contentHeader: v, footer: y, children: b, fullHeight: x = !1, className: S, ...C }) => /* @__PURE__ */ s(r, {
10
+ ...C,
11
+ fullHeight: x,
12
+ className: e("gap-4", S),
13
+ children: [
14
+ c,
15
+ l,
16
+ u,
17
+ d && d.items.length > 0 && /* @__PURE__ */ o("section", {
18
+ "aria-label": d["aria-label"],
19
+ "aria-busy": d.loading,
20
+ children: /* @__PURE__ */ o(i, {
21
+ columns: "repeat(auto-fit, minmax(min(100%, 220px), 1fr))",
22
+ gap: 12,
23
+ ...d.grid,
24
+ children: d.items.map(({ key: e, ...t }) => /* @__PURE__ */ o(n.Metric, {
25
+ ...t,
26
+ loading: d.loading ?? t.loading
27
+ }, e))
28
+ })
29
+ }),
30
+ f,
31
+ /* @__PURE__ */ s(r.Body, {
32
+ scroll: x,
33
+ ...h,
34
+ children: [(m || p) && /* @__PURE__ */ o(r.Left, {
35
+ width: 240,
36
+ bordered: !1,
37
+ scroll: !1,
38
+ ...m,
39
+ children: m?.children === void 0 ? p && /* @__PURE__ */ o(t, { ...p }) : m.children
40
+ }), /* @__PURE__ */ o(r.Main, {
41
+ scroll: x,
42
+ ...g,
43
+ children: /* @__PURE__ */ s(a, {
44
+ direction: "column",
45
+ align: "stretch",
46
+ gap: 16,
47
+ children: [
48
+ _,
49
+ v,
50
+ b === void 0 ? g?.children : b
51
+ ]
52
+ })
53
+ })]
54
+ }),
55
+ y
56
+ ]
57
+ }), l = { DashboardSuite: c };
58
+ //#endregion
59
+ export { c as DashboardSuite, c as default, l as LayoutSuite };
60
+
61
+ //# sourceMappingURL=DashboardSuite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DashboardSuite.js","names":[],"sources":["../../../src/components/LayoutSuite/DashboardSuite.tsx"],"sourcesContent":["import React from 'react';\nimport Page, { type PageProps, type PageBodyProps, type PageMainProps, type PageAsideProps } from '../Page';\nimport Card, { type CardMetricProps } from '../Card';\nimport NavigationList, { type NavigationListProps } from '../NavigationList';\nimport { Grid, Group, type LayoutGridProps } from '../Layout';\nimport { cn } from '../../utils/cn';\n\nexport type DashboardSuiteProps = PageProps & {\n header?: React.ReactNode;\n overview?: React.ReactNode;\n /** Persistent partial/stale/error feedback; data semantics remain with the caller. */\n notice?: React.ReactNode;\n metrics?: {\n 'aria-label': string;\n items: readonly (CardMetricProps & { key: React.Key })[];\n loading?: boolean;\n grid?: Omit<LayoutGridProps, 'children'>;\n };\n /** Cross-dashboard filters, above navigation and main content. */\n filters?: React.ReactNode;\n navigation?: NavigationListProps;\n /** Explicit children (including null) override the built-in NavigationList. */\n left?: PageAsideProps;\n body?: Omit<PageBodyProps, 'children'>;\n main?: PageMainProps;\n toolbar?: React.ReactNode;\n contentHeader?: React.ReactNode;\n footer?: React.ReactNode;\n};\n\n/** Controlled dashboard composition; no fetching, filtering or table dependency. */\nexport const DashboardSuite = ({\n header, overview, notice, metrics, filters, navigation, left, body, main,\n toolbar, contentHeader, footer, children, fullHeight = false, className, ...page\n}: DashboardSuiteProps) => (\n <Page {...page} fullHeight={fullHeight} className={cn('gap-4', className)}>\n {header}\n {overview}\n {notice}\n {metrics && metrics.items.length > 0 && (\n <section aria-label={metrics['aria-label']} aria-busy={metrics.loading}>\n <Grid columns=\"repeat(auto-fit, minmax(min(100%, 220px), 1fr))\" gap={12} {...metrics.grid}>\n {metrics.items.map(({ key, ...metric }) => (\n <Card.Metric key={key} {...metric} loading={metrics.loading ?? metric.loading} />\n ))}\n </Grid>\n </section>\n )}\n {filters}\n <Page.Body scroll={fullHeight} {...body}>\n {(left || navigation) && (\n <Page.Left width={240} bordered={false} scroll={false} {...left}>\n {left?.children !== undefined ? left.children : navigation && <NavigationList {...navigation} />}\n </Page.Left>\n )}\n <Page.Main scroll={fullHeight} {...main}>\n <Group direction=\"column\" align=\"stretch\" gap={16}>\n {toolbar}\n {contentHeader}\n {children !== undefined ? children : main?.children}\n </Group>\n </Page.Main>\n </Page.Body>\n {footer}\n </Page>\n);\n\n// lite exposes only the lightweight suite, without loading the Sidebar/router graph.\nexport const LayoutSuite = { DashboardSuite };\nexport default DashboardSuite;\n"],"mappings":";;;;;;;;AA+BA,IAAa,KAAkB,EAC3B,WAAQ,aAAU,WAAQ,YAAS,YAAS,eAAY,SAAM,SAAM,SACpE,YAAS,kBAAe,WAAQ,aAAU,gBAAa,IAAO,cAAW,GAAG,QAE5E,kBAAC,GAAD;CAAM,GAAI;CAAkB;CAAY,WAAW,EAAG,SAAS,CAAS;CAAxE,UAAA;EACK;EACA;EACA;EACA,KAAW,EAAQ,MAAM,SAAS,KAC/B,kBAAC,WAAD;GAAS,cAAY,EAAQ;GAAe,aAAW,EAAQ;GAC3D,UAAA,kBAAC,GAAD;IAAM,SAAQ;IAAkD,KAAK;IAAI,GAAI,EAAQ;IAChF,UAAA,EAAQ,MAAM,KAAK,EAAE,QAAK,GAAG,QAC1B,kBAAC,EAAK,QAAN;KAAuB,GAAI;KAAQ,SAAS,EAAQ,WAAW,EAAO;IAAU,GAA9D,CAA8D,CACnF;GACC,CAAA;EACD,CAAA;EAEZ;EACD,kBAAC,EAAK,MAAN;GAAW,QAAQ;GAAY,GAAI;GAAnC,UAAA,EACM,KAAQ,MACN,kBAAC,EAAK,MAAN;IAAW,OAAO;IAAK,UAAU;IAAO,QAAQ;IAAO,GAAI;IACtD,UAAA,GAAM,aAAa,KAAA,IAA4B,KAAc,kBAAC,GAAD,EAAgB,GAAI,EAAa,CAAA,IAA/D,EAAK;GAC9B,CAAA,GAEf,kBAAC,EAAK,MAAN;IAAW,QAAQ;IAAY,GAAI;IAC/B,UAAA,kBAAC,GAAD;KAAO,WAAU;KAAS,OAAM;KAAU,KAAK;KAA/C,UAAA;MACK;MACA;MACA,MAAa,KAAA,IAAuB,GAAM,WAAjB;KACvB;;GACA,CAAA,CACJ;;EACV;CACC;IAIG,IAAc,EAAE,kBAAe"}
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import { type PageProps, type PageBodyProps, type PageMainProps, type PageAsideProps, type PagePanelProps, type PageFooterProps, type PageFloatProps } from '../Page';
3
+ import type { PageHeaderProps } from '../PageHeader';
4
+ import Sidebar from '../../layouts/Sidebar';
5
+ import { DashboardSuite } from './DashboardSuite';
6
+ export { DashboardSuite } from './DashboardSuite';
7
+ export type { DashboardSuiteProps } from './DashboardSuite';
8
+ /** Sidebar 保持原有 API;panel 仅配置外层 Page.Left / Page.Right。 */
9
+ export type LayoutSuiteSidebarProps = Omit<React.ComponentProps<typeof Sidebar>, 'language' | 'isCollapsed' | 'side'> & {
10
+ language?: 'zh' | 'en';
11
+ isCollapsed?: boolean;
12
+ /** 独立启用区域边缘调宽,不要求 onToggle 或 onWidthChange。 */
13
+ resizable?: boolean;
14
+ panel?: Omit<PageAsideProps, 'children'>;
15
+ };
16
+ export type LayoutSuiteHeaderProps = PageHeaderProps & PagePanelProps;
17
+ export type LayoutSuiteBaseProps = PageProps & {
18
+ /** 可选三段式页头;Admin / Blog 仍要求配置 header。 */
19
+ header?: LayoutSuiteHeaderProps;
20
+ footer?: PageFooterProps;
21
+ /** body 不接收 children;主内容支持顶层 children 或 main.children。 */
22
+ body?: Omit<PageBodyProps, 'children'>;
23
+ main?: PageMainProps;
24
+ };
25
+ export type AdminSuiteProps = LayoutSuiteBaseProps & {
26
+ header: LayoutSuiteHeaderProps;
27
+ left: LayoutSuiteSidebarProps;
28
+ };
29
+ export type WikiSuiteProps = LayoutSuiteBaseProps & {
30
+ left?: LayoutSuiteSidebarProps;
31
+ right?: LayoutSuiteSidebarProps;
32
+ float?: PageFloatProps;
33
+ };
34
+ export type BlogSuiteProps = LayoutSuiteBaseProps & {
35
+ header: LayoutSuiteHeaderProps;
36
+ footer?: PageFooterProps;
37
+ };
38
+ export type BlankSuiteProps = LayoutSuiteBaseProps & {
39
+ float?: PageFloatProps;
40
+ };
41
+ /** 后台:必填页头和内置导航侧栏。Sidebar 需置于 TanStack Router 上下文中。 */
42
+ export declare const AdminSuite: (props: AdminSuiteProps) => React.JSX.Element;
43
+ /** 文档:可选双侧导航与浮窗。 */
44
+ export declare const WikiSuite: (props: WikiSuiteProps) => React.JSX.Element;
45
+ /** 博客:必填页头,可选页脚。 */
46
+ export declare const BlogSuite: (props: BlogSuiteProps) => React.JSX.Element;
47
+ /** 空白页:主内容与可选浮窗。 */
48
+ export declare const BlankSuite: (props: BlankSuiteProps) => React.JSX.Element;
49
+ export declare const LayoutSuite: {
50
+ AdminSuite: typeof AdminSuite;
51
+ WikiSuite: typeof WikiSuite;
52
+ BlogSuite: typeof BlogSuite;
53
+ BlankSuite: typeof BlankSuite;
54
+ DashboardSuite: typeof DashboardSuite;
55
+ };
56
+ export default LayoutSuite;
@@ -0,0 +1,101 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import t from "../Page/index.js";
3
+ import n from "../../layouts/Sidebar.js";
4
+ /* empty css */
5
+ import { DashboardSuite as r } from "./DashboardSuite.js";
6
+ import { useState as i } from "react";
7
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
8
+ //#region src/components/LayoutSuite/index.tsx
9
+ var s = ({ side: r, panel: o, resizable: s = !1, language: c = "zh", isCollapsed: l = !1, ...u }) => {
10
+ let { initialWidth: d = 280, collapsedWidth: f = 64, onWidthChange: p } = u, m = o?.minWidth ?? u.minWidth ?? 160, h = o?.maxWidth ?? u.maxWidth ?? 400, g = Number.isFinite(m) ? Math.max(0, m) : 160, _ = Math.max(g, Number.isFinite(h) ? h : 400), v = o?.style?.width ?? o?.width ?? d, y = (e) => Math.max(g, Math.min(Number.isFinite(e) ? e : 280, _)), [b, x] = i(() => ({
11
+ initialWidth: d,
12
+ sourceWidth: v,
13
+ minWidth: g,
14
+ maxWidth: _,
15
+ width: typeof v == "number" ? y(v) : v
16
+ })), S = !Object.is(b.initialWidth, d) || !Object.is(b.sourceWidth, v) || !Object.is(b.minWidth, g) || !Object.is(b.maxWidth, _), C = S ? typeof v == "number" ? y(v) : v : b.width;
17
+ S && x({
18
+ initialWidth: d,
19
+ sourceWidth: v,
20
+ minWidth: g,
21
+ maxWidth: _,
22
+ width: C
23
+ });
24
+ let w = (e) => {
25
+ let t = y(e);
26
+ x({
27
+ initialWidth: d,
28
+ sourceWidth: v,
29
+ minWidth: g,
30
+ maxWidth: _,
31
+ width: t
32
+ }), p?.(t);
33
+ }, T = (e) => {
34
+ w(e), o?.onWidthChange !== p && o?.onWidthChange?.(y(e));
35
+ }, E = typeof C == "number" ? C : y(d), D = r === "left" ? t.Left : t.Right;
36
+ return /* @__PURE__ */ a(D, {
37
+ bordered: !1,
38
+ scroll: !1,
39
+ ...o,
40
+ width: l ? f : C,
41
+ style: o?.style ? {
42
+ ...o.style,
43
+ width: void 0
44
+ } : void 0,
45
+ minWidth: g,
46
+ maxWidth: _,
47
+ resizable: !l && (o?.resizable ?? s),
48
+ resizeVariant: u.onToggle ? "line" : o?.resizeVariant,
49
+ onWidthChange: T,
50
+ className: e("dishui-layout-suite-sidebar", !l && typeof C == "string" && "dishui-layout-suite-sidebar-css-width", o?.className),
51
+ children: /* @__PURE__ */ a(n, {
52
+ ...u,
53
+ side: r,
54
+ language: c,
55
+ isCollapsed: l,
56
+ initialWidth: E,
57
+ minWidth: g,
58
+ maxWidth: _,
59
+ onWidthChange: p || u.onToggle && (o?.resizable ?? s) ? T : void 0
60
+ })
61
+ });
62
+ }, c = ({ header: n, left: r, right: i, footer: c, float: l, body: u, main: d, fullHeight: f = !1, children: p, className: m, ...h }) => /* @__PURE__ */ o(t, {
63
+ fullHeight: f,
64
+ ...h,
65
+ className: e("dishui-layout-suite", m),
66
+ children: [
67
+ n && /* @__PURE__ */ a(t.Header, { ...n }),
68
+ /* @__PURE__ */ o(t.Body, {
69
+ gap: 0,
70
+ scroll: f,
71
+ ...u,
72
+ children: [
73
+ r && /* @__PURE__ */ a(s, {
74
+ side: "left",
75
+ ...r
76
+ }),
77
+ /* @__PURE__ */ a(t.Main, {
78
+ scroll: f,
79
+ ...d,
80
+ children: p === void 0 ? d?.children : p
81
+ }),
82
+ i && /* @__PURE__ */ a(s, {
83
+ side: "right",
84
+ ...i
85
+ })
86
+ ]
87
+ }),
88
+ c && /* @__PURE__ */ a(t.Footer, { ...c }),
89
+ l && /* @__PURE__ */ a(t.Float, { ...l })
90
+ ]
91
+ }), l = (e) => /* @__PURE__ */ a(c, { ...e }), u = (e) => /* @__PURE__ */ a(c, { ...e }), d = (e) => /* @__PURE__ */ a(c, { ...e }), f = (e) => /* @__PURE__ */ a(c, { ...e }), p = {
92
+ AdminSuite: l,
93
+ WikiSuite: u,
94
+ BlogSuite: d,
95
+ BlankSuite: f,
96
+ DashboardSuite: r
97
+ };
98
+ //#endregion
99
+ export { l as AdminSuite, f as BlankSuite, d as BlogSuite, r as DashboardSuite, p as LayoutSuite, p as default, u as WikiSuite };
100
+
101
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/LayoutSuite/index.tsx"],"sourcesContent":["import React, { useState } from 'react';\nimport Page, {\n type PageProps,\n type PageBodyProps,\n type PageMainProps,\n type PageAsideProps,\n type PagePanelProps,\n type PageFooterProps,\n type PageFloatProps,\n} from '../Page';\nimport type { PageHeaderProps } from '../PageHeader';\nimport Sidebar from '../../layouts/Sidebar';\nimport { cn } from '../../utils/cn';\nimport './style.css';\nimport { DashboardSuite } from './DashboardSuite';\nexport { DashboardSuite } from './DashboardSuite';\nexport type { DashboardSuiteProps } from './DashboardSuite';\n\n/** Sidebar 保持原有 API;panel 仅配置外层 Page.Left / Page.Right。 */\nexport type LayoutSuiteSidebarProps = Omit<React.ComponentProps<typeof Sidebar>, 'language' | 'isCollapsed' | 'side'> & {\n language?: 'zh' | 'en';\n isCollapsed?: boolean;\n /** 独立启用区域边缘调宽,不要求 onToggle 或 onWidthChange。 */\n resizable?: boolean;\n panel?: Omit<PageAsideProps, 'children'>;\n};\n\nexport type LayoutSuiteHeaderProps = PageHeaderProps & PagePanelProps;\n\nexport type LayoutSuiteBaseProps = PageProps & {\n /** 可选三段式页头;Admin / Blog 仍要求配置 header。 */\n header?: LayoutSuiteHeaderProps;\n footer?: PageFooterProps;\n /** body 不接收 children;主内容支持顶层 children 或 main.children。 */\n body?: Omit<PageBodyProps, 'children'>;\n main?: PageMainProps;\n};\n\nexport type AdminSuiteProps = LayoutSuiteBaseProps & {\n header: LayoutSuiteHeaderProps;\n left: LayoutSuiteSidebarProps;\n};\n\nexport type WikiSuiteProps = LayoutSuiteBaseProps & {\n left?: LayoutSuiteSidebarProps;\n right?: LayoutSuiteSidebarProps;\n float?: PageFloatProps;\n};\n\nexport type BlogSuiteProps = LayoutSuiteBaseProps & {\n header: LayoutSuiteHeaderProps;\n footer?: PageFooterProps;\n};\n\nexport type BlankSuiteProps = LayoutSuiteBaseProps & {\n float?: PageFloatProps;\n};\n\nconst SidebarPanel = ({\n side,\n panel,\n resizable = false,\n language = 'zh',\n isCollapsed = false,\n ...sidebar\n}: LayoutSuiteSidebarProps & { side: 'left' | 'right' }) => {\n const { initialWidth = 280, collapsedWidth = 64, onWidthChange } = sidebar;\n const requestedMin = panel?.minWidth ?? sidebar.minWidth ?? 160;\n const requestedMax = panel?.maxWidth ?? sidebar.maxWidth ?? 400;\n const minWidth = Number.isFinite(requestedMin) ? Math.max(0, requestedMin) : 160;\n const maxWidth = Math.max(minWidth, Number.isFinite(requestedMax) ? requestedMax : 400);\n const panelWidth = panel?.style?.width ?? panel?.width;\n const sourceWidth = panelWidth ?? initialWidth;\n const clamp = (width: number) => Math.max(minWidth, Math.min(Number.isFinite(width) ? width : 280, maxWidth));\n const [widthState, setWidthState] = useState(() => ({\n initialWidth, sourceWidth, minWidth, maxWidth,\n width: typeof sourceWidth === 'number' ? clamp(sourceWidth) : sourceWidth,\n }));\n\n // 与 Sidebar 的配置同步规则一致;折叠不重置用户拖动后的展开宽度。\n const configChanged = !Object.is(widthState.initialWidth, initialWidth)\n || !Object.is(widthState.sourceWidth, sourceWidth)\n || !Object.is(widthState.minWidth, minWidth) || !Object.is(widthState.maxWidth, maxWidth);\n const expandedWidth = configChanged\n ? (typeof sourceWidth === 'number' ? clamp(sourceWidth) : sourceWidth)\n : widthState.width;\n if (configChanged) {\n setWidthState({ initialWidth, sourceWidth, minWidth, maxWidth, width: expandedWidth });\n }\n\n const handleWidthChange = (width: number) => {\n const next = clamp(width);\n setWidthState({ initialWidth, sourceWidth, minWidth, maxWidth, width: next });\n onWidthChange?.(next);\n };\n const handlePanelWidthChange = (width: number) => {\n handleWidthChange(width);\n if (panel?.onWidthChange !== onWidthChange) panel?.onWidthChange?.(clamp(width));\n };\n const sidebarWidth = typeof expandedWidth === 'number' ? expandedWidth : clamp(initialWidth);\n const Aside = side === 'left' ? Page.Left : Page.Right;\n return (\n <Aside\n bordered={false}\n scroll={false}\n {...panel}\n width={isCollapsed ? collapsedWidth : expandedWidth}\n style={panel?.style ? { ...panel.style, width: undefined } : undefined}\n minWidth={minWidth}\n maxWidth={maxWidth}\n resizable={!isCollapsed && (panel?.resizable ?? resizable)}\n resizeVariant={sidebar.onToggle ? 'line' : panel?.resizeVariant}\n onWidthChange={handlePanelWidthChange}\n className={cn('dishui-layout-suite-sidebar',\n !isCollapsed && typeof expandedWidth === 'string' && 'dishui-layout-suite-sidebar-css-width', panel?.className)}\n >\n <Sidebar {...sidebar} side={side} language={language} isCollapsed={isCollapsed}\n initialWidth={sidebarWidth} minWidth={minWidth} maxWidth={maxWidth}\n onWidthChange={onWidthChange || (sidebar.onToggle && (panel?.resizable ?? resizable)) ? handlePanelWidthChange : undefined} />\n </Aside>\n );\n};\n\ntype SuiteFrameProps = LayoutSuiteBaseProps & {\n header?: LayoutSuiteHeaderProps;\n left?: LayoutSuiteSidebarProps;\n right?: LayoutSuiteSidebarProps;\n footer?: PageFooterProps;\n float?: PageFloatProps;\n};\n\nconst SuiteFrame = ({\n header,\n left,\n right,\n footer,\n float,\n body,\n main,\n fullHeight = false,\n children,\n className,\n ...page\n}: SuiteFrameProps) => (\n <Page fullHeight={fullHeight} {...page} className={cn('dishui-layout-suite', className)}>\n {header && <Page.Header {...header} />}\n <Page.Body gap={0} scroll={fullHeight} {...body}>\n {left && <SidebarPanel side=\"left\" {...left} />}\n <Page.Main scroll={fullHeight} {...main}>{children !== undefined ? children : main?.children}</Page.Main>\n {right && <SidebarPanel side=\"right\" {...right} />}\n </Page.Body>\n {footer && <Page.Footer {...footer} />}\n {float && <Page.Float {...float} />}\n </Page>\n);\n\n/** 后台:必填页头和内置导航侧栏。Sidebar 需置于 TanStack Router 上下文中。 */\nexport const AdminSuite = (props: AdminSuiteProps) => <SuiteFrame {...props} />;\n\n/** 文档:可选双侧导航与浮窗。 */\nexport const WikiSuite = (props: WikiSuiteProps) => <SuiteFrame {...props} />;\n\n/** 博客:必填页头,可选页脚。 */\nexport const BlogSuite = (props: BlogSuiteProps) => <SuiteFrame {...props} />;\n\n/** 空白页:主内容与可选浮窗。 */\nexport const BlankSuite = (props: BlankSuiteProps) => <SuiteFrame {...props} />;\n\nexport const LayoutSuite = { AdminSuite, WikiSuite, BlogSuite, BlankSuite, DashboardSuite };\n\nexport default LayoutSuite;\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function mountFocusScope(panel: HTMLElement, overlay: HTMLElement, trigger: HTMLElement | null, close: () => void): () => void;
@@ -0,0 +1,81 @@
1
+ //#region src/components/Modal/focusScope.ts
2
+ var e = [], t = /* @__PURE__ */ new Map(), n = "", r, i = "button, a[href], input, select, textarea, [tabindex], [contenteditable=\"true\"]", a = () => e[e.length - 1], o = (e) => !!e.panel.querySelector("[role=\"combobox\"][aria-expanded=\"true\"]"), s = (e) => {
3
+ let t = /* @__PURE__ */ new Set();
4
+ for (let n of e.panel.querySelectorAll("[aria-haspopup=\"menu\"][aria-expanded=\"true\"][aria-controls]")) for (let e of n.getAttribute("aria-controls").split(/\s+/)) {
5
+ let n = document.getElementById(e);
6
+ n?.parentElement === document.body && n.getAttribute("role") === "menu" && t.add(n);
7
+ }
8
+ return [...t];
9
+ }, c = (e) => {
10
+ let t = o(e) ? Array.from(document.body.children).filter((t) => !e.existing.has(t) && t.matches("[role=\"listbox\"]")) : [];
11
+ return [
12
+ e.panel,
13
+ ...t,
14
+ ...s(e)
15
+ ];
16
+ }, l = (e, t) => c(e).some((e) => e.contains(t)), u = (e, t = !0) => c(e).filter((n) => t || n === e.panel || n.getAttribute("role") !== "menu").flatMap((e) => Array.from(e.querySelectorAll(i))).filter((e) => e.tabIndex >= 0 && !e.matches(":disabled") && !e.closest("[inert]") && e.getClientRects().length > 0 && getComputedStyle(e).visibility !== "hidden"), d = () => {
17
+ t.forEach((e, t) => {
18
+ t.inert = e.inert, e.hidden === null ? t.removeAttribute("aria-hidden") : t.setAttribute("aria-hidden", e.hidden);
19
+ }), t.clear();
20
+ }, f = () => {
21
+ d();
22
+ let e = a();
23
+ if (!e) return;
24
+ let n = [e.overlay, ...c(e)];
25
+ for (let e of Array.from(document.body.children)) e instanceof HTMLElement && !n.includes(e) && (t.set(e, {
26
+ inert: e.inert,
27
+ hidden: e.getAttribute("aria-hidden")
28
+ }), e.inert = !0, e.setAttribute("aria-hidden", "true"));
29
+ }, p = (e) => (u(e)[0] || e.panel).focus({ preventScroll: !0 }), m = (e) => {
30
+ let t = a();
31
+ t && e.target instanceof Node && !l(t, e.target) && p(t);
32
+ }, h = /* @__PURE__ */ new WeakSet(), g = /* @__PURE__ */ new WeakSet(), _ = (e, t) => {
33
+ let n = u(e, !1), r = n.indexOf(document.activeElement);
34
+ (!n.length || r < 0 || (t.shiftKey ? r === 0 : r === n.length - 1)) && (t.preventDefault(), (t.shiftKey ? n.at(-1) || e.panel : n[0] || e.panel).focus());
35
+ }, v = (e) => {
36
+ let t = a();
37
+ if (t && (e.key === "Escape" && o(t) && h.add(e), e.key === "Tab")) {
38
+ if (e.target instanceof Node && s(t).some((t) => t.contains(e.target))) {
39
+ g.add(e);
40
+ return;
41
+ }
42
+ _(t, e);
43
+ }
44
+ }, y = (e) => {
45
+ if (e.key === "Tab" && g.has(e) && !e.defaultPrevented) {
46
+ let t = a();
47
+ t && _(t, e);
48
+ return;
49
+ }
50
+ if (e.key !== "Escape" || e.defaultPrevented || h.has(e)) return;
51
+ let t = a();
52
+ t && (e.preventDefault(), t.close());
53
+ };
54
+ function b(t, i, o, s) {
55
+ let c = {
56
+ panel: t,
57
+ overlay: i,
58
+ trigger: o,
59
+ close: s,
60
+ existing: new Set(document.body.children)
61
+ };
62
+ return e.length || (n = document.body.style.overflow, document.body.style.overflow = "hidden", document.addEventListener("focusin", m), document.addEventListener("keydown", v, !0), window.addEventListener("keydown", y), r = new MutationObserver(f), r.observe(document.body, {
63
+ childList: !0,
64
+ subtree: !0,
65
+ attributes: !0,
66
+ attributeFilter: [
67
+ "aria-expanded",
68
+ "aria-controls",
69
+ "aria-haspopup",
70
+ "role",
71
+ "id"
72
+ ]
73
+ })), e.push(c), l(c, document.activeElement) || p(c), f(), () => {
74
+ let t = a() === c;
75
+ e.splice(e.indexOf(c), 1), f(), e.length || (r?.disconnect(), document.body.style.overflow = n, document.removeEventListener("focusin", m), document.removeEventListener("keydown", v, !0), window.removeEventListener("keydown", y)), t && (o?.isConnected && !o.closest("[inert]") ? o.focus({ preventScroll: !0 }) : a() && p(a()));
76
+ };
77
+ }
78
+ //#endregion
79
+ export { b as mountFocusScope };
80
+
81
+ //# sourceMappingURL=focusScope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focusScope.js","names":[],"sources":["../../../src/components/Modal/focusScope.ts"],"sourcesContent":["// Only the top Modal owns focus and background isolation. Keep original attributes\n// so nested modals and pre-existing inert/aria-hidden regions restore correctly.\ntype Scope = { panel: HTMLElement; overlay: HTMLElement; trigger: HTMLElement | null; close: () => void; existing: Set<Element> };\nconst scopes: Scope[] = [];\nconst locked = new Map<HTMLElement, { inert: boolean; hidden: string | null }>();\nlet overflow = '';\nlet observer: MutationObserver | undefined;\nconst focusable = 'button, a[href], input, select, textarea, [tabindex], [contenteditable=\"true\"]';\nconst top = () => scopes[scopes.length - 1];\nconst expanded = (scope: Scope) => !!scope.panel.querySelector('[role=\"combobox\"][aria-expanded=\"true\"]');\nconst linkedMenus = (scope: Scope): HTMLElement[] => {\n const menus = new Set<HTMLElement>();\n for (const trigger of scope.panel.querySelectorAll('[aria-haspopup=\"menu\"][aria-expanded=\"true\"][aria-controls]')) {\n for (const id of trigger.getAttribute('aria-controls')!.split(/\\s+/)) {\n const menu = document.getElementById(id);\n if (menu?.parentElement === document.body && menu.getAttribute('role') === 'menu') menus.add(menu);\n }\n }\n return [...menus];\n};\nconst roots = (scope: Scope): HTMLElement[] => {\n // Select currently portals an unlinked listbox directly to body. Only admit\n // new listboxes while a combobox in this modal is expanded, not arbitrary portals.\n const portals = expanded(scope) ? Array.from(document.body.children).filter(element =>\n !scope.existing.has(element) && element.matches('[role=\"listbox\"]')) as HTMLElement[] : [];\n return [scope.panel, ...portals, ...linkedMenus(scope)];\n};\nconst contains = (scope: Scope, node: Node) => roots(scope).some(root => root.contains(node));\nconst candidates = (scope: Scope, includeMenus = true) => roots(scope)\n .filter(root => includeMenus || root === scope.panel || root.getAttribute('role') !== 'menu')\n .flatMap(root => Array.from(root.querySelectorAll<HTMLElement>(focusable)))\n .filter(element => element.tabIndex >= 0 && !element.matches(':disabled') && !element.closest('[inert]') && element.getClientRects().length > 0 && getComputedStyle(element).visibility !== 'hidden');\nconst restoreBackground = () => {\n locked.forEach((value, element) => {\n element.inert = value.inert;\n if (value.hidden === null) element.removeAttribute('aria-hidden');\n else element.setAttribute('aria-hidden', value.hidden);\n });\n locked.clear();\n};\nconst isolate = () => {\n restoreBackground();\n const scope = top();\n if (!scope) return;\n const allowed = [scope.overlay, ...roots(scope)];\n for (const child of Array.from(document.body.children)) {\n if (!(child instanceof HTMLElement) || allowed.includes(child)) continue;\n locked.set(child, { inert: child.inert, hidden: child.getAttribute('aria-hidden') });\n child.inert = true;\n child.setAttribute('aria-hidden', 'true');\n }\n};\nconst focusInside = (scope: Scope) => (candidates(scope)[0] || scope.panel).focus({ preventScroll: true });\nconst onFocus = (event: FocusEvent) => {\n const scope = top();\n if (scope && event.target instanceof Node && !contains(scope, event.target)) focusInside(scope);\n};\nconst selectEscapes = new WeakSet<KeyboardEvent>();\nconst menuTabs = new WeakSet<KeyboardEvent>();\nconst trapTab = (scope: Scope, event: KeyboardEvent) => {\n // Menu items use roving focus, not the dialog's sequential tab order.\n const items = candidates(scope, false);\n const index = items.indexOf(document.activeElement as HTMLElement);\n if (!items.length || index < 0 || (event.shiftKey ? index === 0 : index === items.length - 1)) {\n event.preventDefault();\n (event.shiftKey ? items.at(-1) || scope.panel : items[0] || scope.panel).focus();\n }\n};\nconst onKeyCapture = (event: KeyboardEvent) => {\n const scope = top();\n if (!scope) return;\n if (event.key === 'Escape' && expanded(scope)) selectEscapes.add(event);\n if (event.key !== 'Tab') return;\n if (event.target instanceof Node && linkedMenus(scope).some(menu => menu.contains(event.target as Node))) {\n // ButtonMenu closes and returns focus to its trigger during bubbling.\n // Only then can the dialog decide whether native Tab needs to wrap.\n menuTabs.add(event);\n return;\n }\n trapTab(scope, event);\n};\nconst onKey = (event: KeyboardEvent) => {\n if (event.key === 'Tab' && menuTabs.has(event) && !event.defaultPrevented) {\n const scope = top();\n if (scope) trapTab(scope, event);\n return;\n }\n if (event.key !== 'Escape' || event.defaultPrevented || selectEscapes.has(event)) return;\n const scope = top();\n if (scope) { event.preventDefault(); scope.close(); }\n};\nexport function mountFocusScope(panel: HTMLElement, overlay: HTMLElement, trigger: HTMLElement | null, close: () => void) {\n const scope: Scope = { panel, overlay, trigger, close, existing: new Set(document.body.children) };\n if (!scopes.length) {\n overflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n document.addEventListener('focusin', onFocus);\n document.addEventListener('keydown', onKeyCapture, true);\n window.addEventListener('keydown', onKey);\n observer = new MutationObserver(isolate);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['aria-expanded', 'aria-controls', 'aria-haspopup', 'role', 'id'] });\n }\n scopes.push(scope);\n // React autoFocus has already run during commit; don't replace the safe action.\n if (!contains(scope, document.activeElement!)) focusInside(scope);\n isolate();\n return () => {\n const wasTop = top() === scope;\n scopes.splice(scopes.indexOf(scope), 1);\n isolate();\n if (!scopes.length) {\n observer?.disconnect();\n document.body.style.overflow = overflow;\n document.removeEventListener('focusin', onFocus);\n document.removeEventListener('keydown', onKeyCapture, true);\n window.removeEventListener('keydown', onKey);\n }\n if (wasTop) {\n if (trigger?.isConnected && !trigger.closest('[inert]')) trigger.focus({ preventScroll: true });\n else if (top()) focusInside(top());\n }\n };\n}\n"],"mappings":"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"}
@@ -23,9 +23,12 @@ export type ModalProps = {
23
23
  /** 是否显示右上角关闭按钮(默认 true) */
24
24
  showClose?: boolean;
25
25
  className?: string;
26
+ 'aria-label'?: string;
27
+ 'aria-labelledby'?: string;
28
+ 'aria-describedby'?: string;
26
29
  };
27
30
  /**
28
31
  * 轻量模态框:遮罩 + 居中面板,支持 Esc 关闭、遮罩点击关闭与滚动锁定。
29
32
  */
30
- declare const Modal: ({ open, onClose, title, description, children, footer, size, width, height, closeOnOverlay, showClose, className, }: ModalProps) => React.JSX.Element | null;
33
+ declare const Modal: ({ open, onClose, title, description, children, footer, size, width, height, closeOnOverlay, showClose, className, 'aria-label': ariaLabel, 'aria-labelledby': labelledBy, 'aria-describedby': describedBy, }: ModalProps) => React.ReactPortal | null;
31
34
  export default Modal;