@proteos/ui 0.75.0 → 0.76.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/{chunk-NWGG5WFL.cjs → chunk-4ACELYEP.cjs} +23 -4
- package/dist/chunk-4ACELYEP.cjs.map +1 -0
- package/dist/{chunk-NSE4VW3Z.js → chunk-XRN5MNDX.js} +24 -5
- package/dist/chunk-XRN5MNDX.js.map +1 -0
- package/dist/index.cjs +409 -288
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -3
- package/dist/index.d.ts +58 -3
- package/dist/index.js +124 -5
- package/dist/index.js.map +1 -1
- package/dist/{pdf-view-RRMAUK3L.cjs → pdf-view-2DSARHNQ.cjs} +7 -7
- package/dist/{pdf-view-RRMAUK3L.cjs.map → pdf-view-2DSARHNQ.cjs.map} +1 -1
- package/dist/{pdf-view-RQ2OZYHJ.js → pdf-view-2QU4FI37.js} +3 -3
- package/dist/{pdf-view-RQ2OZYHJ.js.map → pdf-view-2QU4FI37.js.map} +1 -1
- package/package.json +2 -2
- package/src/components/button/button.stories.tsx +22 -1
- package/src/components/button/button.test.tsx +45 -1
- package/src/components/button/button.tsx +32 -2
- package/src/components/data-table/data-table.test.tsx +41 -0
- package/src/components/data-table/data-table.tsx +24 -2
- package/src/components/glow-frame/glow-frame.stories.tsx +65 -0
- package/src/components/glow-frame/glow-frame.test.tsx +67 -0
- package/src/components/glow-frame/glow-frame.tsx +182 -0
- package/src/components/glow-frame/index.ts +2 -0
- package/src/index.ts +1 -0
- package/src/styles.css +216 -0
- package/dist/chunk-NSE4VW3Z.js.map +0 -1
- package/dist/chunk-NWGG5WFL.cjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunk4ACELYEP_cjs = require('./chunk-4ACELYEP.cjs');
|
|
4
4
|
var lucideReact = require('lucide-react');
|
|
5
5
|
var react = require('react');
|
|
6
6
|
var reactPdf = require('react-pdf');
|
|
@@ -39,7 +39,7 @@ function PdfView({ name, src, onDownload }) {
|
|
|
39
39
|
setPageNumber(1);
|
|
40
40
|
},
|
|
41
41
|
onLoadError: () => setLoadError(true),
|
|
42
|
-
loading: /* @__PURE__ */ jsxRuntime.jsx(
|
|
42
|
+
loading: /* @__PURE__ */ jsxRuntime.jsx(chunk4ACELYEP_cjs.Skeleton, { "aria-label": `Loading ${name}`, className: "h-full w-full rounded-none" }),
|
|
43
43
|
error: null,
|
|
44
44
|
children: pageWidth ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
45
45
|
reactPdf.Page,
|
|
@@ -54,7 +54,7 @@ function PdfView({ name, src, onDownload }) {
|
|
|
54
54
|
) }),
|
|
55
55
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center justify-center gap-1.5 border-t border-rule bg-surface px-2 py-1", children: [
|
|
56
56
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
57
|
-
|
|
57
|
+
chunk4ACELYEP_cjs.Button,
|
|
58
58
|
{
|
|
59
59
|
variant: "ghost",
|
|
60
60
|
size: "icon-sm",
|
|
@@ -66,7 +66,7 @@ function PdfView({ name, src, onDownload }) {
|
|
|
66
66
|
),
|
|
67
67
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-14 text-center font-sans text-[12px] tabular-nums text-ink-3", children: pageCount ? `${pageNumber} / ${pageCount}` : "\u2013" }),
|
|
68
68
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
69
|
-
|
|
69
|
+
chunk4ACELYEP_cjs.Button,
|
|
70
70
|
{
|
|
71
71
|
variant: "ghost",
|
|
72
72
|
size: "icon-sm",
|
|
@@ -77,7 +77,7 @@ function PdfView({ name, src, onDownload }) {
|
|
|
77
77
|
}
|
|
78
78
|
),
|
|
79
79
|
onDownload ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
80
|
-
|
|
80
|
+
chunk4ACELYEP_cjs.Button,
|
|
81
81
|
{
|
|
82
82
|
variant: "ghost",
|
|
83
83
|
size: "icon-sm",
|
|
@@ -91,5 +91,5 @@ function PdfView({ name, src, onDownload }) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
module.exports = PdfView;
|
|
94
|
-
//# sourceMappingURL=pdf-view-
|
|
95
|
-
//# sourceMappingURL=pdf-view-
|
|
94
|
+
//# sourceMappingURL=pdf-view-2DSARHNQ.cjs.map
|
|
95
|
+
//# sourceMappingURL=pdf-view-2DSARHNQ.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["url-asset:pdfjs-dist/build/pdf.worker.min.mjs?url","../src/components/file-viewer/pdf-view.tsx"],"names":["pdfjs","useState","useRef","useEffect","jsx","jsxs","Document","Skeleton","Page","Button","ChevronLeft","ChevronRight","Download"],"mappings":"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","file":"pdf-view-
|
|
1
|
+
{"version":3,"sources":["url-asset:pdfjs-dist/build/pdf.worker.min.mjs?url","../src/components/file-viewer/pdf-view.tsx"],"names":["pdfjs","useState","useRef","useEffect","jsx","jsxs","Document","Skeleton","Page","Button","ChevronLeft","ChevronRight","Download"],"mappings":"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","file":"pdf-view-2DSARHNQ.cjs","sourcesContent":["export default \"https://unpkg.com/pdfjs-dist@5.4.296/build/pdf.worker.min.mjs\"","import { ChevronLeft, ChevronRight, Download } from 'lucide-react'\nimport pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'\nimport { useEffect, useRef, useState } from 'react'\nimport { Document, Page, pdfjs } from 'react-pdf'\n\nimport { Button } from '../button/button'\nimport { Skeleton } from '../skeleton/skeleton'\n\n/* =========================================================================\n PdfView — the FileViewer's PDF pane, backed by react-pdf (pdf.js).\n Lazy-loaded by the FileViewer (default export) so pdf.js and its worker\n stay out of the main bundle. Text/annotation layers are off: this is a\n read-only preview and the layers would need pdf.js's own stylesheets,\n which the token-based design system deliberately doesn't import.\n ========================================================================= */\n\n// The worker must come from the SAME pdfjs-dist instance react-pdf renders\n// with (API/worker version mismatch throws). `?url` lets Vite emit it as an\n// asset and hand back the served URL. In the published dist build the `?url`\n// module is rewritten to a version-pinned CDN URL (see tsup.config.ts).\n// react-pdf's entry unconditionally stomps workerSrc with the placeholder\n// 'pdf.worker.mjs' when it's imported (which happens right above), so that\n// sentinel counts as unset — only a consumer assignment made after react-pdf\n// loaded can stick.\nif (\n !pdfjs.GlobalWorkerOptions.workerSrc ||\n pdfjs.GlobalWorkerOptions.workerSrc === 'pdf.worker.mjs'\n) {\n pdfjs.GlobalWorkerOptions.workerSrc = pdfWorkerUrl\n}\n\nexport interface PdfViewProps {\n name: string\n /** Blob object URL of the PDF bytes. */\n src: string\n onDownload?: () => void\n}\n\nexport default function PdfView({ name, src, onDownload }: PdfViewProps) {\n const [pageCount, setPageCount] = useState<number | null>(null)\n const [pageNumber, setPageNumber] = useState(1)\n const [loadError, setLoadError] = useState(false)\n\n // Fit-width: render the page at the scroll area's current width.\n const scrollRef = useRef<HTMLDivElement | null>(null)\n const [pageWidth, setPageWidth] = useState<number | null>(null)\n useEffect(() => {\n const element = scrollRef.current\n if (!element) return\n const observer = new ResizeObserver(([entry]) => {\n if (entry) setPageWidth(Math.floor(entry.contentRect.width))\n })\n observer.observe(element)\n return () => observer.disconnect()\n }, [])\n\n if (loadError) {\n return (\n <div className=\"flex h-full w-full items-center justify-center p-4\">\n <span className=\"font-sans text-[13px] text-danger\">\n Could not display this PDF. Download it instead.\n </span>\n </div>\n )\n }\n\n return (\n <div className=\"flex h-full w-full flex-col\">\n <div ref={scrollRef} className=\"min-h-0 flex-1 overflow-auto\">\n <Document\n file={src}\n onLoadSuccess={({ numPages }) => {\n setPageCount(numPages)\n setPageNumber(1)\n }}\n onLoadError={() => setLoadError(true)}\n loading={\n <Skeleton aria-label={`Loading ${name}`} className=\"h-full w-full rounded-none\" />\n }\n error={null}\n >\n {pageWidth ? (\n <Page\n pageNumber={pageNumber}\n width={pageWidth}\n renderTextLayer={false}\n renderAnnotationLayer={false}\n />\n ) : null}\n </Document>\n </div>\n <div className=\"flex shrink-0 items-center justify-center gap-1.5 border-t border-rule bg-surface px-2 py-1\">\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label=\"Previous page\"\n isDisabled={pageNumber <= 1}\n onPress={() => setPageNumber((page) => Math.max(1, page - 1))}\n >\n <ChevronLeft aria-hidden />\n </Button>\n <span className=\"min-w-14 text-center font-sans text-[12px] tabular-nums text-ink-3\">\n {pageCount ? `${pageNumber} / ${pageCount}` : '–'}\n </span>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label=\"Next page\"\n isDisabled={pageCount === null || pageNumber >= pageCount}\n onPress={() => setPageNumber((page) => Math.min(pageCount ?? page, page + 1))}\n >\n <ChevronRight aria-hidden />\n </Button>\n {onDownload ? (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Download ${name}`}\n onPress={onDownload}\n >\n <Download aria-hidden />\n </Button>\n ) : null}\n </div>\n </div>\n )\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Skeleton, Button } from './chunk-
|
|
1
|
+
import { Skeleton, Button } from './chunk-XRN5MNDX.js';
|
|
2
2
|
import { ChevronLeft, ChevronRight, Download } from 'lucide-react';
|
|
3
3
|
import { useState, useRef, useEffect } from 'react';
|
|
4
4
|
import { pdfjs, Document, Page } from 'react-pdf';
|
|
@@ -89,5 +89,5 @@ function PdfView({ name, src, onDownload }) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
export { PdfView as default };
|
|
92
|
-
//# sourceMappingURL=pdf-view-
|
|
93
|
-
//# sourceMappingURL=pdf-view-
|
|
92
|
+
//# sourceMappingURL=pdf-view-2QU4FI37.js.map
|
|
93
|
+
//# sourceMappingURL=pdf-view-2QU4FI37.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["url-asset:pdfjs-dist/build/pdf.worker.min.mjs?url","../src/components/file-viewer/pdf-view.tsx"],"names":[],"mappings":"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","file":"pdf-view-
|
|
1
|
+
{"version":3,"sources":["url-asset:pdfjs-dist/build/pdf.worker.min.mjs?url","../src/components/file-viewer/pdf-view.tsx"],"names":[],"mappings":";;;;;;;AAAA,IAAO,sBAAA,GAAQ,+DAAA;ACwBf,IACE,CAAC,KAAA,CAAM,mBAAA,CAAoB,aAC3B,KAAA,CAAM,mBAAA,CAAoB,cAAc,gBAAA,EACxC;AACA,EAAA,KAAA,CAAM,oBAAoB,SAAA,GAAY,sBAAA;AACxC;AASe,SAAR,OAAA,CAAyB,EAAE,IAAA,EAAM,GAAA,EAAK,YAAW,EAAiB;AACvE,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAwB,IAAI,CAAA;AAC9D,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAAS,CAAC,CAAA;AAC9C,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAS,KAAK,CAAA;AAGhD,EAAA,MAAM,SAAA,GAAY,OAA8B,IAAI,CAAA;AACpD,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAwB,IAAI,CAAA;AAC9D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,UAAU,SAAA,CAAU,OAAA;AAC1B,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,MAAM,WAAW,IAAI,cAAA,CAAe,CAAC,CAAC,KAAK,CAAA,KAAM;AAC/C,MAAA,IAAI,OAAO,YAAA,CAAa,IAAA,CAAK,MAAM,KAAA,CAAM,WAAA,CAAY,KAAK,CAAC,CAAA;AAAA,IAC7D,CAAC,CAAA;AACD,IAAA,QAAA,CAAS,QAAQ,OAAO,CAAA;AACxB,IAAA,OAAO,MAAM,SAAS,UAAA,EAAW;AAAA,EACnC,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,uBACE,GAAA,CAAC,SAAI,SAAA,EAAU,oDAAA,EACb,8BAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mCAAA,EAAoC,QAAA,EAAA,kDAAA,EAEpD,CAAA,EACF,CAAA;AAAA,EAEJ;AAEA,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,6BAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAK,SAAA,EAAW,SAAA,EAAU,8BAAA,EAC7B,QAAA,kBAAA,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAM,GAAA;AAAA,QACN,aAAA,EAAe,CAAC,EAAE,QAAA,EAAS,KAAM;AAC/B,UAAA,YAAA,CAAa,QAAQ,CAAA;AACrB,UAAA,aAAA,CAAc,CAAC,CAAA;AAAA,QACjB,CAAA;AAAA,QACA,WAAA,EAAa,MAAM,YAAA,CAAa,IAAI,CAAA;AAAA,QACpC,OAAA,sBACG,QAAA,EAAA,EAAS,YAAA,EAAY,WAAW,IAAI,CAAA,CAAA,EAAI,WAAU,4BAAA,EAA6B,CAAA;AAAA,QAElF,KAAA,EAAO,IAAA;AAAA,QAEN,QAAA,EAAA,SAAA,mBACC,GAAA;AAAA,UAAC,IAAA;AAAA,UAAA;AAAA,YACC,UAAA;AAAA,YACA,KAAA,EAAO,SAAA;AAAA,YACP,eAAA,EAAiB,KAAA;AAAA,YACjB,qBAAA,EAAuB;AAAA;AAAA,SACzB,GACE;AAAA;AAAA,KACN,EACF,CAAA;AAAA,oBACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,6FAAA,EACb,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UACC,OAAA,EAAQ,OAAA;AAAA,UACR,IAAA,EAAK,SAAA;AAAA,UACL,YAAA,EAAW,eAAA;AAAA,UACX,YAAY,UAAA,IAAc,CAAA;AAAA,UAC1B,OAAA,EAAS,MAAM,aAAA,CAAc,CAAC,IAAA,KAAS,KAAK,GAAA,CAAI,CAAA,EAAG,IAAA,GAAO,CAAC,CAAC,CAAA;AAAA,UAE5D,QAAA,kBAAA,GAAA,CAAC,WAAA,EAAA,EAAY,aAAA,EAAW,IAAA,EAAC;AAAA;AAAA,OAC3B;AAAA,sBACA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,oEAAA,EACb,QAAA,EAAA,SAAA,GAAY,GAAG,UAAU,CAAA,GAAA,EAAM,SAAS,CAAA,CAAA,GAAK,QAAA,EAChD,CAAA;AAAA,sBACA,GAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UACC,OAAA,EAAQ,OAAA;AAAA,UACR,IAAA,EAAK,SAAA;AAAA,UACL,YAAA,EAAW,WAAA;AAAA,UACX,UAAA,EAAY,SAAA,KAAc,IAAA,IAAQ,UAAA,IAAc,SAAA;AAAA,UAChD,OAAA,EAAS,MAAM,aAAA,CAAc,CAAC,IAAA,KAAS,IAAA,CAAK,GAAA,CAAI,SAAA,IAAa,IAAA,EAAM,IAAA,GAAO,CAAC,CAAC,CAAA;AAAA,UAE5E,QAAA,kBAAA,GAAA,CAAC,YAAA,EAAA,EAAa,aAAA,EAAW,IAAA,EAAC;AAAA;AAAA,OAC5B;AAAA,MACC,UAAA,mBACC,GAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UACC,OAAA,EAAQ,OAAA;AAAA,UACR,IAAA,EAAK,SAAA;AAAA,UACL,YAAA,EAAY,YAAY,IAAI,CAAA,CAAA;AAAA,UAC5B,OAAA,EAAS,UAAA;AAAA,UAET,QAAA,kBAAA,GAAA,CAAC,QAAA,EAAA,EAAS,aAAA,EAAW,IAAA,EAAC;AAAA;AAAA,OACxB,GACE;AAAA,KAAA,EACN;AAAA,GAAA,EACF,CAAA;AAEJ","file":"pdf-view-2QU4FI37.js","sourcesContent":["export default \"https://unpkg.com/pdfjs-dist@5.4.296/build/pdf.worker.min.mjs\"","import { ChevronLeft, ChevronRight, Download } from 'lucide-react'\nimport pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'\nimport { useEffect, useRef, useState } from 'react'\nimport { Document, Page, pdfjs } from 'react-pdf'\n\nimport { Button } from '../button/button'\nimport { Skeleton } from '../skeleton/skeleton'\n\n/* =========================================================================\n PdfView — the FileViewer's PDF pane, backed by react-pdf (pdf.js).\n Lazy-loaded by the FileViewer (default export) so pdf.js and its worker\n stay out of the main bundle. Text/annotation layers are off: this is a\n read-only preview and the layers would need pdf.js's own stylesheets,\n which the token-based design system deliberately doesn't import.\n ========================================================================= */\n\n// The worker must come from the SAME pdfjs-dist instance react-pdf renders\n// with (API/worker version mismatch throws). `?url` lets Vite emit it as an\n// asset and hand back the served URL. In the published dist build the `?url`\n// module is rewritten to a version-pinned CDN URL (see tsup.config.ts).\n// react-pdf's entry unconditionally stomps workerSrc with the placeholder\n// 'pdf.worker.mjs' when it's imported (which happens right above), so that\n// sentinel counts as unset — only a consumer assignment made after react-pdf\n// loaded can stick.\nif (\n !pdfjs.GlobalWorkerOptions.workerSrc ||\n pdfjs.GlobalWorkerOptions.workerSrc === 'pdf.worker.mjs'\n) {\n pdfjs.GlobalWorkerOptions.workerSrc = pdfWorkerUrl\n}\n\nexport interface PdfViewProps {\n name: string\n /** Blob object URL of the PDF bytes. */\n src: string\n onDownload?: () => void\n}\n\nexport default function PdfView({ name, src, onDownload }: PdfViewProps) {\n const [pageCount, setPageCount] = useState<number | null>(null)\n const [pageNumber, setPageNumber] = useState(1)\n const [loadError, setLoadError] = useState(false)\n\n // Fit-width: render the page at the scroll area's current width.\n const scrollRef = useRef<HTMLDivElement | null>(null)\n const [pageWidth, setPageWidth] = useState<number | null>(null)\n useEffect(() => {\n const element = scrollRef.current\n if (!element) return\n const observer = new ResizeObserver(([entry]) => {\n if (entry) setPageWidth(Math.floor(entry.contentRect.width))\n })\n observer.observe(element)\n return () => observer.disconnect()\n }, [])\n\n if (loadError) {\n return (\n <div className=\"flex h-full w-full items-center justify-center p-4\">\n <span className=\"font-sans text-[13px] text-danger\">\n Could not display this PDF. Download it instead.\n </span>\n </div>\n )\n }\n\n return (\n <div className=\"flex h-full w-full flex-col\">\n <div ref={scrollRef} className=\"min-h-0 flex-1 overflow-auto\">\n <Document\n file={src}\n onLoadSuccess={({ numPages }) => {\n setPageCount(numPages)\n setPageNumber(1)\n }}\n onLoadError={() => setLoadError(true)}\n loading={\n <Skeleton aria-label={`Loading ${name}`} className=\"h-full w-full rounded-none\" />\n }\n error={null}\n >\n {pageWidth ? (\n <Page\n pageNumber={pageNumber}\n width={pageWidth}\n renderTextLayer={false}\n renderAnnotationLayer={false}\n />\n ) : null}\n </Document>\n </div>\n <div className=\"flex shrink-0 items-center justify-center gap-1.5 border-t border-rule bg-surface px-2 py-1\">\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label=\"Previous page\"\n isDisabled={pageNumber <= 1}\n onPress={() => setPageNumber((page) => Math.max(1, page - 1))}\n >\n <ChevronLeft aria-hidden />\n </Button>\n <span className=\"min-w-14 text-center font-sans text-[12px] tabular-nums text-ink-3\">\n {pageCount ? `${pageNumber} / ${pageCount}` : '–'}\n </span>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label=\"Next page\"\n isDisabled={pageCount === null || pageNumber >= pageCount}\n onPress={() => setPageNumber((page) => Math.min(pageCount ?? page, page + 1))}\n >\n <ChevronRight aria-hidden />\n </Button>\n {onDownload ? (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Download ${name}`}\n onPress={onDownload}\n >\n <Download aria-hidden />\n </Button>\n ) : null}\n </div>\n </div>\n )\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proteos/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.76.0",
|
|
4
4
|
"license": "FSL-1.1-Apache-2.0",
|
|
5
5
|
"description": "Proteos React component library (React Aria Components + Tailwind v4).",
|
|
6
6
|
"repository": {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"tailwind-variants": "^3.2.2",
|
|
57
57
|
"tailwindcss": "^4.2.4",
|
|
58
58
|
"thinking-orbs": "^0.3.1",
|
|
59
|
-
"@proteos/sdk": "0.
|
|
59
|
+
"@proteos/sdk": "0.76.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@biomejs/biome": "^2.4.13",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
2
|
|
|
3
|
-
import { Button,
|
|
3
|
+
import { Button, type ButtonVariants, buttonStyles } from './button'
|
|
4
4
|
|
|
5
5
|
const meta: Meta<typeof Button> = {
|
|
6
6
|
title: 'Components/Button',
|
|
@@ -151,6 +151,27 @@ export const Danger: Story = {
|
|
|
151
151
|
),
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
/** A save in flight: a spinner before the label (in place of the icon on
|
|
155
|
+
* the icon sizes), presses ignored, still focusable and announced busy. */
|
|
156
|
+
export const Pending: Story = {
|
|
157
|
+
render: () => (
|
|
158
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
159
|
+
<Button variant="cta" size="sm" isPending>
|
|
160
|
+
Create Deal
|
|
161
|
+
</Button>
|
|
162
|
+
<Button size="sm" isPending>
|
|
163
|
+
Apply 3 changes
|
|
164
|
+
</Button>
|
|
165
|
+
<Button variant="ghost" size="sm" isPending>
|
|
166
|
+
Send Ava now
|
|
167
|
+
</Button>
|
|
168
|
+
<Button size="icon-md" aria-label="Add" isPending>
|
|
169
|
+
<Plus />
|
|
170
|
+
</Button>
|
|
171
|
+
</div>
|
|
172
|
+
),
|
|
173
|
+
}
|
|
174
|
+
|
|
154
175
|
const columns = 'grid grid-cols-[160px_1fr] items-center gap-y-5 gap-x-6'
|
|
155
176
|
const label = 'font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4'
|
|
156
177
|
|
|
@@ -47,7 +47,16 @@ describe('Button', () => {
|
|
|
47
47
|
})
|
|
48
48
|
|
|
49
49
|
it('renders every variant as a button', () => {
|
|
50
|
-
const variants = [
|
|
50
|
+
const variants = [
|
|
51
|
+
'cta',
|
|
52
|
+
'accent',
|
|
53
|
+
'primary',
|
|
54
|
+
'secondary',
|
|
55
|
+
'ghost',
|
|
56
|
+
'subtle',
|
|
57
|
+
'link',
|
|
58
|
+
'danger',
|
|
59
|
+
] as const
|
|
51
60
|
render(
|
|
52
61
|
<>
|
|
53
62
|
{variants.map((variant) => (
|
|
@@ -89,4 +98,39 @@ describe('Button', () => {
|
|
|
89
98
|
it('defaults to the primary variant', () => {
|
|
90
99
|
expect(buttonStyles({})).toBe(buttonStyles({ variant: 'primary' }))
|
|
91
100
|
})
|
|
101
|
+
|
|
102
|
+
describe('pending', () => {
|
|
103
|
+
it('shows a spinner before the label and ignores presses', async () => {
|
|
104
|
+
const user = userEvent.setup()
|
|
105
|
+
const onPress = vi.fn()
|
|
106
|
+
render(
|
|
107
|
+
<Button isPending onPress={onPress}>
|
|
108
|
+
Create Deal
|
|
109
|
+
</Button>,
|
|
110
|
+
)
|
|
111
|
+
const button = screen.getByRole('button', { name: 'Create Deal' })
|
|
112
|
+
expect(button).toHaveAttribute('data-pending', 'true')
|
|
113
|
+
expect(button.querySelector('[data-slot="pending-spinner"]')).not.toBeNull()
|
|
114
|
+
await user.click(button)
|
|
115
|
+
expect(onPress).not.toHaveBeenCalled()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('replaces the icon on an icon-only button', () => {
|
|
119
|
+
render(
|
|
120
|
+
<Button size="icon-md" aria-label="Add" isPending>
|
|
121
|
+
<span data-testid="icon" />
|
|
122
|
+
</Button>,
|
|
123
|
+
)
|
|
124
|
+
const button = screen.getByRole('button', { name: 'Add' })
|
|
125
|
+
expect(button.querySelector('[data-slot="pending-spinner"]')).not.toBeNull()
|
|
126
|
+
expect(screen.queryByTestId('icon')).not.toBeInTheDocument()
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('renders no spinner when idle', () => {
|
|
130
|
+
render(<Button>Save</Button>)
|
|
131
|
+
expect(
|
|
132
|
+
screen.getByRole('button', { name: 'Save' }).querySelector('[data-slot="pending-spinner"]'),
|
|
133
|
+
).toBeNull()
|
|
134
|
+
})
|
|
135
|
+
})
|
|
92
136
|
})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Loader2 } from 'lucide-react'
|
|
1
2
|
import { Button as AriaButton, type ButtonProps as AriaButtonProps } from 'react-aria-components'
|
|
2
3
|
import { tv, type VariantProps } from 'tailwind-variants'
|
|
3
4
|
|
|
@@ -23,6 +24,11 @@ import { cn } from '../../lib/cn'
|
|
|
23
24
|
and `disabled:` stay on the native pseudo-classes because the RAC root IS
|
|
24
25
|
the focusable <button>. Hover is always a colour step on the surface ramp,
|
|
25
26
|
never opacity; press is translateY(1px) plus one more ramp step.
|
|
27
|
+
|
|
28
|
+
`isPending` (React Aria's: presses are ignored, the button stays
|
|
29
|
+
focusable and announces itself busy) shows a spinner before the label —
|
|
30
|
+
in place of the icon on the icon sizes — so a save that takes a moment
|
|
31
|
+
reads as working, not as a dead button.
|
|
26
32
|
========================================================================= */
|
|
27
33
|
|
|
28
34
|
// Ink fill with the indigo-tinted hover. Kept verbatim from the old primary.
|
|
@@ -124,16 +130,40 @@ export const buttonStyles = tv({
|
|
|
124
130
|
export type ButtonVariants = VariantProps<typeof buttonStyles>
|
|
125
131
|
export type ButtonProps = AriaButtonProps & ButtonVariants
|
|
126
132
|
|
|
127
|
-
|
|
133
|
+
const isIconSize = (size: ButtonVariants['size']) =>
|
|
134
|
+
size === 'icon-sm' || size === 'icon-md' || size === 'icon-lg'
|
|
135
|
+
|
|
136
|
+
export function Button({ variant, size, shape, className, children, ...props }: ButtonProps) {
|
|
128
137
|
return (
|
|
129
138
|
<AriaButton
|
|
130
139
|
{...props}
|
|
131
140
|
className={(renderProps) =>
|
|
132
141
|
cn(
|
|
133
142
|
buttonStyles({ variant, size, shape }),
|
|
143
|
+
renderProps.isPending && 'cursor-progress',
|
|
134
144
|
typeof className === 'function' ? className(renderProps) : className,
|
|
135
145
|
)
|
|
136
146
|
}
|
|
137
|
-
|
|
147
|
+
>
|
|
148
|
+
{(renderProps) => {
|
|
149
|
+
const content = typeof children === 'function' ? children(renderProps) : children
|
|
150
|
+
if (!renderProps.isPending) return content
|
|
151
|
+
const spinner = (
|
|
152
|
+
<Loader2
|
|
153
|
+
className="shrink-0 animate-spin motion-reduce:animate-none"
|
|
154
|
+
aria-hidden
|
|
155
|
+
data-slot="pending-spinner"
|
|
156
|
+
/>
|
|
157
|
+
)
|
|
158
|
+
return isIconSize(size) ? (
|
|
159
|
+
spinner
|
|
160
|
+
) : (
|
|
161
|
+
<>
|
|
162
|
+
{spinner}
|
|
163
|
+
{content}
|
|
164
|
+
</>
|
|
165
|
+
)
|
|
166
|
+
}}
|
|
167
|
+
</AriaButton>
|
|
138
168
|
)
|
|
139
169
|
}
|
|
@@ -196,3 +196,44 @@ describe('DataTable placeholder rows', () => {
|
|
|
196
196
|
expect(screen.getByText('Alpha').closest('[aria-busy]')).toBeNull()
|
|
197
197
|
})
|
|
198
198
|
})
|
|
199
|
+
|
|
200
|
+
/* A host router restores the scroller's position on back navigation before
|
|
201
|
+
the table's effects run — the sort-change reset must not undo it on mount. */
|
|
202
|
+
describe('DataTable scroll restoration', () => {
|
|
203
|
+
it('stamps the restoration id on the scroll container', () => {
|
|
204
|
+
const { container } = render(
|
|
205
|
+
<DataTable<Row>
|
|
206
|
+
query={query(ROWS)}
|
|
207
|
+
columns={columns}
|
|
208
|
+
ariaLabel="Records"
|
|
209
|
+
scrollRestorationId="list:customers"
|
|
210
|
+
/>,
|
|
211
|
+
)
|
|
212
|
+
expect(container.querySelector('[data-scroll-restoration-id="list:customers"]')).not.toBeNull()
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('keeps the restored position on mount and resets it when the sort changes', () => {
|
|
216
|
+
const scrollTo = vi.fn()
|
|
217
|
+
const original = HTMLElement.prototype.scrollTo
|
|
218
|
+
HTMLElement.prototype.scrollTo = scrollTo
|
|
219
|
+
try {
|
|
220
|
+
const sorting = [{ id: 'name', desc: false }]
|
|
221
|
+
const { rerender } = render(
|
|
222
|
+
<DataTable<Row> query={query(ROWS)} columns={columns} ariaLabel="Records" sorting={sorting} />,
|
|
223
|
+
)
|
|
224
|
+
expect(scrollTo).not.toHaveBeenCalled()
|
|
225
|
+
|
|
226
|
+
rerender(
|
|
227
|
+
<DataTable<Row>
|
|
228
|
+
query={query(ROWS)}
|
|
229
|
+
columns={columns}
|
|
230
|
+
ariaLabel="Records"
|
|
231
|
+
sorting={[{ id: 'name', desc: true }]}
|
|
232
|
+
/>,
|
|
233
|
+
)
|
|
234
|
+
expect(scrollTo).toHaveBeenCalledWith({ top: 0 })
|
|
235
|
+
} finally {
|
|
236
|
+
HTMLElement.prototype.scrollTo = original
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
})
|
|
@@ -77,6 +77,18 @@ export type DataTableProps<TRow> = {
|
|
|
77
77
|
onSortingChange?: OnChangeFn<SortingState>
|
|
78
78
|
rowHeight?: number
|
|
79
79
|
overscan?: number
|
|
80
|
+
/**
|
|
81
|
+
* Stable id stamped on the scroll container as `data-scroll-restoration-id`,
|
|
82
|
+
* so a router that restores element scroll on back/forward finds this table
|
|
83
|
+
* by id instead of by its (fragile) DOM path.
|
|
84
|
+
*/
|
|
85
|
+
scrollRestorationId?: string
|
|
86
|
+
/**
|
|
87
|
+
* Scroll offset the table first renders at — a restored position from the
|
|
88
|
+
* host's router. The virtualizer starts its window there, so the rows at
|
|
89
|
+
* that offset paint on the first frame instead of a blank band.
|
|
90
|
+
*/
|
|
91
|
+
initialScrollOffset?: number
|
|
80
92
|
/**
|
|
81
93
|
* Called when a row is clicked. Provides both the original data row and
|
|
82
94
|
* the underlying event so consumers can inspect modifier keys.
|
|
@@ -204,6 +216,8 @@ export function DataTable<TRow>({
|
|
|
204
216
|
onSortingChange,
|
|
205
217
|
rowHeight = DEFAULT_ROW_HEIGHT,
|
|
206
218
|
overscan = 8,
|
|
219
|
+
scrollRestorationId,
|
|
220
|
+
initialScrollOffset,
|
|
207
221
|
onRowClick,
|
|
208
222
|
zebra = true,
|
|
209
223
|
emptyState,
|
|
@@ -384,10 +398,14 @@ export function DataTable<TRow>({
|
|
|
384
398
|
}, [tableRows, groupBy])
|
|
385
399
|
|
|
386
400
|
// Reset scroll to top when sorting changes — without this, the user is
|
|
387
|
-
// staring at a different slice of a freshly-sorted dataset.
|
|
401
|
+
// staring at a different slice of a freshly-sorted dataset. Only on a
|
|
402
|
+
// CHANGE: on mount this would wipe the position the host just restored
|
|
403
|
+
// (the router restores in the layout phase, before this effect runs).
|
|
388
404
|
const scrollerRef = useRef<HTMLDivElement | null>(null)
|
|
389
|
-
|
|
405
|
+
const mountedSorting = useRef(sorting)
|
|
390
406
|
useEffect(() => {
|
|
407
|
+
if (sorting === mountedSorting.current) return
|
|
408
|
+
mountedSorting.current = sorting
|
|
391
409
|
scrollerRef.current?.scrollTo({ top: 0 })
|
|
392
410
|
}, [sorting])
|
|
393
411
|
|
|
@@ -411,6 +429,7 @@ export function DataTable<TRow>({
|
|
|
411
429
|
estimateSize: (index) => (listItems[index]?.kind === 'group' ? headingHeight : rowHeight),
|
|
412
430
|
overscan,
|
|
413
431
|
scrollMargin: listOffset,
|
|
432
|
+
...(initialScrollOffset !== undefined ? { initialOffset: initialScrollOffset } : {}),
|
|
414
433
|
})
|
|
415
434
|
|
|
416
435
|
const sentinelRef = useInfiniteScrollSentinel<HTMLDivElement>({
|
|
@@ -491,6 +510,9 @@ export function DataTable<TRow>({
|
|
|
491
510
|
top, and any `sticky left-0` gutter column freezes to the left. */}
|
|
492
511
|
<TableScroller
|
|
493
512
|
ref={scrollerRef}
|
|
513
|
+
{...(scrollRestorationId !== undefined
|
|
514
|
+
? { 'data-scroll-restoration-id': scrollRestorationId }
|
|
515
|
+
: {})}
|
|
494
516
|
// Scroll padding keeps a row scrolled (or focused) into view clear of
|
|
495
517
|
// the sticky header and, when present, the sticky trailing row —
|
|
496
518
|
// otherwise a freshly added last row lands underneath the strip.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { Button } from '../button'
|
|
5
|
+
import { TextShimmer } from '../text-shimmer'
|
|
6
|
+
import { GlowFrame, type GlowFrameStatus } from './glow-frame'
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof GlowFrame> = {
|
|
9
|
+
title: 'Components/GlowFrame',
|
|
10
|
+
component: GlowFrame,
|
|
11
|
+
tags: ['autodocs'],
|
|
12
|
+
argTypes: {
|
|
13
|
+
status: { control: 'inline-radio', options: ['idle', 'working', 'ready'] },
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default meta
|
|
18
|
+
type Story = StoryObj<typeof GlowFrame>
|
|
19
|
+
|
|
20
|
+
const BOX = 'rounded-md border border-rule bg-surface p-4 text-[13px] text-ink-2'
|
|
21
|
+
|
|
22
|
+
export const Playground: Story = {
|
|
23
|
+
args: { status: 'working', className: 'w-[360px] rounded-md' },
|
|
24
|
+
render: (args) => (
|
|
25
|
+
<GlowFrame {...args}>
|
|
26
|
+
<div className={BOX}>Three fields will change on this deal.</div>
|
|
27
|
+
</GlowFrame>
|
|
28
|
+
),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The full sequence: beams while the model works, a flash when the output
|
|
32
|
+
* lands. */
|
|
33
|
+
export const Sequence: Story = {
|
|
34
|
+
parameters: { controls: { hideNoControlsWarning: true } },
|
|
35
|
+
render: () => {
|
|
36
|
+
const [status, setStatus] = useState<GlowFrameStatus>('idle')
|
|
37
|
+
const run = () => {
|
|
38
|
+
setStatus('working')
|
|
39
|
+
window.setTimeout(() => setStatus('ready'), 3000)
|
|
40
|
+
}
|
|
41
|
+
return (
|
|
42
|
+
<div className="flex w-[360px] flex-col gap-4">
|
|
43
|
+
<GlowFrame status={status} className="rounded-md">
|
|
44
|
+
<div className={BOX}>
|
|
45
|
+
{status === 'working' ? (
|
|
46
|
+
<TextShimmer>Preparing the changes…</TextShimmer>
|
|
47
|
+
) : status === 'ready' ? (
|
|
48
|
+
'Stage → Negotiation, amount → €42,000, close date → 30 Nov.'
|
|
49
|
+
) : (
|
|
50
|
+
'Press run.'
|
|
51
|
+
)}
|
|
52
|
+
</div>
|
|
53
|
+
</GlowFrame>
|
|
54
|
+
<div className="flex gap-2">
|
|
55
|
+
<Button size="sm" onPress={run} isDisabled={status === 'working'}>
|
|
56
|
+
Run
|
|
57
|
+
</Button>
|
|
58
|
+
<Button size="sm" variant="ghost" onPress={() => setStatus('idle')}>
|
|
59
|
+
Reset
|
|
60
|
+
</Button>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
},
|
|
65
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { act, render, screen } from '@testing-library/react'
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { GlowFrame, type GlowFrameStatus } from './glow-frame'
|
|
5
|
+
|
|
6
|
+
function frame(status: GlowFrameStatus) {
|
|
7
|
+
return (
|
|
8
|
+
<GlowFrame status={status}>
|
|
9
|
+
<p>Output</p>
|
|
10
|
+
</GlowFrame>
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const beams = () => document.querySelector('[data-glow="beams"]')
|
|
15
|
+
const flash = () => document.querySelector('[data-glow="flash"]')
|
|
16
|
+
|
|
17
|
+
describe('GlowFrame', () => {
|
|
18
|
+
beforeEach(() => vi.useFakeTimers())
|
|
19
|
+
afterEach(() => vi.useRealTimers())
|
|
20
|
+
|
|
21
|
+
it('renders its children', () => {
|
|
22
|
+
render(frame('idle'))
|
|
23
|
+
expect(screen.getByText('Output')).toBeInTheDocument()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('shows the beams while working and stops them after the fade', () => {
|
|
27
|
+
const { rerender } = render(frame('working'))
|
|
28
|
+
expect(beams()).not.toBeNull()
|
|
29
|
+
|
|
30
|
+
rerender(frame('idle'))
|
|
31
|
+
expect(beams()).not.toBeNull()
|
|
32
|
+
act(() => vi.advanceTimersByTime(800))
|
|
33
|
+
expect(beams()).toBeNull()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('flashes once when the output lands', () => {
|
|
37
|
+
const { rerender } = render(frame('working'))
|
|
38
|
+
expect(flash()).toBeNull()
|
|
39
|
+
|
|
40
|
+
rerender(frame('ready'))
|
|
41
|
+
expect(flash()).not.toBeNull()
|
|
42
|
+
act(() => vi.advanceTimersByTime(900))
|
|
43
|
+
expect(flash()).toBeNull()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('does not flash when mounted with the output already there', () => {
|
|
47
|
+
render(frame('ready'))
|
|
48
|
+
expect(flash()).toBeNull()
|
|
49
|
+
expect(beams()).toBeNull()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('does not flash when the work ends without output', () => {
|
|
53
|
+
const { rerender } = render(frame('working'))
|
|
54
|
+
rerender(frame('idle'))
|
|
55
|
+
expect(flash()).toBeNull()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('flashes again for a second run', () => {
|
|
59
|
+
const { rerender } = render(frame('working'))
|
|
60
|
+
rerender(frame('ready'))
|
|
61
|
+
act(() => vi.advanceTimersByTime(900))
|
|
62
|
+
rerender(frame('working'))
|
|
63
|
+
expect(beams()).not.toBeNull()
|
|
64
|
+
rerender(frame('ready'))
|
|
65
|
+
expect(flash()).not.toBeNull()
|
|
66
|
+
})
|
|
67
|
+
})
|