@proteos/ui 0.61.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 +811 -304
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +365 -15
- package/dist/index.d.ts +365 -15
- package/dist/index.js +537 -39
- 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/availability-calendar/availability-calendar.stories.tsx +78 -0
- package/src/components/availability-calendar/availability-calendar.test.tsx +89 -0
- package/src/components/availability-calendar/availability-calendar.tsx +182 -0
- package/src/components/availability-calendar/index.ts +5 -0
- 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/conversation-brief-card/conversation-brief-preview.tsx +176 -47
- package/src/components/conversation-brief-card/conversation-brief-review.test.tsx +58 -1
- package/src/components/data-table/data-table.test.tsx +65 -0
- package/src/components/data-table/data-table.tsx +33 -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/components/stepper/index.ts +8 -0
- package/src/components/stepper/stepper.stories.tsx +84 -0
- package/src/components/stepper/stepper.test.tsx +79 -0
- package/src/components/stepper/stepper.tsx +127 -0
- package/src/components/time-slot-list/index.ts +2 -0
- package/src/components/time-slot-list/time-slot-list.stories.tsx +100 -0
- package/src/components/time-slot-list/time-slot-list.test.tsx +120 -0
- package/src/components/time-slot-list/time-slot-list.tsx +154 -0
- package/src/components/tooltip/tooltip.tsx +4 -1
- package/src/index.ts +4 -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":"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","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",
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type CalendarDate, getLocalTimeZone, today } from '@internationalized/date'
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
|
|
5
|
+
import { AvailabilityCalendar } from './availability-calendar'
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof AvailabilityCalendar> = {
|
|
8
|
+
title: 'Components/AvailabilityCalendar',
|
|
9
|
+
component: AvailabilityCalendar,
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
decorators: [
|
|
12
|
+
(Story) => (
|
|
13
|
+
<div className="w-[480px] p-6 bg-surface border border-rule rounded-md">
|
|
14
|
+
<Story />
|
|
15
|
+
</div>
|
|
16
|
+
),
|
|
17
|
+
],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default meta
|
|
21
|
+
type Story = StoryObj<typeof AvailabilityCalendar>
|
|
22
|
+
|
|
23
|
+
const now = today(getLocalTimeZone())
|
|
24
|
+
|
|
25
|
+
/** Weekdays from today on, for the next six weeks. */
|
|
26
|
+
function isWeekdayAhead(date: CalendarDate): boolean {
|
|
27
|
+
const offset = date.compare(now)
|
|
28
|
+
if (offset < 0 || offset > 42) return false
|
|
29
|
+
const weekday = date.toDate(getLocalTimeZone()).getDay()
|
|
30
|
+
return weekday !== 0 && weekday !== 6
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const WeekdaysAvailable: Story = {
|
|
34
|
+
render: () => {
|
|
35
|
+
const [value, setValue] = useState<CalendarDate | null>(null)
|
|
36
|
+
return (
|
|
37
|
+
<AvailabilityCalendar
|
|
38
|
+
aria-label="Pick a day"
|
|
39
|
+
value={value}
|
|
40
|
+
onChange={setValue}
|
|
41
|
+
minValue={now}
|
|
42
|
+
isDateUnavailable={(date) => !isWeekdayAhead(date)}
|
|
43
|
+
/>
|
|
44
|
+
)
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const Selected: Story = {
|
|
49
|
+
render: () => {
|
|
50
|
+
const [value, setValue] = useState<CalendarDate | null>(now.add({ days: 3 }))
|
|
51
|
+
return (
|
|
52
|
+
<AvailabilityCalendar
|
|
53
|
+
aria-label="Pick a day"
|
|
54
|
+
value={value}
|
|
55
|
+
onChange={setValue}
|
|
56
|
+
minValue={now}
|
|
57
|
+
isDateUnavailable={(date) => !isWeekdayAhead(date)}
|
|
58
|
+
/>
|
|
59
|
+
)
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const Loading: Story = {
|
|
64
|
+
render: () => (
|
|
65
|
+
<AvailabilityCalendar aria-label="Pick a day" value={null} onChange={() => {}} isLoading />
|
|
66
|
+
),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const NothingAvailable: Story = {
|
|
70
|
+
render: () => (
|
|
71
|
+
<AvailabilityCalendar
|
|
72
|
+
aria-label="Pick a day"
|
|
73
|
+
value={null}
|
|
74
|
+
onChange={() => {}}
|
|
75
|
+
isDateUnavailable={() => true}
|
|
76
|
+
/>
|
|
77
|
+
),
|
|
78
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { CalendarDate } from '@internationalized/date'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import userEvent from '@testing-library/user-event'
|
|
4
|
+
import { useState } from 'react'
|
|
5
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { AvailabilityCalendar } from './availability-calendar'
|
|
8
|
+
|
|
9
|
+
const OCTOBER_FIRST = new CalendarDate(2026, 10, 1)
|
|
10
|
+
const AVAILABLE = new Set(['2026-10-20', '2026-10-21', '2026-10-23'])
|
|
11
|
+
|
|
12
|
+
function Harness({
|
|
13
|
+
initial = null,
|
|
14
|
+
onChange,
|
|
15
|
+
isLoading = false,
|
|
16
|
+
}: {
|
|
17
|
+
initial?: CalendarDate | null
|
|
18
|
+
onChange?: (date: CalendarDate) => void
|
|
19
|
+
isLoading?: boolean
|
|
20
|
+
}) {
|
|
21
|
+
const [value, setValue] = useState<CalendarDate | null>(initial)
|
|
22
|
+
return (
|
|
23
|
+
<AvailabilityCalendar
|
|
24
|
+
aria-label="Pick a day"
|
|
25
|
+
value={value}
|
|
26
|
+
onChange={(next) => {
|
|
27
|
+
setValue(next)
|
|
28
|
+
onChange?.(next)
|
|
29
|
+
}}
|
|
30
|
+
defaultFocusedValue={OCTOBER_FIRST}
|
|
31
|
+
minValue={new CalendarDate(2026, 10, 15)}
|
|
32
|
+
isDateUnavailable={(date) => !AVAILABLE.has(date.toString())}
|
|
33
|
+
isLoading={isLoading}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('AvailabilityCalendar', () => {
|
|
39
|
+
it('renders a labelled grid for the focused month', () => {
|
|
40
|
+
render(<Harness />)
|
|
41
|
+
expect(screen.getByRole('grid', { name: /Pick a day/ })).toBeInTheDocument()
|
|
42
|
+
expect(screen.getByText('October')).toBeInTheDocument()
|
|
43
|
+
expect(screen.getByText('2026')).toBeInTheDocument()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('marks days without availability as unavailable but keeps them in the grid', () => {
|
|
47
|
+
render(<Harness />)
|
|
48
|
+
const available = screen.getByRole('button', { name: /October 20/ })
|
|
49
|
+
const unavailable = screen.getByRole('button', { name: /October 22/ })
|
|
50
|
+
expect(available).not.toHaveAttribute('aria-disabled', 'true')
|
|
51
|
+
expect(unavailable).toHaveAttribute('aria-disabled', 'true')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('selects an available day on click and reports it', async () => {
|
|
55
|
+
const user = userEvent.setup()
|
|
56
|
+
const onChange = vi.fn()
|
|
57
|
+
render(<Harness onChange={onChange} />)
|
|
58
|
+
await user.click(screen.getByRole('button', { name: /October 23/ }))
|
|
59
|
+
expect(onChange).toHaveBeenLastCalledWith(new CalendarDate(2026, 10, 23))
|
|
60
|
+
expect(screen.getByRole('gridcell', { selected: true })).toHaveTextContent('23')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('ignores clicks on unavailable days', async () => {
|
|
64
|
+
const user = userEvent.setup()
|
|
65
|
+
const onChange = vi.fn()
|
|
66
|
+
render(<Harness onChange={onChange} />)
|
|
67
|
+
await user.click(screen.getByRole('button', { name: /October 22/ }))
|
|
68
|
+
expect(onChange).not.toHaveBeenCalled()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('refuses selection while loading and flags the grid busy', async () => {
|
|
72
|
+
const user = userEvent.setup()
|
|
73
|
+
const onChange = vi.fn()
|
|
74
|
+
render(<Harness onChange={onChange} isLoading />)
|
|
75
|
+
expect(screen.getByRole('grid', { name: /Pick a day/ }).closest('[aria-busy]')).toHaveAttribute(
|
|
76
|
+
'aria-busy',
|
|
77
|
+
'true',
|
|
78
|
+
)
|
|
79
|
+
await user.click(screen.getByRole('button', { name: /October 20/ }))
|
|
80
|
+
expect(onChange).not.toHaveBeenCalled()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('moves to the next month with the nav button', async () => {
|
|
84
|
+
const user = userEvent.setup()
|
|
85
|
+
render(<Harness />)
|
|
86
|
+
await user.click(screen.getByRole('button', { name: 'Next month' }))
|
|
87
|
+
expect(screen.getByText('November')).toBeInTheDocument()
|
|
88
|
+
})
|
|
89
|
+
})
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type { CalendarDate, DateValue } from '@internationalized/date'
|
|
2
|
+
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
|
3
|
+
import { useContext } from 'react'
|
|
4
|
+
import {
|
|
5
|
+
Button as AriaButton,
|
|
6
|
+
Calendar as AriaCalendar,
|
|
7
|
+
type CalendarProps as AriaCalendarProps,
|
|
8
|
+
CalendarCell,
|
|
9
|
+
CalendarGrid,
|
|
10
|
+
CalendarGridBody,
|
|
11
|
+
CalendarGridHeader,
|
|
12
|
+
CalendarHeaderCell,
|
|
13
|
+
CalendarStateContext,
|
|
14
|
+
Heading,
|
|
15
|
+
} from 'react-aria-components'
|
|
16
|
+
import { tv, type VariantProps } from 'tailwind-variants'
|
|
17
|
+
|
|
18
|
+
import { cn } from '../../lib/cn'
|
|
19
|
+
import { Skeleton } from '../skeleton/skeleton'
|
|
20
|
+
|
|
21
|
+
/* =========================================================================
|
|
22
|
+
AvailabilityCalendar — a page-level month grid that marks which days have
|
|
23
|
+
something to offer (free times, open seats, stock). Distinct from the
|
|
24
|
+
popover calendar inside DatePicker: square cells, the available days
|
|
25
|
+
carry a tint, the selected day is the one ink fill, unavailable days are
|
|
26
|
+
quiet text, today is a dot. Wraps RAC Calendar so the grid is a real
|
|
27
|
+
`grid` with roving focus, arrow / PageUp / Home keys and announced cells.
|
|
28
|
+
|
|
29
|
+
"Available" is the consumer's call through `isDateUnavailable` — the
|
|
30
|
+
component only paints the answer. Loading paints every cell as a
|
|
31
|
+
skeleton and refuses selection until the consumer knows.
|
|
32
|
+
========================================================================= */
|
|
33
|
+
|
|
34
|
+
export const availabilityCalendarStyles = tv({
|
|
35
|
+
slots: {
|
|
36
|
+
root: 'flex flex-col',
|
|
37
|
+
header: 'flex items-center justify-between mb-3.5',
|
|
38
|
+
heading: 'font-sans text-[15px] text-ink tracking-[-0.01em]',
|
|
39
|
+
headingYear: 'ml-1 text-ink-3',
|
|
40
|
+
nav: 'flex gap-1',
|
|
41
|
+
navButton: [
|
|
42
|
+
'size-7 rounded-md inline-flex items-center justify-center cursor-pointer outline-none',
|
|
43
|
+
'text-ink-3 hovered:text-ink hovered:bg-surface-3 pressed:bg-surface-4',
|
|
44
|
+
'data-[focus-visible]:shadow-[var(--shadow-focus)]',
|
|
45
|
+
'data-[disabled]:opacity-40 data-[disabled]:cursor-default data-[disabled]:hover:bg-transparent',
|
|
46
|
+
],
|
|
47
|
+
grid: 'w-full border-separate border-spacing-1 table-fixed -m-1',
|
|
48
|
+
weekday:
|
|
49
|
+
'font-mono text-[11px] font-medium uppercase tracking-[0.08em] text-ink-3 pb-2 text-center',
|
|
50
|
+
cell: [
|
|
51
|
+
'relative aspect-square flex items-center justify-center cursor-pointer outline-none',
|
|
52
|
+
'font-sans text-[14px] font-medium rounded-sm border border-transparent',
|
|
53
|
+
'transition-[background-color,color,border-color] duration-150',
|
|
54
|
+
// Available (the default when neither unavailable nor disabled).
|
|
55
|
+
'bg-surface-2 text-ink',
|
|
56
|
+
'data-[hovered]:bg-surface-3 data-[hovered]:border-rule-strong',
|
|
57
|
+
'data-[focus-visible]:shadow-[var(--shadow-focus)]',
|
|
58
|
+
// Selected: the one ink fill on the page.
|
|
59
|
+
'data-[selected]:bg-ink data-[selected]:text-surface data-[selected]:border-ink',
|
|
60
|
+
'data-[selected]:data-[hovered]:bg-ink data-[selected]:data-[hovered]:border-ink',
|
|
61
|
+
// Unavailable / past: quiet text, no fill, no hover.
|
|
62
|
+
'data-[unavailable]:bg-transparent data-[unavailable]:text-ink-4 data-[unavailable]:font-normal data-[unavailable]:cursor-default',
|
|
63
|
+
'data-[unavailable]:data-[hovered]:bg-transparent data-[unavailable]:data-[hovered]:border-transparent',
|
|
64
|
+
'data-[disabled]:bg-transparent data-[disabled]:text-ink-4 data-[disabled]:font-normal data-[disabled]:cursor-default',
|
|
65
|
+
'data-[disabled]:data-[hovered]:bg-transparent data-[disabled]:data-[hovered]:border-transparent',
|
|
66
|
+
// Days of the neighbouring months are blank in a month-aligned grid.
|
|
67
|
+
'data-[outside-month]:invisible',
|
|
68
|
+
],
|
|
69
|
+
dot: 'absolute bottom-1.5 left-1/2 -translate-x-1/2 size-1 rounded-full bg-current',
|
|
70
|
+
cellSkeleton: 'absolute inset-0 rounded-sm',
|
|
71
|
+
},
|
|
72
|
+
variants: {
|
|
73
|
+
isLoading: {
|
|
74
|
+
true: { cell: 'bg-transparent cursor-default' },
|
|
75
|
+
false: {},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
defaultVariants: { isLoading: false },
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
export type AvailabilityCalendarVariants = VariantProps<typeof availabilityCalendarStyles>
|
|
82
|
+
|
|
83
|
+
export type AvailabilityCalendarProps = Omit<
|
|
84
|
+
AriaCalendarProps<CalendarDate>,
|
|
85
|
+
'children' | 'value' | 'onChange' | 'className' | 'style' | 'visibleDuration'
|
|
86
|
+
> & {
|
|
87
|
+
/** The selected day; `null` = none. */
|
|
88
|
+
value: CalendarDate | null
|
|
89
|
+
onChange: (date: CalendarDate) => void
|
|
90
|
+
/** Paint every cell as a skeleton and refuse selection. */
|
|
91
|
+
isLoading?: boolean
|
|
92
|
+
/** Accessible name of the grid, e.g. "Pick a day". */
|
|
93
|
+
'aria-label': string
|
|
94
|
+
/** Weekday header style; `short` = "Sun". */
|
|
95
|
+
weekdayStyle?: 'narrow' | 'short' | 'long'
|
|
96
|
+
className?: string
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function AvailabilityCalendar({
|
|
100
|
+
value,
|
|
101
|
+
onChange,
|
|
102
|
+
isLoading = false,
|
|
103
|
+
className,
|
|
104
|
+
isDateUnavailable,
|
|
105
|
+
weekdayStyle = 'short',
|
|
106
|
+
...props
|
|
107
|
+
}: AvailabilityCalendarProps) {
|
|
108
|
+
const styles = availabilityCalendarStyles({ isLoading })
|
|
109
|
+
const unavailable = isLoading ? () => true : isDateUnavailable
|
|
110
|
+
return (
|
|
111
|
+
<AriaCalendar
|
|
112
|
+
{...props}
|
|
113
|
+
value={value}
|
|
114
|
+
onChange={(next) => onChange(next)}
|
|
115
|
+
{...(unavailable ? { isDateUnavailable: unavailable } : {})}
|
|
116
|
+
visibleDuration={{ months: 1 }}
|
|
117
|
+
className={cn(styles.root(), className)}
|
|
118
|
+
>
|
|
119
|
+
<header className={styles.header()}>
|
|
120
|
+
<Heading className={styles.heading()}>
|
|
121
|
+
<MonthHeading yearClassName={styles.headingYear()} />
|
|
122
|
+
</Heading>
|
|
123
|
+
<div className={styles.nav()}>
|
|
124
|
+
<AriaButton slot="previous" aria-label="Previous month" className={styles.navButton()}>
|
|
125
|
+
<ChevronLeft className="size-4" aria-hidden="true" />
|
|
126
|
+
</AriaButton>
|
|
127
|
+
<AriaButton slot="next" aria-label="Next month" className={styles.navButton()}>
|
|
128
|
+
<ChevronRight className="size-4" aria-hidden="true" />
|
|
129
|
+
</AriaButton>
|
|
130
|
+
</div>
|
|
131
|
+
</header>
|
|
132
|
+
{/* RAC filters aria-busy off the grid; the wrapper carries it. */}
|
|
133
|
+
<div aria-busy={isLoading}>
|
|
134
|
+
<CalendarGrid weekdayStyle={weekdayStyle} className={styles.grid()}>
|
|
135
|
+
<CalendarGridHeader>
|
|
136
|
+
{(day) => <CalendarHeaderCell className={styles.weekday()}>{day}</CalendarHeaderCell>}
|
|
137
|
+
</CalendarGridHeader>
|
|
138
|
+
<CalendarGridBody>
|
|
139
|
+
{(date) => (
|
|
140
|
+
<CalendarCell date={date} className={styles.cell()}>
|
|
141
|
+
{({ formattedDate, isToday, isOutsideMonth }) =>
|
|
142
|
+
isLoading && !isOutsideMonth ? (
|
|
143
|
+
<Skeleton className={styles.cellSkeleton()} />
|
|
144
|
+
) : (
|
|
145
|
+
<>
|
|
146
|
+
{formattedDate}
|
|
147
|
+
{isToday && <span className={styles.dot()} aria-hidden="true" />}
|
|
148
|
+
</>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
</CalendarCell>
|
|
152
|
+
)}
|
|
153
|
+
</CalendarGridBody>
|
|
154
|
+
</CalendarGrid>
|
|
155
|
+
</div>
|
|
156
|
+
</AriaCalendar>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** "October 2026" as "October" + a muted year, read off the calendar state so
|
|
161
|
+
* it follows the visible month. RAC's Heading context supplies the plain
|
|
162
|
+
* title; our children override it, the accessible name stays on the grid. */
|
|
163
|
+
function MonthHeading({ yearClassName }: { yearClassName: string }) {
|
|
164
|
+
const state = useContext(CalendarStateContext)
|
|
165
|
+
if (!state) return null
|
|
166
|
+
const start: DateValue = state.visibleRange.start
|
|
167
|
+
const date = start.toDate(state.timeZone)
|
|
168
|
+
const month = new Intl.DateTimeFormat(undefined, {
|
|
169
|
+
month: 'long',
|
|
170
|
+
timeZone: state.timeZone,
|
|
171
|
+
}).format(date)
|
|
172
|
+
const year = new Intl.DateTimeFormat(undefined, {
|
|
173
|
+
year: 'numeric',
|
|
174
|
+
timeZone: state.timeZone,
|
|
175
|
+
}).format(date)
|
|
176
|
+
return (
|
|
177
|
+
<>
|
|
178
|
+
<span className="font-medium">{month}</span>
|
|
179
|
+
<span className={yearClassName}>{year}</span>
|
|
180
|
+
</>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
@@ -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
|
})
|