@fanvue/ui 2.20.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/Accordion/AccordionContent.cjs +1 -1
- package/dist/cjs/components/Accordion/AccordionContent.cjs.map +1 -1
- package/dist/cjs/components/Accordion/AccordionTrigger.cjs +2 -2
- package/dist/cjs/components/Accordion/AccordionTrigger.cjs.map +1 -1
- package/dist/cjs/components/Alert/Alert.cjs +2 -2
- package/dist/cjs/components/Alert/Alert.cjs.map +1 -1
- package/dist/cjs/components/AudioUpload/AudioUpload.cjs +6 -6
- package/dist/cjs/components/AudioUpload/AudioUpload.cjs.map +1 -1
- package/dist/cjs/components/Autocomplete/Autocomplete.cjs +6 -6
- package/dist/cjs/components/Autocomplete/Autocomplete.cjs.map +1 -1
- package/dist/cjs/components/Autocomplete/AutocompleteDropdownContent.cjs +4 -4
- package/dist/cjs/components/Autocomplete/AutocompleteDropdownContent.cjs.map +1 -1
- package/dist/cjs/components/Autocomplete/AutocompleteOptionItem.cjs +1 -1
- package/dist/cjs/components/Autocomplete/AutocompleteOptionItem.cjs.map +1 -1
- package/dist/cjs/components/Autocomplete/AutocompleteTag.cjs +1 -1
- package/dist/cjs/components/Autocomplete/AutocompleteTag.cjs.map +1 -1
- package/dist/cjs/components/Badge/Badge.cjs +12 -12
- package/dist/cjs/components/Badge/Badge.cjs.map +1 -1
- package/dist/cjs/components/Banner/Banner.cjs +11 -11
- package/dist/cjs/components/Banner/Banner.cjs.map +1 -1
- package/dist/cjs/components/BottomNavigation/BottomNavigation.cjs +2 -2
- package/dist/cjs/components/BottomNavigation/BottomNavigation.cjs.map +1 -1
- package/dist/cjs/components/BottomNavigation/BottomNavigationAction.cjs +2 -2
- package/dist/cjs/components/BottomNavigation/BottomNavigationAction.cjs.map +1 -1
- package/dist/cjs/components/Breadcrumb/Breadcrumb.cjs +2 -2
- package/dist/cjs/components/Breadcrumb/Breadcrumb.cjs.map +1 -1
- package/dist/cjs/components/Button/Button.cjs +9 -9
- package/dist/cjs/components/Button/Button.cjs.map +1 -1
- package/dist/cjs/components/Card/Card.cjs +2 -2
- package/dist/cjs/components/Card/Card.cjs.map +1 -1
- package/dist/cjs/components/Chart/ChartCard.cjs +4 -4
- package/dist/cjs/components/Chart/ChartCard.cjs.map +1 -1
- package/dist/cjs/components/Chart/ChartPieLegend.cjs +2 -2
- package/dist/cjs/components/Chart/ChartPieLegend.cjs.map +1 -1
- package/dist/cjs/components/Chart/ChartSeriesToggle.cjs +1 -1
- package/dist/cjs/components/Chart/ChartSeriesToggle.cjs.map +1 -1
- package/dist/cjs/components/ChatInput/ChatInput.cjs +4 -4
- package/dist/cjs/components/ChatInput/ChatInput.cjs.map +1 -1
- package/dist/cjs/components/Checkbox/Checkbox.cjs +3 -3
- package/dist/cjs/components/Checkbox/Checkbox.cjs.map +1 -1
- package/dist/cjs/components/Chip/Chip.cjs +3 -3
- package/dist/cjs/components/Chip/Chip.cjs.map +1 -1
- package/dist/cjs/components/Count/Count.cjs +7 -7
- package/dist/cjs/components/Count/Count.cjs.map +1 -1
- package/dist/cjs/components/CreatorCard/CreatorCard.cjs +4 -4
- package/dist/cjs/components/CreatorCard/CreatorCard.cjs.map +1 -1
- package/dist/cjs/components/CreatorCover/CreatorCover.cjs +5 -5
- package/dist/cjs/components/CreatorCover/CreatorCover.cjs.map +1 -1
- package/dist/cjs/components/CreatorTile/CreatorTile.cjs +2 -2
- package/dist/cjs/components/CreatorTile/CreatorTile.cjs.map +1 -1
- package/dist/cjs/components/DatePicker/DatePicker.cjs +5 -5
- package/dist/cjs/components/DatePicker/DatePicker.cjs.map +1 -1
- package/dist/cjs/components/Dialog/Dialog.cjs +4 -4
- package/dist/cjs/components/Dialog/Dialog.cjs.map +1 -1
- package/dist/cjs/components/Divider/Divider.cjs +1 -1
- package/dist/cjs/components/Divider/Divider.cjs.map +1 -1
- package/dist/cjs/components/Drawer/Drawer.cjs +3 -3
- package/dist/cjs/components/Drawer/Drawer.cjs.map +1 -1
- package/dist/cjs/components/DropdownMenu/DropdownMenu.cjs +13 -13
- package/dist/cjs/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
- package/dist/cjs/components/EmptyState/EmptyState.cjs +6 -6
- package/dist/cjs/components/EmptyState/EmptyState.cjs.map +1 -1
- package/dist/cjs/components/IconButton/IconButton.cjs +6 -6
- package/dist/cjs/components/IconButton/IconButton.cjs.map +1 -1
- package/dist/cjs/components/Icons/AffiliatesIcon.cjs +92 -0
- package/dist/cjs/components/Icons/AffiliatesIcon.cjs.map +1 -0
- package/dist/cjs/components/InfoBox/InfoBox.cjs +4 -4
- package/dist/cjs/components/InfoBox/InfoBox.cjs.map +1 -1
- package/dist/cjs/components/InlineEdit/InlineEdit.cjs +1 -1
- package/dist/cjs/components/InlineEdit/InlineEdit.cjs.map +1 -1
- package/dist/cjs/components/Logo/Logo.cjs +2 -2
- package/dist/cjs/components/Logo/Logo.cjs.map +1 -1
- package/dist/cjs/components/MobileStepper/MobileStepper.cjs +1 -1
- package/dist/cjs/components/MobileStepper/MobileStepper.cjs.map +1 -1
- package/dist/cjs/components/Pagination/Pagination.cjs +1 -1
- package/dist/cjs/components/Pagination/Pagination.cjs.map +1 -1
- package/dist/cjs/components/Pill/Pill.cjs +5 -5
- package/dist/cjs/components/Pill/Pill.cjs.map +1 -1
- package/dist/cjs/components/ProgressBar/ProgressBar.cjs +5 -5
- package/dist/cjs/components/ProgressBar/ProgressBar.cjs.map +1 -1
- package/dist/cjs/components/Radio/Radio.cjs +3 -3
- package/dist/cjs/components/Radio/Radio.cjs.map +1 -1
- package/dist/cjs/components/Select/Select.cjs +11 -8
- package/dist/cjs/components/Select/Select.cjs.map +1 -1
- package/dist/cjs/components/Skeleton/Skeleton.cjs +1 -1
- package/dist/cjs/components/Skeleton/Skeleton.cjs.map +1 -1
- package/dist/cjs/components/Slider/SliderLayout.cjs +12 -5
- package/dist/cjs/components/Slider/SliderLayout.cjs.map +1 -1
- package/dist/cjs/components/Slider/SliderThumb.cjs +3 -3
- package/dist/cjs/components/Slider/SliderThumb.cjs.map +1 -1
- package/dist/cjs/components/Snackbar/Snackbar.cjs +6 -6
- package/dist/cjs/components/Snackbar/Snackbar.cjs.map +1 -1
- package/dist/cjs/components/Stepper/StepperStep.cjs +9 -9
- package/dist/cjs/components/Stepper/StepperStep.cjs.map +1 -1
- package/dist/cjs/components/Switch/Switch.cjs +1 -1
- package/dist/cjs/components/Switch/Switch.cjs.map +1 -1
- package/dist/cjs/components/SwitchField/SwitchField.cjs +2 -2
- package/dist/cjs/components/SwitchField/SwitchField.cjs.map +1 -1
- package/dist/cjs/components/SwitchToggle/SwitchToggle.cjs +1 -1
- package/dist/cjs/components/SwitchToggle/SwitchToggle.cjs.map +1 -1
- package/dist/cjs/components/Table/Table.cjs +7 -7
- package/dist/cjs/components/Table/Table.cjs.map +1 -1
- package/dist/cjs/components/Table/TablePagination.cjs +2 -2
- package/dist/cjs/components/Table/TablePagination.cjs.map +1 -1
- package/dist/cjs/components/Tabs/TabsTrigger.cjs +2 -2
- package/dist/cjs/components/Tabs/TabsTrigger.cjs.map +1 -1
- package/dist/cjs/components/TextArea/TextArea.cjs +5 -5
- package/dist/cjs/components/TextArea/TextArea.cjs.map +1 -1
- package/dist/cjs/components/TextField/TextField.cjs +5 -5
- package/dist/cjs/components/TextField/TextField.cjs.map +1 -1
- package/dist/cjs/components/Toast/Toast.cjs +2 -2
- package/dist/cjs/components/Toast/Toast.cjs.map +1 -1
- package/dist/cjs/components/Tooltip/Tooltip.cjs +1 -1
- package/dist/cjs/components/Tooltip/Tooltip.cjs.map +1 -1
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/components/Accordion/AccordionContent.mjs +1 -1
- package/dist/components/Accordion/AccordionContent.mjs.map +1 -1
- package/dist/components/Accordion/AccordionTrigger.mjs +2 -2
- package/dist/components/Accordion/AccordionTrigger.mjs.map +1 -1
- package/dist/components/Alert/Alert.mjs +2 -2
- package/dist/components/Alert/Alert.mjs.map +1 -1
- package/dist/components/AudioUpload/AudioUpload.mjs +6 -6
- package/dist/components/AudioUpload/AudioUpload.mjs.map +1 -1
- package/dist/components/Autocomplete/Autocomplete.mjs +6 -6
- package/dist/components/Autocomplete/Autocomplete.mjs.map +1 -1
- package/dist/components/Autocomplete/AutocompleteDropdownContent.mjs +4 -4
- package/dist/components/Autocomplete/AutocompleteDropdownContent.mjs.map +1 -1
- package/dist/components/Autocomplete/AutocompleteOptionItem.mjs +1 -1
- package/dist/components/Autocomplete/AutocompleteOptionItem.mjs.map +1 -1
- package/dist/components/Autocomplete/AutocompleteTag.mjs +1 -1
- package/dist/components/Autocomplete/AutocompleteTag.mjs.map +1 -1
- package/dist/components/Badge/Badge.mjs +12 -12
- package/dist/components/Badge/Badge.mjs.map +1 -1
- package/dist/components/Banner/Banner.mjs +11 -11
- package/dist/components/Banner/Banner.mjs.map +1 -1
- package/dist/components/BottomNavigation/BottomNavigation.mjs +2 -2
- package/dist/components/BottomNavigation/BottomNavigation.mjs.map +1 -1
- package/dist/components/BottomNavigation/BottomNavigationAction.mjs +2 -2
- package/dist/components/BottomNavigation/BottomNavigationAction.mjs.map +1 -1
- package/dist/components/Breadcrumb/Breadcrumb.mjs +2 -2
- package/dist/components/Breadcrumb/Breadcrumb.mjs.map +1 -1
- package/dist/components/Button/Button.mjs +9 -9
- package/dist/components/Button/Button.mjs.map +1 -1
- package/dist/components/Card/Card.mjs +2 -2
- package/dist/components/Card/Card.mjs.map +1 -1
- package/dist/components/Chart/ChartCard.mjs +4 -4
- package/dist/components/Chart/ChartCard.mjs.map +1 -1
- package/dist/components/Chart/ChartPieLegend.mjs +2 -2
- package/dist/components/Chart/ChartPieLegend.mjs.map +1 -1
- package/dist/components/Chart/ChartSeriesToggle.mjs +1 -1
- package/dist/components/Chart/ChartSeriesToggle.mjs.map +1 -1
- package/dist/components/ChatInput/ChatInput.mjs +4 -4
- package/dist/components/ChatInput/ChatInput.mjs.map +1 -1
- package/dist/components/Checkbox/Checkbox.mjs +3 -3
- package/dist/components/Checkbox/Checkbox.mjs.map +1 -1
- package/dist/components/Chip/Chip.mjs +3 -3
- package/dist/components/Chip/Chip.mjs.map +1 -1
- package/dist/components/Count/Count.mjs +7 -7
- package/dist/components/Count/Count.mjs.map +1 -1
- package/dist/components/CreatorCard/CreatorCard.mjs +4 -4
- package/dist/components/CreatorCard/CreatorCard.mjs.map +1 -1
- package/dist/components/CreatorCover/CreatorCover.mjs +5 -5
- package/dist/components/CreatorCover/CreatorCover.mjs.map +1 -1
- package/dist/components/CreatorTile/CreatorTile.mjs +2 -2
- package/dist/components/CreatorTile/CreatorTile.mjs.map +1 -1
- package/dist/components/DatePicker/DatePicker.mjs +5 -5
- package/dist/components/DatePicker/DatePicker.mjs.map +1 -1
- package/dist/components/Dialog/Dialog.mjs +4 -4
- package/dist/components/Dialog/Dialog.mjs.map +1 -1
- package/dist/components/Divider/Divider.mjs +1 -1
- package/dist/components/Divider/Divider.mjs.map +1 -1
- package/dist/components/Drawer/Drawer.mjs +3 -3
- package/dist/components/Drawer/Drawer.mjs.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.mjs +13 -13
- package/dist/components/DropdownMenu/DropdownMenu.mjs.map +1 -1
- package/dist/components/EmptyState/EmptyState.mjs +6 -6
- package/dist/components/EmptyState/EmptyState.mjs.map +1 -1
- package/dist/components/IconButton/IconButton.mjs +6 -6
- package/dist/components/IconButton/IconButton.mjs.map +1 -1
- package/dist/components/Icons/AffiliatesIcon.mjs +75 -0
- package/dist/components/Icons/AffiliatesIcon.mjs.map +1 -0
- package/dist/components/InfoBox/InfoBox.mjs +4 -4
- package/dist/components/InfoBox/InfoBox.mjs.map +1 -1
- package/dist/components/InlineEdit/InlineEdit.mjs +1 -1
- package/dist/components/InlineEdit/InlineEdit.mjs.map +1 -1
- package/dist/components/Logo/Logo.mjs +2 -2
- package/dist/components/Logo/Logo.mjs.map +1 -1
- package/dist/components/MobileStepper/MobileStepper.mjs +1 -1
- package/dist/components/MobileStepper/MobileStepper.mjs.map +1 -1
- package/dist/components/Pagination/Pagination.mjs +1 -1
- package/dist/components/Pagination/Pagination.mjs.map +1 -1
- package/dist/components/Pill/Pill.mjs +5 -5
- package/dist/components/Pill/Pill.mjs.map +1 -1
- package/dist/components/ProgressBar/ProgressBar.mjs +5 -5
- package/dist/components/ProgressBar/ProgressBar.mjs.map +1 -1
- package/dist/components/Radio/Radio.mjs +3 -3
- package/dist/components/Radio/Radio.mjs.map +1 -1
- package/dist/components/Select/Select.mjs +11 -8
- package/dist/components/Select/Select.mjs.map +1 -1
- package/dist/components/Skeleton/Skeleton.mjs +1 -1
- package/dist/components/Skeleton/Skeleton.mjs.map +1 -1
- package/dist/components/Slider/SliderLayout.mjs +12 -5
- package/dist/components/Slider/SliderLayout.mjs.map +1 -1
- package/dist/components/Slider/SliderThumb.mjs +3 -3
- package/dist/components/Slider/SliderThumb.mjs.map +1 -1
- package/dist/components/Snackbar/Snackbar.mjs +6 -6
- package/dist/components/Snackbar/Snackbar.mjs.map +1 -1
- package/dist/components/Stepper/StepperStep.mjs +9 -9
- package/dist/components/Stepper/StepperStep.mjs.map +1 -1
- package/dist/components/Switch/Switch.mjs +1 -1
- package/dist/components/Switch/Switch.mjs.map +1 -1
- package/dist/components/SwitchField/SwitchField.mjs +2 -2
- package/dist/components/SwitchField/SwitchField.mjs.map +1 -1
- package/dist/components/SwitchToggle/SwitchToggle.mjs +1 -1
- package/dist/components/SwitchToggle/SwitchToggle.mjs.map +1 -1
- package/dist/components/Table/Table.mjs +7 -7
- package/dist/components/Table/Table.mjs.map +1 -1
- package/dist/components/Table/TablePagination.mjs +2 -2
- package/dist/components/Table/TablePagination.mjs.map +1 -1
- package/dist/components/Tabs/TabsTrigger.mjs +2 -2
- package/dist/components/Tabs/TabsTrigger.mjs.map +1 -1
- package/dist/components/TextArea/TextArea.mjs +5 -5
- package/dist/components/TextArea/TextArea.mjs.map +1 -1
- package/dist/components/TextField/TextField.mjs +5 -5
- package/dist/components/TextField/TextField.mjs.map +1 -1
- package/dist/components/Toast/Toast.mjs +2 -2
- package/dist/components/Toast/Toast.mjs.map +1 -1
- package/dist/components/Tooltip/Tooltip.mjs +1 -1
- package/dist/components/Tooltip/Tooltip.mjs.map +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -1
- package/dist/styles/base.css +2 -2
- package/dist/styles/theme.css +626 -195
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioUpload.mjs","sources":["../../../src/components/AudioUpload/AudioUpload.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { Button } from \"../Button/Button\";\nimport { MicrophoneIcon } from \"../Icons/MicrophoneIcon\";\nimport { StopIcon } from \"../Icons/StopIcon\";\nimport { UploadCloudIcon } from \"../Icons/UploadCloudIcon\";\nimport { AudioWaveform } from \"./AudioWaveform\";\nimport { type AudioValidationError, formatAudioTime, validateAudioFile } from \"./audioUtils\";\nimport {\n DEFAULT_ACCEPTED_TYPES,\n DEFAULT_MAX_FILE_SIZE,\n DEFAULT_MAX_RECORDING_DURATION,\n DEFAULT_MIN_RECORDING_DURATION,\n} from \"./constants\";\nimport { useAudioRecorder } from \"./useAudioRecorder\";\n\n/** A file that was rejected during drop or browse, along with the reasons. */\nexport interface AudioFileRejection {\n /** The rejected file. */\n file: File;\n /** One or more validation errors explaining why the file was rejected. */\n errors: AudioValidationError[];\n}\n\nexport interface AudioUploadProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onDrop\"> {\n /** Maximum file size in bytes. @default 10_485_760 (10MB) */\n maxFileSize?: number;\n /** Accepted audio MIME types. @default DEFAULT_ACCEPTED_TYPES */\n accept?: readonly string[];\n /** Maximum number of files per drop. @default 1 */\n maxFiles?: number;\n /** Whether to show the record audio button. @default true */\n allowRecording?: boolean;\n /** Maximum recording duration in seconds. @default 30 */\n maxRecordingDuration?: number;\n /** Minimum recording duration in seconds. @default 5 */\n minRecordingDuration?: number;\n\n /** Called when valid files are accepted via drop or browse */\n onFilesAccepted?: (files: File[]) => void;\n /** Called when files are rejected (wrong type, too large, etc.) */\n onFilesRejected?: (rejections: AudioFileRejection[]) => void;\n /** Called when a recording completes and meets minimum duration */\n onRecordingComplete?: (blob: Blob, durationMs: number) => void;\n /** Called when recording is stopped but does not meet minimum duration */\n onRecordingTooShort?: (durationMs: number, minDurationMs: number) => void;\n /** Called when microphone permission is denied or unavailable */\n onPermissionError?: (error: Error) => void;\n /** Called when an unexpected recording error occurs */\n onRecordingError?: (error: Error) => void;\n\n /** Upload area title text. @default \"Click to upload, or drag & drop\" */\n uploadTitle?: string;\n /** Upload area description text. @default \"Audio files only, up to 10MB each\" */\n uploadDescription?: string;\n /** Separator text between upload and record. @default \"or\" */\n separatorText?: string;\n /** Record button label. @default \"Record audio\" */\n recordButtonLabel?: string;\n /** Stop recording button aria-label. @default \"Stop recording\" */\n stopButtonAriaLabel?: string;\n\n /** Whether the component is disabled. @default false */\n disabled?: boolean;\n}\n\nfunction partitionFiles(\n files: File[],\n maxFileSize: number,\n accept: readonly string[],\n maxFiles: number,\n): { accepted: File[]; rejected: AudioFileRejection[] } {\n const accepted: File[] = [];\n const rejected: AudioFileRejection[] = [];\n\n for (const file of files) {\n const errors = validateAudioFile(file, { maxFileSize, acceptedTypes: accept });\n if (errors.length > 0) {\n rejected.push({ file, errors });\n } else {\n accepted.push(file);\n }\n }\n\n if (maxFiles > 0 && accepted.length > maxFiles) {\n const excess = accepted.splice(maxFiles);\n for (const file of excess) {\n rejected.push({\n file,\n errors: [{ code: \"too-many-files\", message: `Too many files. Maximum is ${maxFiles}` }],\n });\n }\n }\n\n return { accepted, rejected };\n}\n\n/**\n * Audio file upload with drag-and-drop and optional in-browser recording.\n * Supports file validation, multiple files, and real-time waveform visualization during recording.\n *\n * @example\n * ```tsx\n * <AudioUpload\n * onFilesAccepted={(files) => console.log(files)}\n * onRecordingComplete={(blob, duration) => console.log(blob, duration)}\n * />\n * ```\n */\nexport const AudioUpload = React.forwardRef<HTMLDivElement, AudioUploadProps>(\n (\n {\n className,\n maxFileSize = DEFAULT_MAX_FILE_SIZE,\n accept = DEFAULT_ACCEPTED_TYPES,\n maxFiles = 1,\n allowRecording = true,\n maxRecordingDuration = DEFAULT_MAX_RECORDING_DURATION,\n minRecordingDuration = DEFAULT_MIN_RECORDING_DURATION,\n onFilesAccepted,\n onFilesRejected,\n onRecordingComplete,\n onRecordingTooShort,\n onPermissionError,\n onRecordingError,\n uploadTitle = \"Click to upload, or drag & drop\",\n uploadDescription = \"Audio files only, up to 10MB each\",\n separatorText = \"or\",\n recordButtonLabel = \"Record audio\",\n stopButtonAriaLabel = \"Stop recording\",\n disabled = false,\n ...props\n },\n ref,\n ) => {\n const inputId = React.useId();\n const descriptionId = React.useId();\n const [isDragActive, setIsDragActive] = React.useState(false);\n const stopButtonRef = React.useRef<HTMLButtonElement>(null);\n\n const {\n isRecording,\n elapsedMs,\n startRecording,\n stopRecording,\n analyserNode,\n isSupported: isRecordingSupported,\n } = useAudioRecorder({\n maxDuration: maxRecordingDuration,\n minDuration: minRecordingDuration,\n onComplete: onRecordingComplete,\n onTooShort: onRecordingTooShort,\n onPermissionError,\n onError: onRecordingError,\n });\n\n const acceptString = accept.join(\",\");\n\n // Move focus to stop button when recording starts\n React.useEffect(() => {\n if (isRecording) {\n stopButtonRef.current?.focus();\n }\n }, [isRecording]);\n\n const validateAndAcceptFiles = React.useCallback(\n (files: FileList | File[]) => {\n const { accepted, rejected } = partitionFiles(\n Array.from(files),\n maxFileSize,\n accept,\n maxFiles,\n );\n if (accepted.length > 0) onFilesAccepted?.(accepted);\n if (rejected.length > 0) onFilesRejected?.(rejected);\n },\n [maxFileSize, accept, maxFiles, onFilesAccepted, onFilesRejected],\n );\n\n const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n setIsDragActive(false);\n\n if (disabled) return;\n\n const { files } = e.dataTransfer;\n if (files.length > 0) {\n validateAndAcceptFiles(files);\n }\n };\n\n const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n if (!disabled) {\n setIsDragActive(true);\n }\n };\n\n const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n setIsDragActive(false);\n };\n\n const handleFileInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const { files } = e.target;\n if (files && files.length > 0) {\n validateAndAcceptFiles(files);\n }\n // Reset input so same file can be selected again\n e.target.value = \"\";\n };\n\n const handleRecordClick = (e: React.MouseEvent) => {\n e.stopPropagation();\n startRecording();\n };\n\n const handleStopClick = () => {\n stopRecording();\n };\n\n if (isRecording) {\n const formattedElapsed = formatAudioTime(elapsedMs);\n\n return (\n // biome-ignore lint/a11y/useSemanticElements: <fieldset> would break the public HTMLDivElement ref/props API\n <div\n ref={ref}\n role=\"group\"\n aria-label=\"Audio recording in progress\"\n data-testid=\"audio-upload\"\n data-state=\"recording\"\n className={cn(\n \"flex flex-col items-center justify-center gap-2 rounded-md bg-surface-secondary px-4 py-3\",\n className,\n )}\n {...props}\n >\n <div className=\"flex flex-1 flex-col items-center gap-2\">\n <div\n className=\"flex size-[72px] items-center justify-center rounded-full bg-buttons-primary\"\n aria-hidden=\"true\"\n >\n <MicrophoneIcon className=\"size-5 text-content-primary-inverted\" />\n </div>\n\n <p\n role=\"timer\"\n aria-label=\"Recording time\"\n className=\"typography-regular-body-lg text-content-primary\"\n >\n {formattedElapsed} / {formatAudioTime(maxRecordingDuration * 1000)}\n </p>\n </div>\n\n <div className=\"flex w-full items-center gap-2.5\" aria-hidden=\"true\">\n <AudioWaveform\n analyserNode={analyserNode}\n isRecording={isRecording}\n className=\"flex-1\"\n />\n </div>\n\n <button\n ref={stopButtonRef}\n type=\"button\"\n onClick={handleStopClick}\n className=\"mt-1 flex size-11 items-center justify-center rounded-full bg-error-content text-content-on-brand-inverted transition-colors hover:bg-error-content/80 focus:shadow-focus-ring focus-visible:outline-none\"\n aria-label={stopButtonAriaLabel}\n >\n <StopIcon className=\"size-5\" />\n </button>\n </div>\n );\n }\n\n return (\n // biome-ignore lint/a11y/useSemanticElements: <fieldset> would break the public HTMLDivElement ref/props API\n <div\n ref={ref}\n role=\"group\"\n aria-label=\"Audio upload\"\n data-testid=\"audio-upload\"\n data-state=\"idle\"\n aria-disabled={disabled || undefined}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n className={cn(\n \"flex flex-col items-center justify-center gap-2 rounded-md bg-surface-secondary px-4 py-3 transition-colors\",\n isDragActive && \"bg-brand-primary-muted ring-2 ring-brand-primary-default\",\n disabled && \"pointer-events-none opacity-50\",\n className,\n )}\n {...props}\n >\n <input\n id={inputId}\n type=\"file\"\n accept={acceptString}\n multiple={maxFiles > 1}\n onChange={handleFileInputChange}\n className=\"peer sr-only\"\n disabled={disabled}\n aria-describedby={descriptionId}\n />\n\n <label\n htmlFor={inputId}\n className=\"flex cursor-pointer flex-col items-center gap-2 rounded-xs px-2 py-1 peer-focus-visible:shadow-focus-ring\"\n >\n <UploadCloudIcon className=\"size-5 text-content-primary\" />\n\n <span className=\"typography-semibold-body-lg text-center text-content-primary\">\n {uploadTitle}\n </span>\n\n <span\n id={descriptionId}\n className=\"typography-regular-body-md text-center text-content-primary\"\n >\n {uploadDescription}\n </span>\n </label>\n\n {allowRecording && isRecordingSupported && (\n <>\n <p className=\"typography-regular-body-md text-center text-content-primary\">\n {separatorText}\n </p>\n\n <Button\n variant=\"brand\"\n size=\"40\"\n leftIcon={<MicrophoneIcon className=\"size-5\" />}\n onClick={handleRecordClick}\n disabled={disabled}\n type=\"button\"\n >\n {recordButtonLabel}\n </Button>\n </>\n )}\n </div>\n );\n },\n);\n\nAudioUpload.displayName = \"AudioUpload\";\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AudioUpload.mjs","sources":["../../../src/components/AudioUpload/AudioUpload.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { Button } from \"../Button/Button\";\nimport { MicrophoneIcon } from \"../Icons/MicrophoneIcon\";\nimport { StopIcon } from \"../Icons/StopIcon\";\nimport { UploadCloudIcon } from \"../Icons/UploadCloudIcon\";\nimport { AudioWaveform } from \"./AudioWaveform\";\nimport { type AudioValidationError, formatAudioTime, validateAudioFile } from \"./audioUtils\";\nimport {\n DEFAULT_ACCEPTED_TYPES,\n DEFAULT_MAX_FILE_SIZE,\n DEFAULT_MAX_RECORDING_DURATION,\n DEFAULT_MIN_RECORDING_DURATION,\n} from \"./constants\";\nimport { useAudioRecorder } from \"./useAudioRecorder\";\n\n/** A file that was rejected during drop or browse, along with the reasons. */\nexport interface AudioFileRejection {\n /** The rejected file. */\n file: File;\n /** One or more validation errors explaining why the file was rejected. */\n errors: AudioValidationError[];\n}\n\nexport interface AudioUploadProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onDrop\"> {\n /** Maximum file size in bytes. @default 10_485_760 (10MB) */\n maxFileSize?: number;\n /** Accepted audio MIME types. @default DEFAULT_ACCEPTED_TYPES */\n accept?: readonly string[];\n /** Maximum number of files per drop. @default 1 */\n maxFiles?: number;\n /** Whether to show the record audio button. @default true */\n allowRecording?: boolean;\n /** Maximum recording duration in seconds. @default 30 */\n maxRecordingDuration?: number;\n /** Minimum recording duration in seconds. @default 5 */\n minRecordingDuration?: number;\n\n /** Called when valid files are accepted via drop or browse */\n onFilesAccepted?: (files: File[]) => void;\n /** Called when files are rejected (wrong type, too large, etc.) */\n onFilesRejected?: (rejections: AudioFileRejection[]) => void;\n /** Called when a recording completes and meets minimum duration */\n onRecordingComplete?: (blob: Blob, durationMs: number) => void;\n /** Called when recording is stopped but does not meet minimum duration */\n onRecordingTooShort?: (durationMs: number, minDurationMs: number) => void;\n /** Called when microphone permission is denied or unavailable */\n onPermissionError?: (error: Error) => void;\n /** Called when an unexpected recording error occurs */\n onRecordingError?: (error: Error) => void;\n\n /** Upload area title text. @default \"Click to upload, or drag & drop\" */\n uploadTitle?: string;\n /** Upload area description text. @default \"Audio files only, up to 10MB each\" */\n uploadDescription?: string;\n /** Separator text between upload and record. @default \"or\" */\n separatorText?: string;\n /** Record button label. @default \"Record audio\" */\n recordButtonLabel?: string;\n /** Stop recording button aria-label. @default \"Stop recording\" */\n stopButtonAriaLabel?: string;\n\n /** Whether the component is disabled. @default false */\n disabled?: boolean;\n}\n\nfunction partitionFiles(\n files: File[],\n maxFileSize: number,\n accept: readonly string[],\n maxFiles: number,\n): { accepted: File[]; rejected: AudioFileRejection[] } {\n const accepted: File[] = [];\n const rejected: AudioFileRejection[] = [];\n\n for (const file of files) {\n const errors = validateAudioFile(file, { maxFileSize, acceptedTypes: accept });\n if (errors.length > 0) {\n rejected.push({ file, errors });\n } else {\n accepted.push(file);\n }\n }\n\n if (maxFiles > 0 && accepted.length > maxFiles) {\n const excess = accepted.splice(maxFiles);\n for (const file of excess) {\n rejected.push({\n file,\n errors: [{ code: \"too-many-files\", message: `Too many files. Maximum is ${maxFiles}` }],\n });\n }\n }\n\n return { accepted, rejected };\n}\n\n/**\n * Audio file upload with drag-and-drop and optional in-browser recording.\n * Supports file validation, multiple files, and real-time waveform visualization during recording.\n *\n * @example\n * ```tsx\n * <AudioUpload\n * onFilesAccepted={(files) => console.log(files)}\n * onRecordingComplete={(blob, duration) => console.log(blob, duration)}\n * />\n * ```\n */\nexport const AudioUpload = React.forwardRef<HTMLDivElement, AudioUploadProps>(\n (\n {\n className,\n maxFileSize = DEFAULT_MAX_FILE_SIZE,\n accept = DEFAULT_ACCEPTED_TYPES,\n maxFiles = 1,\n allowRecording = true,\n maxRecordingDuration = DEFAULT_MAX_RECORDING_DURATION,\n minRecordingDuration = DEFAULT_MIN_RECORDING_DURATION,\n onFilesAccepted,\n onFilesRejected,\n onRecordingComplete,\n onRecordingTooShort,\n onPermissionError,\n onRecordingError,\n uploadTitle = \"Click to upload, or drag & drop\",\n uploadDescription = \"Audio files only, up to 10MB each\",\n separatorText = \"or\",\n recordButtonLabel = \"Record audio\",\n stopButtonAriaLabel = \"Stop recording\",\n disabled = false,\n ...props\n },\n ref,\n ) => {\n const inputId = React.useId();\n const descriptionId = React.useId();\n const [isDragActive, setIsDragActive] = React.useState(false);\n const stopButtonRef = React.useRef<HTMLButtonElement>(null);\n\n const {\n isRecording,\n elapsedMs,\n startRecording,\n stopRecording,\n analyserNode,\n isSupported: isRecordingSupported,\n } = useAudioRecorder({\n maxDuration: maxRecordingDuration,\n minDuration: minRecordingDuration,\n onComplete: onRecordingComplete,\n onTooShort: onRecordingTooShort,\n onPermissionError,\n onError: onRecordingError,\n });\n\n const acceptString = accept.join(\",\");\n\n // Move focus to stop button when recording starts\n React.useEffect(() => {\n if (isRecording) {\n stopButtonRef.current?.focus();\n }\n }, [isRecording]);\n\n const validateAndAcceptFiles = React.useCallback(\n (files: FileList | File[]) => {\n const { accepted, rejected } = partitionFiles(\n Array.from(files),\n maxFileSize,\n accept,\n maxFiles,\n );\n if (accepted.length > 0) onFilesAccepted?.(accepted);\n if (rejected.length > 0) onFilesRejected?.(rejected);\n },\n [maxFileSize, accept, maxFiles, onFilesAccepted, onFilesRejected],\n );\n\n const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n setIsDragActive(false);\n\n if (disabled) return;\n\n const { files } = e.dataTransfer;\n if (files.length > 0) {\n validateAndAcceptFiles(files);\n }\n };\n\n const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n if (!disabled) {\n setIsDragActive(true);\n }\n };\n\n const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n setIsDragActive(false);\n };\n\n const handleFileInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const { files } = e.target;\n if (files && files.length > 0) {\n validateAndAcceptFiles(files);\n }\n // Reset input so same file can be selected again\n e.target.value = \"\";\n };\n\n const handleRecordClick = (e: React.MouseEvent) => {\n e.stopPropagation();\n startRecording();\n };\n\n const handleStopClick = () => {\n stopRecording();\n };\n\n if (isRecording) {\n const formattedElapsed = formatAudioTime(elapsedMs);\n\n return (\n // biome-ignore lint/a11y/useSemanticElements: <fieldset> would break the public HTMLDivElement ref/props API\n <div\n ref={ref}\n role=\"group\"\n aria-label=\"Audio recording in progress\"\n data-testid=\"audio-upload\"\n data-state=\"recording\"\n className={cn(\n \"flex flex-col items-center justify-center gap-2 rounded-md bg-surface-secondary px-4 py-3\",\n className,\n )}\n {...props}\n >\n <div className=\"flex flex-1 flex-col items-center gap-2\">\n <div\n className=\"flex size-[72px] items-center justify-center rounded-full bg-buttons-primary-default\"\n aria-hidden=\"true\"\n >\n <MicrophoneIcon className=\"size-5 text-content-primary-inverted\" />\n </div>\n\n <p\n role=\"timer\"\n aria-label=\"Recording time\"\n className=\"typography-body-default-16px-regular text-content-primary\"\n >\n {formattedElapsed} / {formatAudioTime(maxRecordingDuration * 1000)}\n </p>\n </div>\n\n <div className=\"flex w-full items-center gap-2.5\" aria-hidden=\"true\">\n <AudioWaveform\n analyserNode={analyserNode}\n isRecording={isRecording}\n className=\"flex-1\"\n />\n </div>\n\n <button\n ref={stopButtonRef}\n type=\"button\"\n onClick={handleStopClick}\n className=\"mt-1 flex size-11 items-center justify-center rounded-full bg-error-content text-content-always-white transition-colors hover:bg-error-content/80 focus:shadow-focus-ring focus-visible:outline-none\"\n aria-label={stopButtonAriaLabel}\n >\n <StopIcon className=\"size-5\" />\n </button>\n </div>\n );\n }\n\n return (\n // biome-ignore lint/a11y/useSemanticElements: <fieldset> would break the public HTMLDivElement ref/props API\n <div\n ref={ref}\n role=\"group\"\n aria-label=\"Audio upload\"\n data-testid=\"audio-upload\"\n data-state=\"idle\"\n aria-disabled={disabled || undefined}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n className={cn(\n \"flex flex-col items-center justify-center gap-2 rounded-md bg-surface-secondary px-4 py-3 transition-colors\",\n isDragActive && \"bg-brand-primary-muted ring-2 ring-brand-primary-default\",\n disabled && \"pointer-events-none opacity-50\",\n className,\n )}\n {...props}\n >\n <input\n id={inputId}\n type=\"file\"\n accept={acceptString}\n multiple={maxFiles > 1}\n onChange={handleFileInputChange}\n className=\"peer sr-only\"\n disabled={disabled}\n aria-describedby={descriptionId}\n />\n\n <label\n htmlFor={inputId}\n className=\"flex cursor-pointer flex-col items-center gap-2 rounded-xs px-2 py-1 peer-focus-visible:shadow-focus-ring\"\n >\n <UploadCloudIcon className=\"size-5 text-content-primary\" />\n\n <span className=\"typography-body-default-16px-semibold text-center text-content-primary\">\n {uploadTitle}\n </span>\n\n <span\n id={descriptionId}\n className=\"typography-body-small-14px-regular text-center text-content-primary\"\n >\n {uploadDescription}\n </span>\n </label>\n\n {allowRecording && isRecordingSupported && (\n <>\n <p className=\"typography-body-small-14px-regular text-center text-content-primary\">\n {separatorText}\n </p>\n\n <Button\n variant=\"brand\"\n size=\"40\"\n leftIcon={<MicrophoneIcon className=\"size-5\" />}\n onClick={handleRecordClick}\n disabled={disabled}\n type=\"button\"\n >\n {recordButtonLabel}\n </Button>\n </>\n )}\n </div>\n );\n },\n);\n\nAudioUpload.displayName = \"AudioUpload\";\n"],"names":[],"mappings":"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"}
|
|
@@ -16,9 +16,9 @@ const CONTAINER_HEIGHT = {
|
|
|
16
16
|
"32": "min-h-8"
|
|
17
17
|
};
|
|
18
18
|
const INPUT_SIZE_CLASSES = {
|
|
19
|
-
"48": "typography-
|
|
20
|
-
"40": "typography-
|
|
21
|
-
"32": "typography-
|
|
19
|
+
"48": "typography-body-default-16px-regular",
|
|
20
|
+
"40": "typography-body-default-16px-regular",
|
|
21
|
+
"32": "typography-body-small-14px-regular"
|
|
22
22
|
};
|
|
23
23
|
const PADDING_CLASSES = {
|
|
24
24
|
"48": "px-4 py-1.5 gap-3",
|
|
@@ -73,7 +73,7 @@ const Autocomplete = React.forwardRef((props, ref) => {
|
|
|
73
73
|
"label",
|
|
74
74
|
{
|
|
75
75
|
htmlFor: ac.inputId,
|
|
76
|
-
className: "typography-
|
|
76
|
+
className: "typography-description-12px-semibold px-1 pt-1 pb-2 text-content-primary",
|
|
77
77
|
children: label
|
|
78
78
|
}
|
|
79
79
|
),
|
|
@@ -165,7 +165,7 @@ const Autocomplete = React.forwardRef((props, ref) => {
|
|
|
165
165
|
onCloseAutoFocus: (e) => e.preventDefault(),
|
|
166
166
|
style: { zIndex: "var(--fanvue-ui-portal-z-index, 50)" },
|
|
167
167
|
className: cn(
|
|
168
|
-
"w-max min-w-(--radix-popper-anchor-width) max-w-(--radix-popover-content-available-width) overflow-hidden rounded-sm border border-neutral-alphas-200 bg-
|
|
168
|
+
"w-max min-w-(--radix-popper-anchor-width) max-w-(--radix-popover-content-available-width) overflow-hidden rounded-sm border border-neutral-alphas-200 bg-background-primary text-content-primary shadow-[0_4px_16px_rgba(0,0,0,0.10)]",
|
|
169
169
|
"data-[state=closed]:animate-out data-[state=open]:animate-in",
|
|
170
170
|
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
171
171
|
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
@@ -209,7 +209,7 @@ const Autocomplete = React.forwardRef((props, ref) => {
|
|
|
209
209
|
{
|
|
210
210
|
id: ac.helperTextId,
|
|
211
211
|
className: cn(
|
|
212
|
-
"typography-
|
|
212
|
+
"typography-description-12px-regular px-2 pt-1 pb-0.5",
|
|
213
213
|
error ? "text-error-content" : "text-content-secondary"
|
|
214
214
|
),
|
|
215
215
|
children: bottomText
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Autocomplete.mjs","sources":["../../../src/components/Autocomplete/Autocomplete.tsx"],"sourcesContent":["import * as Popover from \"@radix-ui/react-popover\";\nimport * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"@/utils/floatingContentCollisionPadding\";\nimport { ChevronDownIcon } from \"../Icons/ChevronDownIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\nimport { SpinnerIcon } from \"../Icons/SpinnerIcon\";\nimport { AutocompleteDropdownContent } from \"./AutocompleteDropdownContent\";\nimport { AutocompleteTag } from \"./AutocompleteTag\";\nimport { useAutocomplete } from \"./useAutocomplete\";\n\nexport type AutocompleteSize = \"48\" | \"40\" | \"32\";\n\nexport interface AutocompleteOption {\n /** Unique value identifying the option. Returned via `onChange`. */\n value: string;\n /** Visible label. Falls back to `value` when omitted. */\n label?: string;\n /** When `true`, the option renders but cannot be selected. @default false */\n disabled?: boolean;\n /**\n * ID of the group this option belongs to. Must match an entry in the\n * component's `groups` prop. Options without a `groupId` render in an\n * implicit \"ungrouped\" bucket above the first declared group.\n */\n groupId?: string;\n /**\n * Pinned options bypass the search filter and stay visible at the top of\n * the list regardless of the current query. Useful for \"+ Create new\"\n * affordances. Pinned options ignore `groupId` and render before any\n * grouped or ungrouped content. @default false\n */\n pinned?: boolean;\n}\n\n/**\n * Describes a single group rendered above its matching options.\n * Indentation of nested rows (e.g. price under product) is not built in —\n * consumers control it via `renderOption` styling.\n */\nexport interface AutocompleteGroup {\n /** Stable identifier referenced by `option.groupId`. */\n id: string;\n /**\n * Group heading text. Used as the group's accessible name and matched\n * against the search query: when the query matches a group's `label`,\n * every option under that group is kept regardless of whether it\n * individually matches the per-option filter. This supports the common\n * \"heading is the searchable label, items are sub-rows\" shape (e.g.\n * heading = product name, items = prices).\n */\n label: string;\n}\n\ninterface AutocompleteBaseProps {\n label?: string;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n helperText?: string;\n /** @default \"48\" */\n size?: AutocompleteSize;\n /** @default false */\n error?: boolean;\n errorMessage?: string;\n placeholder?: string;\n leftIcon?: React.ReactNode;\n /** @default false */\n fullWidth?: boolean;\n /** @default false */\n disabled?: boolean;\n /** @default false */\n clearable?: boolean;\n clearAriaLabel?: string;\n id?: string;\n className?: string;\n options: AutocompleteOption[];\n inputValue?: string;\n onInputChange?: (value: string) => void;\n filterFn?: (option: AutocompleteOption, query: string) => boolean;\n /** @default false */\n creatable?: boolean;\n creatableLabel?: (inputValue: string) => string;\n onCreate?: (inputValue: string) => void;\n /** @default false */\n loading?: boolean;\n loadingText?: string;\n emptyText?: string;\n renderOption?: (\n option: AutocompleteOption,\n state: { selected: boolean; active: boolean },\n ) => React.ReactNode;\n renderTag?: (option: AutocompleteOption, onRemove: () => void) => React.ReactNode;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /**\n * Ordered list of groups. When provided, options whose `groupId` matches\n * an entry render under the corresponding heading. Groups with no visible\n * (post-filter) options collapse silently. Pinned options render above\n * everything; ungrouped options render between pinned and the first group.\n * Only one level of grouping is supported.\n */\n groups?: AutocompleteGroup[];\n /**\n * Custom renderer for group headings. The returned node is wrapped by the\n * component in an element carrying the `id` referenced by the surrounding\n * `role=\"group\"` wrapper's `aria-labelledby`, so consumers only need to\n * return visual content.\n */\n renderGroupHeading?: (group: AutocompleteGroup) => React.ReactNode;\n}\n\ninterface AutocompleteSingleProps extends AutocompleteBaseProps {\n multiple?: false;\n value?: string | null;\n defaultValue?: string | null;\n onChange?: (value: string | null) => void;\n}\n\ninterface AutocompleteMultiProps extends AutocompleteBaseProps {\n multiple: true;\n value?: string[];\n defaultValue?: string[];\n onChange?: (values: string[]) => void;\n}\n\nexport type AutocompleteProps = AutocompleteSingleProps | AutocompleteMultiProps;\n\nconst CONTAINER_HEIGHT: Record<AutocompleteSize, string> = {\n \"48\": \"min-h-12\",\n \"40\": \"min-h-10\",\n \"32\": \"min-h-8\",\n};\n\nconst INPUT_SIZE_CLASSES: Record<AutocompleteSize, string> = {\n \"48\": \"typography-regular-body-lg\",\n \"40\": \"typography-regular-body-lg\",\n \"32\": \"typography-regular-body-md\",\n};\n\nconst PADDING_CLASSES: Record<AutocompleteSize, string> = {\n \"48\": \"px-4 py-1.5 gap-3\",\n \"40\": \"px-4 py-1 gap-3\",\n \"32\": \"px-3 py-1 gap-2\",\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 \"Autocomplete: no accessible name provided. Pass a `label`, `aria-label`, or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\n/**\n * A combobox input with single- or multi-select, optional async loading, and\n * native support for grouped + pinned options.\n *\n * - Pass `groups` plus an `options` array whose entries reference each group\n * via `groupId` to render hierarchical lists with proper `role=\"group\"` +\n * `aria-labelledby` semantics. Options without a `groupId` render above\n * the first group; options marked `pinned` render above everything and\n * bypass the search filter.\n * - Indentation of nested rows (e.g. price under product) is controlled by\n * the consumer via `renderOption` styling — there is no built-in indent.\n *\n * @example\n * ```tsx\n * <Autocomplete\n * aria-label=\"Choose a product\"\n * options={[\n * { value: \"__new__\", label: \"+ Create new product\", pinned: true },\n * { value: \"product:abc\", label: \"Demo Product\", groupId: \"recent\" },\n * ]}\n * groups={[{ id: \"recent\", label: \"Recent products\" }]}\n * onChange={handleSelect}\n * />\n * ```\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Conditional JSX branches in the render template\nexport const Autocomplete = React.forwardRef<HTMLInputElement, AutocompleteProps>((props, ref) => {\n const {\n label,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledby,\n helperText,\n size = \"48\",\n error = false,\n errorMessage,\n placeholder,\n leftIcon,\n fullWidth = false,\n disabled = false,\n clearable = false,\n clearAriaLabel = \"Clear\",\n className,\n loading = false,\n loadingText,\n emptyText = \"No results\",\n renderOption,\n renderTag,\n renderGroupHeading,\n } = props;\n\n const ac = useAutocomplete(props);\n\n React.useImperativeHandle(ref, () => ac.inputRef.current as HTMLInputElement);\n\n warnMissingAccessibleName(label, ariaLabel, ariaLabelledby);\n\n const bottomText = error && errorMessage ? errorMessage : helperText;\n\n return (\n <Popover.Root open={ac.isOpen && !disabled} onOpenChange={ac.handleOpenChange}>\n <div\n className={cn(\"flex flex-col\", fullWidth && \"w-full\", className)}\n data-autocomplete-root=\"\"\n data-disabled={disabled ? \"\" : undefined}\n data-error={error ? \"\" : undefined}\n >\n {label && (\n <label\n htmlFor={ac.inputId}\n className=\"typography-semibold-body-sm px-1 pt-1 pb-2 text-content-primary\"\n >\n {label}\n </label>\n )}\n\n <Popover.Anchor asChild>\n {/* biome-ignore lint/a11y/noStaticElementInteractions: Container delegates click to the inner input */}\n {/* biome-ignore lint/a11y/useKeyWithClickEvents: Keyboard interaction is handled by the inner combobox input */}\n <div\n className={cn(\n \"flex flex-wrap items-center overflow-hidden rounded-sm border bg-neutral-alphas-100 has-focus-visible:shadow-focus-ring has-focus-visible:outline-none motion-safe:transition-colors\",\n error ? \"border-error-content\" : \"border-transparent\",\n !disabled && !error && \"hover:border-neutral-alphas-400\",\n ac.isOpen && !error && !disabled && \"border-neutral-alphas-400\",\n CONTAINER_HEIGHT[size],\n PADDING_CLASSES[size],\n disabled && \"opacity-50\",\n )}\n onClick={ac.handleContainerClick}\n >\n {leftIcon && (\n <div className=\"flex size-5 shrink-0 items-center justify-center text-content-secondary\">\n {leftIcon}\n </div>\n )}\n\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-1.5\">\n {ac.isMulti &&\n ac.selectedMultiOptions.map((opt) => (\n <AutocompleteTag\n key={opt.value}\n option={opt}\n disabled={disabled}\n onRemove={() => ac.toggleMulti(opt.value)}\n renderTag={renderTag}\n />\n ))}\n\n <input\n ref={ac.inputRef}\n id={ac.inputId}\n role=\"combobox\"\n type=\"text\"\n disabled={disabled}\n aria-expanded={ac.isOpen}\n aria-controls={ac.isOpen ? ac.listboxId : undefined}\n aria-activedescendant={ac.activeDescendantId}\n aria-autocomplete=\"list\"\n aria-describedby={bottomText ? ac.helperTextId : undefined}\n aria-invalid={error || undefined}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n autoComplete=\"off\"\n placeholder={\n ac.isMulti && ac.selectedMultiValues.length > 0 ? undefined : placeholder\n }\n value={ac.displayInputValue}\n onChange={ac.handleInputChange}\n onKeyDown={ac.handleKeyDown}\n onFocus={ac.handleFocus}\n onBlur={ac.handleBlur}\n className={cn(\n \"min-w-[40px] flex-1 truncate bg-transparent text-content-primary no-underline placeholder:text-content-secondary placeholder:opacity-40 focus:outline-none disabled:cursor-not-allowed\",\n INPUT_SIZE_CLASSES[size],\n )}\n />\n </div>\n\n <div className=\"flex shrink-0 items-center gap-1\">\n {loading && <SpinnerIcon className=\"size-4 animate-spin text-content-secondary\" />}\n {clearable && ac.hasClearableValue && !disabled && (\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label={clearAriaLabel}\n className=\"flex size-5 shrink-0 cursor-pointer items-center justify-center text-content-secondary hover:text-content-primary active:scale-95\"\n onClick={ac.handleClear}\n >\n <CloseIcon className=\"size-4\" />\n </button>\n )}\n <div className=\"flex size-5 shrink-0 items-center justify-center text-content-secondary\">\n <ChevronDownIcon\n className={cn(\"size-5 transition-transform\", ac.isOpen && \"rotate-180\")}\n />\n </div>\n </div>\n </div>\n </Popover.Anchor>\n\n <Popover.Portal>\n <Popover.Content\n sideOffset={4}\n collisionPadding={FLOATING_CONTENT_COLLISION_PADDING}\n onOpenAutoFocus={(e) => e.preventDefault()}\n onCloseAutoFocus={(e) => e.preventDefault()}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\" }}\n className={cn(\n \"w-max min-w-(--radix-popper-anchor-width) max-w-(--radix-popover-content-available-width) overflow-hidden rounded-sm border border-neutral-alphas-200 bg-bg-primary text-content-primary shadow-[0_4px_16px_rgba(0,0,0,0.10)]\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n \"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n \"data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2\",\n )}\n >\n <div\n ref={ac.listRef}\n id={ac.listboxId}\n role=\"listbox\"\n aria-label={label ?? ariaLabel ?? \"Options\"}\n aria-multiselectable={ac.isMulti || undefined}\n className=\"max-h-60 overflow-y-auto p-1\"\n >\n <AutocompleteDropdownContent\n loading={loading}\n loadingText={loadingText}\n emptyText={emptyText}\n visibleOptions={ac.visibleOptions}\n visibleSections={ac.visibleSections}\n createOption={ac.createOption}\n listboxId={ac.listboxId}\n activeIndex={ac.activeIndex}\n isMulti={ac.isMulti}\n selectedMultiValues={ac.selectedMultiValues}\n selectedValue={ac.selectedValue}\n onSelect={ac.handleSelect}\n onMouseEnter={ac.setActiveIndex}\n renderOption={renderOption}\n renderGroupHeading={renderGroupHeading}\n />\n </div>\n </Popover.Content>\n </Popover.Portal>\n\n {bottomText && (\n <p\n id={ac.helperTextId}\n className={cn(\n \"typography-regular-body-sm px-2 pt-1 pb-0.5\",\n error ? \"text-error-content\" : \"text-content-secondary\",\n )}\n >\n {bottomText}\n </p>\n )}\n </div>\n </Popover.Root>\n );\n});\n\nAutocomplete.displayName = \"Autocomplete\";\n"],"names":["Popover"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Autocomplete.mjs","sources":["../../../src/components/Autocomplete/Autocomplete.tsx"],"sourcesContent":["import * as Popover from \"@radix-ui/react-popover\";\nimport * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"@/utils/floatingContentCollisionPadding\";\nimport { ChevronDownIcon } from \"../Icons/ChevronDownIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\nimport { SpinnerIcon } from \"../Icons/SpinnerIcon\";\nimport { AutocompleteDropdownContent } from \"./AutocompleteDropdownContent\";\nimport { AutocompleteTag } from \"./AutocompleteTag\";\nimport { useAutocomplete } from \"./useAutocomplete\";\n\nexport type AutocompleteSize = \"48\" | \"40\" | \"32\";\n\nexport interface AutocompleteOption {\n /** Unique value identifying the option. Returned via `onChange`. */\n value: string;\n /** Visible label. Falls back to `value` when omitted. */\n label?: string;\n /** When `true`, the option renders but cannot be selected. @default false */\n disabled?: boolean;\n /**\n * ID of the group this option belongs to. Must match an entry in the\n * component's `groups` prop. Options without a `groupId` render in an\n * implicit \"ungrouped\" bucket above the first declared group.\n */\n groupId?: string;\n /**\n * Pinned options bypass the search filter and stay visible at the top of\n * the list regardless of the current query. Useful for \"+ Create new\"\n * affordances. Pinned options ignore `groupId` and render before any\n * grouped or ungrouped content. @default false\n */\n pinned?: boolean;\n}\n\n/**\n * Describes a single group rendered above its matching options.\n * Indentation of nested rows (e.g. price under product) is not built in —\n * consumers control it via `renderOption` styling.\n */\nexport interface AutocompleteGroup {\n /** Stable identifier referenced by `option.groupId`. */\n id: string;\n /**\n * Group heading text. Used as the group's accessible name and matched\n * against the search query: when the query matches a group's `label`,\n * every option under that group is kept regardless of whether it\n * individually matches the per-option filter. This supports the common\n * \"heading is the searchable label, items are sub-rows\" shape (e.g.\n * heading = product name, items = prices).\n */\n label: string;\n}\n\ninterface AutocompleteBaseProps {\n label?: string;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n helperText?: string;\n /** @default \"48\" */\n size?: AutocompleteSize;\n /** @default false */\n error?: boolean;\n errorMessage?: string;\n placeholder?: string;\n leftIcon?: React.ReactNode;\n /** @default false */\n fullWidth?: boolean;\n /** @default false */\n disabled?: boolean;\n /** @default false */\n clearable?: boolean;\n clearAriaLabel?: string;\n id?: string;\n className?: string;\n options: AutocompleteOption[];\n inputValue?: string;\n onInputChange?: (value: string) => void;\n filterFn?: (option: AutocompleteOption, query: string) => boolean;\n /** @default false */\n creatable?: boolean;\n creatableLabel?: (inputValue: string) => string;\n onCreate?: (inputValue: string) => void;\n /** @default false */\n loading?: boolean;\n loadingText?: string;\n emptyText?: string;\n renderOption?: (\n option: AutocompleteOption,\n state: { selected: boolean; active: boolean },\n ) => React.ReactNode;\n renderTag?: (option: AutocompleteOption, onRemove: () => void) => React.ReactNode;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /**\n * Ordered list of groups. When provided, options whose `groupId` matches\n * an entry render under the corresponding heading. Groups with no visible\n * (post-filter) options collapse silently. Pinned options render above\n * everything; ungrouped options render between pinned and the first group.\n * Only one level of grouping is supported.\n */\n groups?: AutocompleteGroup[];\n /**\n * Custom renderer for group headings. The returned node is wrapped by the\n * component in an element carrying the `id` referenced by the surrounding\n * `role=\"group\"` wrapper's `aria-labelledby`, so consumers only need to\n * return visual content.\n */\n renderGroupHeading?: (group: AutocompleteGroup) => React.ReactNode;\n}\n\ninterface AutocompleteSingleProps extends AutocompleteBaseProps {\n multiple?: false;\n value?: string | null;\n defaultValue?: string | null;\n onChange?: (value: string | null) => void;\n}\n\ninterface AutocompleteMultiProps extends AutocompleteBaseProps {\n multiple: true;\n value?: string[];\n defaultValue?: string[];\n onChange?: (values: string[]) => void;\n}\n\nexport type AutocompleteProps = AutocompleteSingleProps | AutocompleteMultiProps;\n\nconst CONTAINER_HEIGHT: Record<AutocompleteSize, string> = {\n \"48\": \"min-h-12\",\n \"40\": \"min-h-10\",\n \"32\": \"min-h-8\",\n};\n\nconst INPUT_SIZE_CLASSES: Record<AutocompleteSize, string> = {\n \"48\": \"typography-body-default-16px-regular\",\n \"40\": \"typography-body-default-16px-regular\",\n \"32\": \"typography-body-small-14px-regular\",\n};\n\nconst PADDING_CLASSES: Record<AutocompleteSize, string> = {\n \"48\": \"px-4 py-1.5 gap-3\",\n \"40\": \"px-4 py-1 gap-3\",\n \"32\": \"px-3 py-1 gap-2\",\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 \"Autocomplete: no accessible name provided. Pass a `label`, `aria-label`, or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\n/**\n * A combobox input with single- or multi-select, optional async loading, and\n * native support for grouped + pinned options.\n *\n * - Pass `groups` plus an `options` array whose entries reference each group\n * via `groupId` to render hierarchical lists with proper `role=\"group\"` +\n * `aria-labelledby` semantics. Options without a `groupId` render above\n * the first group; options marked `pinned` render above everything and\n * bypass the search filter.\n * - Indentation of nested rows (e.g. price under product) is controlled by\n * the consumer via `renderOption` styling — there is no built-in indent.\n *\n * @example\n * ```tsx\n * <Autocomplete\n * aria-label=\"Choose a product\"\n * options={[\n * { value: \"__new__\", label: \"+ Create new product\", pinned: true },\n * { value: \"product:abc\", label: \"Demo Product\", groupId: \"recent\" },\n * ]}\n * groups={[{ id: \"recent\", label: \"Recent products\" }]}\n * onChange={handleSelect}\n * />\n * ```\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Conditional JSX branches in the render template\nexport const Autocomplete = React.forwardRef<HTMLInputElement, AutocompleteProps>((props, ref) => {\n const {\n label,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledby,\n helperText,\n size = \"48\",\n error = false,\n errorMessage,\n placeholder,\n leftIcon,\n fullWidth = false,\n disabled = false,\n clearable = false,\n clearAriaLabel = \"Clear\",\n className,\n loading = false,\n loadingText,\n emptyText = \"No results\",\n renderOption,\n renderTag,\n renderGroupHeading,\n } = props;\n\n const ac = useAutocomplete(props);\n\n React.useImperativeHandle(ref, () => ac.inputRef.current as HTMLInputElement);\n\n warnMissingAccessibleName(label, ariaLabel, ariaLabelledby);\n\n const bottomText = error && errorMessage ? errorMessage : helperText;\n\n return (\n <Popover.Root open={ac.isOpen && !disabled} onOpenChange={ac.handleOpenChange}>\n <div\n className={cn(\"flex flex-col\", fullWidth && \"w-full\", className)}\n data-autocomplete-root=\"\"\n data-disabled={disabled ? \"\" : undefined}\n data-error={error ? \"\" : undefined}\n >\n {label && (\n <label\n htmlFor={ac.inputId}\n className=\"typography-description-12px-semibold px-1 pt-1 pb-2 text-content-primary\"\n >\n {label}\n </label>\n )}\n\n <Popover.Anchor asChild>\n {/* biome-ignore lint/a11y/noStaticElementInteractions: Container delegates click to the inner input */}\n {/* biome-ignore lint/a11y/useKeyWithClickEvents: Keyboard interaction is handled by the inner combobox input */}\n <div\n className={cn(\n \"flex flex-wrap items-center overflow-hidden rounded-sm border bg-neutral-alphas-100 has-focus-visible:shadow-focus-ring has-focus-visible:outline-none motion-safe:transition-colors\",\n error ? \"border-error-content\" : \"border-transparent\",\n !disabled && !error && \"hover:border-neutral-alphas-400\",\n ac.isOpen && !error && !disabled && \"border-neutral-alphas-400\",\n CONTAINER_HEIGHT[size],\n PADDING_CLASSES[size],\n disabled && \"opacity-50\",\n )}\n onClick={ac.handleContainerClick}\n >\n {leftIcon && (\n <div className=\"flex size-5 shrink-0 items-center justify-center text-content-secondary\">\n {leftIcon}\n </div>\n )}\n\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-1.5\">\n {ac.isMulti &&\n ac.selectedMultiOptions.map((opt) => (\n <AutocompleteTag\n key={opt.value}\n option={opt}\n disabled={disabled}\n onRemove={() => ac.toggleMulti(opt.value)}\n renderTag={renderTag}\n />\n ))}\n\n <input\n ref={ac.inputRef}\n id={ac.inputId}\n role=\"combobox\"\n type=\"text\"\n disabled={disabled}\n aria-expanded={ac.isOpen}\n aria-controls={ac.isOpen ? ac.listboxId : undefined}\n aria-activedescendant={ac.activeDescendantId}\n aria-autocomplete=\"list\"\n aria-describedby={bottomText ? ac.helperTextId : undefined}\n aria-invalid={error || undefined}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n autoComplete=\"off\"\n placeholder={\n ac.isMulti && ac.selectedMultiValues.length > 0 ? undefined : placeholder\n }\n value={ac.displayInputValue}\n onChange={ac.handleInputChange}\n onKeyDown={ac.handleKeyDown}\n onFocus={ac.handleFocus}\n onBlur={ac.handleBlur}\n className={cn(\n \"min-w-[40px] flex-1 truncate bg-transparent text-content-primary no-underline placeholder:text-content-secondary placeholder:opacity-40 focus:outline-none disabled:cursor-not-allowed\",\n INPUT_SIZE_CLASSES[size],\n )}\n />\n </div>\n\n <div className=\"flex shrink-0 items-center gap-1\">\n {loading && <SpinnerIcon className=\"size-4 animate-spin text-content-secondary\" />}\n {clearable && ac.hasClearableValue && !disabled && (\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label={clearAriaLabel}\n className=\"flex size-5 shrink-0 cursor-pointer items-center justify-center text-content-secondary hover:text-content-primary active:scale-95\"\n onClick={ac.handleClear}\n >\n <CloseIcon className=\"size-4\" />\n </button>\n )}\n <div className=\"flex size-5 shrink-0 items-center justify-center text-content-secondary\">\n <ChevronDownIcon\n className={cn(\"size-5 transition-transform\", ac.isOpen && \"rotate-180\")}\n />\n </div>\n </div>\n </div>\n </Popover.Anchor>\n\n <Popover.Portal>\n <Popover.Content\n sideOffset={4}\n collisionPadding={FLOATING_CONTENT_COLLISION_PADDING}\n onOpenAutoFocus={(e) => e.preventDefault()}\n onCloseAutoFocus={(e) => e.preventDefault()}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\" }}\n className={cn(\n \"w-max min-w-(--radix-popper-anchor-width) max-w-(--radix-popover-content-available-width) overflow-hidden rounded-sm border border-neutral-alphas-200 bg-background-primary text-content-primary shadow-[0_4px_16px_rgba(0,0,0,0.10)]\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n \"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n \"data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2\",\n )}\n >\n <div\n ref={ac.listRef}\n id={ac.listboxId}\n role=\"listbox\"\n aria-label={label ?? ariaLabel ?? \"Options\"}\n aria-multiselectable={ac.isMulti || undefined}\n className=\"max-h-60 overflow-y-auto p-1\"\n >\n <AutocompleteDropdownContent\n loading={loading}\n loadingText={loadingText}\n emptyText={emptyText}\n visibleOptions={ac.visibleOptions}\n visibleSections={ac.visibleSections}\n createOption={ac.createOption}\n listboxId={ac.listboxId}\n activeIndex={ac.activeIndex}\n isMulti={ac.isMulti}\n selectedMultiValues={ac.selectedMultiValues}\n selectedValue={ac.selectedValue}\n onSelect={ac.handleSelect}\n onMouseEnter={ac.setActiveIndex}\n renderOption={renderOption}\n renderGroupHeading={renderGroupHeading}\n />\n </div>\n </Popover.Content>\n </Popover.Portal>\n\n {bottomText && (\n <p\n id={ac.helperTextId}\n className={cn(\n \"typography-description-12px-regular px-2 pt-1 pb-0.5\",\n error ? \"text-error-content\" : \"text-content-secondary\",\n )}\n >\n {bottomText}\n </p>\n )}\n </div>\n </Popover.Root>\n );\n});\n\nAutocomplete.displayName = \"Autocomplete\";\n"],"names":["Popover"],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ import { cn } from "../../utils/cn.mjs";
|
|
|
4
4
|
import { SpinnerIcon } from "../Icons/SpinnerIcon.mjs";
|
|
5
5
|
import { AutocompleteOptionItem } from "./AutocompleteOptionItem.mjs";
|
|
6
6
|
function DefaultGroupHeading({ children }) {
|
|
7
|
-
return /* @__PURE__ */ jsx("span", { className: "typography-
|
|
7
|
+
return /* @__PURE__ */ jsx("span", { className: "typography-description-12px-semibold block px-3 pt-2 pb-1 text-content-secondary uppercase tracking-wide", children });
|
|
8
8
|
}
|
|
9
9
|
function AutocompleteDropdownContent({
|
|
10
10
|
loading,
|
|
@@ -28,7 +28,7 @@ function AutocompleteDropdownContent({
|
|
|
28
28
|
// biome-ignore lint/a11y/useSemanticElements: <output> is not appropriate here; using role="status" for live region announcements
|
|
29
29
|
/* @__PURE__ */ jsxs("div", { role: "status", className: "flex items-center justify-center py-4", children: [
|
|
30
30
|
/* @__PURE__ */ jsx(SpinnerIcon, { className: "size-5 animate-spin text-content-secondary" }),
|
|
31
|
-
loadingText && /* @__PURE__ */ jsx("span", { className: "typography-
|
|
31
|
+
loadingText && /* @__PURE__ */ jsx("span", { className: "typography-body-small-14px-regular ml-2 text-content-secondary", children: loadingText })
|
|
32
32
|
] })
|
|
33
33
|
);
|
|
34
34
|
}
|
|
@@ -37,7 +37,7 @@ function AutocompleteDropdownContent({
|
|
|
37
37
|
const hasGroups = groups.length > 0;
|
|
38
38
|
const nonPinnedVisibleCount = ungrouped.length + groups.reduce((n, g) => n + g.options.length, 0) + (createOption ? 1 : 0);
|
|
39
39
|
if (visibleOptions.length === 0) {
|
|
40
|
-
return /* @__PURE__ */ jsx("div", { className: "typography-
|
|
40
|
+
return /* @__PURE__ */ jsx("div", { className: "typography-body-small-14px-regular block px-3 py-4 text-center text-content-secondary", children: emptyText });
|
|
41
41
|
}
|
|
42
42
|
let cursor = 0;
|
|
43
43
|
const renderOptionRow = (option) => {
|
|
@@ -86,7 +86,7 @@ function AutocompleteDropdownContent({
|
|
|
86
86
|
);
|
|
87
87
|
}),
|
|
88
88
|
createOption && renderOptionRow(createOption),
|
|
89
|
-
emptyAfterPinned && /* @__PURE__ */ jsx("div", { className: "typography-
|
|
89
|
+
emptyAfterPinned && /* @__PURE__ */ jsx("div", { className: "typography-body-small-14px-regular block px-3 py-4 text-center text-content-secondary", children: emptyText })
|
|
90
90
|
] });
|
|
91
91
|
}
|
|
92
92
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutocompleteDropdownContent.mjs","sources":["../../../src/components/Autocomplete/AutocompleteDropdownContent.tsx"],"sourcesContent":["import type * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { SpinnerIcon } from \"../Icons/SpinnerIcon\";\nimport type { AutocompleteGroup, AutocompleteOption } from \"./Autocomplete\";\nimport { AutocompleteOptionItem } from \"./AutocompleteOptionItem\";\nimport type { AutocompleteVisibleSections } from \"./constants\";\n\ninterface AutocompleteDropdownContentProps {\n loading: boolean;\n loadingText?: string;\n emptyText?: string;\n visibleOptions: AutocompleteOption[];\n visibleSections: AutocompleteVisibleSections;\n /**\n * Synthetic \"Create new …\" row, when `creatable` is on and the search\n * doesn't already match an existing option. Rendered as a trailing row,\n * always shown regardless of filter, and indexed last in\n * `visibleOptions` so keyboard navigation works.\n */\n createOption?: AutocompleteOption | null;\n listboxId: string;\n activeIndex: number;\n isMulti: boolean;\n selectedMultiValues: string[];\n selectedValue: string | null;\n onSelect: (option: AutocompleteOption) => void;\n onMouseEnter: (index: number) => void;\n renderOption?: (\n option: AutocompleteOption,\n state: { selected: boolean; active: boolean },\n ) => React.ReactNode;\n renderGroupHeading?: (group: AutocompleteGroup) => React.ReactNode;\n}\n\nfunction DefaultGroupHeading({ children }: { children: React.ReactNode }) {\n return (\n <span className=\"typography-
|
|
1
|
+
{"version":3,"file":"AutocompleteDropdownContent.mjs","sources":["../../../src/components/Autocomplete/AutocompleteDropdownContent.tsx"],"sourcesContent":["import type * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { SpinnerIcon } from \"../Icons/SpinnerIcon\";\nimport type { AutocompleteGroup, AutocompleteOption } from \"./Autocomplete\";\nimport { AutocompleteOptionItem } from \"./AutocompleteOptionItem\";\nimport type { AutocompleteVisibleSections } from \"./constants\";\n\ninterface AutocompleteDropdownContentProps {\n loading: boolean;\n loadingText?: string;\n emptyText?: string;\n visibleOptions: AutocompleteOption[];\n visibleSections: AutocompleteVisibleSections;\n /**\n * Synthetic \"Create new …\" row, when `creatable` is on and the search\n * doesn't already match an existing option. Rendered as a trailing row,\n * always shown regardless of filter, and indexed last in\n * `visibleOptions` so keyboard navigation works.\n */\n createOption?: AutocompleteOption | null;\n listboxId: string;\n activeIndex: number;\n isMulti: boolean;\n selectedMultiValues: string[];\n selectedValue: string | null;\n onSelect: (option: AutocompleteOption) => void;\n onMouseEnter: (index: number) => void;\n renderOption?: (\n option: AutocompleteOption,\n state: { selected: boolean; active: boolean },\n ) => React.ReactNode;\n renderGroupHeading?: (group: AutocompleteGroup) => React.ReactNode;\n}\n\nfunction DefaultGroupHeading({ children }: { children: React.ReactNode }) {\n return (\n <span className=\"typography-description-12px-semibold block px-3 pt-2 pb-1 text-content-secondary uppercase tracking-wide\">\n {children}\n </span>\n );\n}\n\nexport function AutocompleteDropdownContent({\n loading,\n loadingText,\n emptyText,\n visibleOptions,\n visibleSections,\n createOption,\n listboxId,\n activeIndex,\n isMulti,\n selectedMultiValues,\n selectedValue,\n onSelect,\n onMouseEnter,\n renderOption,\n renderGroupHeading,\n}: AutocompleteDropdownContentProps) {\n if (loading) {\n return (\n // biome-ignore lint/a11y/useSemanticElements: <output> is not appropriate here; using role=\"status\" for live region announcements\n <div role=\"status\" className=\"flex items-center justify-center py-4\">\n <SpinnerIcon className=\"size-5 animate-spin text-content-secondary\" />\n {loadingText && (\n <span className=\"typography-body-small-14px-regular ml-2 text-content-secondary\">\n {loadingText}\n </span>\n )}\n </div>\n );\n }\n\n const { pinned, ungrouped, groups } = visibleSections;\n const hasTopBlock = pinned.length > 0 || ungrouped.length > 0;\n const hasGroups = groups.length > 0;\n const nonPinnedVisibleCount =\n ungrouped.length + groups.reduce((n, g) => n + g.options.length, 0) + (createOption ? 1 : 0);\n\n if (visibleOptions.length === 0) {\n return (\n <div className=\"typography-body-small-14px-regular block px-3 py-4 text-center text-content-secondary\">\n {emptyText}\n </div>\n );\n }\n\n let cursor = 0;\n const renderOptionRow = (option: AutocompleteOption) => {\n const index = cursor++;\n const isSelected = isMulti\n ? selectedMultiValues.includes(option.value)\n : option.value === selectedValue;\n return (\n <AutocompleteOptionItem\n key={option.value}\n option={option}\n optionId={`${listboxId}-option-${index}`}\n index={index}\n isSelected={isSelected}\n isActive={index === activeIndex}\n onSelect={() => onSelect(option)}\n onMouseEnter={() => onMouseEnter(index)}\n renderOption={renderOption}\n />\n );\n };\n\n const showDivider = hasTopBlock && hasGroups;\n const emptyAfterPinned = nonPinnedVisibleCount === 0 && pinned.length > 0;\n\n return (\n <>\n {pinned.map(renderOptionRow)}\n {ungrouped.map(renderOptionRow)}\n {groups.map((section, sectionIndex) => {\n const headingId = `${listboxId}-group-${section.group.id}`;\n const headingNode = renderGroupHeading\n ? renderGroupHeading(section.group)\n : section.group.label;\n return (\n // biome-ignore lint/a11y/useSemanticElements: <fieldset> carries form semantics and default styling we don't want inside a listbox; role=\"group\" is the correct ARIA pattern here\n <div\n key={section.group.id}\n role=\"group\"\n aria-labelledby={headingId}\n className={cn(\n showDivider && sectionIndex === 0 && \"mt-1 border-neutral-alphas-200 border-t pt-1\",\n )}\n >\n <div role=\"presentation\" id={headingId}>\n {renderGroupHeading ? (\n headingNode\n ) : (\n <DefaultGroupHeading>{headingNode}</DefaultGroupHeading>\n )}\n </div>\n {section.options.map(renderOptionRow)}\n </div>\n );\n })}\n {createOption && renderOptionRow(createOption)}\n {emptyAfterPinned && (\n <div className=\"typography-body-small-14px-regular block px-3 py-4 text-center text-content-secondary\">\n {emptyText}\n </div>\n )}\n </>\n );\n}\n"],"names":[],"mappings":";;;;;AAkCA,SAAS,oBAAoB,EAAE,YAA2C;AACxE,SACE,oBAAC,QAAA,EAAK,WAAU,4GACb,SAAA,CACH;AAEJ;AAEO,SAAS,4BAA4B;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAqC;AACnC,MAAI,SAAS;AACX;AAAA;AAAA,MAEE,qBAAC,OAAA,EAAI,MAAK,UAAS,WAAU,yCAC3B,UAAA;AAAA,QAAA,oBAAC,aAAA,EAAY,WAAU,6CAAA,CAA6C;AAAA,QACnE,eACC,oBAAC,QAAA,EAAK,WAAU,kEACb,UAAA,YAAA,CACH;AAAA,MAAA,EAAA,CAEJ;AAAA;AAAA,EAEJ;AAEA,QAAM,EAAE,QAAQ,WAAW,OAAA,IAAW;AACtC,QAAM,cAAc,OAAO,SAAS,KAAK,UAAU,SAAS;AAC5D,QAAM,YAAY,OAAO,SAAS;AAClC,QAAM,wBACJ,UAAU,SAAS,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,QAAQ,QAAQ,CAAC,KAAK,eAAe,IAAI;AAE5F,MAAI,eAAe,WAAW,GAAG;AAC/B,WACE,oBAAC,OAAA,EAAI,WAAU,yFACZ,UAAA,WACH;AAAA,EAEJ;AAEA,MAAI,SAAS;AACb,QAAM,kBAAkB,CAAC,WAA+B;AACtD,UAAM,QAAQ;AACd,UAAM,aAAa,UACf,oBAAoB,SAAS,OAAO,KAAK,IACzC,OAAO,UAAU;AACrB,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC;AAAA,QACA,UAAU,GAAG,SAAS,WAAW,KAAK;AAAA,QACtC;AAAA,QACA;AAAA,QACA,UAAU,UAAU;AAAA,QACpB,UAAU,MAAM,SAAS,MAAM;AAAA,QAC/B,cAAc,MAAM,aAAa,KAAK;AAAA,QACtC;AAAA,MAAA;AAAA,MARK,OAAO;AAAA,IAAA;AAAA,EAWlB;AAEA,QAAM,cAAc,eAAe;AACnC,QAAM,mBAAmB,0BAA0B,KAAK,OAAO,SAAS;AAExE,SACE,qBAAA,UAAA,EACG,UAAA;AAAA,IAAA,OAAO,IAAI,eAAe;AAAA,IAC1B,UAAU,IAAI,eAAe;AAAA,IAC7B,OAAO,IAAI,CAAC,SAAS,iBAAiB;AACrC,YAAM,YAAY,GAAG,SAAS,UAAU,QAAQ,MAAM,EAAE;AACxD,YAAM,cAAc,qBAChB,mBAAmB,QAAQ,KAAK,IAChC,QAAQ,MAAM;AAClB;AAAA;AAAA,QAEE;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,MAAK;AAAA,YACL,mBAAiB;AAAA,YACjB,WAAW;AAAA,cACT,eAAe,iBAAiB,KAAK;AAAA,YAAA;AAAA,YAGvC,UAAA;AAAA,cAAA,oBAAC,OAAA,EAAI,MAAK,gBAAe,IAAI,WAC1B,+BACC,cAEA,oBAAC,qBAAA,EAAqB,UAAA,YAAA,CAAY,GAEtC;AAAA,cACC,QAAQ,QAAQ,IAAI,eAAe;AAAA,YAAA;AAAA,UAAA;AAAA,UAd/B,QAAQ,MAAM;AAAA,QAAA;AAAA;AAAA,IAiBzB,CAAC;AAAA,IACA,gBAAgB,gBAAgB,YAAY;AAAA,IAC5C,oBACC,oBAAC,OAAA,EAAI,WAAU,yFACZ,UAAA,UAAA,CACH;AAAA,EAAA,GAEJ;AAEJ;"}
|
|
@@ -24,7 +24,7 @@ function AutocompleteOptionItem({
|
|
|
24
24
|
"aria-disabled": option.disabled || void 0,
|
|
25
25
|
"data-option-index": index,
|
|
26
26
|
className: cn(
|
|
27
|
-
"typography-
|
|
27
|
+
"typography-body-default-16px-regular relative flex w-full cursor-pointer select-none items-center gap-2 rounded-xs py-2 pr-2 pl-3 text-content-primary outline-none",
|
|
28
28
|
isActive && "bg-neutral-alphas-100",
|
|
29
29
|
option.disabled && "pointer-events-none opacity-50",
|
|
30
30
|
isCreate && "italic"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutocompleteOptionItem.mjs","sources":["../../../src/components/Autocomplete/AutocompleteOptionItem.tsx"],"sourcesContent":["import type * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { CheckIcon } from \"../Icons/CheckIcon\";\nimport type { AutocompleteOption } from \"./Autocomplete\";\nimport { CREATE_PREFIX, getLabel } from \"./constants\";\n\nexport function AutocompleteOptionItem({\n option,\n optionId,\n index,\n isSelected,\n isActive,\n onSelect,\n onMouseEnter,\n renderOption,\n}: {\n option: AutocompleteOption;\n optionId: string;\n index: number;\n isSelected: boolean;\n isActive: boolean;\n onSelect: () => void;\n onMouseEnter: () => void;\n renderOption?: (\n option: AutocompleteOption,\n state: { selected: boolean; active: boolean },\n ) => React.ReactNode;\n}) {\n const isCreate = option.value.startsWith(CREATE_PREFIX);\n\n return (\n <div\n id={optionId}\n role=\"option\"\n tabIndex={-1}\n aria-selected={isSelected}\n aria-disabled={option.disabled || undefined}\n data-option-index={index}\n className={cn(\n \"typography-
|
|
1
|
+
{"version":3,"file":"AutocompleteOptionItem.mjs","sources":["../../../src/components/Autocomplete/AutocompleteOptionItem.tsx"],"sourcesContent":["import type * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { CheckIcon } from \"../Icons/CheckIcon\";\nimport type { AutocompleteOption } from \"./Autocomplete\";\nimport { CREATE_PREFIX, getLabel } from \"./constants\";\n\nexport function AutocompleteOptionItem({\n option,\n optionId,\n index,\n isSelected,\n isActive,\n onSelect,\n onMouseEnter,\n renderOption,\n}: {\n option: AutocompleteOption;\n optionId: string;\n index: number;\n isSelected: boolean;\n isActive: boolean;\n onSelect: () => void;\n onMouseEnter: () => void;\n renderOption?: (\n option: AutocompleteOption,\n state: { selected: boolean; active: boolean },\n ) => React.ReactNode;\n}) {\n const isCreate = option.value.startsWith(CREATE_PREFIX);\n\n return (\n <div\n id={optionId}\n role=\"option\"\n tabIndex={-1}\n aria-selected={isSelected}\n aria-disabled={option.disabled || undefined}\n data-option-index={index}\n className={cn(\n \"typography-body-default-16px-regular relative flex w-full cursor-pointer select-none items-center gap-2 rounded-xs py-2 pr-2 pl-3 text-content-primary outline-none\",\n isActive && \"bg-neutral-alphas-100\",\n option.disabled && \"pointer-events-none opacity-50\",\n isCreate && \"italic\",\n )}\n onMouseEnter={onMouseEnter}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n if (!option.disabled) onSelect();\n }}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n if (!option.disabled) onSelect();\n }\n }}\n >\n {renderOption ? (\n renderOption(option, { selected: isSelected, active: isActive })\n ) : (\n <>\n <span className=\"min-w-0 flex-1 truncate\">{getLabel(option)}</span>\n {isSelected && (\n <span className=\"ml-auto flex size-4 shrink-0 items-center justify-center\">\n <CheckIcon className=\"size-4 text-content-primary\" />\n </span>\n )}\n </>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;AAMO,SAAS,uBAAuB;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAYG;AACD,QAAM,WAAW,OAAO,MAAM,WAAW,aAAa;AAEtD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,IAAI;AAAA,MACJ,MAAK;AAAA,MACL,UAAU;AAAA,MACV,iBAAe;AAAA,MACf,iBAAe,OAAO,YAAY;AAAA,MAClC,qBAAmB;AAAA,MACnB,WAAW;AAAA,QACT;AAAA,QACA,YAAY;AAAA,QACZ,OAAO,YAAY;AAAA,QACnB,YAAY;AAAA,MAAA;AAAA,MAEd;AAAA,MACA,aAAa,CAAC,MAAM,EAAE,eAAA;AAAA,MACtB,SAAS,MAAM;AACb,YAAI,CAAC,OAAO,SAAU,UAAA;AAAA,MACxB;AAAA,MACA,WAAW,CAAC,MAAM;AAChB,YAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,YAAE,eAAA;AACF,cAAI,CAAC,OAAO,SAAU,UAAA;AAAA,QACxB;AAAA,MACF;AAAA,MAEC,UAAA,eACC,aAAa,QAAQ,EAAE,UAAU,YAAY,QAAQ,SAAA,CAAU,IAE/D,qBAAA,UAAA,EACE,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,WAAU,2BAA2B,UAAA,SAAS,MAAM,GAAE;AAAA,QAC3D,kCACE,QAAA,EAAK,WAAU,4DACd,UAAA,oBAAC,WAAA,EAAU,WAAU,8BAAA,CAA8B,EAAA,CACrD;AAAA,MAAA,EAAA,CAEJ;AAAA,IAAA;AAAA,EAAA;AAIR;"}
|
|
@@ -11,7 +11,7 @@ function AutocompleteTag({
|
|
|
11
11
|
if (renderTag) {
|
|
12
12
|
return /* @__PURE__ */ jsx("span", { children: renderTag(option, onRemove) });
|
|
13
13
|
}
|
|
14
|
-
return /* @__PURE__ */ jsxs("span", { className: "typography-
|
|
14
|
+
return /* @__PURE__ */ jsxs("span", { className: "typography-description-12px-regular inline-flex max-w-full items-center gap-1 rounded-xs bg-neutral-alphas-200 px-2 py-0.5 text-content-primary", children: [
|
|
15
15
|
/* @__PURE__ */ jsx("span", { className: "truncate", children: getLabel(option) }),
|
|
16
16
|
/* @__PURE__ */ jsx(
|
|
17
17
|
"button",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutocompleteTag.mjs","sources":["../../../src/components/Autocomplete/AutocompleteTag.tsx"],"sourcesContent":["import type * as React from \"react\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\nimport type { AutocompleteOption } from \"./Autocomplete\";\nimport { getLabel } from \"./constants\";\n\nexport function AutocompleteTag({\n option,\n disabled,\n onRemove,\n renderTag,\n}: {\n option: AutocompleteOption;\n disabled: boolean;\n onRemove: () => void;\n renderTag?: (option: AutocompleteOption, onRemove: () => void) => React.ReactNode;\n}) {\n if (renderTag) {\n return <span>{renderTag(option, onRemove)}</span>;\n }\n\n return (\n <span className=\"typography-
|
|
1
|
+
{"version":3,"file":"AutocompleteTag.mjs","sources":["../../../src/components/Autocomplete/AutocompleteTag.tsx"],"sourcesContent":["import type * as React from \"react\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\nimport type { AutocompleteOption } from \"./Autocomplete\";\nimport { getLabel } from \"./constants\";\n\nexport function AutocompleteTag({\n option,\n disabled,\n onRemove,\n renderTag,\n}: {\n option: AutocompleteOption;\n disabled: boolean;\n onRemove: () => void;\n renderTag?: (option: AutocompleteOption, onRemove: () => void) => React.ReactNode;\n}) {\n if (renderTag) {\n return <span>{renderTag(option, onRemove)}</span>;\n }\n\n return (\n <span className=\"typography-description-12px-regular inline-flex max-w-full items-center gap-1 rounded-xs bg-neutral-alphas-200 px-2 py-0.5 text-content-primary\">\n <span className=\"truncate\">{getLabel(option)}</span>\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label={`Remove ${getLabel(option)}`}\n className=\"flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-xs text-content-secondary hover:text-content-primary active:scale-95\"\n onClick={(e) => {\n e.stopPropagation();\n onRemove();\n }}\n disabled={disabled}\n >\n <CloseIcon className=\"size-3\" />\n </button>\n </span>\n );\n}\n"],"names":[],"mappings":";;;;AAKO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,MAAI,WAAW;AACb,WAAO,oBAAC,QAAA,EAAM,UAAA,UAAU,QAAQ,QAAQ,GAAE;AAAA,EAC5C;AAEA,SACE,qBAAC,QAAA,EAAK,WAAU,mJACd,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAK,WAAU,YAAY,UAAA,SAAS,MAAM,GAAE;AAAA,IAC7C;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,UAAU;AAAA,QACV,cAAY,UAAU,SAAS,MAAM,CAAC;AAAA,QACtC,WAAU;AAAA,QACV,SAAS,CAAC,MAAM;AACd,YAAE,gBAAA;AACF,mBAAA;AAAA,QACF;AAAA,QACA;AAAA,QAEA,UAAA,oBAAC,WAAA,EAAU,WAAU,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAChC,GACF;AAEJ;"}
|
|
@@ -6,20 +6,20 @@ import { cn } from "../../utils/cn.mjs";
|
|
|
6
6
|
const badgeVariants = {
|
|
7
7
|
variant: {
|
|
8
8
|
default: "bg-neutral-alphas-50 text-content-secondary",
|
|
9
|
-
dark: "bg-neutral-alphas-150 text-content-
|
|
9
|
+
dark: "bg-neutral-alphas-150 text-content-always-white",
|
|
10
10
|
success: "bg-neutral-alphas-50 text-content-secondary",
|
|
11
11
|
warning: "bg-neutral-alphas-50 text-content-secondary",
|
|
12
12
|
error: "bg-neutral-alphas-50 text-content-secondary",
|
|
13
13
|
special: "bg-neutral-alphas-50 text-content-secondary",
|
|
14
14
|
info: "bg-neutral-alphas-50 text-content-secondary",
|
|
15
|
-
online: "bg-
|
|
16
|
-
brand: "bg-brand-primary-default text-content-
|
|
17
|
-
pink: "bg-brand-secondary-default text-content-
|
|
18
|
-
brandLight: "bg-brand-primary-muted text-content-
|
|
19
|
-
pinkLight: "bg-brand-secondary-muted text-content-
|
|
15
|
+
online: "bg-background-primary text-brand-primary-default",
|
|
16
|
+
brand: "bg-brand-primary-default text-content-always-black",
|
|
17
|
+
pink: "bg-brand-secondary-default text-content-always-black",
|
|
18
|
+
brandLight: "bg-brand-primary-muted text-content-always-black",
|
|
19
|
+
pinkLight: "bg-brand-secondary-muted text-content-always-black"
|
|
20
20
|
},
|
|
21
21
|
dotColor: {
|
|
22
|
-
default: "bg-content-
|
|
22
|
+
default: "bg-content-always-black",
|
|
23
23
|
dark: "bg-content-primary-inverted",
|
|
24
24
|
success: "bg-success-content",
|
|
25
25
|
warning: "bg-warning-content",
|
|
@@ -27,10 +27,10 @@ const badgeVariants = {
|
|
|
27
27
|
special: "bg-special-default",
|
|
28
28
|
info: "bg-info-content",
|
|
29
29
|
online: "bg-brand-primary-default",
|
|
30
|
-
brand: "bg-content-
|
|
31
|
-
pink: "bg-content-
|
|
32
|
-
brandLight: "bg-content-
|
|
33
|
-
pinkLight: "bg-content-
|
|
30
|
+
brand: "bg-content-always-black",
|
|
31
|
+
pink: "bg-content-always-black",
|
|
32
|
+
brandLight: "bg-content-always-black",
|
|
33
|
+
pinkLight: "bg-content-always-black"
|
|
34
34
|
}
|
|
35
35
|
};
|
|
36
36
|
const Badge = React.forwardRef(
|
|
@@ -53,7 +53,7 @@ const Badge = React.forwardRef(
|
|
|
53
53
|
"data-testid": "badge",
|
|
54
54
|
className: cn(
|
|
55
55
|
// Base styles
|
|
56
|
-
"typography-
|
|
56
|
+
"typography-description-12px-semibold inline-flex h-5 min-w-0 items-center gap-2 rounded-full px-2",
|
|
57
57
|
// Variant styles
|
|
58
58
|
badgeVariants.variant[variant],
|
|
59
59
|
// Interactive
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Badge.mjs","sources":["../../../src/components/Badge/Badge.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nconst badgeVariants = {\n variant: {\n default: \"bg-neutral-alphas-50 text-content-secondary\",\n dark: \"bg-neutral-alphas-150 text-content-
|
|
1
|
+
{"version":3,"file":"Badge.mjs","sources":["../../../src/components/Badge/Badge.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nconst badgeVariants = {\n variant: {\n default: \"bg-neutral-alphas-50 text-content-secondary\",\n dark: \"bg-neutral-alphas-150 text-content-always-white\",\n success: \"bg-neutral-alphas-50 text-content-secondary\",\n warning: \"bg-neutral-alphas-50 text-content-secondary\",\n error: \"bg-neutral-alphas-50 text-content-secondary\",\n special: \"bg-neutral-alphas-50 text-content-secondary\",\n info: \"bg-neutral-alphas-50 text-content-secondary\",\n online: \"bg-background-primary text-brand-primary-default\",\n brand: \"bg-brand-primary-default text-content-always-black\",\n pink: \"bg-brand-secondary-default text-content-always-black\",\n brandLight: \"bg-brand-primary-muted text-content-always-black\",\n pinkLight: \"bg-brand-secondary-muted text-content-always-black\",\n },\n dotColor: {\n default: \"bg-content-always-black\",\n dark: \"bg-content-primary-inverted\",\n success: \"bg-success-content\",\n warning: \"bg-warning-content\",\n error: \"bg-error-content\",\n special: \"bg-special-default\",\n info: \"bg-info-content\",\n online: \"bg-brand-primary-default\",\n brand: \"bg-content-always-black\",\n pink: \"bg-content-always-black\",\n brandLight: \"bg-content-always-black\",\n pinkLight: \"bg-content-always-black\",\n },\n} as const;\n\n/** Visual style variant of the badge. */\nexport type BadgeVariant =\n | \"default\"\n | \"dark\"\n | \"success\"\n | \"warning\"\n | \"error\"\n | \"special\"\n | \"info\"\n | \"online\"\n | \"brand\"\n | \"pink\"\n | \"brandLight\"\n | \"pinkLight\";\n\nexport interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Visual style variant of the badge. @default \"default\" */\n variant?: BadgeVariant;\n /** Whether to show a coloured status dot at the leading edge. @default true */\n leftDot?: boolean;\n /** Icon element displayed before the label. */\n leftIcon?: React.ReactNode;\n /** Icon element displayed after the label. */\n rightIcon?: React.ReactNode;\n /** Merge props onto a child element instead of rendering a `<span>`. @default false */\n asChild?: boolean;\n}\n\n/**\n * A small inline label for status, category, or metadata information.\n *\n * @example\n * ```tsx\n * <Badge variant=\"success\">Active</Badge>\n * ```\n */\nexport const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(\n (\n {\n className,\n variant = \"default\",\n leftDot = true,\n leftIcon,\n rightIcon,\n asChild = false,\n onClick,\n children,\n ...props\n },\n ref,\n ) => {\n const Comp = asChild ? Slot : \"span\";\n\n return (\n <Comp\n ref={ref}\n data-testid=\"badge\"\n className={cn(\n // Base styles\n \"typography-description-12px-semibold inline-flex h-5 min-w-0 items-center gap-2 rounded-full px-2\",\n // Variant styles\n badgeVariants.variant[variant],\n // Interactive\n onClick && \"cursor-pointer\",\n // Manual CSS overrides\n className,\n )}\n onClick={onClick}\n {...props}\n >\n {leftIcon && (\n <span className=\"flex size-3\" aria-hidden=\"true\">\n {leftIcon}\n </span>\n )}\n {leftDot && (\n <span\n className={cn(\"size-1 shrink-0 rounded-full\", badgeVariants.dotColor[variant])}\n aria-hidden=\"true\"\n />\n )}\n {asChild ? (\n <Slottable>{children}</Slottable>\n ) : (\n <span className=\"min-w-0 truncate\">{children}</span>\n )}\n {rightIcon && (\n <span className=\"flex size-3\" aria-hidden=\"true\">\n {rightIcon}\n </span>\n )}\n </Comp>\n );\n },\n);\n\nBadge.displayName = \"Badge\";\n"],"names":[],"mappings":";;;;;AAIA,MAAM,gBAAgB;AAAA,EACpB,SAAS;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS;AAAA,IACT,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,WAAW;AAAA,EAAA;AAAA,EAEb,UAAU;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS;AAAA,IACT,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,WAAW;AAAA,EAAA;AAEf;AAsCO,MAAM,QAAQ,MAAM;AAAA,EACzB,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,OAAO,UAAU,OAAO;AAE9B,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAY;AAAA,QACZ,WAAW;AAAA;AAAA,UAET;AAAA;AAAA,UAEA,cAAc,QAAQ,OAAO;AAAA;AAAA,UAE7B,WAAW;AAAA;AAAA,UAEX;AAAA,QAAA;AAAA,QAEF;AAAA,QACC,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,gCACE,QAAA,EAAK,WAAU,eAAc,eAAY,QACvC,UAAA,UACH;AAAA,UAED,WACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW,GAAG,gCAAgC,cAAc,SAAS,OAAO,CAAC;AAAA,cAC7E,eAAY;AAAA,YAAA;AAAA,UAAA;AAAA,UAGf,8BACE,WAAA,EAAW,SAAA,CAAS,IAErB,oBAAC,QAAA,EAAK,WAAU,oBAAoB,SAAA,CAAS;AAAA,UAE9C,aACC,oBAAC,QAAA,EAAK,WAAU,eAAc,eAAY,QACvC,UAAA,UAAA,CACH;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEA,MAAM,cAAc;"}
|
|
@@ -97,12 +97,12 @@ function BannerGuideBody({
|
|
|
97
97
|
{
|
|
98
98
|
variant: GUIDE_BADGE_VARIANT[appStoreVariant],
|
|
99
99
|
leftDot: false,
|
|
100
|
-
className: "typography-
|
|
100
|
+
className: "typography-badge-badgecaps self-start",
|
|
101
101
|
children: eyebrow
|
|
102
102
|
}
|
|
103
103
|
),
|
|
104
|
-
title !== void 0 && title !== null && title !== false && /* @__PURE__ */ jsx("p", { id: labelledBy, className: "typography-
|
|
105
|
-
description !== void 0 && description !== null && description !== false && /* @__PURE__ */ jsx("p", { className: "typography-
|
|
104
|
+
title !== void 0 && title !== null && title !== false && /* @__PURE__ */ jsx("p", { id: labelledBy, className: "typography-body-default-16px-semibold text-content-primary", children: title }),
|
|
105
|
+
description !== void 0 && description !== null && description !== false && /* @__PURE__ */ jsx("p", { className: "typography-body-small-14px-regular text-content-secondary", children: description }),
|
|
106
106
|
textAction
|
|
107
107
|
] });
|
|
108
108
|
}
|
|
@@ -114,7 +114,7 @@ function BannerFeatureBody({
|
|
|
114
114
|
textAction,
|
|
115
115
|
labelledBy
|
|
116
116
|
}) {
|
|
117
|
-
const titleClass = layout === "vertical" ? "typography-
|
|
117
|
+
const titleClass = layout === "vertical" ? "typography-body-default-16px-semibold text-content-primary" : "typography-body-default-16px-semibold text-[18px] leading-6 text-content-primary";
|
|
118
118
|
const mediaWrap = layout === "compact" ? "size-20 shrink-0 overflow-hidden rounded-sm" : "size-[132px] shrink-0 overflow-hidden rounded-sm";
|
|
119
119
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
120
120
|
media !== void 0 && media !== null && /* @__PURE__ */ jsx("div", { className: cn(mediaWrap, layout === "vertical" && "w-full [&>*]:mx-auto"), children: media }),
|
|
@@ -127,7 +127,7 @@ function BannerFeatureBody({
|
|
|
127
127
|
),
|
|
128
128
|
children: [
|
|
129
129
|
title !== void 0 && title !== null && title !== false && /* @__PURE__ */ jsx("div", { id: labelledBy, className: titleClass, children: title }),
|
|
130
|
-
description !== void 0 && description !== null && description !== false && /* @__PURE__ */ jsx("p", { className: "typography-
|
|
130
|
+
description !== void 0 && description !== null && description !== false && /* @__PURE__ */ jsx("p", { className: "typography-body-small-14px-regular text-content-secondary", children: description }),
|
|
131
131
|
textAction
|
|
132
132
|
]
|
|
133
133
|
}
|
|
@@ -152,10 +152,10 @@ function BannerSubtleBody({
|
|
|
152
152
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: [
|
|
153
153
|
leadBadge !== void 0 && leadBadge !== null && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: leadBadge }),
|
|
154
154
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
155
|
-
title !== void 0 && title !== null && title !== false && /* @__PURE__ */ jsx("div", { id: labelledBy, className: "typography-
|
|
155
|
+
title !== void 0 && title !== null && title !== false && /* @__PURE__ */ jsx("div", { id: labelledBy, className: "typography-header-heading-xs text-content-primary", children: title }),
|
|
156
156
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
157
|
-
description !== void 0 && description !== null && description !== false && /* @__PURE__ */ jsx("p", { className: "typography-
|
|
158
|
-
secondaryLine !== void 0 && secondaryLine !== null && secondaryLine !== false && /* @__PURE__ */ jsx("p", { className: "typography-
|
|
157
|
+
description !== void 0 && description !== null && description !== false && /* @__PURE__ */ jsx("p", { className: "typography-body-small-14px-regular text-content-primary", children: description }),
|
|
158
|
+
secondaryLine !== void 0 && secondaryLine !== null && secondaryLine !== false && /* @__PURE__ */ jsx("p", { className: "typography-description-12px-regular text-content-primary", children: secondaryLine })
|
|
159
159
|
] })
|
|
160
160
|
] }),
|
|
161
161
|
stackedAction !== void 0 && stackedAction !== null && /* @__PURE__ */ jsx("div", { className: "flex flex-col items-end self-start", children: stackedAction }),
|
|
@@ -176,11 +176,11 @@ function BannerInverseBody({
|
|
|
176
176
|
dismissSlot
|
|
177
177
|
}) {
|
|
178
178
|
const mediaSizeDefault = "size-12 shrink-0 overflow-hidden rounded-xl";
|
|
179
|
-
const titleClassInverse = "typography-
|
|
179
|
+
const titleClassInverse = "typography-header-heading-xs text-content-primary-inverted";
|
|
180
180
|
const textColumn = /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
181
|
-
eyebrow !== void 0 && eyebrow !== null && eyebrow !== false && /* @__PURE__ */ jsx("p", { className: "typography-
|
|
181
|
+
eyebrow !== void 0 && eyebrow !== null && eyebrow !== false && /* @__PURE__ */ jsx("p", { className: "typography-description-12px-semibold text-content-primary-inverted", children: eyebrow }),
|
|
182
182
|
title !== void 0 && title !== null && title !== false && /* @__PURE__ */ jsx("div", { id: labelledBy, className: titleClassInverse, children: title }),
|
|
183
|
-
description !== void 0 && description !== null && description !== false && /* @__PURE__ */ jsx("p", { className: "typography-
|
|
183
|
+
description !== void 0 && description !== null && description !== false && /* @__PURE__ */ jsx("p", { className: "typography-body-small-14px-regular text-content-primary-inverted", children: description })
|
|
184
184
|
] });
|
|
185
185
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
186
186
|
media !== void 0 && media !== null && /* @__PURE__ */ jsx("div", { className: mediaSizeDefault, children: media }),
|