@seekalfred/primitives 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -4
- package/dist/alert/alert.d.ts +49 -4
- package/dist/alert/alert.js +66 -38
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert/index.d.ts +2 -2
- package/dist/alert/index.js +2 -2
- package/dist/alert-dialog/alert-dialog.d.ts +17 -4
- package/dist/alert-dialog/alert-dialog.js +26 -2
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/badge/badge.d.ts +1 -1
- package/dist/button/button.d.ts +2 -2
- package/dist/calendar/calendar.d.ts +1 -1
- package/dist/card/card.d.ts +22 -1
- package/dist/card/card.js +60 -7
- package/dist/card/card.js.map +1 -1
- package/dist/carousel/carousel.d.ts +21 -0
- package/dist/carousel/carousel.js +34 -8
- package/dist/carousel/carousel.js.map +1 -1
- package/dist/copy-button/copy-button.d.ts +18 -2
- package/dist/copy-button/copy-button.js +29 -8
- package/dist/copy-button/copy-button.js.map +1 -1
- package/dist/donut-chart/donut-chart.d.ts +22 -0
- package/dist/donut-chart/donut-chart.js +35 -18
- package/dist/donut-chart/donut-chart.js.map +1 -1
- package/dist/donut-chart/index.d.ts +2 -2
- package/dist/empty-state/empty-state.d.ts +21 -0
- package/dist/empty-state/empty-state.js +16 -4
- package/dist/empty-state/empty-state.js.map +1 -1
- package/dist/empty-state/index.d.ts +2 -2
- package/dist/empty-state/index.js +2 -2
- package/dist/filter-bar/filter-bar.d.ts +4 -1
- package/dist/filter-bar/filter-bar.js +53 -9
- package/dist/filter-bar/filter-bar.js.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.js +7 -7
- package/dist/input/input.js +4 -4
- package/dist/input/input.js.map +1 -1
- package/dist/lib/field-shell.js +10 -3
- package/dist/lib/field-shell.js.map +1 -1
- package/dist/lib/field-surface.js +22 -1
- package/dist/lib/field-surface.js.map +1 -1
- package/dist/lib/use-copy-feedback.js +17 -3
- package/dist/lib/use-copy-feedback.js.map +1 -1
- package/dist/line-chart/index.d.ts +2 -2
- package/dist/line-chart/line-chart.d.ts +42 -2
- package/dist/line-chart/line-chart.js +143 -14
- package/dist/line-chart/line-chart.js.map +1 -1
- package/dist/money-input/money-input.d.ts +22 -1
- package/dist/money-input/money-input.js +13 -7
- package/dist/money-input/money-input.js.map +1 -1
- package/dist/morph-select/morph-select.d.ts +19 -1
- package/dist/morph-select/morph-select.js +21 -2
- package/dist/morph-select/morph-select.js.map +1 -1
- package/dist/multi-select/index.d.ts +2 -2
- package/dist/multi-select/index.js +2 -2
- package/dist/multi-select/multi-select.d.ts +43 -2
- package/dist/multi-select/multi-select.js +93 -36
- package/dist/multi-select/multi-select.js.map +1 -1
- package/dist/password-field/password-field.d.ts +16 -0
- package/dist/password-field/password-field.js +37 -8
- package/dist/password-field/password-field.js.map +1 -1
- package/dist/popover/index.d.ts +2 -2
- package/dist/popover/index.js +2 -2
- package/dist/popover/popover.d.ts +39 -2
- package/dist/popover/popover.js +29 -2
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.d.ts +16 -2
- package/dist/progress/progress.js +15 -8
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/index.d.ts +2 -2
- package/dist/radio-group/index.js +2 -2
- package/dist/radio-group/radio-group.d.ts +33 -3
- package/dist/radio-group/radio-group.js +109 -9
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/scroll-area/scroll-area.js +4 -3
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/search-field/search-field.d.ts +22 -1
- package/dist/search-field/search-field.js +22 -7
- package/dist/search-field/search-field.js.map +1 -1
- package/dist/select/select.d.ts +1 -1
- package/dist/select/select.js +8 -6
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +21 -9
- package/dist/separator/separator.js +33 -11
- package/dist/separator/separator.js.map +1 -1
- package/dist/stat-card/index.d.ts +2 -2
- package/dist/stat-card/index.js +2 -2
- package/dist/stat-card/stat-card.d.ts +34 -1
- package/dist/stat-card/stat-card.js +51 -21
- package/dist/stat-card/stat-card.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/table/table.d.ts +1 -1
- package/dist/time-picker/time-picker.d.ts +36 -1
- package/dist/time-picker/time-picker.js +33 -5
- package/dist/time-picker/time-picker.js.map +1 -1
- package/dist/toast/toast.d.ts +2 -2
- package/dist/usage-meter/index.d.ts +2 -2
- package/dist/usage-meter/usage-meter.d.ts +30 -0
- package/dist/usage-meter/usage-meter.js +56 -38
- package/dist/usage-meter/usage-meter.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"carousel.js","names":[],"sources":["../../src/carousel/carousel.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n Children,\n forwardRef,\n isValidElement,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type MouseEvent,\n type PointerEvent,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { useIsomorphicLayoutEffect, useReducedMotion } from \"../lib/motion.js\";\n\n/**\n * Carousel: a row of slides people browse by swiping, a trackpad, dragging\n * with a mouse, the arrow keys, or the controls below it. Use it for a short,\n * ordered set of peers such as products or case studies, where the neighbours\n * stay in view as a hint that there is more.\n *\n * The slides sit on a native scroll-snap track, so touch and trackpad swipes\n * are the browser's own. The active slide is centred; its neighbours shrink and\n * dim with their distance, and each page indicator stretches into a pill as the\n * track nears its slide, all written straight from the scroll position without a\n * re-render. A mouse can drag the track, and a flick carries on to the next\n * slide. Arrow keys, Home and End move between slides from the track or the\n * indicators. `interval` adds a play control that rotates through the slides;\n * rotation pauses on hover, keyboard focus and touch, while the carousel is off\n * screen or the tab is hidden, and is unavailable under reduced motion (where\n * scrolling also jumps instead of gliding).\n */\nexport interface CarouselProps {\n /** Names the carousel for assistive technology, such as \"From the Harbour suites\". */\n label: string;\n /** One child per slide. */\n children: ReactNode;\n /** Controlled active slide. Pair with `onIndexChange`. */\n index?: number;\n /** @default 0 */\n defaultIndex?: number;\n onIndexChange?: (index: number) => void;\n /** Width of one slide as a CSS length; `cqw` is a share of the carousel's width. @default \"min(80cqw, 340px)\" */\n slideSize?: string;\n /** Names each slide and its indicator. @default (index, count) => `${index + 1} of ${count}` */\n slideLabel?: (index: number, count: number) => string;\n /** Milliseconds per slide. Adds a play control that rotates through the slides. */\n interval?: number;\n /** Starts rotating on mount. Off by default, so motion begins with the viewer. @default false */\n autoplay?: boolean;\n /** Names of the controls, for translation. */\n labels?: {\n /** @default \"Previous slide\" */\n previous?: string;\n /** @default \"Next slide\" */\n next?: string;\n /** @default \"Start slide rotation\" */\n play?: string;\n /** @default \"Pause slide rotation\" */\n pause?: string;\n /** Names the group of indicators. @default \"Choose a slide\" */\n slides?: string;\n };\n /** Extra classes for the carousel. Merged last. */\n className?: string;\n}\n\n/** Movement before a mouse press is read as a drag. */\nconst SLOP = 6;\n/** Release speed in px/s that always moves at least one slide. */\nconst FLICK = 360;\n/** Where a flick would come to rest: velocity (px/s) projected with a paging deceleration rate. */\nconst project = (velocity: number, rate = 0.995) => ((velocity / 1000) * rate) / (1 - rate);\nconst clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\nconst defaultSlideLabel = (index: number, count: number) => `${index + 1} of ${count}`;\n\nconst PlayGlyph = () => (\n <svg width={15} height={15} viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M4.5 2.8v10.4a.6.6 0 0 0 .9.5l8.2-5.2a.6.6 0 0 0 0-1L5.4 2.3a.6.6 0 0 0-.9.5Z\" />\n </svg>\n);\nconst PauseGlyph = () => (\n <svg width={15} height={15} viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"3.5\" y=\"2.5\" width=\"3.2\" height=\"11\" rx=\"1\" />\n <rect x=\"9.3\" y=\"2.5\" width=\"3.2\" height=\"11\" rx=\"1\" />\n </svg>\n);\nconst Chevron = ({ back }: { back?: boolean }) => (\n <svg width={18} height={18} viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.75} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d={back ? \"M10 3.5 5.5 8l4.5 4.5\" : \"M6 3.5 10.5 8 6 12.5\"} />\n </svg>\n);\n\ntype Drag = { pointer: number; startX: number; startLeft: number; from: number; locked: boolean; samples: [number, number][] };\n\nexport const Carousel = forwardRef<HTMLElement, CarouselProps>(function Carousel(\n {\n label,\n children,\n index: controlledIndex,\n defaultIndex = 0,\n onIndexChange,\n slideSize,\n slideLabel = defaultSlideLabel,\n interval,\n autoplay = false,\n labels,\n className,\n },\n ref,\n) {\n const slides = Children.toArray(children);\n const count = slides.length;\n const last = Math.max(0, count - 1);\n const id = useId();\n const reduced = useReducedMotion();\n const [ownIndex, setOwnIndex] = useState(() => clamp(defaultIndex, 0, last));\n const index = clamp(controlledIndex ?? ownIndex, 0, last);\n const [playing, setPlaying] = useState(autoplay);\n const [hovered, setHovered] = useState(false);\n const [touching, setTouching] = useState(false);\n const [focusPaused, setFocusPaused] = useState(false);\n const [inView, setInView] = useState(true);\n const [pageVisible, setPageVisible] = useState(true);\n const [announcement, setAnnouncement] = useState(\"\");\n\n const root = useRef<HTMLElement | null>(null);\n const rootRef = useMemo(() => mergeRefs(ref, root), [ref]);\n const viewport = useRef<HTMLDivElement>(null);\n const track = useRef<HTMLDivElement>(null);\n const play = useRef<HTMLButtonElement>(null);\n const tabs = useRef<(HTMLButtonElement | null)[]>([]);\n // The slide the carousel is on, as the component last decided it (state lags a render behind).\n const target = useRef(index);\n // Set while a scroll we started is in flight, so the in-between slides are not reported as the viewer's choice.\n const travelling = useRef<number | null>(null);\n const backstop = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const drag = useRef<Drag | null>(null);\n const swallowClick = useRef(false);\n\n const canRotate = Boolean(interval) && count > 1 && !reduced;\n const running = canRotate && playing && !hovered && !touching && !focusPaused && inView && pageVisible;\n\n const step = () => {\n const [first, second] = Array.from(track.current?.children ?? []) as HTMLElement[];\n return first && second ? second.offsetLeft - first.offsetLeft : (first?.offsetWidth ?? 0);\n };\n /** The scroll position that centres slide `i`. */\n const leftOf = (i: number) => {\n const node = track.current?.children[i] as HTMLElement | undefined;\n const box = viewport.current;\n return node && box ? node.offsetLeft - (box.clientWidth - node.offsetWidth) / 2 : 0;\n };\n\n /** Writes how far each slide and indicator is from the centre, straight from the scroll position. Returns the position in slides. */\n const paint = useCallback(() => {\n const box = viewport.current;\n const size = step();\n if (!box || !size) return target.current;\n const position = box.scrollLeft / size;\n Array.from(track.current?.children ?? []).forEach((slide, i) => {\n (slide as HTMLElement).style.setProperty(\"--d\", String(Math.min(1, Math.abs(position - i))));\n });\n tabs.current.forEach((tab, i) => {\n const dot = tab?.firstElementChild;\n if (dot instanceof HTMLElement) dot.style.setProperty(\"--e\", String(Math.max(0, 1 - Math.abs(clamp(position, 0, last) - i))));\n });\n return position;\n }, [last]);\n\n function scrollToIndex(i: number, smooth: boolean) {\n const box = viewport.current;\n if (!box) return;\n const left = leftOf(i);\n if (!smooth || reduced || Math.abs(box.scrollLeft - left) < 1) {\n travelling.current = null;\n box.scrollTo({ left, behavior: \"auto\" });\n paint();\n return;\n }\n travelling.current = i;\n clearTimeout(backstop.current);\n // A scroll that never reports arriving (a resize mid-flight) must not hold the carousel deaf to the viewer's own swipes.\n backstop.current = setTimeout(() => (travelling.current = null), 1000);\n box.scrollTo({ left, behavior: \"smooth\" });\n }\n\n function commit(next: number, announce: boolean) {\n target.current = next;\n if (controlledIndex === undefined) setOwnIndex(next);\n onIndexChange?.(next);\n if (announce) setAnnouncement(slideLabel(next, count));\n }\n\n function select(to: number, announce = true) {\n const next = clamp(to, 0, last);\n if (next !== target.current) commit(next, announce);\n scrollToIndex(next, true);\n }\n\n // The start slide is in place before first paint, so nothing glides on mount; later resizes keep the carousel on its slide.\n useIsomorphicLayoutEffect(() => {\n scrollToIndex(target.current, false);\n const box = viewport.current;\n if (!box || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => {\n if (!drag.current && travelling.current === null) scrollToIndex(target.current, false);\n });\n observer.observe(box);\n return () => observer.disconnect();\n }, []);\n\n // The viewer's own slides, inert unless active, are the only ones in the tab order or the accessibility tree's reach.\n useIsomorphicLayoutEffect(() => {\n Array.from(track.current?.children ?? []).forEach((slide, i) => slide.toggleAttribute(\"inert\", i !== index));\n }, [index, count]);\n\n // A controlled index that moves by itself brings the carousel along.\n useEffect(() => {\n if (target.current !== index) {\n target.current = index;\n scrollToIndex(index, true);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- only a changed index matters\n }, [index]);\n\n useEffect(() => () => clearTimeout(backstop.current), []);\n\n function onScroll() {\n const position = paint();\n const box = viewport.current;\n if (!box || drag.current?.locked) return;\n if (travelling.current !== null) {\n if (Math.abs(box.scrollLeft - leftOf(travelling.current)) < 2) travelling.current = null;\n return;\n }\n const next = clamp(Math.round(position), 0, last);\n if (next !== target.current) commit(next, true);\n }\n\n const advance = () => select(index >= last ? 0 : index + 1, false);\n\n useEffect(() => {\n const node = root.current;\n if (!interval || !node) return;\n const observer = typeof IntersectionObserver === \"undefined\" ? null : new IntersectionObserver(([entry]) => entry && setInView(entry.isIntersecting));\n observer?.observe(node);\n const onVisibility = () => setPageVisible(document.visibilityState !== \"hidden\");\n document.addEventListener(\"visibilitychange\", onVisibility);\n return () => {\n observer?.disconnect();\n document.removeEventListener(\"visibilitychange\", onVisibility);\n };\n }, [interval]);\n\n function onPointerDown(event: PointerEvent<HTMLDivElement>) {\n if (count < 2 || drag.current || event.pointerType !== \"mouse\" || event.button !== 0) return;\n swallowClick.current = false;\n drag.current = { pointer: event.pointerId, startX: event.clientX, startLeft: event.currentTarget.scrollLeft, from: target.current, locked: false, samples: [] };\n }\n\n function onPointerMove(event: PointerEvent<HTMLDivElement>) {\n const current = drag.current;\n if (!current || current.pointer !== event.pointerId) return;\n const box = event.currentTarget;\n if (!current.locked) {\n if (Math.abs(event.clientX - current.startX) < SLOP) return;\n // The slop is consumed, so the track starts moving from the pointer without a jump. Snapping is off while it is held.\n current.locked = true;\n current.startX = event.clientX;\n travelling.current = null;\n try {\n box.setPointerCapture(event.pointerId);\n } catch {\n // The pointer already ended; the drag simply stops following it.\n }\n box.dataset.dragging = \"\";\n }\n box.scrollLeft = current.startLeft - (event.clientX - current.startX);\n current.samples.push([event.timeStamp, box.scrollLeft]);\n if (current.samples.length > 16) current.samples.shift();\n }\n\n function finish(event: PointerEvent<HTMLDivElement>, cancelled: boolean) {\n const current = drag.current;\n if (!current || current.pointer !== event.pointerId) return;\n drag.current = null;\n if (!current.locked) return;\n const box = event.currentTarget;\n delete box.dataset.dragging;\n swallowClick.current = true;\n const recent = current.samples.filter(([time]) => event.timeStamp - time <= 90);\n const first = recent[0];\n const lastSample = recent[recent.length - 1];\n const velocity =\n !cancelled && first && lastSample && recent.length > 1 && event.timeStamp - lastSample[0] <= 50 && lastSample[0] > first[0]\n ? ((lastSample[1] - first[1]) / (lastSample[0] - first[0])) * 1000\n : 0;\n const size = step() || 1;\n // The landing slide comes from where the flick would carry the track, not from where the pointer let go.\n let to = Math.round((box.scrollLeft + project(velocity)) / size);\n if (Math.abs(velocity) > FLICK) {\n const direction = velocity > 0 ? 1 : -1;\n if ((to - current.from) * direction < 1) to = current.from + direction;\n }\n select(to);\n }\n\n /** A tap on a neighbour brings it to the centre; a click that ends a drag is not a tap. */\n function onClick(event: MouseEvent<HTMLDivElement>) {\n if (swallowClick.current) {\n swallowClick.current = false;\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n const hit = Array.from(track.current?.children ?? []).findIndex((slide) => {\n const rect = slide.getBoundingClientRect();\n return event.clientX >= rect.left && event.clientX <= rect.right;\n });\n if (hit >= 0 && hit !== target.current) select(hit);\n }\n\n function onKeyDown(event: KeyboardEvent<HTMLElement>) {\n if (event.altKey || event.ctrlKey || event.metaKey || count < 2) return;\n const node = event.target as HTMLElement;\n if (node.closest(\"input, textarea, select, [contenteditable]\")) return;\n const from = target.current;\n const to =\n event.key === \"ArrowLeft\" ? from - 1 : event.key === \"ArrowRight\" ? from + 1 : event.key === \"Home\" ? 0 : event.key === \"End\" ? last : null;\n if (to === null) return;\n event.preventDefault();\n const next = clamp(to, 0, last);\n select(next);\n if (node.getAttribute(\"role\") === \"tab\") tabs.current[next]?.focus({ preventScroll: true });\n else if (track.current?.contains(node)) viewport.current?.focus({ preventScroll: true });\n }\n\n const text = { previous: \"Previous slide\", next: \"Next slide\", play: \"Start slide rotation\", pause: \"Pause slide rotation\", slides: \"Choose a slide\", ...labels };\n\n return (\n <section\n ref={rootRef}\n data-slot=\"carousel\"\n aria-roledescription=\"carousel\"\n aria-label={label}\n className={cn(\"carousel\", className)}\n style={slideSize ? ({ \"--carousel-slide-size\": slideSize } as CSSProperties) : undefined}\n onKeyDown={onKeyDown}\n onFocus={(event) => setFocusPaused(event.target !== play.current && event.target.matches(\":focus-visible\"))}\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget)) setFocusPaused(false);\n }}\n >\n <div\n ref={viewport}\n role=\"region\"\n aria-label=\"Slides\"\n tabIndex={0}\n data-slot=\"carousel-viewport\"\n className=\"carousel-viewport\"\n onScroll={onScroll}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={(event) => finish(event, false)}\n onPointerCancel={(event) => finish(event, true)}\n onPointerEnter={(event) => event.pointerType === \"mouse\" && setHovered(true)}\n onPointerLeave={() => setHovered(false)}\n onTouchStart={() => setTouching(true)}\n onTouchEnd={() => setTouching(false)}\n onTouchCancel={() => setTouching(false)}\n onClickCapture={onClick}\n onDragStart={(event) => event.preventDefault()}\n >\n <div ref={track} className=\"carousel-track\">\n {slides.map((slide, i) => (\n <div\n key={isValidElement(slide) && slide.key != null ? slide.key : i}\n id={`${id}-slide-${i}`}\n role=\"tabpanel\"\n aria-roledescription=\"slide\"\n aria-label={slideLabel(i, count)}\n data-active={i === index || undefined}\n className=\"carousel-slide\"\n >\n {slide}\n </div>\n ))}\n </div>\n </div>\n {count > 1 ? (\n <div className=\"carousel-controls\">\n {canRotate ? (\n <button\n ref={play}\n type=\"button\"\n aria-label={playing ? text.pause : text.play}\n className=\"carousel-control\"\n onClick={() => setPlaying(!playing)}\n >\n {playing ? <PauseGlyph /> : <PlayGlyph />}\n </button>\n ) : null}\n <div role=\"tablist\" aria-label={text.slides} className=\"carousel-tabs\">\n {slides.map((_, i) => (\n <button\n key={i}\n ref={(node) => {\n tabs.current[i] = node;\n }}\n type=\"button\"\n role=\"tab\"\n aria-selected={i === index}\n aria-controls={`${id}-slide-${i}`}\n aria-label={slideLabel(i, count)}\n tabIndex={i === index ? 0 : -1}\n className=\"carousel-tab\"\n onClick={() => select(i)}\n >\n <span aria-hidden=\"true\" className=\"carousel-dot\">\n <span className=\"carousel-dot-fill\" />\n {canRotate && playing && i === index ? (\n <span\n key={index}\n className=\"carousel-drain\"\n style={{ animationDuration: `${interval}ms`, animationPlayState: running ? \"running\" : \"paused\" }}\n onAnimationEnd={advance}\n />\n ) : null}\n </span>\n </button>\n ))}\n </div>\n <div className=\"carousel-arrows\">\n <button\n type=\"button\"\n aria-label={text.previous}\n aria-disabled={index === 0}\n className=\"carousel-control\"\n onClick={() => target.current > 0 && select(target.current - 1)}\n >\n <Chevron back />\n </button>\n <button\n type=\"button\"\n aria-label={text.next}\n aria-disabled={index === last}\n className=\"carousel-control\"\n onClick={() => target.current < last && select(target.current + 1)}\n >\n <Chevron />\n </button>\n </div>\n </div>\n ) : null}\n <span className=\"sr-only\" aria-live=\"polite\" aria-atomic=\"true\">\n {announcement}\n </span>\n </section>\n );\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"carousel.js","names":[],"sources":["../../src/carousel/carousel.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n Children,\n forwardRef,\n isValidElement,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type MouseEvent,\n type PointerEvent,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { useIsomorphicLayoutEffect, useReducedMotion } from \"../lib/motion.js\";\n\n/**\n * Carousel: a row of slides people browse by swiping, a trackpad, dragging\n * with a mouse, the arrow keys, or the controls below it. Use it for a short,\n * ordered set of peers such as products or case studies, where the neighbours\n * stay in view as a hint that there is more.\n *\n * The slides sit on a native scroll-snap track, so touch and trackpad swipes\n * are the browser's own. The active slide is centred; its neighbours shrink and\n * dim with their distance, and each page indicator stretches into a pill as the\n * track nears its slide, all written straight from the scroll position without a\n * re-render. A mouse can drag the track, and a flick carries on to the next\n * slide. Arrow keys, Home and End move between slides from the track or the\n * indicators. `interval` adds a play control that rotates through the slides;\n * rotation pauses on hover, keyboard focus and touch, while the carousel is off\n * screen or the tab is hidden, and is unavailable under reduced motion (where\n * scrolling also jumps instead of gliding).\n *\n * `loop` wraps the ends: next on the last slide goes to the first, previous on\n * the first to the last, from the controls and the arrow keys alike (a swipe\n * or drag still stops at the end of the track). `peek={false}` shows one slide\n * at a time across the full width, with no neighbours in view, for a viewer\n * such as a single creative asset.\n */\nexport interface CarouselProps {\n /** Names the carousel for assistive technology, such as \"From the Harbour suites\". */\n label: string;\n /** One child per slide. */\n children: ReactNode;\n /** Controlled active slide. Pair with `onIndexChange`. */\n index?: number;\n /** @default 0 */\n defaultIndex?: number;\n onIndexChange?: (index: number) => void;\n /** Width of one slide as a CSS length; `cqw` is a share of the carousel's width. @default \"min(80cqw, 340px)\" */\n slideSize?: string;\n /** Names each slide and its indicator. @default (index, count) => `${index + 1} of ${count}` */\n slideLabel?: (index: number, count: number) => string;\n /** Milliseconds per slide. Adds a play control that rotates through the slides. */\n interval?: number;\n /** Starts rotating on mount. Off by default, so motion begins with the viewer. @default false */\n autoplay?: boolean;\n /**\n * Wraps around at the ends: next on the last slide goes to the first and\n * previous on the first goes to the last, from the controls and the arrow\n * keys. The controls are then never disabled. Home and End still go to the\n * first and last slide; a swipe or drag stops at the end of the track.\n * @default false\n */\n loop?: boolean;\n /**\n * Neighbouring slides peek in at the sides. `false` shows one slide at a\n * time across the full width (`slideSize` is ignored), with the controls\n * spanning it too.\n * @default true\n */\n peek?: boolean;\n /** Names of the controls, for translation. */\n labels?: {\n /** @default \"Previous slide\" */\n previous?: string;\n /** @default \"Next slide\" */\n next?: string;\n /** @default \"Start slide rotation\" */\n play?: string;\n /** @default \"Pause slide rotation\" */\n pause?: string;\n /** Names the group of indicators. @default \"Choose a slide\" */\n slides?: string;\n };\n /** Extra classes for the carousel. Merged last. */\n className?: string;\n}\n\n/** Movement before a mouse press is read as a drag. */\nconst SLOP = 6;\n/** Release speed in px/s that always moves at least one slide. */\nconst FLICK = 360;\n/**\n * How long a glide may take before the carousel stops waiting for it: one slide,\n * plus more per extra slide (a `loop` wrap crosses them all), capped.\n */\nconst BACKSTOP = { base: 1000, perSlide: 250, max: 3000 };\n/** Where a flick would come to rest: velocity (px/s) projected with a paging deceleration rate. */\nconst project = (velocity: number, rate = 0.995) => ((velocity / 1000) * rate) / (1 - rate);\nconst clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\nconst defaultSlideLabel = (index: number, count: number) => `${index + 1} of ${count}`;\n\nconst PlayGlyph = () => (\n <svg width={15} height={15} viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M4.5 2.8v10.4a.6.6 0 0 0 .9.5l8.2-5.2a.6.6 0 0 0 0-1L5.4 2.3a.6.6 0 0 0-.9.5Z\" />\n </svg>\n);\nconst PauseGlyph = () => (\n <svg width={15} height={15} viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"3.5\" y=\"2.5\" width=\"3.2\" height=\"11\" rx=\"1\" />\n <rect x=\"9.3\" y=\"2.5\" width=\"3.2\" height=\"11\" rx=\"1\" />\n </svg>\n);\nconst Chevron = ({ back }: { back?: boolean }) => (\n <svg width={18} height={18} viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.75} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d={back ? \"M10 3.5 5.5 8l4.5 4.5\" : \"M6 3.5 10.5 8 6 12.5\"} />\n </svg>\n);\n\ntype Drag = { pointer: number; startX: number; startLeft: number; from: number; locked: boolean; samples: [number, number][] };\n\nexport const Carousel = forwardRef<HTMLElement, CarouselProps>(function Carousel(\n {\n label,\n children,\n index: controlledIndex,\n defaultIndex = 0,\n onIndexChange,\n slideSize,\n slideLabel = defaultSlideLabel,\n interval,\n autoplay = false,\n loop = false,\n peek = true,\n labels,\n className,\n },\n ref,\n) {\n const slides = Children.toArray(children);\n const count = slides.length;\n const last = Math.max(0, count - 1);\n const id = useId();\n const reduced = useReducedMotion();\n const [ownIndex, setOwnIndex] = useState(() => clamp(defaultIndex, 0, last));\n const index = clamp(controlledIndex ?? ownIndex, 0, last);\n const [playing, setPlaying] = useState(autoplay);\n const [hovered, setHovered] = useState(false);\n const [touching, setTouching] = useState(false);\n const [focusPaused, setFocusPaused] = useState(false);\n const [inView, setInView] = useState(true);\n const [pageVisible, setPageVisible] = useState(true);\n const [announcement, setAnnouncement] = useState(\"\");\n\n const root = useRef<HTMLElement | null>(null);\n const rootRef = useMemo(() => mergeRefs(ref, root), [ref]);\n const viewport = useRef<HTMLDivElement>(null);\n const track = useRef<HTMLDivElement>(null);\n const play = useRef<HTMLButtonElement>(null);\n const tabs = useRef<(HTMLButtonElement | null)[]>([]);\n // The slide the carousel is on, as the component last decided it (state lags a render behind).\n const target = useRef(index);\n // Set while a scroll we started is in flight, so the in-between slides are not reported as the viewer's choice.\n const travelling = useRef<number | null>(null);\n const backstop = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const drag = useRef<Drag | null>(null);\n const swallowClick = useRef(false);\n\n const canRotate = Boolean(interval) && count > 1 && !reduced;\n const running = canRotate && playing && !hovered && !touching && !focusPaused && inView && pageVisible;\n\n const step = () => {\n const [first, second] = Array.from(track.current?.children ?? []) as HTMLElement[];\n return first && second ? second.offsetLeft - first.offsetLeft : (first?.offsetWidth ?? 0);\n };\n /** The scroll position that centres slide `i`. */\n const leftOf = (i: number) => {\n const node = track.current?.children[i] as HTMLElement | undefined;\n const box = viewport.current;\n return node && box ? node.offsetLeft - (box.clientWidth - node.offsetWidth) / 2 : 0;\n };\n\n /** Writes how far each slide and indicator is from the centre, straight from the scroll position. Returns the position in slides. */\n const paint = useCallback(() => {\n const box = viewport.current;\n const size = step();\n if (!box || !size) return target.current;\n const position = box.scrollLeft / size;\n Array.from(track.current?.children ?? []).forEach((slide, i) => {\n (slide as HTMLElement).style.setProperty(\"--d\", String(Math.min(1, Math.abs(position - i))));\n });\n tabs.current.forEach((tab, i) => {\n const dot = tab?.firstElementChild;\n if (dot instanceof HTMLElement) dot.style.setProperty(\"--e\", String(Math.max(0, 1 - Math.abs(clamp(position, 0, last) - i))));\n });\n return position;\n }, [last]);\n\n function scrollToIndex(i: number, smooth: boolean) {\n const box = viewport.current;\n if (!box) return;\n const left = leftOf(i);\n if (!smooth || reduced || Math.abs(box.scrollLeft - left) < 1) {\n travelling.current = null;\n box.scrollTo({ left, behavior: \"auto\" });\n paint();\n return;\n }\n travelling.current = i;\n clearTimeout(backstop.current);\n // A scroll that never reports arriving (a resize mid-flight) must not hold the carousel deaf to the viewer's own swipes.\n // The glide normally ends on `scrollend`; this timer is the fallback, scaled to the\n // distance so a long wrap is not cut short.\n const distance = Math.abs(box.scrollLeft - left) / (step() || 1);\n const wait = Math.min(\n BACKSTOP.max,\n BACKSTOP.base + BACKSTOP.perSlide * Math.max(0, distance - 1),\n );\n backstop.current = setTimeout(() => (travelling.current = null), wait);\n box.scrollTo({ left, behavior: \"smooth\" });\n }\n\n function commit(next: number, announce: boolean) {\n target.current = next;\n if (controlledIndex === undefined) setOwnIndex(next);\n onIndexChange?.(next);\n if (announce) setAnnouncement(slideLabel(next, count));\n }\n\n function select(to: number, announce = true) {\n const next = clamp(to, 0, last);\n if (next !== target.current) commit(next, announce);\n scrollToIndex(next, true);\n }\n\n // The start slide is in place before first paint, so nothing glides on mount; later resizes keep the carousel on its slide.\n useIsomorphicLayoutEffect(() => {\n scrollToIndex(target.current, false);\n const box = viewport.current;\n if (!box || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => {\n if (!drag.current && travelling.current === null) scrollToIndex(target.current, false);\n });\n observer.observe(box);\n return () => observer.disconnect();\n }, []);\n\n // The viewer's own slides, inert unless active, are the only ones in the tab order or the accessibility tree's reach.\n useIsomorphicLayoutEffect(() => {\n Array.from(track.current?.children ?? []).forEach((slide, i) => slide.toggleAttribute(\"inert\", i !== index));\n }, [index, count]);\n\n // A controlled index that moves by itself brings the carousel along.\n useEffect(() => {\n if (target.current !== index) {\n target.current = index;\n scrollToIndex(index, true);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- only a changed index matters\n }, [index]);\n\n useEffect(() => () => clearTimeout(backstop.current), []);\n\n // A glide is over when the browser says the scroll has ended, however long it took.\n useEffect(() => {\n const box = viewport.current;\n if (!box) return;\n const onScrollEnd = () => {\n if (travelling.current === null) return;\n travelling.current = null;\n clearTimeout(backstop.current);\n };\n box.addEventListener(\"scrollend\", onScrollEnd);\n return () => box.removeEventListener(\"scrollend\", onScrollEnd);\n }, []);\n\n function onScroll() {\n const position = paint();\n const box = viewport.current;\n if (!box || drag.current?.locked) return;\n if (travelling.current !== null) {\n if (Math.abs(box.scrollLeft - leftOf(travelling.current)) < 2) travelling.current = null;\n return;\n }\n const next = clamp(Math.round(position), 0, last);\n if (next !== target.current) commit(next, true);\n }\n\n const advance = () => select(index >= last ? 0 : index + 1, false);\n /** The slide `by` steps from `from`: clamped at the ends, or around them with `loop`. */\n const stepFrom = (from: number, by: number) => (loop ? (from + by + count) % count : clamp(from + by, 0, last));\n\n useEffect(() => {\n const node = root.current;\n if (!interval || !node) return;\n const observer = typeof IntersectionObserver === \"undefined\" ? null : new IntersectionObserver(([entry]) => entry && setInView(entry.isIntersecting));\n observer?.observe(node);\n const onVisibility = () => setPageVisible(document.visibilityState !== \"hidden\");\n document.addEventListener(\"visibilitychange\", onVisibility);\n return () => {\n observer?.disconnect();\n document.removeEventListener(\"visibilitychange\", onVisibility);\n };\n }, [interval]);\n\n function onPointerDown(event: PointerEvent<HTMLDivElement>) {\n if (count < 2 || drag.current || event.pointerType !== \"mouse\" || event.button !== 0) return;\n swallowClick.current = false;\n drag.current = { pointer: event.pointerId, startX: event.clientX, startLeft: event.currentTarget.scrollLeft, from: target.current, locked: false, samples: [] };\n }\n\n function onPointerMove(event: PointerEvent<HTMLDivElement>) {\n const current = drag.current;\n if (!current || current.pointer !== event.pointerId) return;\n const box = event.currentTarget;\n if (!current.locked) {\n if (Math.abs(event.clientX - current.startX) < SLOP) return;\n // The slop is consumed, so the track starts moving from the pointer without a jump. Snapping is off while it is held.\n current.locked = true;\n current.startX = event.clientX;\n travelling.current = null;\n try {\n box.setPointerCapture(event.pointerId);\n } catch {\n // The pointer already ended; the drag simply stops following it.\n }\n box.dataset.dragging = \"\";\n }\n box.scrollLeft = current.startLeft - (event.clientX - current.startX);\n current.samples.push([event.timeStamp, box.scrollLeft]);\n if (current.samples.length > 16) current.samples.shift();\n }\n\n function finish(event: PointerEvent<HTMLDivElement>, cancelled: boolean) {\n const current = drag.current;\n if (!current || current.pointer !== event.pointerId) return;\n drag.current = null;\n if (!current.locked) return;\n const box = event.currentTarget;\n delete box.dataset.dragging;\n swallowClick.current = true;\n const recent = current.samples.filter(([time]) => event.timeStamp - time <= 90);\n const first = recent[0];\n const lastSample = recent[recent.length - 1];\n const velocity =\n !cancelled && first && lastSample && recent.length > 1 && event.timeStamp - lastSample[0] <= 50 && lastSample[0] > first[0]\n ? ((lastSample[1] - first[1]) / (lastSample[0] - first[0])) * 1000\n : 0;\n const size = step() || 1;\n // The landing slide comes from where the flick would carry the track, not from where the pointer let go.\n let to = Math.round((box.scrollLeft + project(velocity)) / size);\n if (Math.abs(velocity) > FLICK) {\n const direction = velocity > 0 ? 1 : -1;\n if ((to - current.from) * direction < 1) to = current.from + direction;\n }\n select(to);\n }\n\n /** A tap on a neighbour brings it to the centre; a click that ends a drag is not a tap. */\n function onClick(event: MouseEvent<HTMLDivElement>) {\n if (swallowClick.current) {\n swallowClick.current = false;\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n const hit = Array.from(track.current?.children ?? []).findIndex((slide) => {\n const rect = slide.getBoundingClientRect();\n return event.clientX >= rect.left && event.clientX <= rect.right;\n });\n if (hit >= 0 && hit !== target.current) select(hit);\n }\n\n function onKeyDown(event: KeyboardEvent<HTMLElement>) {\n if (event.altKey || event.ctrlKey || event.metaKey || count < 2) return;\n const node = event.target as HTMLElement;\n if (node.closest(\"input, textarea, select, [contenteditable]\")) return;\n const from = target.current;\n const to =\n event.key === \"ArrowLeft\" ? stepFrom(from, -1) : event.key === \"ArrowRight\" ? stepFrom(from, 1) : event.key === \"Home\" ? 0 : event.key === \"End\" ? last : null;\n if (to === null) return;\n event.preventDefault();\n const next = clamp(to, 0, last);\n select(next);\n if (node.getAttribute(\"role\") === \"tab\") tabs.current[next]?.focus({ preventScroll: true });\n else if (track.current?.contains(node)) viewport.current?.focus({ preventScroll: true });\n }\n\n const text = { previous: \"Previous slide\", next: \"Next slide\", play: \"Start slide rotation\", pause: \"Pause slide rotation\", slides: \"Choose a slide\", ...labels };\n\n return (\n <section\n ref={rootRef}\n data-slot=\"carousel\"\n aria-roledescription=\"carousel\"\n aria-label={label}\n data-loop={loop || undefined}\n data-peek={peek ? undefined : \"false\"}\n className={cn(\"carousel\", className)}\n style={slideSize && peek ? ({ \"--carousel-slide-size\": slideSize } as CSSProperties) : undefined}\n onKeyDown={onKeyDown}\n onFocus={(event) => setFocusPaused(event.target !== play.current && event.target.matches(\":focus-visible\"))}\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget)) setFocusPaused(false);\n }}\n >\n <div\n ref={viewport}\n role=\"region\"\n aria-label=\"Slides\"\n tabIndex={0}\n data-slot=\"carousel-viewport\"\n className=\"carousel-viewport\"\n onScroll={onScroll}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={(event) => finish(event, false)}\n onPointerCancel={(event) => finish(event, true)}\n onPointerEnter={(event) => event.pointerType === \"mouse\" && setHovered(true)}\n onPointerLeave={() => setHovered(false)}\n onTouchStart={() => setTouching(true)}\n onTouchEnd={() => setTouching(false)}\n onTouchCancel={() => setTouching(false)}\n onClickCapture={onClick}\n onDragStart={(event) => event.preventDefault()}\n >\n <div ref={track} className=\"carousel-track\">\n {slides.map((slide, i) => (\n <div\n key={isValidElement(slide) && slide.key != null ? slide.key : i}\n id={`${id}-slide-${i}`}\n role=\"tabpanel\"\n aria-roledescription=\"slide\"\n aria-label={slideLabel(i, count)}\n data-active={i === index || undefined}\n className=\"carousel-slide\"\n >\n {slide}\n </div>\n ))}\n </div>\n </div>\n {count > 1 ? (\n <div className=\"carousel-controls\">\n {canRotate ? (\n <button\n ref={play}\n type=\"button\"\n aria-label={playing ? text.pause : text.play}\n className=\"carousel-control\"\n onClick={() => setPlaying(!playing)}\n >\n {playing ? <PauseGlyph /> : <PlayGlyph />}\n </button>\n ) : null}\n <div role=\"tablist\" aria-label={text.slides} className=\"carousel-tabs\">\n {slides.map((_, i) => (\n <button\n key={i}\n ref={(node) => {\n tabs.current[i] = node;\n }}\n type=\"button\"\n role=\"tab\"\n aria-selected={i === index}\n aria-controls={`${id}-slide-${i}`}\n aria-label={slideLabel(i, count)}\n tabIndex={i === index ? 0 : -1}\n className=\"carousel-tab\"\n onClick={() => select(i)}\n >\n <span aria-hidden=\"true\" className=\"carousel-dot\">\n <span className=\"carousel-dot-fill\" />\n {canRotate && playing && i === index ? (\n <span\n key={index}\n className=\"carousel-drain\"\n style={{ animationDuration: `${interval}ms`, animationPlayState: running ? \"running\" : \"paused\" }}\n onAnimationEnd={advance}\n />\n ) : null}\n </span>\n </button>\n ))}\n </div>\n <div className=\"carousel-arrows\">\n <button\n type=\"button\"\n aria-label={text.previous}\n aria-disabled={!loop && index === 0}\n className=\"carousel-control\"\n onClick={() => (loop || target.current > 0) && select(stepFrom(target.current, -1))}\n >\n <Chevron back />\n </button>\n <button\n type=\"button\"\n aria-label={text.next}\n aria-disabled={!loop && index === last}\n className=\"carousel-control\"\n onClick={() => (loop || target.current < last) && select(stepFrom(target.current, 1))}\n >\n <Chevron />\n </button>\n </div>\n </div>\n ) : null}\n <span className=\"sr-only\" aria-live=\"polite\" aria-atomic=\"true\">\n {announcement}\n </span>\n </section>\n );\n});\n"],"mappings":";;;;;;;;AAiGA,MAAM,OAAO;;AAEb,MAAM,QAAQ;;;;;AAKd,MAAM,WAAW;CAAE,MAAM;CAAM,UAAU;CAAK,KAAK;AAAK;;AAExD,MAAM,WAAW,UAAkB,OAAO,SAAY,WAAW,MAAQ,QAAS,IAAI;AACtF,MAAM,SAAS,OAAe,KAAa,QAAgB,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC7F,MAAM,qBAAqB,OAAe,UAAkB,GAAG,QAAQ,EAAE,MAAM;AAE/E,MAAM,kBACJ,oBAAC,OAAD;CAAK,OAAO;CAAI,QAAQ;CAAI,SAAQ;CAAY,MAAK;CAAe,eAAY;CAC9E,UAAA,oBAAC,QAAD,EAAM,GAAE,gFAAiF,CAAA;AACtF,CAAA;AAEP,MAAM,mBACJ,qBAAC,OAAD;CAAK,OAAO;CAAI,QAAQ;CAAI,SAAQ;CAAY,MAAK;CAAe,eAAY;CAAhF,UAAA,CACE,oBAAC,QAAD;EAAM,GAAE;EAAM,GAAE;EAAM,OAAM;EAAM,QAAO;EAAK,IAAG;CAAK,CAAA,GACtD,oBAAC,QAAD;EAAM,GAAE;EAAM,GAAE;EAAM,OAAM;EAAM,QAAO;EAAK,IAAG;CAAK,CAAA,CACnD;;AAEP,MAAM,WAAW,EAAE,WACjB,oBAAC,OAAD;CAAK,OAAO;CAAI,QAAQ;CAAI,SAAQ;CAAY,MAAK;CAAO,QAAO;CAAe,aAAa;CAAM,eAAc;CAAQ,gBAAe;CAAQ,eAAY;CAC5J,UAAA,oBAAC,QAAD,EAAM,GAAG,OAAO,0BAA0B,uBAAyB,CAAA;AAChE,CAAA;AAKP,MAAa,WAAW,WAAuC,SAAS,SACtE,EACE,OACA,UACA,OAAO,iBACP,eAAe,GACf,eACA,WACA,aAAa,mBACb,UACA,WAAW,OACX,OAAO,OACP,OAAO,MACP,QACA,aAEF,KACA;CACA,MAAM,SAAS,SAAS,QAAQ,QAAQ;CACxC,MAAM,QAAQ,OAAO;CACrB,MAAM,OAAO,KAAK,IAAI,GAAG,QAAQ,CAAC;CAClC,MAAM,KAAK,MAAM;CACjB,MAAM,UAAU,iBAAiB;CACjC,MAAM,CAAC,UAAU,eAAe,eAAe,MAAM,cAAc,GAAG,IAAI,CAAC;CAC3E,MAAM,QAAQ,MAAM,mBAAmB,UAAU,GAAG,IAAI;CACxD,MAAM,CAAC,SAAS,cAAc,SAAS,QAAQ;CAC/C,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAC9C,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,MAAM,CAAC,QAAQ,aAAa,SAAS,IAAI;CACzC,MAAM,CAAC,aAAa,kBAAkB,SAAS,IAAI;CACnD,MAAM,CAAC,cAAc,mBAAmB,SAAS,EAAE;CAEnD,MAAM,OAAO,OAA2B,IAAI;CAC5C,MAAM,UAAU,cAAc,UAAU,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC;CACzD,MAAM,WAAW,OAAuB,IAAI;CAC5C,MAAM,QAAQ,OAAuB,IAAI;CACzC,MAAM,OAAO,OAA0B,IAAI;CAC3C,MAAM,OAAO,OAAqC,CAAC,CAAC;CAEpD,MAAM,SAAS,OAAO,KAAK;CAE3B,MAAM,aAAa,OAAsB,IAAI;CAC7C,MAAM,WAAW,OAAkD,KAAA,CAAS;CAC5E,MAAM,OAAO,OAAoB,IAAI;CACrC,MAAM,eAAe,OAAO,KAAK;CAEjC,MAAM,YAAY,QAAQ,QAAQ,KAAK,QAAQ,KAAK,CAAC;CACrD,MAAM,UAAU,aAAa,WAAW,CAAC,WAAW,CAAC,YAAY,CAAC,eAAe,UAAU;CAE3F,MAAM,aAAa;EACjB,MAAM,CAAC,OAAO,UAAU,MAAM,KAAK,MAAM,SAAS,YAAY,CAAC,CAAC;EAChE,OAAO,SAAS,SAAS,OAAO,aAAa,MAAM,aAAc,OAAO,eAAe;CACzF;;CAEA,MAAM,UAAU,MAAc;EAC5B,MAAM,OAAO,MAAM,SAAS,SAAS;EACrC,MAAM,MAAM,SAAS;EACrB,OAAO,QAAQ,MAAM,KAAK,cAAc,IAAI,cAAc,KAAK,eAAe,IAAI;CACpF;;CAGA,MAAM,QAAQ,kBAAkB;EAC9B,MAAM,MAAM,SAAS;EACrB,MAAM,OAAO,KAAK;EAClB,IAAI,CAAC,OAAO,CAAC,MAAM,OAAO,OAAO;EACjC,MAAM,WAAW,IAAI,aAAa;EAClC,MAAM,KAAK,MAAM,SAAS,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,OAAO,MAAM;GAC9D,MAAuB,MAAM,YAAY,OAAO,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC;EAC7F,CAAC;EACD,KAAK,QAAQ,SAAS,KAAK,MAAM;GAC/B,MAAM,MAAM,KAAK;GACjB,IAAI,eAAe,aAAa,IAAI,MAAM,YAAY,OAAO,OAAO,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;EAC9H,CAAC;EACD,OAAO;CACT,GAAG,CAAC,IAAI,CAAC;CAET,SAAS,cAAc,GAAW,QAAiB;EACjD,MAAM,MAAM,SAAS;EACrB,IAAI,CAAC,KAAK;EACV,MAAM,OAAO,OAAO,CAAC;EACrB,IAAI,CAAC,UAAU,WAAW,KAAK,IAAI,IAAI,aAAa,IAAI,IAAI,GAAG;GAC7D,WAAW,UAAU;GACrB,IAAI,SAAS;IAAE;IAAM,UAAU;GAAO,CAAC;GACvC,MAAM;GACN;EACF;EACA,WAAW,UAAU;EACrB,aAAa,SAAS,OAAO;EAI7B,MAAM,WAAW,KAAK,IAAI,IAAI,aAAa,IAAI,KAAK,KAAK,KAAK;EAC9D,MAAM,OAAO,KAAK,IAChB,SAAS,KACT,SAAS,OAAO,SAAS,WAAW,KAAK,IAAI,GAAG,WAAW,CAAC,CAC9D;EACA,SAAS,UAAU,iBAAkB,WAAW,UAAU,MAAO,IAAI;EACrE,IAAI,SAAS;GAAE;GAAM,UAAU;EAAS,CAAC;CAC3C;CAEA,SAAS,OAAO,MAAc,UAAmB;EAC/C,OAAO,UAAU;EACjB,IAAI,oBAAoB,KAAA,GAAW,YAAY,IAAI;EACnD,gBAAgB,IAAI;EACpB,IAAI,UAAU,gBAAgB,WAAW,MAAM,KAAK,CAAC;CACvD;CAEA,SAAS,OAAO,IAAY,WAAW,MAAM;EAC3C,MAAM,OAAO,MAAM,IAAI,GAAG,IAAI;EAC9B,IAAI,SAAS,OAAO,SAAS,OAAO,MAAM,QAAQ;EAClD,cAAc,MAAM,IAAI;CAC1B;CAGA,gCAAgC;EAC9B,cAAc,OAAO,SAAS,KAAK;EACnC,MAAM,MAAM,SAAS;EACrB,IAAI,CAAC,OAAO,OAAO,mBAAmB,aAAa;EACnD,MAAM,WAAW,IAAI,qBAAqB;GACxC,IAAI,CAAC,KAAK,WAAW,WAAW,YAAY,MAAM,cAAc,OAAO,SAAS,KAAK;EACvF,CAAC;EACD,SAAS,QAAQ,GAAG;EACpB,aAAa,SAAS,WAAW;CACnC,GAAG,CAAC,CAAC;CAGL,gCAAgC;EAC9B,MAAM,KAAK,MAAM,SAAS,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,OAAO,MAAM,MAAM,gBAAgB,SAAS,MAAM,KAAK,CAAC;CAC7G,GAAG,CAAC,OAAO,KAAK,CAAC;CAGjB,gBAAgB;EACd,IAAI,OAAO,YAAY,OAAO;GAC5B,OAAO,UAAU;GACjB,cAAc,OAAO,IAAI;EAC3B;CAEF,GAAG,CAAC,KAAK,CAAC;CAEV,sBAAsB,aAAa,SAAS,OAAO,GAAG,CAAC,CAAC;CAGxD,gBAAgB;EACd,MAAM,MAAM,SAAS;EACrB,IAAI,CAAC,KAAK;EACV,MAAM,oBAAoB;GACxB,IAAI,WAAW,YAAY,MAAM;GACjC,WAAW,UAAU;GACrB,aAAa,SAAS,OAAO;EAC/B;EACA,IAAI,iBAAiB,aAAa,WAAW;EAC7C,aAAa,IAAI,oBAAoB,aAAa,WAAW;CAC/D,GAAG,CAAC,CAAC;CAEL,SAAS,WAAW;EAClB,MAAM,WAAW,MAAM;EACvB,MAAM,MAAM,SAAS;EACrB,IAAI,CAAC,OAAO,KAAK,SAAS,QAAQ;EAClC,IAAI,WAAW,YAAY,MAAM;GAC/B,IAAI,KAAK,IAAI,IAAI,aAAa,OAAO,WAAW,OAAO,CAAC,IAAI,GAAG,WAAW,UAAU;GACpF;EACF;EACA,MAAM,OAAO,MAAM,KAAK,MAAM,QAAQ,GAAG,GAAG,IAAI;EAChD,IAAI,SAAS,OAAO,SAAS,OAAO,MAAM,IAAI;CAChD;CAEA,MAAM,gBAAgB,OAAO,SAAS,OAAO,IAAI,QAAQ,GAAG,KAAK;;CAEjE,MAAM,YAAY,MAAc,OAAgB,QAAQ,OAAO,KAAK,SAAS,QAAQ,MAAM,OAAO,IAAI,GAAG,IAAI;CAE7G,gBAAgB;EACd,MAAM,OAAO,KAAK;EAClB,IAAI,CAAC,YAAY,CAAC,MAAM;EACxB,MAAM,WAAW,OAAO,yBAAyB,cAAc,OAAO,IAAI,sBAAsB,CAAC,WAAW,SAAS,UAAU,MAAM,cAAc,CAAC;EACpJ,UAAU,QAAQ,IAAI;EACtB,MAAM,qBAAqB,eAAe,SAAS,oBAAoB,QAAQ;EAC/E,SAAS,iBAAiB,oBAAoB,YAAY;EAC1D,aAAa;GACX,UAAU,WAAW;GACrB,SAAS,oBAAoB,oBAAoB,YAAY;EAC/D;CACF,GAAG,CAAC,QAAQ,CAAC;CAEb,SAAS,cAAc,OAAqC;EAC1D,IAAI,QAAQ,KAAK,KAAK,WAAW,MAAM,gBAAgB,WAAW,MAAM,WAAW,GAAG;EACtF,aAAa,UAAU;EACvB,KAAK,UAAU;GAAE,SAAS,MAAM;GAAW,QAAQ,MAAM;GAAS,WAAW,MAAM,cAAc;GAAY,MAAM,OAAO;GAAS,QAAQ;GAAO,SAAS,CAAC;EAAE;CAChK;CAEA,SAAS,cAAc,OAAqC;EAC1D,MAAM,UAAU,KAAK;EACrB,IAAI,CAAC,WAAW,QAAQ,YAAY,MAAM,WAAW;EACrD,MAAM,MAAM,MAAM;EAClB,IAAI,CAAC,QAAQ,QAAQ;GACnB,IAAI,KAAK,IAAI,MAAM,UAAU,QAAQ,MAAM,IAAI,MAAM;GAErD,QAAQ,SAAS;GACjB,QAAQ,SAAS,MAAM;GACvB,WAAW,UAAU;GACrB,IAAI;IACF,IAAI,kBAAkB,MAAM,SAAS;GACvC,QAAQ,CAER;GACA,IAAI,QAAQ,WAAW;EACzB;EACA,IAAI,aAAa,QAAQ,aAAa,MAAM,UAAU,QAAQ;EAC9D,QAAQ,QAAQ,KAAK,CAAC,MAAM,WAAW,IAAI,UAAU,CAAC;EACtD,IAAI,QAAQ,QAAQ,SAAS,IAAI,QAAQ,QAAQ,MAAM;CACzD;CAEA,SAAS,OAAO,OAAqC,WAAoB;EACvE,MAAM,UAAU,KAAK;EACrB,IAAI,CAAC,WAAW,QAAQ,YAAY,MAAM,WAAW;EACrD,KAAK,UAAU;EACf,IAAI,CAAC,QAAQ,QAAQ;EACrB,MAAM,MAAM,MAAM;EAClB,OAAO,IAAI,QAAQ;EACnB,aAAa,UAAU;EACvB,MAAM,SAAS,QAAQ,QAAQ,QAAQ,CAAC,UAAU,MAAM,YAAY,QAAQ,EAAE;EAC9E,MAAM,QAAQ,OAAO;EACrB,MAAM,aAAa,OAAO,OAAO,SAAS;EAC1C,MAAM,WACJ,CAAC,aAAa,SAAS,cAAc,OAAO,SAAS,KAAK,MAAM,YAAY,WAAW,MAAM,MAAM,WAAW,KAAK,MAAM,MACnH,WAAW,KAAK,MAAM,OAAO,WAAW,KAAK,MAAM,MAAO,MAC5D;EACN,MAAM,OAAO,KAAK,KAAK;EAEvB,IAAI,KAAK,KAAK,OAAO,IAAI,aAAa,QAAQ,QAAQ,KAAK,IAAI;EAC/D,IAAI,KAAK,IAAI,QAAQ,IAAI,OAAO;GAC9B,MAAM,YAAY,WAAW,IAAI,IAAI;GACrC,KAAK,KAAK,QAAQ,QAAQ,YAAY,GAAG,KAAK,QAAQ,OAAO;EAC/D;EACA,OAAO,EAAE;CACX;;CAGA,SAAS,QAAQ,OAAmC;EAClD,IAAI,aAAa,SAAS;GACxB,aAAa,UAAU;GACvB,MAAM,eAAe;GACrB,MAAM,gBAAgB;GACtB;EACF;EACA,MAAM,MAAM,MAAM,KAAK,MAAM,SAAS,YAAY,CAAC,CAAC,CAAC,CAAC,WAAW,UAAU;GACzE,MAAM,OAAO,MAAM,sBAAsB;GACzC,OAAO,MAAM,WAAW,KAAK,QAAQ,MAAM,WAAW,KAAK;EAC7D,CAAC;EACD,IAAI,OAAO,KAAK,QAAQ,OAAO,SAAS,OAAO,GAAG;CACpD;CAEA,SAAS,UAAU,OAAmC;EACpD,IAAI,MAAM,UAAU,MAAM,WAAW,MAAM,WAAW,QAAQ,GAAG;EACjE,MAAM,OAAO,MAAM;EACnB,IAAI,KAAK,QAAQ,4CAA4C,GAAG;EAChE,MAAM,OAAO,OAAO;EACpB,MAAM,KACJ,MAAM,QAAQ,cAAc,SAAS,MAAM,EAAE,IAAI,MAAM,QAAQ,eAAe,SAAS,MAAM,CAAC,IAAI,MAAM,QAAQ,SAAS,IAAI,MAAM,QAAQ,QAAQ,OAAO;EAC5J,IAAI,OAAO,MAAM;EACjB,MAAM,eAAe;EACrB,MAAM,OAAO,MAAM,IAAI,GAAG,IAAI;EAC9B,OAAO,IAAI;EACX,IAAI,KAAK,aAAa,MAAM,MAAM,OAAO,KAAK,QAAQ,KAAK,EAAE,MAAM,EAAE,eAAe,KAAK,CAAC;OACrF,IAAI,MAAM,SAAS,SAAS,IAAI,GAAG,SAAS,SAAS,MAAM,EAAE,eAAe,KAAK,CAAC;CACzF;CAEA,MAAM,OAAO;EAAE,UAAU;EAAkB,MAAM;EAAc,MAAM;EAAwB,OAAO;EAAwB,QAAQ;EAAkB,GAAG;CAAO;CAEhK,OACE,qBAAC,WAAD;EACE,KAAK;EACL,aAAU;EACV,wBAAqB;EACrB,cAAY;EACZ,aAAW,QAAQ,KAAA;EACnB,aAAW,OAAO,KAAA,IAAY;EAC9B,WAAW,GAAG,YAAY,SAAS;EACnC,OAAO,aAAa,OAAQ,EAAE,yBAAyB,UAAU,IAAsB,KAAA;EAC5E;EACX,UAAU,UAAU,eAAe,MAAM,WAAW,KAAK,WAAW,MAAM,OAAO,QAAQ,gBAAgB,CAAC;EAC1G,SAAS,UAAU;GACjB,IAAI,CAAC,MAAM,cAAc,SAAS,MAAM,aAAa,GAAG,eAAe,KAAK;EAC9E;EAbF,UAAA;GAeE,oBAAC,OAAD;IACE,KAAK;IACL,MAAK;IACL,cAAW;IACX,UAAU;IACV,aAAU;IACV,WAAU;IACA;IACK;IACA;IACf,cAAc,UAAU,OAAO,OAAO,KAAK;IAC3C,kBAAkB,UAAU,OAAO,OAAO,IAAI;IAC9C,iBAAiB,UAAU,MAAM,gBAAgB,WAAW,WAAW,IAAI;IAC3E,sBAAsB,WAAW,KAAK;IACtC,oBAAoB,YAAY,IAAI;IACpC,kBAAkB,YAAY,KAAK;IACnC,qBAAqB,YAAY,KAAK;IACtC,gBAAgB;IAChB,cAAc,UAAU,MAAM,eAAe;IAE7C,UAAA,oBAAC,OAAD;KAAK,KAAK;KAAO,WAAU;KACxB,UAAA,OAAO,KAAK,OAAO,MAClB,oBAAC,OAAD;MAEE,IAAI,GAAG,GAAG,SAAS;MACnB,MAAK;MACL,wBAAqB;MACrB,cAAY,WAAW,GAAG,KAAK;MAC/B,eAAa,MAAM,SAAS,KAAA;MAC5B,WAAU;MAET,UAAA;KACE,GATE,eAAe,KAAK,KAAK,MAAM,OAAO,OAAO,MAAM,MAAM,CAS3D,CACN;IACE,CAAA;GACF,CAAA;GACJ,QAAQ,IACP,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACG,YACC,oBAAC,UAAD;MACE,KAAK;MACL,MAAK;MACL,cAAY,UAAU,KAAK,QAAQ,KAAK;MACxC,WAAU;MACV,eAAe,WAAW,CAAC,OAAO;MAEjC,UAAA,UAAU,oBAAC,YAAD,CAAa,CAAA,IAAI,oBAAC,WAAD,CAAY,CAAA;KAClC,CAAA,IACN;KACJ,oBAAC,OAAD;MAAK,MAAK;MAAU,cAAY,KAAK;MAAQ,WAAU;MACpD,UAAA,OAAO,KAAK,GAAG,MACd,oBAAC,UAAD;OAEE,MAAM,SAAS;QACb,KAAK,QAAQ,KAAK;OACpB;OACA,MAAK;OACL,MAAK;OACL,iBAAe,MAAM;OACrB,iBAAe,GAAG,GAAG,SAAS;OAC9B,cAAY,WAAW,GAAG,KAAK;OAC/B,UAAU,MAAM,QAAQ,IAAI;OAC5B,WAAU;OACV,eAAe,OAAO,CAAC;OAEvB,UAAA,qBAAC,QAAD;QAAM,eAAY;QAAO,WAAU;QAAnC,UAAA,CACE,oBAAC,QAAD,EAAM,WAAU,oBAAqB,CAAA,GACpC,aAAa,WAAW,MAAM,QAC7B,oBAAC,QAAD;SAEE,WAAU;SACV,OAAO;UAAE,mBAAmB,GAAG,SAAS;UAAK,oBAAoB,UAAU,YAAY;SAAS;SAChG,gBAAgB;QACjB,GAJM,KAIN,IACC,IACA;;MACA,GAxBD,CAwBC,CACT;KACE,CAAA;KACL,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,oBAAC,UAAD;OACE,MAAK;OACL,cAAY,KAAK;OACjB,iBAAe,CAAC,QAAQ,UAAU;OAClC,WAAU;OACV,gBAAgB,QAAQ,OAAO,UAAU,MAAM,OAAO,SAAS,OAAO,SAAS,EAAE,CAAC;OAElF,UAAA,oBAAC,SAAD,EAAS,MAAA,KAAM,CAAA;MACT,CAAA,GACR,oBAAC,UAAD;OACE,MAAK;OACL,cAAY,KAAK;OACjB,iBAAe,CAAC,QAAQ,UAAU;OAClC,WAAU;OACV,gBAAgB,QAAQ,OAAO,UAAU,SAAS,OAAO,SAAS,OAAO,SAAS,CAAC,CAAC;OAEpF,UAAA,oBAAC,SAAD,CAAU,CAAA;MACJ,CAAA,CACL;;IACF;GACH,CAAA,IAAA;GACJ,oBAAC,QAAD;IAAM,WAAU;IAAU,aAAU;IAAS,eAAY;IACtD,UAAA;GACG,CAAA;EACC;;AAEb,CAAC"}
|
|
@@ -6,11 +6,24 @@ export interface CopyButtonProps {
|
|
|
6
6
|
children?: string;
|
|
7
7
|
/** Only the icon, a square button. The label is still its accessible name. */
|
|
8
8
|
iconOnly?: boolean;
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* `plain` has no border until hovered. `primary` is the filled brand button, for a copy that is the
|
|
11
|
+
* main action of its row or dialog. @default "outline"
|
|
12
|
+
*/
|
|
13
|
+
variant?: "outline" | "plain" | "primary";
|
|
14
|
+
/**
|
|
15
|
+
* Control height, from the density scale: `sm` sits in a row, `md` matches a default Button.
|
|
16
|
+
* @default "sm"
|
|
17
|
+
*/
|
|
18
|
+
size?: "sm" | "md";
|
|
11
19
|
isDisabled?: boolean;
|
|
12
20
|
/** Called once the text has reached the clipboard. */
|
|
13
21
|
onCopied?: () => void;
|
|
22
|
+
/**
|
|
23
|
+
* Called when the clipboard refused the text, with what it rejected with. The button already shows
|
|
24
|
+
* "Failed" and announces it; use this to keep a toast or log of your own.
|
|
25
|
+
*/
|
|
26
|
+
onError?: (error: unknown) => void;
|
|
14
27
|
/** Extra classes. Merged last, so they win conflicts with ours. */
|
|
15
28
|
className?: string;
|
|
16
29
|
}
|
|
@@ -26,6 +39,9 @@ export interface CopyButtonProps {
|
|
|
26
39
|
* @example
|
|
27
40
|
* <CopyButton value={token} />
|
|
28
41
|
* <CopyButton value={url} iconOnly variant="plain" />
|
|
42
|
+
* <CopyButton value={ticket} variant="primary" size="md" onError={() => toast("Copy failed")}>
|
|
43
|
+
* Copy as ticket
|
|
44
|
+
* </CopyButton>
|
|
29
45
|
*/
|
|
30
46
|
export declare const CopyButton: import("react").ForwardRefExoticComponent<CopyButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
31
47
|
//#endregion
|
|
@@ -3,12 +3,29 @@ import { cn } from "../lib/cn.js";
|
|
|
3
3
|
import { buttonVariants } from "../button/button.js";
|
|
4
4
|
import { MorphText } from "../lib/morph-text.js";
|
|
5
5
|
import { IconSwap } from "../lib/icon-swap.js";
|
|
6
|
-
import { useCopyFeedback } from "../lib/use-copy-feedback.js";
|
|
6
|
+
import { callSafely, useCopyFeedback } from "../lib/use-copy-feedback.js";
|
|
7
7
|
import { forwardRef } from "react";
|
|
8
8
|
import { Button } from "react-aria-components";
|
|
9
9
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
//#region src/copy-button/copy-button.tsx
|
|
11
11
|
const ICON = "size-4";
|
|
12
|
+
/** CopyButton's variant names, mapped onto Button's. */
|
|
13
|
+
const BUTTON_VARIANT = {
|
|
14
|
+
outline: "outlineNeutral",
|
|
15
|
+
plain: "ghost",
|
|
16
|
+
primary: "primary"
|
|
17
|
+
};
|
|
18
|
+
/** An icon-only button is square: as wide as its rung is tall. */
|
|
19
|
+
const ICON_ONLY_WIDTH = {
|
|
20
|
+
sm: " w-(--density-control-h-sm) px-0",
|
|
21
|
+
md: " w-(--density-control-h-md) px-0"
|
|
22
|
+
};
|
|
23
|
+
/** The result icon's tone on a page-surface button; at rest it takes the label's ink. */
|
|
24
|
+
const RESULT_TONE = {
|
|
25
|
+
idle: void 0,
|
|
26
|
+
copied: "text-success-strong",
|
|
27
|
+
error: "text-destructive-strong"
|
|
28
|
+
};
|
|
12
29
|
/** The tick draws itself from its short stroke, the way a hand would write it: quick to start, then easing into place. */
|
|
13
30
|
function DrawnCheck() {
|
|
14
31
|
return /* @__PURE__ */ jsx("svg", {
|
|
@@ -70,17 +87,21 @@ function StateIcon({ state }) {
|
|
|
70
87
|
* @example
|
|
71
88
|
* <CopyButton value={token} />
|
|
72
89
|
* <CopyButton value={url} iconOnly variant="plain" />
|
|
90
|
+
* <CopyButton value={ticket} variant="primary" size="md" onError={() => toast("Copy failed")}>
|
|
91
|
+
* Copy as ticket
|
|
92
|
+
* </CopyButton>
|
|
73
93
|
*/
|
|
74
|
-
const CopyButton = forwardRef(function CopyButton({ value, children: label = "Copy", iconOnly = false, variant = "outline", isDisabled, onCopied, className }, ref) {
|
|
94
|
+
const CopyButton = forwardRef(function CopyButton({ value, children: label = "Copy", iconOnly = false, variant = "outline", size = "sm", isDisabled, onCopied, onError, className }, ref) {
|
|
75
95
|
const { state, copy } = useCopyFeedback();
|
|
76
96
|
const text = state === "copied" ? "Copied" : state === "error" ? "Failed" : label;
|
|
77
97
|
const pressStart = (event) => {
|
|
78
98
|
event.target.style.setProperty("--press-scale", iconOnly ? "0.96" : "0.97");
|
|
79
99
|
};
|
|
80
100
|
const classes = cn(buttonVariants({
|
|
81
|
-
variant: variant
|
|
82
|
-
size
|
|
83
|
-
}) + " [--release-duration:var(--duration-gentle)] [--release-ease:var(--ease-gentle)]" + (iconOnly ?
|
|
101
|
+
variant: BUTTON_VARIANT[variant],
|
|
102
|
+
size
|
|
103
|
+
}) + " [--release-duration:var(--duration-gentle)] [--release-ease:var(--ease-gentle)]" + (iconOnly ? ICON_ONLY_WIDTH[size] : ""), className);
|
|
104
|
+
const onBrandFill = variant === "primary";
|
|
84
105
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Button, {
|
|
85
106
|
ref,
|
|
86
107
|
"data-slot": "button",
|
|
@@ -89,8 +110,8 @@ const CopyButton = forwardRef(function CopyButton({ value, children: label = "Co
|
|
|
89
110
|
isDisabled,
|
|
90
111
|
onPressStart: pressStart,
|
|
91
112
|
onPress: () => {
|
|
92
|
-
copy(value).then((ok) => {
|
|
93
|
-
if (ok) onCopied?.();
|
|
113
|
+
copy(value, onError).then((ok) => {
|
|
114
|
+
if (ok) callSafely(() => onCopied?.());
|
|
94
115
|
});
|
|
95
116
|
},
|
|
96
117
|
className: classes,
|
|
@@ -99,7 +120,7 @@ const CopyButton = forwardRef(function CopyButton({ value, children: label = "Co
|
|
|
99
120
|
calm: true,
|
|
100
121
|
className: cn(ICON, "relative"),
|
|
101
122
|
children: /* @__PURE__ */ jsx("span", {
|
|
102
|
-
className:
|
|
123
|
+
className: onBrandFill ? void 0 : RESULT_TONE[state],
|
|
103
124
|
children: /* @__PURE__ */ jsx(StateIcon, { state })
|
|
104
125
|
})
|
|
105
126
|
}), iconOnly ? null : /* @__PURE__ */ jsxs("span", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copy-button.js","names":["AriaButton"],"sources":["../../src/copy-button/copy-button.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef } from \"react\";\nimport { Button as AriaButton, type PressEvent } from \"react-aria-components\";\n\nimport { buttonVariants } from \"../button/button.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { IconSwap } from \"../lib/icon-swap.js\";\nimport { MorphText } from \"../lib/morph-text.js\";\nimport { useCopyFeedback, type CopyFeedbackState } from \"../lib/use-copy-feedback.js\";\n\nexport interface CopyButtonProps {\n /** What goes on the clipboard. */\n value: string;\n /** The label at rest, and the button's accessible name. @default \"Copy\" */\n children?: string;\n /** Only the icon, a square button. The label is still its accessible name. */\n iconOnly?: boolean;\n
|
|
1
|
+
{"version":3,"file":"copy-button.js","names":["AriaButton"],"sources":["../../src/copy-button/copy-button.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef } from \"react\";\nimport { Button as AriaButton, type PressEvent } from \"react-aria-components\";\n\nimport { buttonVariants } from \"../button/button.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { IconSwap } from \"../lib/icon-swap.js\";\nimport { MorphText } from \"../lib/morph-text.js\";\nimport { callSafely, useCopyFeedback, type CopyFeedbackState } from \"../lib/use-copy-feedback.js\";\n\nexport interface CopyButtonProps {\n /** What goes on the clipboard. */\n value: string;\n /** The label at rest, and the button's accessible name. @default \"Copy\" */\n children?: string;\n /** Only the icon, a square button. The label is still its accessible name. */\n iconOnly?: boolean;\n /**\n * `plain` has no border until hovered. `primary` is the filled brand button, for a copy that is the\n * main action of its row or dialog. @default \"outline\"\n */\n variant?: \"outline\" | \"plain\" | \"primary\";\n /**\n * Control height, from the density scale: `sm` sits in a row, `md` matches a default Button.\n * @default \"sm\"\n */\n size?: \"sm\" | \"md\";\n isDisabled?: boolean;\n /** Called once the text has reached the clipboard. */\n onCopied?: () => void;\n /**\n * Called when the clipboard refused the text, with what it rejected with. The button already shows\n * \"Failed\" and announces it; use this to keep a toast or log of your own.\n */\n onError?: (error: unknown) => void;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nconst ICON = \"size-4\";\n\n/** CopyButton's variant names, mapped onto Button's. */\nconst BUTTON_VARIANT = {\n outline: \"outlineNeutral\",\n plain: \"ghost\",\n primary: \"primary\",\n} as const;\n\n/** An icon-only button is square: as wide as its rung is tall. */\nconst ICON_ONLY_WIDTH = {\n sm: \" w-(--density-control-h-sm) px-0\",\n md: \" w-(--density-control-h-md) px-0\",\n} as const;\n\n/** The result icon's tone on a page-surface button; at rest it takes the label's ink. */\nconst RESULT_TONE: Record<CopyFeedbackState, string | undefined> = {\n idle: undefined,\n copied: \"text-success-strong\",\n error: \"text-destructive-strong\",\n};\n\n/** The tick draws itself from its short stroke, the way a hand would write it: quick to start, then easing into place. */\nfunction DrawnCheck() {\n return (\n <svg\n className={ICON}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.75}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M4 12l5 5L20 6\"\n pathLength={1}\n className=\"[stroke-dasharray:1] motion-safe:animate-check-draw-calm\"\n />\n </svg>\n );\n}\n\nfunction StateIcon({ state }: { state: CopyFeedbackState }) {\n if (state === \"copied\") return <DrawnCheck />;\n const common = {\n className: ICON,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 1.75,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n \"aria-hidden\": true,\n } as const;\n if (state === \"error\") {\n return (\n <svg {...common}>\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 8v4M12 16h.01\" />\n </svg>\n );\n }\n return (\n <svg {...common}>\n <rect x=\"8\" y=\"8\" width=\"14\" height=\"14\" rx=\"2\" />\n <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n </svg>\n );\n}\n\n/**\n * Copies a value and says so, calmly. The icon trades places in one soft breath\n * (copy, then a tick that draws itself, or an alert if the clipboard refused)\n * while the label's changed letters rise in, and the button never changes width:\n * its label cell reserves the widest of the three texts.\n *\n * It goes back to rest by itself after a moment, and a live region announces the\n * result. The accessible name is always the resting label.\n *\n * @example\n * <CopyButton value={token} />\n * <CopyButton value={url} iconOnly variant=\"plain\" />\n * <CopyButton value={ticket} variant=\"primary\" size=\"md\" onError={() => toast(\"Copy failed\")}>\n * Copy as ticket\n * </CopyButton>\n */\nexport const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(function CopyButton(\n {\n value,\n children: label = \"Copy\",\n iconOnly = false,\n variant = \"outline\",\n size = \"sm\",\n isDisabled,\n onCopied,\n onError,\n className,\n },\n ref,\n) {\n const { state, copy } = useCopyFeedback();\n const text = state === \"copied\" ? \"Copied\" : state === \"error\" ? \"Failed\" : label;\n\n // Not width-based like Button's press: an icon button presses a little deeper, every other one the same.\n const pressStart = (event: PressEvent) => {\n (event.target as HTMLElement).style.setProperty(\"--press-scale\", iconOnly ? \"0.96\" : \"0.97\");\n };\n\n const classes = cn(\n buttonVariants({ variant: BUTTON_VARIANT[variant], size }) +\n \" [--release-duration:var(--duration-gentle)] [--release-ease:var(--ease-gentle)]\" +\n (iconOnly ? ICON_ONLY_WIDTH[size] : \"\"),\n className,\n );\n // On the brand fill the result icon keeps the label's ink: the success and destructive tones are set\n // against the page surface, not the orange.\n const onBrandFill = variant === \"primary\";\n\n return (\n <>\n <AriaButton\n ref={ref}\n data-slot=\"button\"\n data-copy-state={state}\n aria-label={label}\n isDisabled={isDisabled}\n onPressStart={pressStart}\n onPress={() => {\n void copy(value, onError).then((ok) => {\n if (ok) callSafely(() => onCopied?.());\n });\n }}\n className={classes}\n >\n <IconSwap iconKey={state} calm className={cn(ICON, \"relative\")}>\n <span className={onBrandFill ? undefined : RESULT_TONE[state]}>\n <StateIcon state={state} />\n </span>\n </IconSwap>\n {iconOnly ? null : (\n // The cell reserves the widest state, so nothing moves but the letters. The inline padding gives blurred\n // edge letters room before the horizontal clip; the negative margin keeps the box the same size.\n <span\n className=\"-mx-1 grid min-w-0 overflow-x-clip px-1 text-start whitespace-nowrap\"\n aria-hidden=\"true\"\n >\n {[label, \"Copied\", \"Failed\"].map((measure) => (\n <span key={measure} className=\"invisible col-start-1 row-start-1\">\n {measure}\n </span>\n ))}\n <span className=\"col-start-1 row-start-1 justify-self-start\">\n <MorphText text={text} morphWidth={false} calm />\n </span>\n </span>\n )}\n </AriaButton>\n <span className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {state === \"idle\"\n ? \"\"\n : state === \"error\"\n ? label + \": Could not copy\"\n : label + \": Copied\"}\n </span>\n </>\n );\n});\n"],"mappings":"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"}
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* focus, selection) slides out along its middle and thickens; the rest dim.
|
|
11
11
|
* The centre rolls like a drum between the total and each segment, and every
|
|
12
12
|
* number counts to its new value. Small parts fold into one "Other" segment.
|
|
13
|
+
* Where the parts do not add up to anything (a rate such as CTR, CPC or ROAS
|
|
14
|
+
* per source), `center` hides the total or puts a figure of the caller's own
|
|
15
|
+
* at rest instead; the segment readouts still roll in on hover and focus.
|
|
13
16
|
*
|
|
14
17
|
* Plain SVG and CSS; one small spring tween moves the arcs. The legend is the
|
|
15
18
|
* keyboard path (or the ring itself, without a legend), and a visually hidden
|
|
@@ -23,6 +26,16 @@ export interface DonutChartDatum {
|
|
|
23
26
|
/** Any CSS colour. Segments default to chart-1 to chart-4 in data order, then neutral steps. */
|
|
24
27
|
color?: string;
|
|
25
28
|
}
|
|
29
|
+
/** A figure of the caller's own for the centre at rest, in place of the summed total. */
|
|
30
|
+
export interface DonutChartCenter {
|
|
31
|
+
/** The line above the figure, such as "Blended CTR". */
|
|
32
|
+
label: string;
|
|
33
|
+
/**
|
|
34
|
+
* The figure. A number is formatted with `formatValue` and counts to each new
|
|
35
|
+
* value, with `unit` under it; a string is shown as written (`"2.4%"`).
|
|
36
|
+
*/
|
|
37
|
+
value: number | string;
|
|
38
|
+
}
|
|
26
39
|
export interface DonutChartProps {
|
|
27
40
|
data: DonutChartDatum[];
|
|
28
41
|
/** What the whole is, such as "Visits by source". Names the chart for assistive technology. */
|
|
@@ -32,6 +45,15 @@ export interface DonutChartProps {
|
|
|
32
45
|
formatValue?: (value: number) => string;
|
|
33
46
|
/** Centre label at rest, above the total. @default "Total" */
|
|
34
47
|
totalLabel?: string;
|
|
48
|
+
/**
|
|
49
|
+
* What the centre shows at rest. `"total"` sums the visible segments under
|
|
50
|
+
* `totalLabel`. `"none"` leaves it empty, for parts whose sum means nothing
|
|
51
|
+
* (CTR, CPC or ROAS by source). An object puts a figure of your own there
|
|
52
|
+
* instead, such as the blended rate. Hovering or focusing a segment still
|
|
53
|
+
* shows that segment's readout either way.
|
|
54
|
+
* @default "total"
|
|
55
|
+
*/
|
|
56
|
+
center?: "total" | "none" | DonutChartCenter;
|
|
35
57
|
/** Diameter in pixels. The chart scales down to fit narrower containers. @default 208 */
|
|
36
58
|
size?: number;
|
|
37
59
|
/** Ring thickness in pixels. @default 24 */
|
|
@@ -115,7 +115,7 @@ function Count({ value, format }) {
|
|
|
115
115
|
const shown = useCountTo(fine ? value * 1e4 : value);
|
|
116
116
|
return /* @__PURE__ */ jsx(Fragment$1, { children: format(fine ? shown / 1e4 : shown) });
|
|
117
117
|
}
|
|
118
|
-
const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", formatValue = (value) => GROUPED.format(value), totalLabel = "Total", size = 208, thickness = 24, groupBelow = .04, maxSegments = 6, otherLabel = "Other", activeKey, defaultActiveKey = null, onActiveChange, hiddenKeys, defaultHiddenKeys, onHiddenKeysChange, legendAction = "toggle", legend = true, emptyLabel = "No data yet", className }, ref) {
|
|
118
|
+
const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", formatValue = (value) => GROUPED.format(value), totalLabel = "Total", center = "total", size = 208, thickness = 24, groupBelow = .04, maxSegments = 6, otherLabel = "Other", activeKey, defaultActiveKey = null, onActiveChange, hiddenKeys, defaultHiddenKeys, onHiddenKeysChange, legendAction = "toggle", legend = true, emptyLabel = "No data yet", className }, ref) {
|
|
119
119
|
const figure = useRef(null);
|
|
120
120
|
const refs = useMemo(() => mergeRefs(ref, figure), [ref]);
|
|
121
121
|
const legendId = useId();
|
|
@@ -167,7 +167,8 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
|
|
|
167
167
|
onHiddenKeysChange?.(next);
|
|
168
168
|
const item = slices.find((slice) => slice.key === key);
|
|
169
169
|
const nextTotal = slices.reduce((sum, slice) => sum + (next.includes(slice.key) ? 0 : slice.value), 0);
|
|
170
|
-
|
|
170
|
+
const totalText = center === "total" ? ` ${totalLabel} ${formatValue(nextTotal)}${suffix}.` : "";
|
|
171
|
+
if (item) setAnnouncement(`${item.label} ${isHidden ? "shown" : "hidden"}.${totalText}`);
|
|
171
172
|
};
|
|
172
173
|
const c = size / 2;
|
|
173
174
|
const outer = c - MARGIN;
|
|
@@ -320,7 +321,9 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
|
|
|
320
321
|
if (key) select(selected === key ? null : key);
|
|
321
322
|
};
|
|
322
323
|
const format = (value) => `${formatValue(value)}${suffix}`;
|
|
323
|
-
const
|
|
324
|
+
const showShare = center === "total";
|
|
325
|
+
const shareOf = (value) => showShare ? `, ${shareText(value / (total || 1))}` : "";
|
|
326
|
+
const describe = (item) => `${item.label}, ${format(item.value)}${shareOf(item.value)}${item.members ? `. Includes ${item.members.map((member) => member.label).join(", ")}` : ""}`;
|
|
324
327
|
const rows = useRef([]);
|
|
325
328
|
const step = (key, index, count) => {
|
|
326
329
|
return {
|
|
@@ -367,20 +370,32 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
|
|
|
367
370
|
const onLegendBlur = (event) => {
|
|
368
371
|
if (!(event.relatedTarget instanceof Node && event.currentTarget.closest("ul")?.contains(event.relatedTarget))) setPreview(null);
|
|
369
372
|
};
|
|
373
|
+
const rest = center === "total" ? {
|
|
374
|
+
label: totalLabel,
|
|
375
|
+
value: total
|
|
376
|
+
} : center === "none" ? {
|
|
377
|
+
label: " ",
|
|
378
|
+
value: ""
|
|
379
|
+
} : center;
|
|
370
380
|
const identity = activeSlice ? activeSlice.key : "__total";
|
|
381
|
+
const custom = typeof center === "object" && total > 0;
|
|
371
382
|
const readouts = [{
|
|
372
383
|
key: "__total",
|
|
373
|
-
label: total ?
|
|
374
|
-
value: total,
|
|
375
|
-
meta: null
|
|
384
|
+
label: total ? rest.label : emptyLabel,
|
|
385
|
+
value: total ? rest.value : 0,
|
|
386
|
+
meta: null,
|
|
387
|
+
custom
|
|
376
388
|
}, ...slices.map((item) => ({
|
|
377
389
|
key: item.key,
|
|
378
390
|
label: item.label,
|
|
379
391
|
value: item.value,
|
|
380
|
-
meta: item
|
|
392
|
+
meta: item,
|
|
393
|
+
custom: false
|
|
381
394
|
}))];
|
|
382
395
|
const activeIndex = Math.max(0, readouts.findIndex((item) => item.key === identity));
|
|
383
|
-
const
|
|
396
|
+
const restValue = (value) => typeof value === "number" ? format(value) : value;
|
|
397
|
+
const restText = center === "total" ? `${totalLabel} ${format(total)}. ` : center === "none" ? "" : `${[center.label, restValue(center.value)].filter(Boolean).join(" ")}. `;
|
|
398
|
+
const summary = total ? `${label}. ${restText}${visible.map(describe).join(". ")}.` : `${label}. ${emptyLabel}.`;
|
|
384
399
|
const members = (item) => item.members ? item.members.length > 2 ? `${item.members.slice(0, 2).map((member) => member.label).join(", ")} and ${item.members.length - 2} more` : item.members.map((member) => member.label).join(" and ") : null;
|
|
385
400
|
return /* @__PURE__ */ jsxs("figure", {
|
|
386
401
|
ref: refs,
|
|
@@ -427,6 +442,7 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
|
|
|
427
442
|
}, item.key))]
|
|
428
443
|
}), /* @__PURE__ */ jsx("div", {
|
|
429
444
|
"aria-hidden": "true",
|
|
445
|
+
"data-center": center === "total" || center === "none" ? center : "custom",
|
|
430
446
|
className: "donut-chart-center",
|
|
431
447
|
children: readouts.map((item, index) => /* @__PURE__ */ jsxs("span", {
|
|
432
448
|
"data-place": Math.sign(index - activeIndex),
|
|
@@ -438,22 +454,22 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
|
|
|
438
454
|
}),
|
|
439
455
|
/* @__PURE__ */ jsx("span", {
|
|
440
456
|
className: "block max-w-full min-h-8 truncate text-h3 font-medium tabular-nums",
|
|
441
|
-
children: item.value || item.meta ? /* @__PURE__ */ jsx(Count, {
|
|
457
|
+
children: typeof item.value === "string" ? item.value || " " : item.value || item.meta || item.custom ? /* @__PURE__ */ jsx(Count, {
|
|
442
458
|
value: item.value,
|
|
443
459
|
format: formatValue
|
|
444
460
|
}) : " "
|
|
445
461
|
}),
|
|
446
462
|
/* @__PURE__ */ jsx("span", {
|
|
447
463
|
className: "block w-full min-h-5 truncate text-xs tabular-nums text-muted-foreground",
|
|
448
|
-
children: item.meta ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
464
|
+
children: item.meta && showShare ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
449
465
|
/* @__PURE__ */ jsx(Count, {
|
|
450
|
-
value: total && !hidden.has(item.key) ? item.value / total : 0,
|
|
466
|
+
value: total && !hidden.has(item.key) ? item.meta.value / total : 0,
|
|
451
467
|
format: shareText
|
|
452
468
|
}),
|
|
453
|
-
" of",
|
|
454
469
|
" ",
|
|
470
|
+
"of ",
|
|
455
471
|
totalLabel.toLowerCase()
|
|
456
|
-
] }) : unit || " "
|
|
472
|
+
] }) : typeof item.value === "number" ? unit || " " : " "
|
|
457
473
|
})
|
|
458
474
|
]
|
|
459
475
|
}, item.key))
|
|
@@ -479,6 +495,7 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
|
|
|
479
495
|
"data-active": item.key === current || void 0,
|
|
480
496
|
"data-pinned": selected === item.key || void 0,
|
|
481
497
|
"data-hidden": off || void 0,
|
|
498
|
+
"data-share": showShare ? void 0 : "none",
|
|
482
499
|
style: { "--slice": colorFor(item) },
|
|
483
500
|
onClick: () => legendAction === "toggle" ? setHidden(item.key) : select(selected === item.key ? null : item.key),
|
|
484
501
|
onPointerEnter: (event) => {
|
|
@@ -513,14 +530,14 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
|
|
|
513
530
|
format: formatValue
|
|
514
531
|
})
|
|
515
532
|
}),
|
|
516
|
-
/* @__PURE__ */ jsx("span", {
|
|
533
|
+
showShare ? /* @__PURE__ */ jsx("span", {
|
|
517
534
|
"aria-hidden": "true",
|
|
518
535
|
className: "donut-chart-row-value justify-self-end font-medium tabular-nums",
|
|
519
536
|
children: /* @__PURE__ */ jsx(Count, {
|
|
520
537
|
value: off || !total ? 0 : item.value / total,
|
|
521
538
|
format: shareText
|
|
522
539
|
})
|
|
523
|
-
})
|
|
540
|
+
}) : null
|
|
524
541
|
]
|
|
525
542
|
})
|
|
526
543
|
}, item.key);
|
|
@@ -548,10 +565,10 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
|
|
|
548
565
|
scope: "col",
|
|
549
566
|
children: "Value"
|
|
550
567
|
}),
|
|
551
|
-
/* @__PURE__ */ jsx("th", {
|
|
568
|
+
showShare ? /* @__PURE__ */ jsx("th", {
|
|
552
569
|
scope: "col",
|
|
553
570
|
children: "Share"
|
|
554
|
-
})
|
|
571
|
+
}) : null
|
|
555
572
|
] }) }),
|
|
556
573
|
/* @__PURE__ */ jsx("tbody", { children: visible.flatMap((item) => item.members ? item.members.map((member) => ({
|
|
557
574
|
key: member.key,
|
|
@@ -563,7 +580,7 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
|
|
|
563
580
|
children: item.label
|
|
564
581
|
}),
|
|
565
582
|
/* @__PURE__ */ jsx("td", { children: format(item.value) }),
|
|
566
|
-
/* @__PURE__ */ jsx("td", { children: shareText(item.value / total) })
|
|
583
|
+
showShare ? /* @__PURE__ */ jsx("td", { children: shareText(item.value / total) }) : null
|
|
567
584
|
] }, item.key)) })
|
|
568
585
|
] })
|
|
569
586
|
}) : null
|