@mieweb/ui 0.1.0 → 0.1.1
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/LICENSE +39 -15
- package/README.md +9 -1
- package/dist/{chunk-CLNOI5J7.js → chunk-4SMSH4OY.js} +4 -4
- package/dist/chunk-4SMSH4OY.js.map +1 -0
- package/dist/chunk-5T3AWNHG.cjs +471 -0
- package/dist/chunk-5T3AWNHG.cjs.map +1 -0
- package/dist/chunk-74K3RRU7.cjs +4 -0
- package/dist/{chunk-ZO46CFVN.cjs.map → chunk-74K3RRU7.cjs.map} +1 -1
- package/dist/{chunk-VWXGUNBR.cjs → chunk-AKTUXJPI.cjs} +107 -18
- package/dist/chunk-AKTUXJPI.cjs.map +1 -0
- package/dist/{chunk-NH2JVQ6V.cjs → chunk-I7L6CQXR.cjs} +21 -9
- package/dist/chunk-I7L6CQXR.cjs.map +1 -0
- package/dist/{chunk-KJOFWJHV.js → chunk-MFB4FS7D.js} +120 -81
- package/dist/chunk-MFB4FS7D.js.map +1 -0
- package/dist/{chunk-LEE3NMNP.cjs → chunk-NL3CZNBH.cjs} +120 -81
- package/dist/chunk-NL3CZNBH.cjs.map +1 -0
- package/dist/{chunk-BR2XGATJ.cjs → chunk-NNEFAUHV.cjs} +4 -4
- package/dist/chunk-NNEFAUHV.cjs.map +1 -0
- package/dist/chunk-SCV7C55E.cjs +11 -0
- package/dist/chunk-SCV7C55E.cjs.map +1 -0
- package/dist/{chunk-D5IBXXF2.js → chunk-SD44QJIP.js} +20 -8
- package/dist/chunk-SD44QJIP.js.map +1 -0
- package/dist/{chunk-QBWVTJKF.js → chunk-UBRDKNLQ.js} +107 -18
- package/dist/chunk-UBRDKNLQ.js.map +1 -0
- package/dist/chunk-V2DF2GUE.js +3 -0
- package/dist/{chunk-ZQ4XMJH7.js.map → chunk-V2DF2GUE.js.map} +1 -1
- package/dist/chunk-VSQF22GL.js +9 -0
- package/dist/chunk-VSQF22GL.js.map +1 -0
- package/dist/chunk-XVZ4SLQB.js +447 -0
- package/dist/chunk-XVZ4SLQB.js.map +1 -0
- package/dist/components/AudioPlayer/index.cjs +6 -6
- package/dist/components/AudioPlayer/index.d.cts +5 -1
- package/dist/components/AudioPlayer/index.d.ts +5 -1
- package/dist/components/AudioPlayer/index.js +1 -1
- package/dist/components/Modal/index.cjs +11 -10
- package/dist/components/Modal/index.js +3 -2
- package/dist/components/RecordButton/index.cjs +4 -8
- package/dist/components/RecordButton/index.d.cts +57 -44
- package/dist/components/RecordButton/index.d.ts +57 -44
- package/dist/components/RecordButton/index.js +1 -1
- package/dist/components/Select/index.cjs +3 -4
- package/dist/components/Select/index.js +1 -2
- package/dist/components/Spinner/index.d.cts +1 -1
- package/dist/components/Spinner/index.d.ts +1 -1
- package/dist/hooks/index.cjs +3 -2
- package/dist/hooks/index.js +2 -1
- package/dist/index.cjs +880 -667
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -11
- package/dist/index.d.ts +16 -11
- package/dist/index.js +801 -588
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/utils/index.cjs +6 -1
- package/dist/utils/index.d.cts +14 -1
- package/dist/utils/index.d.ts +14 -1
- package/dist/utils/index.js +2 -1
- package/package.json +7 -7
- package/dist/chunk-BR2XGATJ.cjs.map +0 -1
- package/dist/chunk-CLNOI5J7.js.map +0 -1
- package/dist/chunk-D5IBXXF2.js.map +0 -1
- package/dist/chunk-FQ5G7J24.js +0 -297
- package/dist/chunk-FQ5G7J24.js.map +0 -1
- package/dist/chunk-HLW3XD5R.cjs +0 -322
- package/dist/chunk-HLW3XD5R.cjs.map +0 -1
- package/dist/chunk-KJOFWJHV.js.map +0 -1
- package/dist/chunk-LEE3NMNP.cjs.map +0 -1
- package/dist/chunk-NH2JVQ6V.cjs.map +0 -1
- package/dist/chunk-QBWVTJKF.js.map +0 -1
- package/dist/chunk-VWXGUNBR.cjs.map +0 -1
- package/dist/chunk-ZO46CFVN.cjs +0 -4
- package/dist/chunk-ZQ4XMJH7.js +0 -3
package/dist/utils/index.cjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
require('../chunk-
|
|
3
|
+
require('../chunk-74K3RRU7.cjs');
|
|
4
4
|
var chunkBTJHYGPI_cjs = require('../chunk-BTJHYGPI.cjs');
|
|
5
5
|
var chunkKMN7JX2X_cjs = require('../chunk-KMN7JX2X.cjs');
|
|
6
|
+
var chunkSCV7C55E_cjs = require('../chunk-SCV7C55E.cjs');
|
|
6
7
|
var chunkOR5DRJCW_cjs = require('../chunk-OR5DRJCW.cjs');
|
|
7
8
|
|
|
8
9
|
|
|
@@ -55,6 +56,10 @@ Object.defineProperty(exports, "parseDateValue", {
|
|
|
55
56
|
enumerable: true,
|
|
56
57
|
get: function () { return chunkKMN7JX2X_cjs.parseDateValue; }
|
|
57
58
|
});
|
|
59
|
+
Object.defineProperty(exports, "isStorybookDocsMode", {
|
|
60
|
+
enumerable: true,
|
|
61
|
+
get: function () { return chunkSCV7C55E_cjs.isStorybookDocsMode; }
|
|
62
|
+
});
|
|
58
63
|
Object.defineProperty(exports, "cn", {
|
|
59
64
|
enumerable: true,
|
|
60
65
|
get: function () { return chunkOR5DRJCW_cjs.cn; }
|
package/dist/utils/index.d.cts
CHANGED
|
@@ -70,4 +70,17 @@ declare function isDateInPast(value: string): boolean;
|
|
|
70
70
|
*/
|
|
71
71
|
declare function isDateInFuture(value: string): boolean;
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Environment detection utilities for handling special rendering contexts
|
|
75
|
+
* like Storybook, testing environments, etc.
|
|
76
|
+
*/
|
|
77
|
+
/**
|
|
78
|
+
* Check if we're in Storybook docs mode (multiple stories rendered inline).
|
|
79
|
+
* In docs mode, we need to disable certain behaviors like scroll locking
|
|
80
|
+
* and focus trapping that would interfere with the documentation page.
|
|
81
|
+
*
|
|
82
|
+
* @returns true if running in Storybook docs mode
|
|
83
|
+
*/
|
|
84
|
+
declare function isStorybookDocsMode(): boolean;
|
|
85
|
+
|
|
86
|
+
export { calculateAge, cn, formatDateValue, formatPhoneNumber, isDateEmpty, isDateInFuture, isDateInPast, isPhoneNumberEmpty, isStorybookDocsMode, isValidDate, isValidDrivingAge, isValidPhoneNumber, parseDateValue, unformatPhoneNumber };
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -70,4 +70,17 @@ declare function isDateInPast(value: string): boolean;
|
|
|
70
70
|
*/
|
|
71
71
|
declare function isDateInFuture(value: string): boolean;
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Environment detection utilities for handling special rendering contexts
|
|
75
|
+
* like Storybook, testing environments, etc.
|
|
76
|
+
*/
|
|
77
|
+
/**
|
|
78
|
+
* Check if we're in Storybook docs mode (multiple stories rendered inline).
|
|
79
|
+
* In docs mode, we need to disable certain behaviors like scroll locking
|
|
80
|
+
* and focus trapping that would interfere with the documentation page.
|
|
81
|
+
*
|
|
82
|
+
* @returns true if running in Storybook docs mode
|
|
83
|
+
*/
|
|
84
|
+
declare function isStorybookDocsMode(): boolean;
|
|
85
|
+
|
|
86
|
+
export { calculateAge, cn, formatDateValue, formatPhoneNumber, isDateEmpty, isDateInFuture, isDateInPast, isPhoneNumberEmpty, isStorybookDocsMode, isValidDate, isValidDrivingAge, isValidPhoneNumber, parseDateValue, unformatPhoneNumber };
|
package/dist/utils/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import '../chunk-
|
|
1
|
+
import '../chunk-V2DF2GUE.js';
|
|
2
2
|
export { formatPhoneNumber, isPhoneNumberEmpty, isValidPhoneNumber, unformatPhoneNumber } from '../chunk-CEHWXAAI.js';
|
|
3
3
|
export { calculateAge, formatDateValue, isDateEmpty, isDateInFuture, isDateInPast, isValidDate, isValidDrivingAge, parseDateValue } from '../chunk-SN52QMRT.js';
|
|
4
|
+
export { isStorybookDocsMode } from '../chunk-VSQF22GL.js';
|
|
4
5
|
export { cn } from '../chunk-F3SOEIN2.js';
|
|
5
6
|
//# sourceMappingURL=index.js.map
|
|
6
7
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mieweb/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A themeable, accessible React component library built with Tailwind CSS",
|
|
5
|
-
"author": "
|
|
6
|
-
"license": "
|
|
5
|
+
"author": "Medical Informatics Engineering, Inc.",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/mieweb/ui.git"
|
|
@@ -196,13 +196,13 @@
|
|
|
196
196
|
"postcss": "^8.5.1",
|
|
197
197
|
"prettier": "^3.4.2",
|
|
198
198
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
|
199
|
-
"react": "^19.2.
|
|
200
|
-
"react-dom": "^19.2.
|
|
199
|
+
"react": "^19.2.4",
|
|
200
|
+
"react-dom": "^19.2.4",
|
|
201
201
|
"storybook": "^10.1.11",
|
|
202
|
-
"tailwindcss": "^4.1.
|
|
202
|
+
"tailwindcss": "^4.1.18",
|
|
203
203
|
"tsup": "^8.3.6",
|
|
204
204
|
"typescript": "^5.7.3",
|
|
205
|
-
"vite": "^
|
|
205
|
+
"vite": "^7.3.1",
|
|
206
206
|
"vitest": "^3.0.4",
|
|
207
207
|
"wavesurfer.js": "^7.8.17"
|
|
208
208
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useFocusTrap.ts"],"names":["useRef","useEffect"],"mappings":";;;;;AAsBO,SAAS,YAAA,CACd,UAAU,IAAA,EACW;AACrB,EAAA,MAAM,YAAA,GAAeA,aAAU,IAAI,CAAA;AAEnC,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,OAAA,IAAW,CAAC,YAAA,CAAa,OAAA,EAAS;AAEvC,IAAA,MAAM,YAAY,YAAA,CAAa,OAAA;AAC/B,IAAA,MAAM,oBAAoB,SAAA,CAAU,gBAAA;AAAA,MAClC;AAAA,KACF;AAEA,IAAA,IAAI,iBAAA,CAAkB,WAAW,CAAA,EAAG;AAEpC,IAAA,MAAM,YAAA,GAAe,kBAAkB,CAAC,CAAA;AACxC,IAAA,MAAM,WAAA,GAAc,iBAAA,CAAkB,iBAAA,CAAkB,MAAA,GAAS,CAAC,CAAA;AAGlE,IAAA,YAAA,CAAa,KAAA,EAAM;AAEnB,IAAA,MAAM,aAAA,GAAgB,CAAC,KAAA,KAAoC;AACzD,MAAA,IAAI,KAAA,CAAM,QAAQ,KAAA,EAAO;AAEzB,MAAA,IAAI,MAAM,QAAA,EAAU;AAElB,QAAA,IAAI,QAAA,CAAS,kBAAkB,YAAA,EAAc;AAC3C,UAAA,KAAA,CAAM,cAAA,EAAe;AACrB,UAAA,WAAA,CAAY,KAAA,EAAM;AAAA,QACpB;AAAA,MACF,CAAA,MAAO;AAEL,QAAA,IAAI,QAAA,CAAS,kBAAkB,WAAA,EAAa;AAC1C,UAAA,KAAA,CAAM,cAAA,EAAe;AACrB,UAAA,YAAA,CAAa,KAAA,EAAM;AAAA,QACrB;AAAA,MACF;AAAA,IACF,CAAA;AAEA,IAAA,SAAA,CAAU,gBAAA,CAAiB,WAAW,aAAa,CAAA;AACnD,IAAA,OAAO,MAAM,SAAA,CAAU,mBAAA,CAAoB,SAAA,EAAW,aAAa,CAAA;AAAA,EACrE,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,OAAO,YAAA;AACT","file":"chunk-BR2XGATJ.cjs","sourcesContent":["import { useEffect, useRef, type RefObject } from 'react';\n\n/**\n * Hook that traps focus within a container element.\n * Essential for accessibility in modals and dialogs.\n *\n * @param enabled - Whether focus trapping is active\n * @returns ref to attach to the container element\n *\n * @example\n * ```tsx\n * function Modal({ isOpen }: { isOpen: boolean }) {\n * const containerRef = useFocusTrap<HTMLDivElement>(isOpen);\n * return (\n * <div ref={containerRef} role=\"dialog\" aria-modal=\"true\">\n * <button>First focusable</button>\n * <button>Last focusable</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useFocusTrap<T extends HTMLElement>(\n enabled = true\n): RefObject<T | null> {\n const containerRef = useRef<T>(null);\n\n useEffect(() => {\n if (!enabled || !containerRef.current) return;\n\n const container = containerRef.current;\n const focusableElements = container.querySelectorAll<HTMLElement>(\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n );\n\n if (focusableElements.length === 0) return;\n\n const firstElement = focusableElements[0];\n const lastElement = focusableElements[focusableElements.length - 1];\n\n // Focus the first element when trap is enabled\n firstElement.focus();\n\n const handleKeyDown = (event: globalThis.KeyboardEvent) => {\n if (event.key !== 'Tab') return;\n\n if (event.shiftKey) {\n // Shift + Tab\n if (document.activeElement === firstElement) {\n event.preventDefault();\n lastElement.focus();\n }\n } else {\n // Tab\n if (document.activeElement === lastElement) {\n event.preventDefault();\n firstElement.focus();\n }\n }\n };\n\n container.addEventListener('keydown', handleKeyDown);\n return () => container.removeEventListener('keydown', handleKeyDown);\n }, [enabled]);\n\n return containerRef;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useFocusTrap.ts"],"names":[],"mappings":";;;AAsBO,SAAS,YAAA,CACd,UAAU,IAAA,EACW;AACrB,EAAA,MAAM,YAAA,GAAe,OAAU,IAAI,CAAA;AAEnC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,OAAA,IAAW,CAAC,YAAA,CAAa,OAAA,EAAS;AAEvC,IAAA,MAAM,YAAY,YAAA,CAAa,OAAA;AAC/B,IAAA,MAAM,oBAAoB,SAAA,CAAU,gBAAA;AAAA,MAClC;AAAA,KACF;AAEA,IAAA,IAAI,iBAAA,CAAkB,WAAW,CAAA,EAAG;AAEpC,IAAA,MAAM,YAAA,GAAe,kBAAkB,CAAC,CAAA;AACxC,IAAA,MAAM,WAAA,GAAc,iBAAA,CAAkB,iBAAA,CAAkB,MAAA,GAAS,CAAC,CAAA;AAGlE,IAAA,YAAA,CAAa,KAAA,EAAM;AAEnB,IAAA,MAAM,aAAA,GAAgB,CAAC,KAAA,KAAoC;AACzD,MAAA,IAAI,KAAA,CAAM,QAAQ,KAAA,EAAO;AAEzB,MAAA,IAAI,MAAM,QAAA,EAAU;AAElB,QAAA,IAAI,QAAA,CAAS,kBAAkB,YAAA,EAAc;AAC3C,UAAA,KAAA,CAAM,cAAA,EAAe;AACrB,UAAA,WAAA,CAAY,KAAA,EAAM;AAAA,QACpB;AAAA,MACF,CAAA,MAAO;AAEL,QAAA,IAAI,QAAA,CAAS,kBAAkB,WAAA,EAAa;AAC1C,UAAA,KAAA,CAAM,cAAA,EAAe;AACrB,UAAA,YAAA,CAAa,KAAA,EAAM;AAAA,QACrB;AAAA,MACF;AAAA,IACF,CAAA;AAEA,IAAA,SAAA,CAAU,gBAAA,CAAiB,WAAW,aAAa,CAAA;AACnD,IAAA,OAAO,MAAM,SAAA,CAAU,mBAAA,CAAoB,SAAA,EAAW,aAAa,CAAA;AAAA,EACrE,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,OAAO,YAAA;AACT","file":"chunk-CLNOI5J7.js","sourcesContent":["import { useEffect, useRef, type RefObject } from 'react';\n\n/**\n * Hook that traps focus within a container element.\n * Essential for accessibility in modals and dialogs.\n *\n * @param enabled - Whether focus trapping is active\n * @returns ref to attach to the container element\n *\n * @example\n * ```tsx\n * function Modal({ isOpen }: { isOpen: boolean }) {\n * const containerRef = useFocusTrap<HTMLDivElement>(isOpen);\n * return (\n * <div ref={containerRef} role=\"dialog\" aria-modal=\"true\">\n * <button>First focusable</button>\n * <button>Last focusable</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useFocusTrap<T extends HTMLElement>(\n enabled = true\n): RefObject<T | null> {\n const containerRef = useRef<T>(null);\n\n useEffect(() => {\n if (!enabled || !containerRef.current) return;\n\n const container = containerRef.current;\n const focusableElements = container.querySelectorAll<HTMLElement>(\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n );\n\n if (focusableElements.length === 0) return;\n\n const firstElement = focusableElements[0];\n const lastElement = focusableElements[focusableElements.length - 1];\n\n // Focus the first element when trap is enabled\n firstElement.focus();\n\n const handleKeyDown = (event: globalThis.KeyboardEvent) => {\n if (event.key !== 'Tab') return;\n\n if (event.shiftKey) {\n // Shift + Tab\n if (document.activeElement === firstElement) {\n event.preventDefault();\n lastElement.focus();\n }\n } else {\n // Tab\n if (document.activeElement === lastElement) {\n event.preventDefault();\n firstElement.focus();\n }\n }\n };\n\n container.addEventListener('keydown', handleKeyDown);\n return () => container.removeEventListener('keydown', handleKeyDown);\n }, [enabled]);\n\n return containerRef;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/Modal/Modal.tsx"],"names":[],"mappings":"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","file":"chunk-D5IBXXF2.js","sourcesContent":["import * as React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../../utils/cn';\nimport { useFocusTrap } from '../../hooks/useFocusTrap';\nimport { useEscapeKey } from '../../hooks/useEscapeKey';\n\nconst modalOverlayVariants = cva(\n [\n 'fixed inset-0 z-50',\n 'bg-black/50 backdrop-blur-sm',\n 'data-[state=open]:animate-in data-[state=open]:fade-in-0',\n 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0',\n ],\n {\n variants: {},\n defaultVariants: {},\n }\n);\n\nconst modalContentVariants = cva(\n [\n 'fixed left-1/2 top-1/2 z-50',\n '-translate-x-1/2 -translate-y-1/2',\n 'w-full bg-card text-card-foreground',\n 'border border-border rounded-xl shadow-lg',\n 'focus:outline-none',\n 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',\n 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',\n 'duration-200',\n ],\n {\n variants: {\n size: {\n sm: 'max-w-sm',\n md: 'max-w-md',\n lg: 'max-w-lg',\n xl: 'max-w-xl',\n '2xl': 'max-w-2xl',\n '3xl': 'max-w-3xl',\n '4xl': 'max-w-4xl',\n full: 'max-w-[calc(100vw-2rem)]',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n);\n\nexport interface ModalProps extends VariantProps<typeof modalContentVariants> {\n /** Whether the modal is open */\n open: boolean;\n /** Callback when the modal should close */\n onOpenChange: (open: boolean) => void;\n /** Modal content */\n children: React.ReactNode;\n /** Whether to close when clicking the overlay */\n closeOnOverlayClick?: boolean;\n /** Whether to close when pressing Escape */\n closeOnEscape?: boolean;\n /** Additional class name for the modal content */\n className?: string;\n /** ID for the modal, used for accessibility */\n id?: string;\n /** Accessible label for the modal */\n 'aria-label'?: string;\n /** ID of the element that labels the modal */\n 'aria-labelledby'?: string;\n /** ID of the element that describes the modal */\n 'aria-describedby'?: string;\n}\n\n/**\n * An accessible modal/dialog component.\n *\n * @example\n * ```tsx\n * <Modal open={isOpen} onOpenChange={setIsOpen} size=\"lg\">\n * <ModalHeader>\n * <ModalTitle>Confirm Action</ModalTitle>\n * <ModalClose />\n * </ModalHeader>\n * <ModalBody>\n * Are you sure you want to continue?\n * </ModalBody>\n * <ModalFooter>\n * <Button variant=\"secondary\" onClick={() => setIsOpen(false)}>Cancel</Button>\n * <Button onClick={handleConfirm}>Confirm</Button>\n * </ModalFooter>\n * </Modal>\n * ```\n */\nfunction Modal({\n open,\n onOpenChange,\n children,\n size,\n closeOnOverlayClick = true,\n closeOnEscape = true,\n className,\n id,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy,\n}: ModalProps) {\n const generatedId = React.useId();\n const modalId = id || generatedId;\n\n // Focus trap (only active when modal is open)\n const focusTrapRef = useFocusTrap<HTMLDivElement>(open);\n\n // Handle escape key\n useEscapeKey(() => {\n if (closeOnEscape && open) {\n onOpenChange(false);\n }\n }, open);\n\n // Handle overlay click\n const handleOverlayClick = React.useCallback(\n (e: React.MouseEvent) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n onOpenChange(false);\n }\n },\n [closeOnOverlayClick, onOpenChange]\n );\n\n // Prevent body scroll when modal is open\n React.useEffect(() => {\n if (open) {\n const originalOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n return () => {\n document.body.style.overflow = originalOverflow;\n };\n }\n }, [open]);\n\n if (!open) return null;\n\n return (\n <ModalContext.Provider\n value={{ onClose: () => onOpenChange(false), modalId }}\n >\n {/* Portal to body */}\n <div className=\"fixed inset-0 z-50\">\n {/* Overlay */}\n <div\n className={cn(modalOverlayVariants())}\n data-state={open ? 'open' : 'closed'}\n onClick={handleOverlayClick}\n aria-hidden=\"true\"\n />\n {/* Content */}\n <div\n ref={focusTrapRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy || `${modalId}-title`}\n aria-describedby={ariaDescribedBy}\n id={modalId}\n tabIndex={-1}\n data-state={open ? 'open' : 'closed'}\n className={cn(modalContentVariants({ size }), className)}\n >\n {children}\n </div>\n </div>\n </ModalContext.Provider>\n );\n}\n\nModal.displayName = 'Modal';\n\n// ============================================================================\n// Modal Context\n// ============================================================================\n\ninterface ModalContextValue {\n onClose: () => void;\n modalId: string;\n}\n\nconst ModalContext = React.createContext<ModalContextValue | undefined>(\n undefined\n);\n\nfunction useModalContext() {\n const context = React.useContext(ModalContext);\n if (!context) {\n throw new Error('Modal components must be used within a Modal');\n }\n return context;\n}\n\n// ============================================================================\n// Modal Header\n// ============================================================================\n\nexport type ModalHeaderProps = React.HTMLAttributes<HTMLDivElement>;\n\n/**\n * Header section of a Modal.\n */\nconst ModalHeader = React.forwardRef<HTMLDivElement, ModalHeaderProps>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'flex items-center justify-between',\n 'border-border border-b px-6 py-4',\n className\n )}\n {...props}\n />\n )\n);\n\nModalHeader.displayName = 'ModalHeader';\n\n// ============================================================================\n// Modal Title\n// ============================================================================\n\nexport type ModalTitleProps = React.HTMLAttributes<HTMLHeadingElement>;\n\n/**\n * Title for a Modal.\n */\nconst ModalTitle = React.forwardRef<HTMLHeadingElement, ModalTitleProps>(\n ({ className, children, ...props }, ref) => {\n const { modalId } = useModalContext();\n return (\n <h2\n ref={ref}\n id={`${modalId}-title`}\n className={cn(\n 'text-lg leading-none font-semibold tracking-tight',\n className\n )}\n {...props}\n >\n {children}\n </h2>\n );\n }\n);\n\nModalTitle.displayName = 'ModalTitle';\n\n// ============================================================================\n// Modal Close Button\n// ============================================================================\n\nexport type ModalCloseProps = React.ButtonHTMLAttributes<HTMLButtonElement>;\n\n/**\n * Close button for a Modal.\n */\nconst ModalClose = React.forwardRef<HTMLButtonElement, ModalCloseProps>(\n ({ className, children, onClick, ...props }, ref) => {\n const { onClose } = useModalContext();\n\n const handleClick = React.useCallback(\n (e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (!e.defaultPrevented) {\n onClose();\n }\n },\n [onClick, onClose]\n );\n\n return (\n <button\n ref={ref}\n type=\"button\"\n onClick={handleClick}\n className={cn(\n 'inline-flex h-8 w-8 items-center justify-center rounded-lg',\n 'text-muted-foreground hover:text-foreground',\n 'hover:bg-muted transition-colors',\n 'focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none',\n className\n )}\n aria-label=\"Close\"\n {...props}\n >\n {children || <CloseIcon />}\n </button>\n );\n }\n);\n\nModalClose.displayName = 'ModalClose';\n\n// ============================================================================\n// Modal Body\n// ============================================================================\n\nexport type ModalBodyProps = React.HTMLAttributes<HTMLDivElement>;\n\n/**\n * Main content area of a Modal.\n */\nconst ModalBody = React.forwardRef<HTMLDivElement, ModalBodyProps>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn('px-6 py-4', className)} {...props} />\n )\n);\n\nModalBody.displayName = 'ModalBody';\n\n// ============================================================================\n// Modal Footer\n// ============================================================================\n\nexport type ModalFooterProps = React.HTMLAttributes<HTMLDivElement>;\n\n/**\n * Footer section of a Modal, typically for action buttons.\n */\nconst ModalFooter = React.forwardRef<HTMLDivElement, ModalFooterProps>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'flex items-center justify-end gap-3',\n 'border-border border-t px-6 py-4',\n className\n )}\n {...props}\n />\n )\n);\n\nModalFooter.displayName = 'ModalFooter';\n\n// ============================================================================\n// Close Icon\n// ============================================================================\n\nfunction CloseIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n );\n}\n\nexport {\n Modal,\n ModalHeader,\n ModalTitle,\n ModalClose,\n ModalBody,\n ModalFooter,\n modalContentVariants,\n modalOverlayVariants,\n};\n"]}
|
package/dist/chunk-FQ5G7J24.js
DELETED
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
import { cn } from './chunk-F3SOEIN2.js';
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { cva } from 'class-variance-authority';
|
|
4
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
|
-
|
|
6
|
-
var recordButtonVariants = cva(
|
|
7
|
-
[
|
|
8
|
-
"relative inline-flex items-center justify-center",
|
|
9
|
-
"rounded-full transition-all duration-200",
|
|
10
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
11
|
-
"disabled:pointer-events-none disabled:opacity-50"
|
|
12
|
-
],
|
|
13
|
-
{
|
|
14
|
-
variants: {
|
|
15
|
-
variant: {
|
|
16
|
-
default: [
|
|
17
|
-
"text-neutral-500 hover:text-neutral-700 hover:bg-neutral-100",
|
|
18
|
-
"dark:text-neutral-400 dark:hover:text-neutral-200 dark:hover:bg-neutral-800"
|
|
19
|
-
],
|
|
20
|
-
filled: [
|
|
21
|
-
"bg-neutral-100 text-neutral-600 hover:bg-neutral-200",
|
|
22
|
-
"dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700"
|
|
23
|
-
],
|
|
24
|
-
primary: [
|
|
25
|
-
"bg-primary-600 text-white hover:bg-primary-700",
|
|
26
|
-
"dark:bg-primary-500 dark:hover:bg-primary-600"
|
|
27
|
-
]
|
|
28
|
-
},
|
|
29
|
-
size: {
|
|
30
|
-
sm: "h-7 w-7",
|
|
31
|
-
md: "h-9 w-9",
|
|
32
|
-
lg: "h-11 w-11"
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
defaultVariants: {
|
|
36
|
-
variant: "default",
|
|
37
|
-
size: "md"
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
);
|
|
41
|
-
var recordingIndicatorVariants = cva(
|
|
42
|
-
[
|
|
43
|
-
"absolute -top-1 -right-1",
|
|
44
|
-
"flex items-center justify-center",
|
|
45
|
-
"rounded-full bg-red-500 text-white",
|
|
46
|
-
"animate-pulse"
|
|
47
|
-
],
|
|
48
|
-
{
|
|
49
|
-
variants: {
|
|
50
|
-
size: {
|
|
51
|
-
sm: "h-3 w-3",
|
|
52
|
-
md: "h-4 w-4",
|
|
53
|
-
lg: "h-5 w-5"
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
defaultVariants: {
|
|
57
|
-
size: "md"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
);
|
|
61
|
-
function formatDuration(seconds) {
|
|
62
|
-
const mins = Math.floor(seconds / 60);
|
|
63
|
-
const secs = Math.floor(seconds % 60);
|
|
64
|
-
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
65
|
-
}
|
|
66
|
-
function MicrophoneIcon({ className }) {
|
|
67
|
-
return /* @__PURE__ */ jsx(
|
|
68
|
-
"svg",
|
|
69
|
-
{
|
|
70
|
-
className,
|
|
71
|
-
fill: "none",
|
|
72
|
-
viewBox: "0 0 24 24",
|
|
73
|
-
strokeWidth: 2,
|
|
74
|
-
stroke: "currentColor",
|
|
75
|
-
"aria-hidden": "true",
|
|
76
|
-
children: /* @__PURE__ */ jsx(
|
|
77
|
-
"path",
|
|
78
|
-
{
|
|
79
|
-
strokeLinecap: "round",
|
|
80
|
-
strokeLinejoin: "round",
|
|
81
|
-
d: "M12 18.75a6 6 0 006-6v-1.5m-6 7.5a6 6 0 01-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 01-3-3V4.5a3 3 0 116 0v8.25a3 3 0 01-3 3z"
|
|
82
|
-
}
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
function StopIcon({ className }) {
|
|
88
|
-
return /* @__PURE__ */ jsx(
|
|
89
|
-
"svg",
|
|
90
|
-
{
|
|
91
|
-
className,
|
|
92
|
-
fill: "currentColor",
|
|
93
|
-
viewBox: "0 0 24 24",
|
|
94
|
-
"aria-hidden": "true",
|
|
95
|
-
children: /* @__PURE__ */ jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" })
|
|
96
|
-
}
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
function SpinnerIcon({ className }) {
|
|
100
|
-
return /* @__PURE__ */ jsxs(
|
|
101
|
-
"svg",
|
|
102
|
-
{
|
|
103
|
-
className: cn("animate-spin", className),
|
|
104
|
-
fill: "none",
|
|
105
|
-
viewBox: "0 0 24 24",
|
|
106
|
-
"aria-hidden": "true",
|
|
107
|
-
children: [
|
|
108
|
-
/* @__PURE__ */ jsx(
|
|
109
|
-
"circle",
|
|
110
|
-
{
|
|
111
|
-
className: "opacity-25",
|
|
112
|
-
cx: "12",
|
|
113
|
-
cy: "12",
|
|
114
|
-
r: "10",
|
|
115
|
-
stroke: "currentColor",
|
|
116
|
-
strokeWidth: "4"
|
|
117
|
-
}
|
|
118
|
-
),
|
|
119
|
-
/* @__PURE__ */ jsx(
|
|
120
|
-
"path",
|
|
121
|
-
{
|
|
122
|
-
className: "opacity-75",
|
|
123
|
-
fill: "currentColor",
|
|
124
|
-
d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
125
|
-
}
|
|
126
|
-
)
|
|
127
|
-
]
|
|
128
|
-
}
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
function RecordButton({
|
|
132
|
-
onRecordingComplete,
|
|
133
|
-
onRecordingStart,
|
|
134
|
-
onError,
|
|
135
|
-
maxDuration = 0,
|
|
136
|
-
mimeType = "audio/webm",
|
|
137
|
-
disabled = false,
|
|
138
|
-
variant,
|
|
139
|
-
size,
|
|
140
|
-
className,
|
|
141
|
-
"aria-label": ariaLabel,
|
|
142
|
-
showDuration = false,
|
|
143
|
-
idleIcon,
|
|
144
|
-
recordingIcon,
|
|
145
|
-
transcriptionState,
|
|
146
|
-
showTranscriptionState = false
|
|
147
|
-
}) {
|
|
148
|
-
const [state, setState] = React.useState("idle");
|
|
149
|
-
const [duration, setDuration] = React.useState(0);
|
|
150
|
-
const mediaRecorderRef = React.useRef(null);
|
|
151
|
-
const streamRef = React.useRef(null);
|
|
152
|
-
const chunksRef = React.useRef([]);
|
|
153
|
-
const timerRef = React.useRef(void 0);
|
|
154
|
-
const startTimeRef = React.useRef(0);
|
|
155
|
-
const isRecording = state === "recording";
|
|
156
|
-
const isProcessing = state === "processing";
|
|
157
|
-
const isTranscribing = transcriptionState === "transcribing" || transcriptionState === "streaming";
|
|
158
|
-
React.useEffect(() => {
|
|
159
|
-
return () => {
|
|
160
|
-
if (timerRef.current) {
|
|
161
|
-
clearInterval(timerRef.current);
|
|
162
|
-
}
|
|
163
|
-
if (streamRef.current) {
|
|
164
|
-
streamRef.current.getTracks().forEach((track) => track.stop());
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
}, []);
|
|
168
|
-
const stopRecording = React.useCallback(() => {
|
|
169
|
-
if (timerRef.current) {
|
|
170
|
-
clearInterval(timerRef.current);
|
|
171
|
-
}
|
|
172
|
-
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
|
|
173
|
-
mediaRecorderRef.current.stop();
|
|
174
|
-
}
|
|
175
|
-
if (streamRef.current) {
|
|
176
|
-
streamRef.current.getTracks().forEach((track) => track.stop());
|
|
177
|
-
}
|
|
178
|
-
}, []);
|
|
179
|
-
const startRecording = React.useCallback(async () => {
|
|
180
|
-
if (disabled || isRecording || isProcessing) return;
|
|
181
|
-
try {
|
|
182
|
-
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
183
|
-
streamRef.current = stream;
|
|
184
|
-
const options = { mimeType };
|
|
185
|
-
if (!MediaRecorder.isTypeSupported(mimeType)) {
|
|
186
|
-
mediaRecorderRef.current = new MediaRecorder(stream);
|
|
187
|
-
} else {
|
|
188
|
-
mediaRecorderRef.current = new MediaRecorder(stream, options);
|
|
189
|
-
}
|
|
190
|
-
chunksRef.current = [];
|
|
191
|
-
mediaRecorderRef.current.ondataavailable = (e) => {
|
|
192
|
-
if (e.data.size > 0) {
|
|
193
|
-
chunksRef.current.push(e.data);
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
mediaRecorderRef.current.onstop = () => {
|
|
197
|
-
setState("processing");
|
|
198
|
-
const blob = new Blob(chunksRef.current, { type: mimeType });
|
|
199
|
-
const finalDuration = duration;
|
|
200
|
-
setTimeout(() => {
|
|
201
|
-
onRecordingComplete?.(blob, finalDuration);
|
|
202
|
-
setState("idle");
|
|
203
|
-
setDuration(0);
|
|
204
|
-
}, 200);
|
|
205
|
-
};
|
|
206
|
-
mediaRecorderRef.current.start(100);
|
|
207
|
-
startTimeRef.current = Date.now();
|
|
208
|
-
setState("recording");
|
|
209
|
-
onRecordingStart?.();
|
|
210
|
-
timerRef.current = window.setInterval(() => {
|
|
211
|
-
const elapsed = (Date.now() - startTimeRef.current) / 1e3;
|
|
212
|
-
setDuration(elapsed);
|
|
213
|
-
if (maxDuration > 0 && elapsed >= maxDuration) {
|
|
214
|
-
stopRecording();
|
|
215
|
-
}
|
|
216
|
-
}, 100);
|
|
217
|
-
} catch (error) {
|
|
218
|
-
onError?.(error);
|
|
219
|
-
setState("idle");
|
|
220
|
-
}
|
|
221
|
-
}, [
|
|
222
|
-
disabled,
|
|
223
|
-
isRecording,
|
|
224
|
-
isProcessing,
|
|
225
|
-
mimeType,
|
|
226
|
-
maxDuration,
|
|
227
|
-
duration,
|
|
228
|
-
onRecordingComplete,
|
|
229
|
-
onRecordingStart,
|
|
230
|
-
onError,
|
|
231
|
-
stopRecording
|
|
232
|
-
]);
|
|
233
|
-
const handleClick = React.useCallback(() => {
|
|
234
|
-
if (isRecording) {
|
|
235
|
-
stopRecording();
|
|
236
|
-
} else {
|
|
237
|
-
startRecording();
|
|
238
|
-
}
|
|
239
|
-
}, [isRecording, startRecording, stopRecording]);
|
|
240
|
-
const iconSize = size === "sm" ? "h-4 w-4" : size === "lg" ? "h-6 w-6" : "h-5 w-5";
|
|
241
|
-
const getIcon = () => {
|
|
242
|
-
if (isProcessing || isTranscribing) {
|
|
243
|
-
return /* @__PURE__ */ jsx(SpinnerIcon, { className: iconSize });
|
|
244
|
-
}
|
|
245
|
-
if (isRecording) {
|
|
246
|
-
return recordingIcon || /* @__PURE__ */ jsx(StopIcon, { className: iconSize });
|
|
247
|
-
}
|
|
248
|
-
return idleIcon || /* @__PURE__ */ jsx(MicrophoneIcon, { className: iconSize });
|
|
249
|
-
};
|
|
250
|
-
const getAriaLabel = () => {
|
|
251
|
-
if (ariaLabel) return ariaLabel;
|
|
252
|
-
if (isTranscribing) return "Transcribing audio";
|
|
253
|
-
if (isProcessing) return "Processing recording";
|
|
254
|
-
if (isRecording) return "Stop recording";
|
|
255
|
-
return "Start recording";
|
|
256
|
-
};
|
|
257
|
-
const getTranscriptionLabel = () => {
|
|
258
|
-
if (transcriptionState === "streaming") return "Listening...";
|
|
259
|
-
if (transcriptionState === "transcribing") return "Transcribing...";
|
|
260
|
-
return null;
|
|
261
|
-
};
|
|
262
|
-
return /* @__PURE__ */ jsxs("div", { className: "relative inline-flex items-center gap-2", children: [
|
|
263
|
-
/* @__PURE__ */ jsxs(
|
|
264
|
-
"button",
|
|
265
|
-
{
|
|
266
|
-
type: "button",
|
|
267
|
-
onClick: handleClick,
|
|
268
|
-
disabled: disabled || isProcessing || isTranscribing,
|
|
269
|
-
className: cn(
|
|
270
|
-
recordButtonVariants({ variant, size }),
|
|
271
|
-
isRecording && "text-red-600 dark:text-red-400",
|
|
272
|
-
isTranscribing && "text-primary-600 dark:text-primary-400",
|
|
273
|
-
className
|
|
274
|
-
),
|
|
275
|
-
"aria-label": getAriaLabel(),
|
|
276
|
-
"aria-pressed": isRecording,
|
|
277
|
-
children: [
|
|
278
|
-
getIcon(),
|
|
279
|
-
isRecording && !isTranscribing && /* @__PURE__ */ jsx(
|
|
280
|
-
"span",
|
|
281
|
-
{
|
|
282
|
-
className: cn(recordingIndicatorVariants({ size })),
|
|
283
|
-
"aria-hidden": "true"
|
|
284
|
-
}
|
|
285
|
-
)
|
|
286
|
-
]
|
|
287
|
-
}
|
|
288
|
-
),
|
|
289
|
-
showDuration && isRecording && /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-red-600 tabular-nums dark:text-red-400", children: formatDuration(duration) }),
|
|
290
|
-
showTranscriptionState && getTranscriptionLabel() && /* @__PURE__ */ jsx("span", { className: "text-primary-600 dark:text-primary-400 text-xs font-medium", children: getTranscriptionLabel() })
|
|
291
|
-
] });
|
|
292
|
-
}
|
|
293
|
-
RecordButton.displayName = "RecordButton";
|
|
294
|
-
|
|
295
|
-
export { RecordButton, formatDuration, recordButtonVariants, recordingIndicatorVariants };
|
|
296
|
-
//# sourceMappingURL=chunk-FQ5G7J24.js.map
|
|
297
|
-
//# sourceMappingURL=chunk-FQ5G7J24.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/RecordButton/RecordButton.tsx"],"names":[],"mappings":"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","file":"chunk-FQ5G7J24.js","sourcesContent":["import * as React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../../utils/cn';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type RecordButtonState = 'idle' | 'recording' | 'processing';\n\n/** Transcription state for integration with transcription services */\nexport type TranscriptionState =\n | 'idle'\n | 'recording'\n | 'transcribing'\n | 'streaming'\n | 'complete'\n | 'error';\n\nexport interface TranscriptionResult {\n /** The transcribed text */\n text: string;\n /** Whether this is a partial (streaming) or final result */\n isFinal: boolean;\n /** Confidence score (0-1) if available */\n confidence?: number;\n}\n\nexport interface RecordButtonProps extends VariantProps<\n typeof recordButtonVariants\n> {\n /** Callback when recording is complete with the audio blob */\n onRecordingComplete?: (blob: Blob, duration: number) => void;\n /** Callback when recording starts */\n onRecordingStart?: () => void;\n /** Callback when an error occurs */\n onError?: (error: Error) => void;\n /** Maximum recording duration in seconds (0 for unlimited) */\n maxDuration?: number;\n /** Audio MIME type */\n mimeType?: string;\n /** Whether the button is disabled */\n disabled?: boolean;\n /** Additional class name */\n className?: string;\n /** Accessible label */\n 'aria-label'?: string;\n /** Show recording duration while recording */\n showDuration?: boolean;\n /** Custom idle icon */\n idleIcon?: React.ReactNode;\n /** Custom recording icon */\n recordingIcon?: React.ReactNode;\n /** Current transcription state (for external control) */\n transcriptionState?: TranscriptionState;\n /** Show transcription state indicator */\n showTranscriptionState?: boolean;\n}\n\n// ============================================================================\n// Variants\n// ============================================================================\n\nconst recordButtonVariants = cva(\n [\n 'relative inline-flex items-center justify-center',\n 'rounded-full transition-all duration-200',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n 'disabled:pointer-events-none disabled:opacity-50',\n ],\n {\n variants: {\n variant: {\n default: [\n 'text-neutral-500 hover:text-neutral-700 hover:bg-neutral-100',\n 'dark:text-neutral-400 dark:hover:text-neutral-200 dark:hover:bg-neutral-800',\n ],\n filled: [\n 'bg-neutral-100 text-neutral-600 hover:bg-neutral-200',\n 'dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700',\n ],\n primary: [\n 'bg-primary-600 text-white hover:bg-primary-700',\n 'dark:bg-primary-500 dark:hover:bg-primary-600',\n ],\n },\n size: {\n sm: 'h-7 w-7',\n md: 'h-9 w-9',\n lg: 'h-11 w-11',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'md',\n },\n }\n);\n\nconst recordingIndicatorVariants = cva(\n [\n 'absolute -top-1 -right-1',\n 'flex items-center justify-center',\n 'rounded-full bg-red-500 text-white',\n 'animate-pulse',\n ],\n {\n variants: {\n size: {\n sm: 'h-3 w-3',\n md: 'h-4 w-4',\n lg: 'h-5 w-5',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n);\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction formatDuration(seconds: number): string {\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n}\n\n// ============================================================================\n// Icons\n// ============================================================================\n\nfunction MicrophoneIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n strokeWidth={2}\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M12 18.75a6 6 0 006-6v-1.5m-6 7.5a6 6 0 01-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 01-3-3V4.5a3 3 0 116 0v8.25a3 3 0 01-3 3z\"\n />\n </svg>\n );\n}\n\nfunction StopIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n fill=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"2\" />\n </svg>\n );\n}\n\nfunction SpinnerIcon({ className }: { className?: string }) {\n return (\n <svg\n className={cn('animate-spin', className)}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n );\n}\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\n/**\n * A simple microphone recording button that can be placed anywhere.\n * Perfect for adding voice input to text fields, chat inputs, or forms.\n *\n * @example\n * ```tsx\n * // Basic usage\n * <RecordButton\n * onRecordingComplete={(blob, duration) => {\n * console.log('Recorded:', blob, duration);\n * }}\n * />\n *\n * // In an input field\n * <div className=\"relative\">\n * <Input className=\"pr-12\" />\n * <div className=\"absolute right-2 top-1/2 -translate-y-1/2\">\n * <RecordButton size=\"sm\" onRecordingComplete={handleRecording} />\n * </div>\n * </div>\n *\n * // With max duration\n * <RecordButton maxDuration={30} showDuration />\n * ```\n */\nfunction RecordButton({\n onRecordingComplete,\n onRecordingStart,\n onError,\n maxDuration = 0,\n mimeType = 'audio/webm',\n disabled = false,\n variant,\n size,\n className,\n 'aria-label': ariaLabel,\n showDuration = false,\n idleIcon,\n recordingIcon,\n transcriptionState,\n showTranscriptionState = false,\n}: RecordButtonProps) {\n const [state, setState] = React.useState<RecordButtonState>('idle');\n const [duration, setDuration] = React.useState(0);\n\n const mediaRecorderRef = React.useRef<MediaRecorder | null>(null);\n const streamRef = React.useRef<MediaStream | null>(null);\n const chunksRef = React.useRef<Blob[]>([]);\n const timerRef = React.useRef<number | undefined>(undefined);\n const startTimeRef = React.useRef<number>(0);\n\n const isRecording = state === 'recording';\n const isProcessing = state === 'processing';\n const isTranscribing =\n transcriptionState === 'transcribing' || transcriptionState === 'streaming';\n\n // Cleanup on unmount\n React.useEffect(() => {\n return () => {\n if (timerRef.current) {\n clearInterval(timerRef.current);\n }\n if (streamRef.current) {\n streamRef.current.getTracks().forEach((track) => track.stop());\n }\n };\n }, []);\n\n const stopRecording = React.useCallback(() => {\n if (timerRef.current) {\n clearInterval(timerRef.current);\n }\n\n if (\n mediaRecorderRef.current &&\n mediaRecorderRef.current.state !== 'inactive'\n ) {\n mediaRecorderRef.current.stop();\n }\n\n if (streamRef.current) {\n streamRef.current.getTracks().forEach((track) => track.stop());\n }\n }, []);\n\n const startRecording = React.useCallback(async () => {\n if (disabled || isRecording || isProcessing) return;\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n streamRef.current = stream;\n\n const options = { mimeType };\n if (!MediaRecorder.isTypeSupported(mimeType)) {\n mediaRecorderRef.current = new MediaRecorder(stream);\n } else {\n mediaRecorderRef.current = new MediaRecorder(stream, options);\n }\n\n chunksRef.current = [];\n\n mediaRecorderRef.current.ondataavailable = (e) => {\n if (e.data.size > 0) {\n chunksRef.current.push(e.data);\n }\n };\n\n mediaRecorderRef.current.onstop = () => {\n setState('processing');\n\n const blob = new Blob(chunksRef.current, { type: mimeType });\n const finalDuration = duration;\n\n // Small delay to show processing state\n setTimeout(() => {\n onRecordingComplete?.(blob, finalDuration);\n setState('idle');\n setDuration(0);\n }, 200);\n };\n\n mediaRecorderRef.current.start(100);\n startTimeRef.current = Date.now();\n setState('recording');\n onRecordingStart?.();\n\n timerRef.current = window.setInterval(() => {\n const elapsed = (Date.now() - startTimeRef.current) / 1000;\n setDuration(elapsed);\n\n if (maxDuration > 0 && elapsed >= maxDuration) {\n stopRecording();\n }\n }, 100);\n } catch (error) {\n onError?.(error as Error);\n setState('idle');\n }\n }, [\n disabled,\n isRecording,\n isProcessing,\n mimeType,\n maxDuration,\n duration,\n onRecordingComplete,\n onRecordingStart,\n onError,\n stopRecording,\n ]);\n\n const handleClick = React.useCallback(() => {\n if (isRecording) {\n stopRecording();\n } else {\n startRecording();\n }\n }, [isRecording, startRecording, stopRecording]);\n\n const iconSize =\n size === 'sm' ? 'h-4 w-4' : size === 'lg' ? 'h-6 w-6' : 'h-5 w-5';\n\n const getIcon = () => {\n if (isProcessing || isTranscribing) {\n return <SpinnerIcon className={iconSize} />;\n }\n if (isRecording) {\n return recordingIcon || <StopIcon className={iconSize} />;\n }\n return idleIcon || <MicrophoneIcon className={iconSize} />;\n };\n\n const getAriaLabel = () => {\n if (ariaLabel) return ariaLabel;\n if (isTranscribing) return 'Transcribing audio';\n if (isProcessing) return 'Processing recording';\n if (isRecording) return 'Stop recording';\n return 'Start recording';\n };\n\n const getTranscriptionLabel = () => {\n if (transcriptionState === 'streaming') return 'Listening...';\n if (transcriptionState === 'transcribing') return 'Transcribing...';\n return null;\n };\n\n return (\n <div className=\"relative inline-flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={disabled || isProcessing || isTranscribing}\n className={cn(\n recordButtonVariants({ variant, size }),\n isRecording && 'text-red-600 dark:text-red-400',\n isTranscribing && 'text-primary-600 dark:text-primary-400',\n className\n )}\n aria-label={getAriaLabel()}\n aria-pressed={isRecording}\n >\n {getIcon()}\n {isRecording && !isTranscribing && (\n <span\n className={cn(recordingIndicatorVariants({ size }))}\n aria-hidden=\"true\"\n />\n )}\n </button>\n {showDuration && isRecording && (\n <span className=\"font-mono text-xs text-red-600 tabular-nums dark:text-red-400\">\n {formatDuration(duration)}\n </span>\n )}\n {showTranscriptionState && getTranscriptionLabel() && (\n <span className=\"text-primary-600 dark:text-primary-400 text-xs font-medium\">\n {getTranscriptionLabel()}\n </span>\n )}\n </div>\n );\n}\n\nRecordButton.displayName = 'RecordButton';\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport {\n RecordButton,\n recordButtonVariants,\n recordingIndicatorVariants,\n formatDuration,\n};\n"]}
|