@fanvue/ui 3.16.0 → 3.18.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/README.md +1 -1
- package/dist/cjs/components/AudioPlayer/AudioPlayer.cjs +16 -89
- package/dist/cjs/components/AudioPlayer/AudioPlayer.cjs.map +1 -1
- package/dist/cjs/components/Icons/ReceiveMoneyIcon.cjs +79 -0
- package/dist/cjs/components/Icons/ReceiveMoneyIcon.cjs.map +1 -0
- package/dist/cjs/components/PhoneField/PhoneField.cjs +198 -0
- package/dist/cjs/components/PhoneField/PhoneField.cjs.map +1 -0
- package/dist/cjs/components/RatingSummary/RatingSummary.cjs +30 -23
- package/dist/cjs/components/RatingSummary/RatingSummary.cjs.map +1 -1
- package/dist/cjs/components/TextArea/TextArea.cjs +2 -2
- package/dist/cjs/components/TextArea/TextArea.cjs.map +1 -1
- package/dist/cjs/components/TextField/TextField.cjs +13 -6
- package/dist/cjs/components/TextField/TextField.cjs.map +1 -1
- package/dist/cjs/components/VoiceNote/VoiceNote.cjs +305 -0
- package/dist/cjs/components/VoiceNote/VoiceNote.cjs.map +1 -0
- package/dist/cjs/index.cjs +6 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/utils/audioWaveform.cjs +76 -0
- package/dist/cjs/utils/audioWaveform.cjs.map +1 -0
- package/dist/cjs/utils/useAudioPlayback.cjs +123 -0
- package/dist/cjs/utils/useAudioPlayback.cjs.map +1 -0
- package/dist/cjs/utils/useFittedBarCount.cjs +42 -0
- package/dist/cjs/utils/useFittedBarCount.cjs.map +1 -0
- package/dist/cjs/utils/useWaveformPeaks.cjs +48 -0
- package/dist/cjs/utils/useWaveformPeaks.cjs.map +1 -0
- package/dist/cjs/utils/useWaveformSeek.cjs +100 -0
- package/dist/cjs/utils/useWaveformSeek.cjs.map +1 -0
- package/dist/components/AudioPlayer/AudioPlayer.mjs +7 -80
- package/dist/components/AudioPlayer/AudioPlayer.mjs.map +1 -1
- package/dist/components/Icons/ReceiveMoneyIcon.mjs +62 -0
- package/dist/components/Icons/ReceiveMoneyIcon.mjs.map +1 -0
- package/dist/components/PhoneField/PhoneField.mjs +181 -0
- package/dist/components/PhoneField/PhoneField.mjs.map +1 -0
- package/dist/components/RatingSummary/RatingSummary.mjs +30 -23
- package/dist/components/RatingSummary/RatingSummary.mjs.map +1 -1
- package/dist/components/TextArea/TextArea.mjs +2 -2
- package/dist/components/TextArea/TextArea.mjs.map +1 -1
- package/dist/components/TextField/TextField.mjs +13 -6
- package/dist/components/TextField/TextField.mjs.map +1 -1
- package/dist/components/VoiceNote/VoiceNote.mjs +288 -0
- package/dist/components/VoiceNote/VoiceNote.mjs.map +1 -0
- package/dist/index.d.ts +158 -0
- package/dist/index.mjs +6 -0
- package/dist/index.mjs.map +1 -1
- package/dist/styles/base.css +11 -2
- package/dist/utils/audioWaveform.mjs +76 -0
- package/dist/utils/audioWaveform.mjs.map +1 -0
- package/dist/utils/useAudioPlayback.mjs +106 -0
- package/dist/utils/useAudioPlayback.mjs.map +1 -0
- package/dist/utils/useFittedBarCount.mjs +25 -0
- package/dist/utils/useFittedBarCount.mjs.map +1 -0
- package/dist/utils/useWaveformPeaks.mjs +31 -0
- package/dist/utils/useWaveformPeaks.mjs.map +1 -0
- package/dist/utils/useWaveformSeek.mjs +83 -0
- package/dist/utils/useWaveformSeek.mjs.map +1 -0
- package/package.json +1 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { generateFallbackPeaks, decodeAudioPeaks, resamplePeaks, formatTime, RAW_PEAK_COUNT } from "../../utils/audioWaveform.mjs";
|
|
4
5
|
import { cn } from "../../utils/cn.mjs";
|
|
6
|
+
import { useFittedBarCount } from "../../utils/useFittedBarCount.mjs";
|
|
5
7
|
import { PauseIcon } from "../Icons/PauseIcon.mjs";
|
|
6
8
|
import { PlayIcon } from "../Icons/PlayIcon.mjs";
|
|
7
9
|
const BAR_WIDTH_PX = 4;
|
|
@@ -9,72 +11,8 @@ const BAR_GAP_PX = 4;
|
|
|
9
11
|
const MIN_BAR_HEIGHT_PX = 4;
|
|
10
12
|
const MAX_BAR_HEIGHT_PX = 26;
|
|
11
13
|
const DEFAULT_BAR_COUNT = 24;
|
|
12
|
-
const RAW_PEAK_COUNT = 128;
|
|
13
14
|
const SEEK_STEP_SECONDS = 1;
|
|
14
15
|
const SEEK_STEP_LARGE_SECONDS = 5;
|
|
15
|
-
function formatTime(seconds) {
|
|
16
|
-
if (seconds === void 0 || !Number.isFinite(seconds)) return "--:--";
|
|
17
|
-
const totalSeconds = Math.max(0, Math.floor(seconds));
|
|
18
|
-
const minutes = Math.floor(totalSeconds / 60);
|
|
19
|
-
const remainingSeconds = totalSeconds % 60;
|
|
20
|
-
return `${minutes}:${String(remainingSeconds).padStart(2, "0")}`;
|
|
21
|
-
}
|
|
22
|
-
function hashString(value) {
|
|
23
|
-
let hash = 2166136261;
|
|
24
|
-
for (let i = 0; i < value.length; i++) {
|
|
25
|
-
hash ^= value.charCodeAt(i);
|
|
26
|
-
hash = Math.imul(hash, 16777619);
|
|
27
|
-
}
|
|
28
|
-
return hash >>> 0;
|
|
29
|
-
}
|
|
30
|
-
function createSeededRandom(seed) {
|
|
31
|
-
let state = seed;
|
|
32
|
-
return () => {
|
|
33
|
-
state = state + 1831565813 | 0;
|
|
34
|
-
let t = Math.imul(state ^ state >>> 15, 1 | state);
|
|
35
|
-
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
36
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
function generateFallbackPeaks(src, count) {
|
|
40
|
-
const random = createSeededRandom(hashString(src));
|
|
41
|
-
return Array.from({ length: count }, () => 0.2 + random() * 0.8);
|
|
42
|
-
}
|
|
43
|
-
function computePeaksFromChannelData(channelData, count) {
|
|
44
|
-
const blockSize = Math.max(1, Math.floor(channelData.length / count));
|
|
45
|
-
const peaks = [];
|
|
46
|
-
for (let i = 0; i < count; i++) {
|
|
47
|
-
const start = i * blockSize;
|
|
48
|
-
let max = 0;
|
|
49
|
-
for (let j = 0; j < blockSize && start + j < channelData.length; j++) {
|
|
50
|
-
max = Math.max(max, Math.abs(channelData[start + j] ?? 0));
|
|
51
|
-
}
|
|
52
|
-
peaks.push(max);
|
|
53
|
-
}
|
|
54
|
-
return peaks;
|
|
55
|
-
}
|
|
56
|
-
function resamplePeaks(peaks, barCount) {
|
|
57
|
-
if (peaks.length === 0 || barCount <= 0) return [];
|
|
58
|
-
const step = peaks.length / barCount;
|
|
59
|
-
return Array.from(
|
|
60
|
-
{ length: barCount },
|
|
61
|
-
(_, i) => peaks[Math.min(peaks.length - 1, Math.floor(i * step))] ?? 0
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
async function decodeAudioPeaks(src, signal) {
|
|
65
|
-
const AudioContextCtor = window.AudioContext ?? window.webkitAudioContext;
|
|
66
|
-
if (!AudioContextCtor) throw new Error("WebAudio unsupported");
|
|
67
|
-
const response = await fetch(src, { signal });
|
|
68
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
69
|
-
const audioContext = new AudioContextCtor();
|
|
70
|
-
try {
|
|
71
|
-
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
72
|
-
return computePeaksFromChannelData(audioBuffer.getChannelData(0), RAW_PEAK_COUNT);
|
|
73
|
-
} finally {
|
|
74
|
-
if (audioContext.state !== "closed") audioContext.close().catch(() => {
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
16
|
const AudioPlayer = React.forwardRef(
|
|
79
17
|
({
|
|
80
18
|
className,
|
|
@@ -95,7 +33,11 @@ const AudioPlayer = React.forwardRef(
|
|
|
95
33
|
const playing = isControlled ? controlledPlaying : internalPlaying;
|
|
96
34
|
const [currentTime, setCurrentTime] = React.useState(0);
|
|
97
35
|
const [mediaDuration, setMediaDuration] = React.useState(void 0);
|
|
98
|
-
const
|
|
36
|
+
const barCount = useFittedBarCount(trackRef, {
|
|
37
|
+
barWidthPx: BAR_WIDTH_PX,
|
|
38
|
+
gapPx: BAR_GAP_PX,
|
|
39
|
+
fallback: DEFAULT_BAR_COUNT
|
|
40
|
+
});
|
|
99
41
|
const [peaks, setPeaks] = React.useState(
|
|
100
42
|
() => generateFallbackPeaks(src, RAW_PEAK_COUNT)
|
|
101
43
|
);
|
|
@@ -135,21 +77,6 @@ const AudioPlayer = React.forwardRef(
|
|
|
135
77
|
abortController.abort();
|
|
136
78
|
};
|
|
137
79
|
}, [src]);
|
|
138
|
-
React.useEffect(() => {
|
|
139
|
-
const track = trackRef.current;
|
|
140
|
-
if (!track) return;
|
|
141
|
-
const updateFromWidth = (width) => {
|
|
142
|
-
if (width <= 0) return;
|
|
143
|
-
setBarCount(Math.max(1, Math.floor((width + BAR_GAP_PX) / (BAR_WIDTH_PX + BAR_GAP_PX))));
|
|
144
|
-
};
|
|
145
|
-
updateFromWidth(track.getBoundingClientRect().width);
|
|
146
|
-
const observer = new ResizeObserver((entries) => {
|
|
147
|
-
const entry = entries[0];
|
|
148
|
-
if (entry) updateFromWidth(entry.contentRect.width);
|
|
149
|
-
});
|
|
150
|
-
observer.observe(track);
|
|
151
|
-
return () => observer.disconnect();
|
|
152
|
-
}, []);
|
|
153
80
|
React.useEffect(() => {
|
|
154
81
|
const audio = audioRef.current;
|
|
155
82
|
if (!audio) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioPlayer.mjs","sources":["../../../src/components/AudioPlayer/AudioPlayer.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { PauseIcon } from \"../Icons/PauseIcon\";\nimport { PlayIcon } from \"../Icons/PlayIcon\";\n\n/** Width of a single waveform bar, in pixels. */\nconst BAR_WIDTH_PX = 4;\n/** Gap between waveform bars, in pixels. */\nconst BAR_GAP_PX = 4;\n/** Shortest a waveform bar can render, in pixels. */\nconst MIN_BAR_HEIGHT_PX = 4;\n/** Tallest a waveform bar can render, in pixels — matches the 40px row height. */\nconst MAX_BAR_HEIGHT_PX = 26;\n/** Bar count used before the container has been measured (e.g. during SSR or in jsdom, where layout is unavailable). */\nconst DEFAULT_BAR_COUNT = 24;\n/** Number of amplitude samples kept internally, resampled to the rendered bar count. */\nconst RAW_PEAK_COUNT = 128;\n/** Seconds moved per Arrow key press while seeking. */\nconst SEEK_STEP_SECONDS = 1;\n/** Seconds moved per Page Up/Down press while seeking. */\nconst SEEK_STEP_LARGE_SECONDS = 5;\n\n/** Height of the audio player row, in pixels. Matches both the Vault card overlay and the \"Generated Audio\" modal row in Figma — both use an identical 32px play button and 40px row. */\nexport type AudioPlayerSize = \"40\";\n\nexport interface AudioPlayerProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onPlay\" | \"onPause\" | \"onEnded\"> {\n /** URL of the audio file to play. */\n src: string;\n /**\n * Total duration in seconds, used to render the timestamp before the\n * media's own metadata has loaded (and as a fallback if it never does).\n */\n duration?: number;\n /**\n * Whether playback is active (controlled). Note: once `onEnded` fires, the\n * browser has already stopped native playback. To loop or replay under\n * controlled usage, toggle this prop `false` then `true` (rather than\n * leaving it `true`) — the sync effect only calls `play()` again when this\n * value actually changes.\n */\n playing?: boolean;\n /** Initial playback state (uncontrolled). @default false */\n defaultPlaying?: boolean;\n /** Called when playback starts. */\n onPlay?: () => void;\n /** Called when playback is paused. */\n onPause?: () => void;\n /** Called when playback reaches the end of the media. */\n onEnded?: () => void;\n /** Height of the player row, in pixels. @default \"40\" */\n size?: AudioPlayerSize;\n}\n\nfunction formatTime(seconds: number | undefined): string {\n if (seconds === undefined || !Number.isFinite(seconds)) return \"--:--\";\n const totalSeconds = Math.max(0, Math.floor(seconds));\n const minutes = Math.floor(totalSeconds / 60);\n const remainingSeconds = totalSeconds % 60;\n return `${minutes}:${String(remainingSeconds).padStart(2, \"0\")}`;\n}\n\n/** Deterministic 32-bit string hash (FNV-1a), used to seed the fallback waveform. */\nfunction hashString(value: string): number {\n let hash = 2166136261;\n for (let i = 0; i < value.length; i++) {\n hash ^= value.charCodeAt(i);\n hash = Math.imul(hash, 16777619);\n }\n return hash >>> 0;\n}\n\n/** Seeded PRNG (mulberry32) — deterministic across runs, unlike `Math.random`. */\nfunction createSeededRandom(seed: number): () => number {\n let state = seed;\n return () => {\n state = (state + 0x6d2b79f5) | 0;\n let t = Math.imul(state ^ (state >>> 15), 1 | state);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/** Deterministic placeholder amplitudes used when audio can't be decoded (network/CORS/format failure). */\nfunction generateFallbackPeaks(src: string, count: number): number[] {\n const random = createSeededRandom(hashString(src));\n return Array.from({ length: count }, () => 0.2 + random() * 0.8);\n}\n\n/** Downsamples decoded PCM data to `count` peak (max-abs) amplitudes. */\nfunction computePeaksFromChannelData(channelData: Float32Array, count: number): number[] {\n const blockSize = Math.max(1, Math.floor(channelData.length / count));\n const peaks: number[] = [];\n for (let i = 0; i < count; i++) {\n const start = i * blockSize;\n let max = 0;\n for (let j = 0; j < blockSize && start + j < channelData.length; j++) {\n max = Math.max(max, Math.abs(channelData[start + j] ?? 0));\n }\n peaks.push(max);\n }\n return peaks;\n}\n\n/** Resamples a peaks array to the number of bars that currently fit the container. */\nfunction resamplePeaks(peaks: number[], barCount: number): number[] {\n if (peaks.length === 0 || barCount <= 0) return [];\n const step = peaks.length / barCount;\n return Array.from(\n { length: barCount },\n (_, i) => peaks[Math.min(peaks.length - 1, Math.floor(i * step))] ?? 0,\n );\n}\n\n/** Decodes `src` via WebAudio and returns downsampled peak amplitudes. Throws on any failure. */\nasync function decodeAudioPeaks(src: string, signal: AbortSignal): Promise<number[]> {\n const AudioContextCtor =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!AudioContextCtor) throw new Error(\"WebAudio unsupported\");\n\n const response = await fetch(src, { signal });\n const arrayBuffer = await response.arrayBuffer();\n const audioContext = new AudioContextCtor();\n try {\n const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);\n return computePeaksFromChannelData(audioBuffer.getChannelData(0), RAW_PEAK_COUNT);\n } finally {\n if (audioContext.state !== \"closed\") audioContext.close().catch(() => {});\n }\n}\n\n/**\n * A compact playback control for a single audio clip: a play/pause toggle, a\n * static amplitude waveform that doubles as a seek scrubber, and elapsed/total\n * timestamps. Designed to sit as an overlay on media thumbnails (Vault cards)\n * as well as inline rows (the AI Voice Message \"Generated Audio\" row).\n *\n * The waveform is seeded from the decoded audio when possible, falling back\n * to a deterministic (not random) placeholder derived from `src` so the\n * result is stable across renders, SSR, and Chromatic snapshots.\n *\n * @example\n * ```tsx\n * <AudioPlayer src=\"https://example.com/clip.mp3\" duration={5} />\n * ```\n */\nexport const AudioPlayer = React.forwardRef<HTMLDivElement, AudioPlayerProps>(\n (\n {\n className,\n src,\n duration,\n playing: controlledPlaying,\n defaultPlaying = false,\n onPlay,\n onPause,\n onEnded,\n size = \"40\",\n ...props\n },\n ref,\n ) => {\n const audioRef = React.useRef<HTMLAudioElement>(null);\n const trackRef = React.useRef<HTMLDivElement>(null);\n\n const [internalPlaying, setInternalPlaying] = React.useState(defaultPlaying);\n const isControlled = controlledPlaying !== undefined;\n const playing = isControlled ? controlledPlaying : internalPlaying;\n\n const [currentTime, setCurrentTime] = React.useState(0);\n const [mediaDuration, setMediaDuration] = React.useState<number | undefined>(undefined);\n const [barCount, setBarCount] = React.useState(DEFAULT_BAR_COUNT);\n const [peaks, setPeaks] = React.useState<number[]>(() =>\n generateFallbackPeaks(src, RAW_PEAK_COUNT),\n );\n const [isDragging, setIsDragging] = React.useState(false);\n\n const displayDuration = mediaDuration ?? duration;\n const hasStarted = playing || currentTime > 0;\n\n // Latest-value refs so the src-change effect below can react to a src swap\n // without re-running whenever `playing`/`isControlled`/`onPause` change.\n const playingRef = React.useRef(playing);\n playingRef.current = playing;\n const isControlledRef = React.useRef(isControlled);\n isControlledRef.current = isControlled;\n const onPauseRef = React.useRef(onPause);\n onPauseRef.current = onPause;\n\n // Reset transient playback state when the source changes. Skips the\n // initial mount (there's nothing to reset yet) and, if playback was\n // active, stops it — the browser silently drops playback on a source\n // swap, so the UI (and any controlled parent) must be told via onPause.\n const isFirstSrcRenderRef = React.useRef(true);\n React.useEffect(() => {\n if (isFirstSrcRenderRef.current) {\n isFirstSrcRenderRef.current = false;\n return;\n }\n setCurrentTime(0);\n setMediaDuration(undefined);\n setPeaks(generateFallbackPeaks(src, RAW_PEAK_COUNT));\n if (playingRef.current) {\n if (!isControlledRef.current) setInternalPlaying(false);\n onPauseRef.current?.();\n }\n }, [src]);\n\n // Decode the audio to derive real waveform amplitudes, falling back to a\n // deterministic placeholder on any failure (network, CORS, unsupported format).\n React.useEffect(() => {\n let cancelled = false;\n const abortController = new AbortController();\n\n decodeAudioPeaks(src, abortController.signal)\n .then((decodedPeaks) => {\n if (!cancelled) setPeaks(decodedPeaks);\n })\n .catch(() => {\n if (!cancelled) setPeaks(generateFallbackPeaks(src, RAW_PEAK_COUNT));\n });\n\n return () => {\n cancelled = true;\n abortController.abort();\n };\n }, [src]);\n\n // Measure the track to decide how many bars fit; falls back to DEFAULT_BAR_COUNT\n // when layout is unavailable (SSR, jsdom).\n React.useEffect(() => {\n const track = trackRef.current;\n if (!track) return;\n\n const updateFromWidth = (width: number) => {\n if (width <= 0) return;\n setBarCount(Math.max(1, Math.floor((width + BAR_GAP_PX) / (BAR_WIDTH_PX + BAR_GAP_PX))));\n };\n\n updateFromWidth(track.getBoundingClientRect().width);\n\n const observer = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (entry) updateFromWidth(entry.contentRect.width);\n });\n observer.observe(track);\n return () => observer.disconnect();\n }, []);\n\n // Sync the controlled/uncontrolled `playing` state to the media element.\n React.useEffect(() => {\n const audio = audioRef.current;\n if (!audio) return;\n if (playing) {\n audio.play().catch(() => {\n if (!isControlled) setInternalPlaying(false);\n onPauseRef.current?.();\n });\n } else {\n audio.pause();\n }\n }, [playing, isControlled]);\n\n const handleToggle = () => {\n const next = !playing;\n if (!isControlled) setInternalPlaying(next);\n if (next) onPlay?.();\n else onPause?.();\n };\n\n const seekTo = React.useCallback(\n (time: number) => {\n if (displayDuration === undefined) return;\n const clamped = Math.min(Math.max(time, 0), displayDuration);\n setCurrentTime(clamped);\n const audio = audioRef.current;\n if (audio) audio.currentTime = clamped;\n },\n [displayDuration],\n );\n\n const seekFromClientX = React.useCallback(\n (clientX: number) => {\n const track = trackRef.current;\n if (!track || displayDuration === undefined) return;\n const rect = track.getBoundingClientRect();\n if (rect.width <= 0) return;\n const fraction = Math.min(Math.max((clientX - rect.left) / rect.width, 0), 1);\n seekTo(fraction * displayDuration);\n },\n [displayDuration, seekTo],\n );\n\n // Track drag outside the element via window listeners rather than pointer\n // capture, which some environments (and jsdom) don't fully implement.\n React.useEffect(() => {\n if (!isDragging) return;\n const handleWindowPointerMove = (event: PointerEvent) => seekFromClientX(event.clientX);\n const handleWindowPointerUp = () => setIsDragging(false);\n window.addEventListener(\"pointermove\", handleWindowPointerMove);\n window.addEventListener(\"pointerup\", handleWindowPointerUp);\n return () => {\n window.removeEventListener(\"pointermove\", handleWindowPointerMove);\n window.removeEventListener(\"pointerup\", handleWindowPointerUp);\n };\n }, [isDragging, seekFromClientX]);\n\n const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {\n seekFromClientX(event.clientX);\n setIsDragging(true);\n };\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (displayDuration === undefined) return;\n switch (event.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n event.preventDefault();\n seekTo(currentTime + SEEK_STEP_SECONDS);\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n event.preventDefault();\n seekTo(currentTime - SEEK_STEP_SECONDS);\n break;\n case \"PageUp\":\n event.preventDefault();\n seekTo(currentTime + SEEK_STEP_LARGE_SECONDS);\n break;\n case \"PageDown\":\n event.preventDefault();\n seekTo(currentTime - SEEK_STEP_LARGE_SECONDS);\n break;\n case \"Home\":\n event.preventDefault();\n seekTo(0);\n break;\n case \"End\":\n event.preventDefault();\n seekTo(displayDuration);\n break;\n default:\n break;\n }\n };\n\n const bars = resamplePeaks(peaks, barCount);\n const progressFraction =\n displayDuration && displayDuration > 0\n ? Math.min(Math.max(currentTime / displayDuration, 0), 1)\n : 0;\n const playedBarCount = hasStarted ? Math.round(progressFraction * bars.length) : bars.length;\n const barData = bars.map((amplitude, index) => {\n const isPlayed = index < playedBarCount;\n return {\n heightPx: MIN_BAR_HEIGHT_PX + amplitude * (MAX_BAR_HEIGHT_PX - MIN_BAR_HEIGHT_PX),\n className: cn(\n \"w-1 shrink-0 rounded-3xs bg-messages-waveform-default\",\n \"motion-safe:transition-opacity motion-safe:duration-150 motion-safe:ease-in-out\",\n isPlayed ? \"opacity-100\" : \"opacity-50\",\n ),\n };\n });\n\n return (\n <div\n ref={ref}\n data-size={size}\n className={cn(\"flex w-full items-center gap-3\", className)}\n {...props}\n >\n <button\n type=\"button\"\n onClick={handleToggle}\n aria-label={playing ? \"Pause\" : \"Play\"}\n className={cn(\n \"flex size-8 shrink-0 items-center justify-center rounded-full\",\n \"bg-buttons-secondary-default text-icons-primary backdrop-blur-[40px]\",\n \"motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out\",\n \"hover:bg-buttons-secondary-hover\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n )}\n >\n {playing ? <PauseIcon size={16} filled /> : <PlayIcon size={16} filled />}\n </button>\n\n <div className=\"flex min-w-0 flex-1 items-center gap-3\">\n <div\n ref={trackRef}\n role=\"slider\"\n tabIndex={0}\n aria-label=\"Seek\"\n aria-orientation=\"horizontal\"\n aria-valuemin={0}\n aria-valuemax={displayDuration}\n aria-valuenow={Math.round(currentTime)}\n aria-valuetext={\n displayDuration !== undefined\n ? `${formatTime(currentTime)} of ${formatTime(displayDuration)}`\n : formatTime(currentTime)\n }\n onPointerDown={handlePointerDown}\n onKeyDown={handleKeyDown}\n className={cn(\n \"flex h-10 min-w-0 flex-1 cursor-pointer touch-none select-none items-center gap-1 overflow-hidden\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n )}\n >\n {barData.map((bar, index) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: bars are a fixed-count, non-reorderable list\n <span key={index} className={bar.className} style={{ height: bar.heightPx }} />\n ))}\n </div>\n\n <span className=\"typography-body-small-14px-semibold flex shrink-0 items-center gap-0.5 whitespace-nowrap\">\n {hasStarted ? (\n <>\n <span className=\"text-content-primary\">{formatTime(currentTime)}</span>\n <span className=\"text-content-secondary\">/{formatTime(displayDuration)}</span>\n </>\n ) : (\n <span className=\"text-content-primary\">{formatTime(displayDuration)}</span>\n )}\n </span>\n </div>\n\n {/* biome-ignore lint/a11y/useMediaCaption: this is a UI sound clip (voice note / short recording), not spoken/narrated content requiring captions. */}\n <audio\n ref={audioRef}\n src={src}\n preload=\"metadata\"\n className=\"hidden\"\n onLoadedMetadata={(event) => {\n const { duration: nativeDuration } = event.currentTarget;\n setMediaDuration(Number.isFinite(nativeDuration) ? nativeDuration : undefined);\n }}\n onTimeUpdate={(event) => {\n if (isDragging) return;\n setCurrentTime(event.currentTarget.currentTime);\n }}\n onEnded={() => {\n setCurrentTime(0);\n if (!isControlled) setInternalPlaying(false);\n onEnded?.();\n }}\n />\n </div>\n );\n },\n);\n\nAudioPlayer.displayName = \"AudioPlayer\";\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AudioPlayer.mjs","sources":["../../../src/components/AudioPlayer/AudioPlayer.tsx"],"sourcesContent":["import * as React from \"react\";\nimport {\n decodeAudioPeaks,\n formatTime,\n generateFallbackPeaks,\n RAW_PEAK_COUNT,\n resamplePeaks,\n} from \"../../utils/audioWaveform\";\nimport { cn } from \"../../utils/cn\";\nimport { useFittedBarCount } from \"../../utils/useFittedBarCount\";\nimport { PauseIcon } from \"../Icons/PauseIcon\";\nimport { PlayIcon } from \"../Icons/PlayIcon\";\n\n/** Width of a single waveform bar, in pixels. */\nconst BAR_WIDTH_PX = 4;\n/** Gap between waveform bars, in pixels. */\nconst BAR_GAP_PX = 4;\n/** Shortest a waveform bar can render, in pixels. */\nconst MIN_BAR_HEIGHT_PX = 4;\n/** Tallest a waveform bar can render, in pixels — matches the 40px row height. */\nconst MAX_BAR_HEIGHT_PX = 26;\n/** Bar count used before the container has been measured (e.g. during SSR or in jsdom, where layout is unavailable). */\nconst DEFAULT_BAR_COUNT = 24;\n/** Seconds moved per Arrow key press while seeking. */\nconst SEEK_STEP_SECONDS = 1;\n/** Seconds moved per Page Up/Down press while seeking. */\nconst SEEK_STEP_LARGE_SECONDS = 5;\n\n/** Height of the audio player row, in pixels. Matches both the Vault card overlay and the \"Generated Audio\" modal row in Figma — both use an identical 32px play button and 40px row. */\nexport type AudioPlayerSize = \"40\";\n\nexport interface AudioPlayerProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onPlay\" | \"onPause\" | \"onEnded\"> {\n /** URL of the audio file to play. */\n src: string;\n /**\n * Total duration in seconds, used to render the timestamp before the\n * media's own metadata has loaded (and as a fallback if it never does).\n */\n duration?: number;\n /**\n * Whether playback is active (controlled). Note: once `onEnded` fires, the\n * browser has already stopped native playback. To loop or replay under\n * controlled usage, toggle this prop `false` then `true` (rather than\n * leaving it `true`) — the sync effect only calls `play()` again when this\n * value actually changes.\n */\n playing?: boolean;\n /** Initial playback state (uncontrolled). @default false */\n defaultPlaying?: boolean;\n /** Called when playback starts. */\n onPlay?: () => void;\n /** Called when playback is paused. */\n onPause?: () => void;\n /** Called when playback reaches the end of the media. */\n onEnded?: () => void;\n /** Height of the player row, in pixels. @default \"40\" */\n size?: AudioPlayerSize;\n}\n\n/**\n * A compact playback control for a single audio clip: a play/pause toggle, a\n * static amplitude waveform that doubles as a seek scrubber, and elapsed/total\n * timestamps. Designed to sit as an overlay on media thumbnails (Vault cards)\n * as well as inline rows (the AI Voice Message \"Generated Audio\" row).\n *\n * The waveform is seeded from the decoded audio when possible, falling back\n * to a deterministic (not random) placeholder derived from `src` so the\n * result is stable across renders, SSR, and Chromatic snapshots.\n *\n * @example\n * ```tsx\n * <AudioPlayer src=\"https://example.com/clip.mp3\" duration={5} />\n * ```\n */\nexport const AudioPlayer = React.forwardRef<HTMLDivElement, AudioPlayerProps>(\n (\n {\n className,\n src,\n duration,\n playing: controlledPlaying,\n defaultPlaying = false,\n onPlay,\n onPause,\n onEnded,\n size = \"40\",\n ...props\n },\n ref,\n ) => {\n const audioRef = React.useRef<HTMLAudioElement>(null);\n const trackRef = React.useRef<HTMLDivElement>(null);\n\n const [internalPlaying, setInternalPlaying] = React.useState(defaultPlaying);\n const isControlled = controlledPlaying !== undefined;\n const playing = isControlled ? controlledPlaying : internalPlaying;\n\n const [currentTime, setCurrentTime] = React.useState(0);\n const [mediaDuration, setMediaDuration] = React.useState<number | undefined>(undefined);\n const barCount = useFittedBarCount(trackRef, {\n barWidthPx: BAR_WIDTH_PX,\n gapPx: BAR_GAP_PX,\n fallback: DEFAULT_BAR_COUNT,\n });\n const [peaks, setPeaks] = React.useState<number[]>(() =>\n generateFallbackPeaks(src, RAW_PEAK_COUNT),\n );\n const [isDragging, setIsDragging] = React.useState(false);\n\n const displayDuration = mediaDuration ?? duration;\n const hasStarted = playing || currentTime > 0;\n\n // Latest-value refs so the src-change effect below can react to a src swap\n // without re-running whenever `playing`/`isControlled`/`onPause` change.\n const playingRef = React.useRef(playing);\n playingRef.current = playing;\n const isControlledRef = React.useRef(isControlled);\n isControlledRef.current = isControlled;\n const onPauseRef = React.useRef(onPause);\n onPauseRef.current = onPause;\n\n // Reset transient playback state when the source changes. Skips the\n // initial mount (there's nothing to reset yet) and, if playback was\n // active, stops it — the browser silently drops playback on a source\n // swap, so the UI (and any controlled parent) must be told via onPause.\n const isFirstSrcRenderRef = React.useRef(true);\n React.useEffect(() => {\n if (isFirstSrcRenderRef.current) {\n isFirstSrcRenderRef.current = false;\n return;\n }\n setCurrentTime(0);\n setMediaDuration(undefined);\n setPeaks(generateFallbackPeaks(src, RAW_PEAK_COUNT));\n if (playingRef.current) {\n if (!isControlledRef.current) setInternalPlaying(false);\n onPauseRef.current?.();\n }\n }, [src]);\n\n // Decode the audio to derive real waveform amplitudes, falling back to a\n // deterministic placeholder on any failure (network, CORS, unsupported format).\n React.useEffect(() => {\n let cancelled = false;\n const abortController = new AbortController();\n\n decodeAudioPeaks(src, abortController.signal)\n .then((decodedPeaks) => {\n if (!cancelled) setPeaks(decodedPeaks);\n })\n .catch(() => {\n if (!cancelled) setPeaks(generateFallbackPeaks(src, RAW_PEAK_COUNT));\n });\n\n return () => {\n cancelled = true;\n abortController.abort();\n };\n }, [src]);\n\n // Sync the controlled/uncontrolled `playing` state to the media element.\n React.useEffect(() => {\n const audio = audioRef.current;\n if (!audio) return;\n if (playing) {\n audio.play().catch(() => {\n if (!isControlled) setInternalPlaying(false);\n onPauseRef.current?.();\n });\n } else {\n audio.pause();\n }\n }, [playing, isControlled]);\n\n const handleToggle = () => {\n const next = !playing;\n if (!isControlled) setInternalPlaying(next);\n if (next) onPlay?.();\n else onPause?.();\n };\n\n const seekTo = React.useCallback(\n (time: number) => {\n if (displayDuration === undefined) return;\n const clamped = Math.min(Math.max(time, 0), displayDuration);\n setCurrentTime(clamped);\n const audio = audioRef.current;\n if (audio) audio.currentTime = clamped;\n },\n [displayDuration],\n );\n\n const seekFromClientX = React.useCallback(\n (clientX: number) => {\n const track = trackRef.current;\n if (!track || displayDuration === undefined) return;\n const rect = track.getBoundingClientRect();\n if (rect.width <= 0) return;\n const fraction = Math.min(Math.max((clientX - rect.left) / rect.width, 0), 1);\n seekTo(fraction * displayDuration);\n },\n [displayDuration, seekTo],\n );\n\n // Track drag outside the element via window listeners rather than pointer\n // capture, which some environments (and jsdom) don't fully implement.\n React.useEffect(() => {\n if (!isDragging) return;\n const handleWindowPointerMove = (event: PointerEvent) => seekFromClientX(event.clientX);\n const handleWindowPointerUp = () => setIsDragging(false);\n window.addEventListener(\"pointermove\", handleWindowPointerMove);\n window.addEventListener(\"pointerup\", handleWindowPointerUp);\n return () => {\n window.removeEventListener(\"pointermove\", handleWindowPointerMove);\n window.removeEventListener(\"pointerup\", handleWindowPointerUp);\n };\n }, [isDragging, seekFromClientX]);\n\n const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {\n seekFromClientX(event.clientX);\n setIsDragging(true);\n };\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (displayDuration === undefined) return;\n switch (event.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n event.preventDefault();\n seekTo(currentTime + SEEK_STEP_SECONDS);\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n event.preventDefault();\n seekTo(currentTime - SEEK_STEP_SECONDS);\n break;\n case \"PageUp\":\n event.preventDefault();\n seekTo(currentTime + SEEK_STEP_LARGE_SECONDS);\n break;\n case \"PageDown\":\n event.preventDefault();\n seekTo(currentTime - SEEK_STEP_LARGE_SECONDS);\n break;\n case \"Home\":\n event.preventDefault();\n seekTo(0);\n break;\n case \"End\":\n event.preventDefault();\n seekTo(displayDuration);\n break;\n default:\n break;\n }\n };\n\n const bars = resamplePeaks(peaks, barCount);\n const progressFraction =\n displayDuration && displayDuration > 0\n ? Math.min(Math.max(currentTime / displayDuration, 0), 1)\n : 0;\n const playedBarCount = hasStarted ? Math.round(progressFraction * bars.length) : bars.length;\n const barData = bars.map((amplitude, index) => {\n const isPlayed = index < playedBarCount;\n return {\n heightPx: MIN_BAR_HEIGHT_PX + amplitude * (MAX_BAR_HEIGHT_PX - MIN_BAR_HEIGHT_PX),\n className: cn(\n \"w-1 shrink-0 rounded-3xs bg-messages-waveform-default\",\n \"motion-safe:transition-opacity motion-safe:duration-150 motion-safe:ease-in-out\",\n isPlayed ? \"opacity-100\" : \"opacity-50\",\n ),\n };\n });\n\n return (\n <div\n ref={ref}\n data-size={size}\n className={cn(\"flex w-full items-center gap-3\", className)}\n {...props}\n >\n <button\n type=\"button\"\n onClick={handleToggle}\n aria-label={playing ? \"Pause\" : \"Play\"}\n className={cn(\n \"flex size-8 shrink-0 items-center justify-center rounded-full\",\n \"bg-buttons-secondary-default text-icons-primary backdrop-blur-[40px]\",\n \"motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out\",\n \"hover:bg-buttons-secondary-hover\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n )}\n >\n {playing ? <PauseIcon size={16} filled /> : <PlayIcon size={16} filled />}\n </button>\n\n <div className=\"flex min-w-0 flex-1 items-center gap-3\">\n <div\n ref={trackRef}\n role=\"slider\"\n tabIndex={0}\n aria-label=\"Seek\"\n aria-orientation=\"horizontal\"\n aria-valuemin={0}\n aria-valuemax={displayDuration}\n aria-valuenow={Math.round(currentTime)}\n aria-valuetext={\n displayDuration !== undefined\n ? `${formatTime(currentTime)} of ${formatTime(displayDuration)}`\n : formatTime(currentTime)\n }\n onPointerDown={handlePointerDown}\n onKeyDown={handleKeyDown}\n className={cn(\n \"flex h-10 min-w-0 flex-1 cursor-pointer touch-none select-none items-center gap-1 overflow-hidden\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n )}\n >\n {barData.map((bar, index) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: bars are a fixed-count, non-reorderable list\n <span key={index} className={bar.className} style={{ height: bar.heightPx }} />\n ))}\n </div>\n\n <span className=\"typography-body-small-14px-semibold flex shrink-0 items-center gap-0.5 whitespace-nowrap\">\n {hasStarted ? (\n <>\n <span className=\"text-content-primary\">{formatTime(currentTime)}</span>\n <span className=\"text-content-secondary\">/{formatTime(displayDuration)}</span>\n </>\n ) : (\n <span className=\"text-content-primary\">{formatTime(displayDuration)}</span>\n )}\n </span>\n </div>\n\n {/* biome-ignore lint/a11y/useMediaCaption: this is a UI sound clip (voice note / short recording), not spoken/narrated content requiring captions. */}\n <audio\n ref={audioRef}\n src={src}\n preload=\"metadata\"\n className=\"hidden\"\n onLoadedMetadata={(event) => {\n const { duration: nativeDuration } = event.currentTarget;\n setMediaDuration(Number.isFinite(nativeDuration) ? nativeDuration : undefined);\n }}\n onTimeUpdate={(event) => {\n if (isDragging) return;\n setCurrentTime(event.currentTarget.currentTime);\n }}\n onEnded={() => {\n setCurrentTime(0);\n if (!isControlled) setInternalPlaying(false);\n onEnded?.();\n }}\n />\n </div>\n );\n },\n);\n\nAudioPlayer.displayName = \"AudioPlayer\";\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { BaseIcon } from "./BaseIcon.mjs";
|
|
5
|
+
const VARIANTS = {
|
|
6
|
+
16: {
|
|
7
|
+
outlined: [
|
|
8
|
+
{
|
|
9
|
+
d: "M11.713 2.573a.504.504 0 0 1-.5-.5V1.34c0-.273.227-.5.5-.5s.5.227.5.5v.733c0 .274-.227.5-.5.5m0 5.127a.504.504 0 0 1-.5-.5v-.733c0-.274.227-.5.5-.5s.5.226.5.5V7.2c0 .273-.227.5-.5.5"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
d: "M12.153 6.973h-.806c-.554 0-1.06-.3-1.34-.773a.501.501 0 1 1 .866-.507c.1.167.28.273.48.28h.8a.532.532 0 0 0 .134-1.047l-1.374-.346a1.535 1.535 0 0 1-1.146-1.674 1.526 1.526 0 0 1 1.513-1.34h.807c.553 0 1.06.3 1.333.774a.501.501 0 1 1-.867.506.56.56 0 0 0-.473-.273h-.8a.532.532 0 0 0-.133 1.047l1.373.346a1.526 1.526 0 0 1-.367 3.007m-8.48 8.187H2.227a1.22 1.22 0 0 1-1.22-1.22V9.613c0-.673.546-1.22 1.22-1.22h1.44c.673 0 1.22.547 1.22 1.22v4.327a1.22 1.22 0 0 1-1.22 1.22zM2.227 9.393c-.12 0-.22.1-.22.22v4.327c0 .12.1.22.22.22h1.44c.12 0 .22-.1.22-.22V9.613c0-.12-.1-.22-.22-.22z"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
d: "M8.527 15.087a3.4 3.4 0 0 1-.82-.1l-1.914-.48a2.4 2.4 0 0 0-.58-.074h-.82a.504.504 0 0 1-.5-.5c0-.273.227-.5.5-.5h.82c.274 0 .554.034.82.1l1.914.48c.34.087.7.094 1.046.027l2.114-.42c.46-.093.886-.32 1.22-.653l1.493-1.494a.58.58 0 0 0 .173-.42.59.59 0 0 0-.947-.473l-1.74 1.307a1.93 1.93 0 0 1-1.166.386H8.727a.504.504 0 0 1-.5-.5c0-.273.226-.5.5-.5h1.413c.2 0 .4-.066.567-.186l1.74-1.307a1.6 1.6 0 0 1 2.08.147c.3.3.466.7.466 1.126a1.6 1.6 0 0 1-.466 1.127l-1.494 1.493a3.4 3.4 0 0 1-1.726.927l-2.114.42q-.33.069-.66.067z"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
d: "M9.793 12.28H8.72a.504.504 0 0 1-.5-.5c0-.273.227-.5.5-.5h1.073c.16 0 .307-.06.42-.173a.6.6 0 0 0 .174-.42v-.22a.59.59 0 0 0-.447-.573L8.287 9.48a2.92 2.92 0 0 0-2.3.4l-1.314.874a.496.496 0 0 1-.693-.14.496.496 0 0 1 .14-.694l1.313-.873c.907-.6 2.047-.8 3.1-.54l1.654.413a1.59 1.59 0 0 1 1.206 1.547v.22c0 .427-.166.827-.466 1.127s-.7.466-1.127.466z"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
24: {
|
|
23
|
+
outlined: [
|
|
24
|
+
{
|
|
25
|
+
d: "M17.57 3.86c-.41 0-.75-.34-.75-.75v-1.1c0-.41.34-.75.75-.75s.75.34.75.75v1.1c0 .41-.34.75-.75.75m0 7.69c-.41 0-.75-.34-.75-.75V9.7c0-.41.34-.75.75-.75s.75.34.75.75v1.1c0 .41-.34.75-.75.75"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
d: "M18.23 10.46h-1.21c-.83 0-1.59-.45-2.01-1.16a.752.752 0 1 1 1.3-.76c.15.25.42.41.72.42h1.2a.798.798 0 0 0 .2-1.57l-2.06-.52a2.3 2.3 0 0 1-1.72-2.51 2.29 2.29 0 0 1 2.27-2.01h1.21c.83 0 1.59.45 2 1.16a.752.752 0 1 1-1.3.76.84.84 0 0 0-.71-.41h-1.2a.798.798 0 0 0-.2 1.57l2.06.52c1.12.28 1.86 1.36 1.72 2.5a2.29 2.29 0 0 1-2.27 2.01M5.51 22.74H3.34a1.83 1.83 0 0 1-1.83-1.83v-6.49c0-1.01.82-1.83 1.83-1.83H5.5c1.01 0 1.83.82 1.83 1.83v6.49c0 1.01-.82 1.83-1.83 1.83zm-2.17-8.65c-.18 0-.33.15-.33.33v6.49c0 .18.15.33.33.33H5.5c.18 0 .33-.15.33-.33v-6.49c0-.18-.15-.33-.33-.33z"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
d: "M12.79 22.63q-.63 0-1.23-.15l-2.87-.72c-.28-.07-.58-.11-.87-.11H6.59c-.41 0-.75-.34-.75-.75s.34-.75.75-.75h1.23c.41 0 .83.05 1.23.15l2.87.72c.51.13 1.05.14 1.57.04l3.17-.63c.69-.14 1.33-.48 1.83-.98l2.24-2.24c.17-.17.26-.39.26-.63s-.09-.46-.26-.63a.884.884 0 0 0-1.16-.08l-2.61 1.96c-.5.38-1.12.58-1.75.58h-2.12c-.41 0-.75-.34-.75-.75s.34-.75.75-.75h2.12c.3 0 .6-.1.85-.28l2.61-1.96c.94-.71 2.29-.61 3.12.22.45.45.7 1.05.7 1.69a2.4 2.4 0 0 1-.7 1.69l-2.24 2.24a5.1 5.1 0 0 1-2.59 1.39l-3.17.63c-.33.07-.66.1-.99.1z"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
d: "M14.69 18.42h-1.61c-.41 0-.75-.34-.75-.75s.34-.75.75-.75h1.61c.24 0 .46-.09.63-.26s.26-.39.26-.63v-.33c0-.41-.28-.76-.67-.86l-2.48-.62a4.38 4.38 0 0 0-3.45.6l-1.97 1.31c-.35.23-.81.14-1.04-.21a.745.745 0 0 1 .21-1.04l1.97-1.31a5.92 5.92 0 0 1 4.65-.81l2.48.62c1.07.27 1.81 1.22 1.81 2.32v.33c0 .64-.25 1.24-.7 1.69s-1.05.7-1.69.7z"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
32: {
|
|
39
|
+
outlined: [
|
|
40
|
+
{
|
|
41
|
+
d: "M23.426 5.146c-.547 0-1-.453-1-1V2.68c0-.547.453-1 1-1s1 .453 1 1v1.466c0 .547-.454 1-1 1m0 10.254c-.547 0-1-.453-1-1v-1.466c0-.547.453-1 1-1s1 .453 1 1V14.4c0 .547-.454 1-1 1"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
d: "M24.307 13.946h-1.613c-1.107 0-2.12-.6-2.68-1.547-.28-.48-.12-1.093.36-1.373s1.093-.12 1.373.36c.2.333.56.547.96.56h1.6a1.064 1.064 0 0 0 .267-2.093l-2.747-.694a3.07 3.07 0 0 1-2.293-3.346 3.05 3.05 0 0 1 3.026-2.68h1.614c1.106 0 2.12.6 2.666 1.546a1.003 1.003 0 1 1-1.733 1.014c-.2-.334-.56-.547-.947-.547h-1.6a1.065 1.065 0 0 0-.266 2.093l2.746.694a3.064 3.064 0 0 1 2.294 3.333 3.05 3.05 0 0 1-3.027 2.68M7.347 30.32H4.453a2.44 2.44 0 0 1-2.44-2.44v-8.653a2.44 2.44 0 0 1 2.44-2.44h2.88a2.44 2.44 0 0 1 2.44 2.44v8.653a2.44 2.44 0 0 1-2.44 2.44zM4.453 18.787c-.24 0-.44.2-.44.44v8.653c0 .24.2.44.44.44h2.88c.24 0 .44-.2.44-.44v-8.653c0-.24-.2-.44-.44-.44z"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
d: "M17.053 30.173q-.84 0-1.64-.2l-3.826-.96a5 5 0 0 0-1.16-.147h-1.64c-.547 0-1-.453-1-1s.453-1 1-1h1.64a6.8 6.8 0 0 1 1.64.2l3.826.96c.68.174 1.4.187 2.094.054l4.226-.84a4.8 4.8 0 0 0 2.44-1.307l2.987-2.987c.227-.226.347-.52.347-.84s-.12-.613-.347-.84a1.18 1.18 0 0 0-1.547-.106l-3.48 2.613a3.86 3.86 0 0 1-2.333.773h-2.827c-.546 0-1-.453-1-1s.454-1 1-1h2.827c.4 0 .8-.133 1.133-.373l3.48-2.613c1.254-.947 3.054-.814 4.16.293.6.6.934 1.4.934 2.253s-.347 1.667-.934 2.254l-2.986 2.986a6.8 6.8 0 0 1-3.454 1.854l-4.226.84c-.44.093-.88.133-1.32.133z"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
d: "M19.587 24.56H17.44c-.547 0-1-.453-1-1s.453-1 1-1h2.147c.32 0 .613-.12.84-.346a1.17 1.17 0 0 0 .346-.84v-.44c0-.547-.373-1.014-.893-1.147l-3.307-.827a5.84 5.84 0 0 0-4.6.8l-2.626 1.747a.993.993 0 0 1-1.387-.28.993.993 0 0 1 .28-1.387l2.627-1.746a7.9 7.9 0 0 1 6.2-1.08l3.306.826a3.18 3.18 0 0 1 2.414 3.094v.44c0 .853-.334 1.653-.934 2.253s-1.4.933-2.253.933z"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const ReceiveMoneyIcon = React.forwardRef(
|
|
56
|
+
(props, ref) => /* @__PURE__ */ jsx(BaseIcon, { ref, variants: VARIANTS, ...props })
|
|
57
|
+
);
|
|
58
|
+
ReceiveMoneyIcon.displayName = "ReceiveMoneyIcon";
|
|
59
|
+
export {
|
|
60
|
+
ReceiveMoneyIcon
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=ReceiveMoneyIcon.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReceiveMoneyIcon.mjs","sources":["../../../src/components/Icons/ReceiveMoneyIcon.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { BaseIcon } from \"./BaseIcon\";\nimport type { BaseIconProps, IconVariants } from \"./types\";\n\nconst VARIANTS: IconVariants = {\n 16: {\n outlined: [\n {\n d: \"M11.713 2.573a.504.504 0 0 1-.5-.5V1.34c0-.273.227-.5.5-.5s.5.227.5.5v.733c0 .274-.227.5-.5.5m0 5.127a.504.504 0 0 1-.5-.5v-.733c0-.274.227-.5.5-.5s.5.226.5.5V7.2c0 .273-.227.5-.5.5\",\n },\n {\n d: \"M12.153 6.973h-.806c-.554 0-1.06-.3-1.34-.773a.501.501 0 1 1 .866-.507c.1.167.28.273.48.28h.8a.532.532 0 0 0 .134-1.047l-1.374-.346a1.535 1.535 0 0 1-1.146-1.674 1.526 1.526 0 0 1 1.513-1.34h.807c.553 0 1.06.3 1.333.774a.501.501 0 1 1-.867.506.56.56 0 0 0-.473-.273h-.8a.532.532 0 0 0-.133 1.047l1.373.346a1.526 1.526 0 0 1-.367 3.007m-8.48 8.187H2.227a1.22 1.22 0 0 1-1.22-1.22V9.613c0-.673.546-1.22 1.22-1.22h1.44c.673 0 1.22.547 1.22 1.22v4.327a1.22 1.22 0 0 1-1.22 1.22zM2.227 9.393c-.12 0-.22.1-.22.22v4.327c0 .12.1.22.22.22h1.44c.12 0 .22-.1.22-.22V9.613c0-.12-.1-.22-.22-.22z\",\n },\n {\n d: \"M8.527 15.087a3.4 3.4 0 0 1-.82-.1l-1.914-.48a2.4 2.4 0 0 0-.58-.074h-.82a.504.504 0 0 1-.5-.5c0-.273.227-.5.5-.5h.82c.274 0 .554.034.82.1l1.914.48c.34.087.7.094 1.046.027l2.114-.42c.46-.093.886-.32 1.22-.653l1.493-1.494a.58.58 0 0 0 .173-.42.59.59 0 0 0-.947-.473l-1.74 1.307a1.93 1.93 0 0 1-1.166.386H8.727a.504.504 0 0 1-.5-.5c0-.273.226-.5.5-.5h1.413c.2 0 .4-.066.567-.186l1.74-1.307a1.6 1.6 0 0 1 2.08.147c.3.3.466.7.466 1.126a1.6 1.6 0 0 1-.466 1.127l-1.494 1.493a3.4 3.4 0 0 1-1.726.927l-2.114.42q-.33.069-.66.067z\",\n },\n {\n d: \"M9.793 12.28H8.72a.504.504 0 0 1-.5-.5c0-.273.227-.5.5-.5h1.073c.16 0 .307-.06.42-.173a.6.6 0 0 0 .174-.42v-.22a.59.59 0 0 0-.447-.573L8.287 9.48a2.92 2.92 0 0 0-2.3.4l-1.314.874a.496.496 0 0 1-.693-.14.496.496 0 0 1 .14-.694l1.313-.873c.907-.6 2.047-.8 3.1-.54l1.654.413a1.59 1.59 0 0 1 1.206 1.547v.22c0 .427-.166.827-.466 1.127s-.7.466-1.127.466z\",\n },\n ],\n },\n 24: {\n outlined: [\n {\n d: \"M17.57 3.86c-.41 0-.75-.34-.75-.75v-1.1c0-.41.34-.75.75-.75s.75.34.75.75v1.1c0 .41-.34.75-.75.75m0 7.69c-.41 0-.75-.34-.75-.75V9.7c0-.41.34-.75.75-.75s.75.34.75.75v1.1c0 .41-.34.75-.75.75\",\n },\n {\n d: \"M18.23 10.46h-1.21c-.83 0-1.59-.45-2.01-1.16a.752.752 0 1 1 1.3-.76c.15.25.42.41.72.42h1.2a.798.798 0 0 0 .2-1.57l-2.06-.52a2.3 2.3 0 0 1-1.72-2.51 2.29 2.29 0 0 1 2.27-2.01h1.21c.83 0 1.59.45 2 1.16a.752.752 0 1 1-1.3.76.84.84 0 0 0-.71-.41h-1.2a.798.798 0 0 0-.2 1.57l2.06.52c1.12.28 1.86 1.36 1.72 2.5a2.29 2.29 0 0 1-2.27 2.01M5.51 22.74H3.34a1.83 1.83 0 0 1-1.83-1.83v-6.49c0-1.01.82-1.83 1.83-1.83H5.5c1.01 0 1.83.82 1.83 1.83v6.49c0 1.01-.82 1.83-1.83 1.83zm-2.17-8.65c-.18 0-.33.15-.33.33v6.49c0 .18.15.33.33.33H5.5c.18 0 .33-.15.33-.33v-6.49c0-.18-.15-.33-.33-.33z\",\n },\n {\n d: \"M12.79 22.63q-.63 0-1.23-.15l-2.87-.72c-.28-.07-.58-.11-.87-.11H6.59c-.41 0-.75-.34-.75-.75s.34-.75.75-.75h1.23c.41 0 .83.05 1.23.15l2.87.72c.51.13 1.05.14 1.57.04l3.17-.63c.69-.14 1.33-.48 1.83-.98l2.24-2.24c.17-.17.26-.39.26-.63s-.09-.46-.26-.63a.884.884 0 0 0-1.16-.08l-2.61 1.96c-.5.38-1.12.58-1.75.58h-2.12c-.41 0-.75-.34-.75-.75s.34-.75.75-.75h2.12c.3 0 .6-.1.85-.28l2.61-1.96c.94-.71 2.29-.61 3.12.22.45.45.7 1.05.7 1.69a2.4 2.4 0 0 1-.7 1.69l-2.24 2.24a5.1 5.1 0 0 1-2.59 1.39l-3.17.63c-.33.07-.66.1-.99.1z\",\n },\n {\n d: \"M14.69 18.42h-1.61c-.41 0-.75-.34-.75-.75s.34-.75.75-.75h1.61c.24 0 .46-.09.63-.26s.26-.39.26-.63v-.33c0-.41-.28-.76-.67-.86l-2.48-.62a4.38 4.38 0 0 0-3.45.6l-1.97 1.31c-.35.23-.81.14-1.04-.21a.745.745 0 0 1 .21-1.04l1.97-1.31a5.92 5.92 0 0 1 4.65-.81l2.48.62c1.07.27 1.81 1.22 1.81 2.32v.33c0 .64-.25 1.24-.7 1.69s-1.05.7-1.69.7z\",\n },\n ],\n },\n 32: {\n outlined: [\n {\n d: \"M23.426 5.146c-.547 0-1-.453-1-1V2.68c0-.547.453-1 1-1s1 .453 1 1v1.466c0 .547-.454 1-1 1m0 10.254c-.547 0-1-.453-1-1v-1.466c0-.547.453-1 1-1s1 .453 1 1V14.4c0 .547-.454 1-1 1\",\n },\n {\n d: \"M24.307 13.946h-1.613c-1.107 0-2.12-.6-2.68-1.547-.28-.48-.12-1.093.36-1.373s1.093-.12 1.373.36c.2.333.56.547.96.56h1.6a1.064 1.064 0 0 0 .267-2.093l-2.747-.694a3.07 3.07 0 0 1-2.293-3.346 3.05 3.05 0 0 1 3.026-2.68h1.614c1.106 0 2.12.6 2.666 1.546a1.003 1.003 0 1 1-1.733 1.014c-.2-.334-.56-.547-.947-.547h-1.6a1.065 1.065 0 0 0-.266 2.093l2.746.694a3.064 3.064 0 0 1 2.294 3.333 3.05 3.05 0 0 1-3.027 2.68M7.347 30.32H4.453a2.44 2.44 0 0 1-2.44-2.44v-8.653a2.44 2.44 0 0 1 2.44-2.44h2.88a2.44 2.44 0 0 1 2.44 2.44v8.653a2.44 2.44 0 0 1-2.44 2.44zM4.453 18.787c-.24 0-.44.2-.44.44v8.653c0 .24.2.44.44.44h2.88c.24 0 .44-.2.44-.44v-8.653c0-.24-.2-.44-.44-.44z\",\n },\n {\n d: \"M17.053 30.173q-.84 0-1.64-.2l-3.826-.96a5 5 0 0 0-1.16-.147h-1.64c-.547 0-1-.453-1-1s.453-1 1-1h1.64a6.8 6.8 0 0 1 1.64.2l3.826.96c.68.174 1.4.187 2.094.054l4.226-.84a4.8 4.8 0 0 0 2.44-1.307l2.987-2.987c.227-.226.347-.52.347-.84s-.12-.613-.347-.84a1.18 1.18 0 0 0-1.547-.106l-3.48 2.613a3.86 3.86 0 0 1-2.333.773h-2.827c-.546 0-1-.453-1-1s.454-1 1-1h2.827c.4 0 .8-.133 1.133-.373l3.48-2.613c1.254-.947 3.054-.814 4.16.293.6.6.934 1.4.934 2.253s-.347 1.667-.934 2.254l-2.986 2.986a6.8 6.8 0 0 1-3.454 1.854l-4.226.84c-.44.093-.88.133-1.32.133z\",\n },\n {\n d: \"M19.587 24.56H17.44c-.547 0-1-.453-1-1s.453-1 1-1h2.147c.32 0 .613-.12.84-.346a1.17 1.17 0 0 0 .346-.84v-.44c0-.547-.373-1.014-.893-1.147l-3.307-.827a5.84 5.84 0 0 0-4.6.8l-2.626 1.747a.993.993 0 0 1-1.387-.28.993.993 0 0 1 .28-1.387l2.627-1.746a7.9 7.9 0 0 1 6.2-1.08l3.306.826a3.18 3.18 0 0 1 2.414 3.094v.44c0 .853-.334 1.653-.934 2.253s-1.4.933-2.253.933z\",\n },\n ],\n },\n};\n\n/** Props for {@link ReceiveMoneyIcon}. See {@link BaseIconProps} for the shared shape. */\nexport type ReceiveMoneyIconProps = BaseIconProps;\n\n/**\n * Receive Money icon. Renders at sizes 16, 24, or 32 px.\n *\n * @example\n * ```tsx\n * <ReceiveMoneyIcon size={24} />\n * ```\n */\nexport const ReceiveMoneyIcon = React.forwardRef<SVGSVGElement, ReceiveMoneyIconProps>(\n (props, ref) => <BaseIcon ref={ref} variants={VARIANTS} {...props} />,\n);\n\nReceiveMoneyIcon.displayName = \"ReceiveMoneyIcon\";\n"],"names":[],"mappings":";;;;AAIA,MAAM,WAAyB;AAAA,EAC7B,IAAI;AAAA,IACF,UAAU;AAAA,MACR;AAAA,QACE,GAAG;AAAA,MAAA;AAAA,MAEL;AAAA,QACE,GAAG;AAAA,MAAA;AAAA,MAEL;AAAA,QACE,GAAG;AAAA,MAAA;AAAA,MAEL;AAAA,QACE,GAAG;AAAA,MAAA;AAAA,IACL;AAAA,EACF;AAAA,EAEF,IAAI;AAAA,IACF,UAAU;AAAA,MACR;AAAA,QACE,GAAG;AAAA,MAAA;AAAA,MAEL;AAAA,QACE,GAAG;AAAA,MAAA;AAAA,MAEL;AAAA,QACE,GAAG;AAAA,MAAA;AAAA,MAEL;AAAA,QACE,GAAG;AAAA,MAAA;AAAA,IACL;AAAA,EACF;AAAA,EAEF,IAAI;AAAA,IACF,UAAU;AAAA,MACR;AAAA,QACE,GAAG;AAAA,MAAA;AAAA,MAEL;AAAA,QACE,GAAG;AAAA,MAAA;AAAA,MAEL;AAAA,QACE,GAAG;AAAA,MAAA;AAAA,MAEL;AAAA,QACE,GAAG;AAAA,MAAA;AAAA,IACL;AAAA,EACF;AAEJ;AAaO,MAAM,mBAAmB,MAAM;AAAA,EACpC,CAAC,OAAO,QAAQ,oBAAC,YAAS,KAAU,UAAU,UAAW,GAAG,MAAA,CAAO;AACrE;AAEA,iBAAiB,cAAc;"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../../utils/cn.mjs";
|
|
5
|
+
import { ChevronDownIcon } from "../Icons/ChevronDownIcon.mjs";
|
|
6
|
+
const CONTAINER_HEIGHT = {
|
|
7
|
+
"48": "h-12",
|
|
8
|
+
"40": "h-10",
|
|
9
|
+
"32": "h-8"
|
|
10
|
+
};
|
|
11
|
+
const CONTAINER_PADDING_X = {
|
|
12
|
+
"48": "px-4",
|
|
13
|
+
"40": "px-4",
|
|
14
|
+
"32": "px-3"
|
|
15
|
+
};
|
|
16
|
+
const INPUT_TYPOGRAPHY = {
|
|
17
|
+
"48": "typography-body-default-16px-regular autofill-body-lg",
|
|
18
|
+
"40": "typography-body-default-16px-regular autofill-body-lg",
|
|
19
|
+
"32": "typography-body-small-14px-regular autofill-body-md"
|
|
20
|
+
};
|
|
21
|
+
const PREFIX_TYPOGRAPHY = {
|
|
22
|
+
"48": "typography-body-default-16px-regular",
|
|
23
|
+
"40": "typography-body-default-16px-regular",
|
|
24
|
+
"32": "typography-body-small-14px-regular"
|
|
25
|
+
};
|
|
26
|
+
function getContainerClassName(size, error, disabled) {
|
|
27
|
+
return cn(
|
|
28
|
+
"relative flex items-center gap-2 overflow-hidden rounded-sm border bg-inputs-inputs-primary has-focus-visible:shadow-focus-ring has-focus-visible:outline-none motion-safe:transition-colors",
|
|
29
|
+
CONTAINER_PADDING_X[size],
|
|
30
|
+
CONTAINER_HEIGHT[size],
|
|
31
|
+
error ? "border-error-content" : "border-border-primary",
|
|
32
|
+
!disabled && !error && "hover:border-neutral-alphas-400",
|
|
33
|
+
disabled && "opacity-50"
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
function warnMissingAccessibleName(label, ariaLabel, ariaLabelledBy) {
|
|
37
|
+
if (process.env.NODE_ENV !== "production") {
|
|
38
|
+
if (!label && !ariaLabel && !ariaLabelledBy) {
|
|
39
|
+
console.warn(
|
|
40
|
+
"PhoneField: no accessible name provided. Pass a `label`, `aria-label`, or `aria-labelledby` prop."
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function PhoneFieldHelperText({
|
|
46
|
+
id,
|
|
47
|
+
error,
|
|
48
|
+
children
|
|
49
|
+
}) {
|
|
50
|
+
return /* @__PURE__ */ jsx(
|
|
51
|
+
"p",
|
|
52
|
+
{
|
|
53
|
+
id,
|
|
54
|
+
className: cn(
|
|
55
|
+
"typography-description-12px-regular pt-2",
|
|
56
|
+
error ? "text-error-content" : "text-content-tertiary"
|
|
57
|
+
),
|
|
58
|
+
children
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const PhoneField = React.forwardRef(
|
|
63
|
+
({
|
|
64
|
+
label,
|
|
65
|
+
helperText,
|
|
66
|
+
size = "48",
|
|
67
|
+
error = false,
|
|
68
|
+
errorMessage,
|
|
69
|
+
dialCode,
|
|
70
|
+
flag,
|
|
71
|
+
onCountrySelect,
|
|
72
|
+
countryButtonLabel = "Select country",
|
|
73
|
+
className,
|
|
74
|
+
id,
|
|
75
|
+
disabled,
|
|
76
|
+
fullWidth = false,
|
|
77
|
+
type = "tel",
|
|
78
|
+
...props
|
|
79
|
+
}, ref) => {
|
|
80
|
+
const generatedId = React.useId();
|
|
81
|
+
const inputId = id || generatedId;
|
|
82
|
+
const helperTextId = `${inputId}-helper`;
|
|
83
|
+
const dialCodeId = `${inputId}-dial-code`;
|
|
84
|
+
const bottomText = error && errorMessage ? errorMessage : helperText;
|
|
85
|
+
const describedBy = [dialCode != null ? dialCodeId : null, bottomText ? helperTextId : null].filter(Boolean).join(" ") || void 0;
|
|
86
|
+
const innerRef = React.useRef(null);
|
|
87
|
+
const setRefs = React.useCallback(
|
|
88
|
+
(node) => {
|
|
89
|
+
innerRef.current = node;
|
|
90
|
+
if (typeof ref === "function") ref(node);
|
|
91
|
+
else if (ref) ref.current = node;
|
|
92
|
+
},
|
|
93
|
+
[ref]
|
|
94
|
+
);
|
|
95
|
+
const handleContainerMouseDown = (event) => {
|
|
96
|
+
if (disabled) return;
|
|
97
|
+
const target = event.target;
|
|
98
|
+
if (target === innerRef.current) return;
|
|
99
|
+
if (target.closest("[data-pf-interactive]")) return;
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
innerRef.current?.focus();
|
|
102
|
+
};
|
|
103
|
+
warnMissingAccessibleName(label, props["aria-label"], props["aria-labelledby"]);
|
|
104
|
+
return /* @__PURE__ */ jsxs(
|
|
105
|
+
"div",
|
|
106
|
+
{
|
|
107
|
+
className: cn("flex flex-col", fullWidth && "w-full", className),
|
|
108
|
+
"data-disabled": disabled ? "" : void 0,
|
|
109
|
+
"data-error": error ? "" : void 0,
|
|
110
|
+
children: [
|
|
111
|
+
label && /* @__PURE__ */ jsx(
|
|
112
|
+
"label",
|
|
113
|
+
{
|
|
114
|
+
htmlFor: inputId,
|
|
115
|
+
className: "typography-description-12px-semibold pb-2 text-content-primary",
|
|
116
|
+
children: label
|
|
117
|
+
}
|
|
118
|
+
),
|
|
119
|
+
/* @__PURE__ */ jsxs(
|
|
120
|
+
"div",
|
|
121
|
+
{
|
|
122
|
+
className: getContainerClassName(size, error, disabled),
|
|
123
|
+
onMouseDown: handleContainerMouseDown,
|
|
124
|
+
children: [
|
|
125
|
+
/* @__PURE__ */ jsxs(
|
|
126
|
+
"button",
|
|
127
|
+
{
|
|
128
|
+
type: "button",
|
|
129
|
+
"data-pf-interactive": "",
|
|
130
|
+
onClick: onCountrySelect,
|
|
131
|
+
disabled,
|
|
132
|
+
"aria-label": countryButtonLabel,
|
|
133
|
+
className: "flex shrink-0 items-center gap-1 rounded-2xs focus-visible:shadow-focus-ring focus-visible:outline-none disabled:cursor-not-allowed",
|
|
134
|
+
children: [
|
|
135
|
+
flag != null && /* @__PURE__ */ jsx("span", { className: "flex size-6 shrink-0 items-center justify-center overflow-hidden rounded-full", children: flag }),
|
|
136
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, { size: 16, className: "shrink-0 text-content-secondary" })
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
),
|
|
140
|
+
dialCode != null && /* @__PURE__ */ jsx(
|
|
141
|
+
"span",
|
|
142
|
+
{
|
|
143
|
+
id: dialCodeId,
|
|
144
|
+
className: cn(
|
|
145
|
+
"shrink-0 select-none whitespace-nowrap text-content-primary",
|
|
146
|
+
PREFIX_TYPOGRAPHY[size]
|
|
147
|
+
),
|
|
148
|
+
children: dialCode
|
|
149
|
+
}
|
|
150
|
+
),
|
|
151
|
+
/* @__PURE__ */ jsx(
|
|
152
|
+
"input",
|
|
153
|
+
{
|
|
154
|
+
ref: setRefs,
|
|
155
|
+
id: inputId,
|
|
156
|
+
type,
|
|
157
|
+
inputMode: "tel",
|
|
158
|
+
disabled,
|
|
159
|
+
"aria-describedby": describedBy,
|
|
160
|
+
"aria-invalid": error || void 0,
|
|
161
|
+
className: cn(
|
|
162
|
+
"h-full min-w-0 flex-1 bg-transparent text-content-primary no-underline placeholder:text-content-tertiary focus:outline-none disabled:cursor-not-allowed",
|
|
163
|
+
INPUT_TYPOGRAPHY[size]
|
|
164
|
+
),
|
|
165
|
+
...props
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
),
|
|
171
|
+
bottomText && /* @__PURE__ */ jsx(PhoneFieldHelperText, { id: helperTextId, error, children: bottomText })
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
PhoneField.displayName = "PhoneField";
|
|
178
|
+
export {
|
|
179
|
+
PhoneField
|
|
180
|
+
};
|
|
181
|
+
//# sourceMappingURL=PhoneField.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PhoneField.mjs","sources":["../../../src/components/PhoneField/PhoneField.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { ChevronDownIcon } from \"../Icons/ChevronDownIcon\";\n\n/** Phone field height in pixels. */\nexport type PhoneFieldSize = \"48\" | \"40\" | \"32\";\n\nexport interface PhoneFieldProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"size\" | \"prefix\"> {\n /** Label text displayed above the input. Also used as the accessible name. */\n label?: string;\n /** Helper text displayed below the input. Replaced by `errorMessage` when `error` is `true`. */\n helperText?: string;\n /** Height of the phone field in pixels. @default \"48\" */\n size?: PhoneFieldSize;\n /** Whether the field is in an error state. @default false */\n error?: boolean;\n /** Error message displayed below the input. Shown instead of `helperText` when `error` is `true`. */\n errorMessage?: string;\n /** Dial code of the selected country, shown before the number (e.g. `\"+39\"`). */\n dialCode?: React.ReactNode;\n /** Flag of the selected country, rendered in the country selector (e.g. an image or flag icon). */\n flag?: React.ReactNode;\n /** Fired when the country selector (flag + chevron) is activated. Open the country picker here. */\n onCountrySelect?: () => void;\n /** Accessible name for the country selector button. @default \"Select country\" */\n countryButtonLabel?: string;\n /** Whether the field stretches to fill its container width. @default false */\n fullWidth?: boolean;\n}\n\nconst CONTAINER_HEIGHT: Record<PhoneFieldSize, string> = {\n \"48\": \"h-12\",\n \"40\": \"h-10\",\n \"32\": \"h-8\",\n};\n\nconst CONTAINER_PADDING_X: Record<PhoneFieldSize, string> = {\n \"48\": \"px-4\",\n \"40\": \"px-4\",\n \"32\": \"px-3\",\n};\n\nconst INPUT_TYPOGRAPHY: Record<PhoneFieldSize, string> = {\n \"48\": \"typography-body-default-16px-regular autofill-body-lg\",\n \"40\": \"typography-body-default-16px-regular autofill-body-lg\",\n \"32\": \"typography-body-small-14px-regular autofill-body-md\",\n};\n\nconst PREFIX_TYPOGRAPHY: Record<PhoneFieldSize, string> = {\n \"48\": \"typography-body-default-16px-regular\",\n \"40\": \"typography-body-default-16px-regular\",\n \"32\": \"typography-body-small-14px-regular\",\n};\n\nfunction getContainerClassName(size: PhoneFieldSize, error: boolean, disabled?: boolean) {\n return cn(\n \"relative flex items-center gap-2 overflow-hidden rounded-sm border bg-inputs-inputs-primary has-focus-visible:shadow-focus-ring has-focus-visible:outline-none motion-safe:transition-colors\",\n CONTAINER_PADDING_X[size],\n CONTAINER_HEIGHT[size],\n error ? \"border-error-content\" : \"border-border-primary\",\n !disabled && !error && \"hover:border-neutral-alphas-400\",\n disabled && \"opacity-50\",\n );\n}\n\nfunction warnMissingAccessibleName(label?: string, ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!label && !ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"PhoneField: no accessible name provided. Pass a `label`, `aria-label`, or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\nfunction PhoneFieldHelperText({\n id,\n error,\n children,\n}: {\n id: string;\n error: boolean;\n children: React.ReactNode;\n}) {\n return (\n <p\n id={id}\n className={cn(\n \"typography-description-12px-regular pt-2\",\n error ? \"text-error-content\" : \"text-content-tertiary\",\n )}\n >\n {children}\n </p>\n );\n}\n\n/**\n * A phone number input with a country selector (flag + chevron), a fixed dial\n * code prefix, and a `tel` input. The country picker itself is owned by the\n * consumer: pass the selected country's `flag` and `dialCode`, and open your\n * picker from `onCountrySelect`.\n *\n * Provide at least one of `label`, `aria-label`, or `aria-labelledby` for\n * accessibility — a console warning is emitted in development if none are set.\n *\n * @example\n * ```tsx\n * <PhoneField\n * label=\"Phone number\"\n * flag={<img src={italyFlag} alt=\"\" />}\n * dialCode=\"+39\"\n * onCountrySelect={openCountryPicker}\n * value={number}\n * onChange={(e) => setNumber(e.target.value)}\n * />\n * ```\n */\nexport const PhoneField = React.forwardRef<HTMLInputElement, PhoneFieldProps>(\n (\n {\n label,\n helperText,\n size = \"48\",\n error = false,\n errorMessage,\n dialCode,\n flag,\n onCountrySelect,\n countryButtonLabel = \"Select country\",\n className,\n id,\n disabled,\n fullWidth = false,\n type = \"tel\",\n ...props\n },\n ref,\n ) => {\n const generatedId = React.useId();\n const inputId = id || generatedId;\n const helperTextId = `${inputId}-helper`;\n const dialCodeId = `${inputId}-dial-code`;\n const bottomText = error && errorMessage ? errorMessage : helperText;\n\n const describedBy =\n [dialCode != null ? dialCodeId : null, bottomText ? helperTextId : null]\n .filter(Boolean)\n .join(\" \") || undefined;\n\n const innerRef = React.useRef<HTMLInputElement>(null);\n const setRefs = React.useCallback(\n (node: HTMLInputElement | null) => {\n innerRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as React.RefObject<HTMLInputElement | null>).current = node;\n },\n [ref],\n );\n\n const handleContainerMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\n if (disabled) return;\n const target = event.target as HTMLElement;\n if (target === innerRef.current) return;\n if (target.closest(\"[data-pf-interactive]\")) return;\n event.preventDefault();\n innerRef.current?.focus();\n };\n\n warnMissingAccessibleName(label, props[\"aria-label\"], props[\"aria-labelledby\"]);\n\n return (\n <div\n className={cn(\"flex flex-col\", fullWidth && \"w-full\", className)}\n data-disabled={disabled ? \"\" : undefined}\n data-error={error ? \"\" : undefined}\n >\n {label && (\n <label\n htmlFor={inputId}\n className=\"typography-description-12px-semibold pb-2 text-content-primary\"\n >\n {label}\n </label>\n )}\n\n {/* biome-ignore lint/a11y/noStaticElementInteractions: focus bridge delegates pointer clicks on the padding to the input */}\n <div\n className={getContainerClassName(size, error, disabled)}\n onMouseDown={handleContainerMouseDown}\n >\n <button\n type=\"button\"\n data-pf-interactive=\"\"\n onClick={onCountrySelect}\n disabled={disabled}\n aria-label={countryButtonLabel}\n className=\"flex shrink-0 items-center gap-1 rounded-2xs focus-visible:shadow-focus-ring focus-visible:outline-none disabled:cursor-not-allowed\"\n >\n {flag != null && (\n <span className=\"flex size-6 shrink-0 items-center justify-center overflow-hidden rounded-full\">\n {flag}\n </span>\n )}\n <ChevronDownIcon size={16} className=\"shrink-0 text-content-secondary\" />\n </button>\n\n {dialCode != null && (\n <span\n id={dialCodeId}\n className={cn(\n \"shrink-0 select-none whitespace-nowrap text-content-primary\",\n PREFIX_TYPOGRAPHY[size],\n )}\n >\n {dialCode}\n </span>\n )}\n\n <input\n ref={setRefs}\n id={inputId}\n type={type}\n inputMode=\"tel\"\n disabled={disabled}\n aria-describedby={describedBy}\n aria-invalid={error || undefined}\n className={cn(\n \"h-full min-w-0 flex-1 bg-transparent text-content-primary no-underline placeholder:text-content-tertiary focus:outline-none disabled:cursor-not-allowed\",\n INPUT_TYPOGRAPHY[size],\n )}\n {...props}\n />\n </div>\n\n {bottomText && (\n <PhoneFieldHelperText id={helperTextId} error={error}>\n {bottomText}\n </PhoneFieldHelperText>\n )}\n </div>\n );\n },\n);\n\nPhoneField.displayName = \"PhoneField\";\n"],"names":[],"mappings":"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"}
|
|
@@ -51,31 +51,38 @@ const RatingSummary = React.forwardRef(
|
|
|
51
51
|
] })
|
|
52
52
|
] })
|
|
53
53
|
] }),
|
|
54
|
-
/* @__PURE__ */ jsx(
|
|
55
|
-
"
|
|
54
|
+
/* @__PURE__ */ jsx(
|
|
55
|
+
"ul",
|
|
56
56
|
{
|
|
57
|
-
className: "
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
57
|
+
className: "m-0 grid list-none grid-cols-[1fr_auto] gap-4 p-0",
|
|
58
|
+
"aria-label": "Rating breakdown",
|
|
59
|
+
children: rows.map(({ rating, count }) => /* @__PURE__ */ jsx("li", { className: "col-span-2 grid grid-cols-subgrid", children: /* @__PURE__ */ jsxs(
|
|
60
|
+
"div",
|
|
61
|
+
{
|
|
62
|
+
className: "col-span-2 grid grid-cols-subgrid items-center",
|
|
63
|
+
role: "img",
|
|
64
|
+
"aria-label": formatRatingLabel ? formatRatingLabel(rating, count) : `${rating} ${rating === 1 ? "star" : "stars"}, ${formatCount(count)}`,
|
|
65
|
+
children: [
|
|
66
|
+
/* @__PURE__ */ jsx("div", { className: "relative h-4 overflow-hidden rounded-full bg-neutral-alphas-50", children: /* @__PURE__ */ jsx(
|
|
67
|
+
"div",
|
|
68
|
+
{
|
|
69
|
+
className: "absolute inset-y-0 left-0 rounded-full bg-brand-primary-default transition-[width] duration-300 ease-in-out",
|
|
70
|
+
style: { width: maxCount === 0 ? "0%" : `${count / maxCount * 100}%` }
|
|
71
|
+
}
|
|
72
|
+
) }),
|
|
73
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
|
|
74
|
+
/* @__PURE__ */ jsx("span", { className: "typography-body-small-14px-semibold text-content-primary", children: rating }),
|
|
75
|
+
/* @__PURE__ */ jsxs("span", { className: "typography-body-small-14px-regular text-content-tertiary", children: [
|
|
76
|
+
"(",
|
|
77
|
+
formatCount(count),
|
|
78
|
+
")"
|
|
79
|
+
] })
|
|
80
|
+
] })
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
) }, rating))
|
|
77
84
|
}
|
|
78
|
-
)
|
|
85
|
+
)
|
|
79
86
|
] });
|
|
80
87
|
}
|
|
81
88
|
);
|