@exode-team/react-recorder 1.1.12 → 1.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/components/AudioRecorder.js +50 -0
- package/dist/components/AudioRecorder.js.map +1 -0
- package/dist/components/AudioRecorder.messages.js +47 -0
- package/dist/components/AudioRecorder.messages.js.map +1 -0
- package/dist/components/AudioRecorder.styled.js +16 -0
- package/dist/components/AudioRecorder.styled.js.map +1 -0
- package/dist/components/IdleControl/IdleControl.js +12 -0
- package/dist/components/IdleControl/IdleControl.js.map +1 -0
- package/dist/components/IdleControl/IdleControl.styled.js +68 -0
- package/dist/components/IdleControl/IdleControl.styled.js.map +1 -0
- package/dist/components/RecordingShell/RecordingShell.js +80 -0
- package/dist/components/RecordingShell/RecordingShell.js.map +1 -0
- package/dist/components/RecordingShell/RecordingShell.styled.js +159 -0
- package/dist/components/RecordingShell/RecordingShell.styled.js.map +1 -0
- package/dist/components/VoiceMessagePlayer/VoiceMessagePlayer.js +151 -0
- package/dist/components/VoiceMessagePlayer/VoiceMessagePlayer.js.map +1 -0
- package/dist/components/VoiceMessagePlayer/VoiceMessagePlayer.styled.js +92 -0
- package/dist/components/VoiceMessagePlayer/VoiceMessagePlayer.styled.js.map +1 -0
- package/dist/components/VoiceRecorderInput/PreviewBars.js +52 -0
- package/dist/components/VoiceRecorderInput/PreviewBars.js.map +1 -0
- package/dist/components/VoiceRecorderInput/TimelineBars.js +31 -0
- package/dist/components/VoiceRecorderInput/TimelineBars.js.map +1 -0
- package/dist/components/VoiceRecorderInput/VoiceRecorderInput.js +51 -0
- package/dist/components/VoiceRecorderInput/VoiceRecorderInput.js.map +1 -0
- package/dist/components/VoiceRecorderInput/VoiceRecorderInput.styled.js +175 -0
- package/dist/components/VoiceRecorderInput/VoiceRecorderInput.styled.js.map +1 -0
- package/dist/components/WaveformBars/WaveformBars.js +29 -0
- package/dist/components/WaveformBars/WaveformBars.js.map +1 -0
- package/dist/components/WaveformBars/WaveformBars.styled.js +20 -0
- package/dist/components/WaveformBars/WaveformBars.styled.js.map +1 -0
- package/dist/constants.js +17 -0
- package/dist/constants.js.map +1 -0
- package/dist/hooks/useAudioBlobLoader.d.ts +1 -5
- package/dist/hooks/useAudioBlobLoader.d.ts.map +1 -1
- package/dist/hooks/useAudioBlobLoader.js +175 -0
- package/dist/hooks/useAudioBlobLoader.js.map +1 -0
- package/dist/hooks/useAudioPlayback.js +119 -0
- package/dist/hooks/useAudioPlayback.js.map +1 -0
- package/dist/hooks/useAudioRecorderHandlers.js +137 -0
- package/dist/hooks/useAudioRecorderHandlers.js.map +1 -0
- package/dist/hooks/useMediaRecorder.js +127 -0
- package/dist/hooks/useMediaRecorder.js.map +1 -0
- package/dist/hooks/useRecorderVisualization.js +94 -0
- package/dist/hooks/useRecorderVisualization.js.map +1 -0
- package/dist/hooks/useRecordingSender.js +53 -0
- package/dist/hooks/useRecordingSender.js.map +1 -0
- package/dist/hooks/useRecordingTiming.js +93 -0
- package/dist/hooks/useRecordingTiming.js.map +1 -0
- package/dist/hooks/useVoiceMessagePlayer.js +177 -0
- package/dist/hooks/useVoiceMessagePlayer.js.map +1 -0
- package/dist/hooks/useVoiceRecorder.js +153 -0
- package/dist/hooks/useVoiceRecorder.js.map +1 -0
- package/dist/hooks/useVoiceRecorderInput.js +163 -0
- package/dist/hooks/useVoiceRecorderInput.js.map +1 -0
- package/dist/index.cjs +108 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +13 -2612
- package/dist/index.js.map +1 -1
- package/dist/platform/animation-frame-adapter.js +14 -0
- package/dist/platform/animation-frame-adapter.js.map +1 -0
- package/dist/platform/audio-analyzer-adapter.d.ts.map +1 -1
- package/dist/platform/audio-analyzer-adapter.js +74 -0
- package/dist/platform/audio-analyzer-adapter.js.map +1 -0
- package/dist/platform/audio-blob-fetcher.js +19 -0
- package/dist/platform/audio-blob-fetcher.js.map +1 -0
- package/dist/platform/audio-playback-adapter.d.ts.map +1 -1
- package/dist/platform/audio-playback-adapter.js +114 -0
- package/dist/platform/audio-playback-adapter.js.map +1 -0
- package/dist/platform/audio-playback-manager.js +26 -0
- package/dist/platform/audio-playback-manager.js.map +1 -0
- package/dist/platform/media-recorder-adapter.js +23 -0
- package/dist/platform/media-recorder-adapter.js.map +1 -0
- package/dist/platform/microphone.js +24 -0
- package/dist/platform/microphone.js.map +1 -0
- package/dist/platform/mpeg-frame.js +60 -0
- package/dist/platform/mpeg-frame.js.map +1 -0
- package/dist/utils/format.js +13 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/math.js +16 -0
- package/dist/utils/math.js.map +1 -0
- package/dist/utils/pseudo-waveform.d.ts +12 -0
- package/dist/utils/pseudo-waveform.d.ts.map +1 -0
- package/dist/utils/pseudo-waveform.js +40 -0
- package/dist/utils/pseudo-waveform.js.map +1 -0
- package/dist/utils/voice-blob-cache.js +35 -0
- package/dist/utils/voice-blob-cache.js.map +1 -0
- package/dist/utils/voice-playback-chain.js +42 -0
- package/dist/utils/voice-playback-chain.js.map +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -146,7 +146,7 @@ const Messages = () => {
|
|
|
146
146
|
| `duration` | `number` | — | — | Длительность в секундах (отображается до загрузки аудио). |
|
|
147
147
|
| `autoPreload` | `boolean` | — | `true` | Автозагрузка аудио при попадании в viewport (IntersectionObserver). |
|
|
148
148
|
| `waveformData` | `number[]` | — | — | Предварительно вычисленные уровни waveform. |
|
|
149
|
-
| `preloadedBlob` | `Blob \| null` | — | — |
|
|
149
|
+
| `preloadedBlob` | `Blob \| null` | — | — | Локальная запись. Играет из него, только пока нет `src`; при заданном `src` идёт на него, а blob переиспользуется для waveform. |
|
|
150
150
|
| `onEnded` | `() => void` | — | — | Колбэк окончания воспроизведения. |
|
|
151
151
|
| `className` | `string` | — | — | CSS-класс контейнера. |
|
|
152
152
|
| `onClick` | `(e: React.MouseEvent) => void`| — | — | Обработчик клика по контейнеру. |
|
|
@@ -154,7 +154,7 @@ const Messages = () => {
|
|
|
154
154
|
**Ref API (`VoiceMessagePlayerRef`):**
|
|
155
155
|
|
|
156
156
|
- `play()` — программный запуск воспроизведения.
|
|
157
|
-
- `prefetch()` —
|
|
157
|
+
- `prefetch()` — подготовка аудио без воспроизведения (waveform считается фоном и play не задерживает).
|
|
158
158
|
|
|
159
159
|
#### `VoicePlaybackChain`
|
|
160
160
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useMemo, useState, useCallback } from 'react';
|
|
3
|
+
import { useVoiceRecorder } from '../hooks/useVoiceRecorder.js';
|
|
4
|
+
import { useAudioRecorderHandlers } from '../hooks/useAudioRecorderHandlers.js';
|
|
5
|
+
import { Container } from './AudioRecorder.styled.js';
|
|
6
|
+
import { IdleControl } from './IdleControl/IdleControl.js';
|
|
7
|
+
import { RecordingShell } from './RecordingShell/RecordingShell.js';
|
|
8
|
+
import { mergeMessages } from './AudioRecorder.messages.js';
|
|
9
|
+
|
|
10
|
+
const AudioRecorder = (props) => {
|
|
11
|
+
const { fileName, uploadId, mimeType, className, uploader, onUploaded, onUploadError, onRecordingStop, onRecordingError, onRecordingStart, onRecordingDelete, onPermissionDenied, autoResetAfterUpload, messages: customMessages, } = props;
|
|
12
|
+
const messages = useMemo(() => mergeMessages(customMessages), [customMessages]);
|
|
13
|
+
const [resolvedUploadId] = useState(() => uploadId !== null && uploadId !== void 0 ? uploadId : (typeof crypto !== 'undefined' && 'randomUUID' in crypto
|
|
14
|
+
? crypto.randomUUID()
|
|
15
|
+
: `upload-${Date.now()}`));
|
|
16
|
+
const handleRecorderError = useCallback((error) => {
|
|
17
|
+
onRecordingError === null || onRecordingError === void 0 ? void 0 : onRecordingError(error);
|
|
18
|
+
onUploadError === null || onUploadError === void 0 ? void 0 : onUploadError(error);
|
|
19
|
+
}, [onRecordingError, onUploadError]);
|
|
20
|
+
const vibrate = useCallback((pattern) => {
|
|
21
|
+
if (typeof navigator !== 'undefined' && 'vibrate' in navigator) {
|
|
22
|
+
navigator.vibrate(pattern);
|
|
23
|
+
}
|
|
24
|
+
}, []);
|
|
25
|
+
const recorder = useVoiceRecorder({
|
|
26
|
+
fileName,
|
|
27
|
+
mimeType,
|
|
28
|
+
onRecordingStop,
|
|
29
|
+
onRecordingStart,
|
|
30
|
+
onRecordingDelete,
|
|
31
|
+
onPermissionDenied,
|
|
32
|
+
autoResetAfterUpload,
|
|
33
|
+
uploader,
|
|
34
|
+
uploadId: resolvedUploadId,
|
|
35
|
+
onRecordingError: handleRecorderError,
|
|
36
|
+
onUploaded: onUploaded !== null && onUploaded !== void 0 ? onUploaded : (() => { }),
|
|
37
|
+
});
|
|
38
|
+
const { isSending, handleCancel, handleSendRequest, handleRecordToggle, handlePlaybackToggle, } = useAudioRecorderHandlers({
|
|
39
|
+
vibrate,
|
|
40
|
+
recorder,
|
|
41
|
+
messages,
|
|
42
|
+
onUploadError,
|
|
43
|
+
});
|
|
44
|
+
const { isPaused, isPlaying, formatTime, audioLevels, isRecording, lastDuration, playbackTime, hasRecording, previewLevels, recordingTime, playbackDuration, } = recorder;
|
|
45
|
+
const showRecorderBar = isRecording || hasRecording || isSending;
|
|
46
|
+
return (jsx(Container, { className: className, children: showRecorderBar ? (jsx(RecordingShell, { isPaused: isPaused, isSending: isSending, isPlaying: isPlaying, formatTime: formatTime, onCancel: handleCancel, isRecording: isRecording, audioLevels: audioLevels, onSend: handleSendRequest, playbackTime: playbackTime, lastDuration: lastDuration, hasRecording: hasRecording, previewLevels: previewLevels, recordingTime: recordingTime, playbackDuration: playbackDuration, onRecordToggle: handleRecordToggle, sendAriaLabel: messages.buttons.send, onTogglePlayback: handlePlaybackToggle, cancelAriaLabel: messages.buttons.cancel, playAriaLabel: messages.aria.playRecording, stopAriaLabel: messages.aria.stopRecording, startAriaLabel: messages.aria.startRecording, pauseRecordingAriaLabel: messages.aria.pauseRecording, resumeRecordingAriaLabel: messages.aria.resumeRecording, pausePlaybackAriaLabel: messages.aria.pausePlayback })) : (jsx(IdleControl, { onClick: handleRecordToggle, startAriaLabel: messages.aria.startRecording, instruction: messages.instruction })) }));
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export { AudioRecorder };
|
|
50
|
+
//# sourceMappingURL=AudioRecorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioRecorder.js","sources":["../../src/components/AudioRecorder.tsx"],"sourcesContent":["/**\n * AudioRecorder\n *\n * @author: exode <hello@exode.ru>\n */\n\nimport React, { useCallback, useMemo, useState } from 'react';\n\nimport { useVoiceRecorder } from '../hooks';\nimport { useAudioRecorderHandlers } from '../hooks/useAudioRecorderHandlers';\n\nimport { Container } from './AudioRecorder.styled';\nimport { IdleControl } from './IdleControl/IdleControl';\nimport { RecordingShell } from './RecordingShell/RecordingShell';\n\nimport { mergeMessages, type RecorderMessagesInput } from './AudioRecorder.messages';\n\nimport {\n type StorageFileEntity,\n type UploadStrategy,\n type UseVoiceRecorderOptions,\n} from '../types';\n\n\ntype RecorderOptionOverrides = Omit<\n UseVoiceRecorderOptions<Partial<StorageFileEntity>>,\n 'uploadId' | 'onUploaded' | 'uploader'\n>;\n\ninterface Props extends Partial<RecorderOptionOverrides> {\n uploader: UploadStrategy<Partial<StorageFileEntity>>;\n messages?: RecorderMessagesInput;\n uploadId?: string;\n className?: string;\n onUploadError?: (error: unknown) => void;\n onUploaded?: (file: Partial<StorageFileEntity>, duration: number) => void | Promise<void>;\n}\n\n\nconst AudioRecorder = (props: Props) => {\n\n const {\n fileName,\n uploadId,\n mimeType,\n className,\n uploader,\n onUploaded,\n onUploadError,\n onRecordingStop,\n onRecordingError,\n onRecordingStart,\n onRecordingDelete,\n onPermissionDenied,\n autoResetAfterUpload,\n messages: customMessages,\n } = props;\n\n const messages = useMemo(() => mergeMessages(customMessages), [ customMessages ]);\n\n const [ resolvedUploadId ] = useState(\n () =>\n uploadId ??\n (typeof crypto !== 'undefined' && 'randomUUID' in crypto\n ? crypto.randomUUID()\n : `upload-${Date.now()}`),\n );\n\n const handleRecorderError = useCallback(\n (error: unknown) => {\n onRecordingError?.(error);\n onUploadError?.(error);\n },\n [ onRecordingError, onUploadError ],\n );\n\n const vibrate = useCallback((pattern: number | number[]) => {\n if (typeof navigator !== 'undefined' && 'vibrate' in navigator) {\n navigator.vibrate(pattern);\n }\n }, []);\n\n const recorder = useVoiceRecorder<Partial<StorageFileEntity>>({\n fileName,\n mimeType,\n onRecordingStop,\n onRecordingStart,\n onRecordingDelete,\n onPermissionDenied,\n autoResetAfterUpload,\n uploader,\n uploadId: resolvedUploadId,\n onRecordingError: handleRecorderError,\n onUploaded: onUploaded ?? (() => {}),\n });\n\n const {\n isSending,\n handleCancel,\n handleSendRequest,\n handleRecordToggle,\n handlePlaybackToggle,\n } = useAudioRecorderHandlers({\n vibrate,\n recorder,\n messages,\n onUploadError,\n });\n\n const {\n isPaused,\n isPlaying,\n formatTime,\n audioLevels,\n isRecording,\n lastDuration,\n playbackTime,\n hasRecording,\n previewLevels,\n recordingTime,\n playbackDuration,\n } = recorder;\n\n const showRecorderBar = isRecording || hasRecording || isSending;\n\n return (\n <Container className={className}>\n {showRecorderBar ? (\n <RecordingShell isPaused={isPaused}\n isSending={isSending}\n isPlaying={isPlaying}\n formatTime={formatTime}\n onCancel={handleCancel}\n isRecording={isRecording}\n audioLevels={audioLevels}\n onSend={handleSendRequest}\n playbackTime={playbackTime}\n lastDuration={lastDuration}\n hasRecording={hasRecording}\n previewLevels={previewLevels}\n recordingTime={recordingTime}\n playbackDuration={playbackDuration}\n onRecordToggle={handleRecordToggle}\n sendAriaLabel={messages.buttons.send}\n onTogglePlayback={handlePlaybackToggle}\n cancelAriaLabel={messages.buttons.cancel}\n playAriaLabel={messages.aria.playRecording}\n stopAriaLabel={messages.aria.stopRecording}\n startAriaLabel={messages.aria.startRecording}\n pauseRecordingAriaLabel={messages.aria.pauseRecording}\n resumeRecordingAriaLabel={messages.aria.resumeRecording}\n pausePlaybackAriaLabel={messages.aria.pausePlayback}/>\n ) : (\n <IdleControl onClick={handleRecordToggle}\n startAriaLabel={messages.aria.startRecording}\n instruction={messages.instruction}/>\n )}\n </Container>\n );\n};\n\n\nexport { AudioRecorder };\n"],"names":["_jsx"],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AudioRecorder messages — i18n-ready message definitions and merge utility
|
|
3
|
+
*
|
|
4
|
+
* @author: exode <hello@exode.ru>
|
|
5
|
+
*/
|
|
6
|
+
const DEFAULT_MESSAGES = {
|
|
7
|
+
buttons: {
|
|
8
|
+
cancel: 'Cancel',
|
|
9
|
+
send: 'Send',
|
|
10
|
+
},
|
|
11
|
+
instruction: 'Tap to start recording',
|
|
12
|
+
aria: {
|
|
13
|
+
startRecording: 'Start recording voice message',
|
|
14
|
+
stopRecording: 'Stop recording voice message',
|
|
15
|
+
pauseRecording: 'Pause recording',
|
|
16
|
+
resumeRecording: 'Resume recording',
|
|
17
|
+
playRecording: 'Play recording',
|
|
18
|
+
pausePlayback: 'Pause playback',
|
|
19
|
+
},
|
|
20
|
+
errors: {
|
|
21
|
+
loadFileFailed: 'Failed to upload audio file',
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
const mergeMessages = (custom) => {
|
|
25
|
+
var _a;
|
|
26
|
+
if (!custom) {
|
|
27
|
+
return DEFAULT_MESSAGES;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
buttons: {
|
|
31
|
+
...DEFAULT_MESSAGES.buttons,
|
|
32
|
+
...custom.buttons,
|
|
33
|
+
},
|
|
34
|
+
instruction: (_a = custom.instruction) !== null && _a !== void 0 ? _a : DEFAULT_MESSAGES.instruction,
|
|
35
|
+
aria: {
|
|
36
|
+
...DEFAULT_MESSAGES.aria,
|
|
37
|
+
...custom.aria,
|
|
38
|
+
},
|
|
39
|
+
errors: {
|
|
40
|
+
...DEFAULT_MESSAGES.errors,
|
|
41
|
+
...custom.errors,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export { DEFAULT_MESSAGES, mergeMessages };
|
|
47
|
+
//# sourceMappingURL=AudioRecorder.messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioRecorder.messages.js","sources":["../../src/components/AudioRecorder.messages.ts"],"sourcesContent":["/**\n * AudioRecorder messages — i18n-ready message definitions and merge utility\n *\n * @author: exode <hello@exode.ru>\n */\n\n\nexport interface RecorderMessages {\n buttons: {\n send: string;\n cancel: string;\n };\n instruction: string;\n aria: {\n startRecording: string;\n stopRecording: string;\n pauseRecording: string;\n resumeRecording: string;\n playRecording: string;\n pausePlayback: string;\n };\n errors: {\n loadFileFailed: string;\n };\n}\n\nexport type RecorderMessagesInput = Partial<{\n buttons: Partial<RecorderMessages['buttons']>;\n instruction: RecorderMessages['instruction'];\n aria: Partial<RecorderMessages['aria']>;\n errors: Partial<RecorderMessages['errors']>;\n}>;\n\nexport const DEFAULT_MESSAGES: RecorderMessages = {\n buttons: {\n cancel: 'Cancel',\n send: 'Send',\n },\n instruction: 'Tap to start recording',\n aria: {\n startRecording: 'Start recording voice message',\n stopRecording: 'Stop recording voice message',\n pauseRecording: 'Pause recording',\n resumeRecording: 'Resume recording',\n playRecording: 'Play recording',\n pausePlayback: 'Pause playback',\n },\n errors: {\n loadFileFailed: 'Failed to upload audio file',\n },\n};\n\nexport const mergeMessages = (custom?: RecorderMessagesInput): RecorderMessages => {\n if (!custom) {\n return DEFAULT_MESSAGES;\n }\n\n return {\n buttons: {\n ...DEFAULT_MESSAGES.buttons,\n ...custom.buttons,\n },\n instruction: custom.instruction ?? DEFAULT_MESSAGES.instruction,\n aria: {\n ...DEFAULT_MESSAGES.aria,\n ...custom.aria,\n },\n errors: {\n ...DEFAULT_MESSAGES.errors,\n ...custom.errors,\n },\n };\n};\n"],"names":[],"mappings":"AAAA;;;;AAIG;AA6BI,MAAM,gBAAgB,GAAqB;AAC9C,IAAA,OAAO,EAAE;AACL,QAAA,MAAM,EAAE,QAAQ;AAChB,QAAA,IAAI,EAAE,MAAM;AACf,KAAA;AACD,IAAA,WAAW,EAAE,wBAAwB;AACrC,IAAA,IAAI,EAAE;AACF,QAAA,cAAc,EAAE,+BAA+B;AAC/C,QAAA,aAAa,EAAE,8BAA8B;AAC7C,QAAA,cAAc,EAAE,iBAAiB;AACjC,QAAA,eAAe,EAAE,kBAAkB;AACnC,QAAA,aAAa,EAAE,gBAAgB;AAC/B,QAAA,aAAa,EAAE,gBAAgB;AAClC,KAAA;AACD,IAAA,MAAM,EAAE;AACJ,QAAA,cAAc,EAAE,6BAA6B;AAChD,KAAA;;AAGE,MAAM,aAAa,GAAG,CAAC,MAA8B,KAAsB;;IAC9E,IAAI,CAAC,MAAM,EAAE;AACT,QAAA,OAAO,gBAAgB;IAC3B;IAEA,OAAO;AACH,QAAA,OAAO,EAAE;YACL,GAAG,gBAAgB,CAAC,OAAO;YAC3B,GAAG,MAAM,CAAC,OAAO;AACpB,SAAA;QACD,WAAW,EAAE,MAAA,MAAM,CAAC,WAAW,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,gBAAgB,CAAC,WAAW;AAC/D,QAAA,IAAI,EAAE;YACF,GAAG,gBAAgB,CAAC,IAAI;YACxB,GAAG,MAAM,CAAC,IAAI;AACjB,SAAA;AACD,QAAA,MAAM,EAAE;YACJ,GAAG,gBAAgB,CAAC,MAAM;YAC1B,GAAG,MAAM,CAAC,MAAM;AACnB,SAAA;KACJ;AACL;;;;"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AudioRecorder styled components
|
|
5
|
+
*
|
|
6
|
+
* @author: exode <hello@exode.ru>
|
|
7
|
+
*/
|
|
8
|
+
const Container = styled.div `
|
|
9
|
+
position: relative;
|
|
10
|
+
width: 100%;
|
|
11
|
+
transition: color 200ms ease, background-color 200ms ease;
|
|
12
|
+
color: var(--text_primary, #0a0a0a);
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
export { Container };
|
|
16
|
+
//# sourceMappingURL=AudioRecorder.styled.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioRecorder.styled.js","sources":["../../src/components/AudioRecorder.styled.ts"],"sourcesContent":["/**\n * AudioRecorder styled components\n *\n * @author: exode <hello@exode.ru>\n */\n\nimport styled from 'styled-components';\n\n\nexport const Container = styled.div`\n position: relative;\n width: 100%;\n transition: color 200ms ease, background-color 200ms ease;\n color: var(--text_primary, #0a0a0a);\n`;\n"],"names":[],"mappings":";;AAAA;;;;AAIG;AAKI,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import 'react';
|
|
3
|
+
import { Icon32Microphone } from '@vkontakte/icons';
|
|
4
|
+
import { IdleWrapper, IdleButton, IdleButtonOutline, IdleButtonGlow, IdleInstruction } from './IdleControl.styled.js';
|
|
5
|
+
|
|
6
|
+
const IdleControl = (props) => {
|
|
7
|
+
const { onClick, instruction, startAriaLabel, } = props;
|
|
8
|
+
return (jsxs(IdleWrapper, { children: [jsxs(IdleButton, { type: "button", onClick: onClick, "aria-label": startAriaLabel, children: [jsx(IdleButtonOutline, { "aria-hidden": true }), jsx(IdleButtonGlow, { "aria-hidden": true }), jsx(Icon32Microphone, { height: 48, width: 48 })] }), jsx(IdleInstruction, { children: instruction })] }));
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export { IdleControl };
|
|
12
|
+
//# sourceMappingURL=IdleControl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IdleControl.js","sources":["../../../src/components/IdleControl/IdleControl.tsx"],"sourcesContent":["/**\n * IdleControl\n *\n * @author: exode <hello@exode.ru>\n */\n\nimport React from 'react';\n\nimport { Icon32Microphone } from '@vkontakte/icons';\n\nimport { IdleButton, IdleButtonGlow, IdleButtonOutline, IdleInstruction, IdleWrapper } from './IdleControl.styled';\n\n\ninterface Props {\n onClick: () => void;\n instruction: string;\n startAriaLabel: string;\n}\n\n\nconst IdleControl = (props: Props) => {\n\n const {\n onClick,\n instruction,\n startAriaLabel,\n } = props;\n\n return (\n <IdleWrapper>\n <IdleButton type=\"button\" onClick={onClick} aria-label={startAriaLabel}>\n <IdleButtonOutline aria-hidden/>\n <IdleButtonGlow aria-hidden/>\n\n <Icon32Microphone height={48} width={48}/>\n </IdleButton>\n\n <IdleInstruction>\n {instruction}\n </IdleInstruction>\n </IdleWrapper>\n );\n};\n\n\nexport { IdleControl };"],"names":["_jsxs","_jsx"],"mappings":";;;;;AAoBA,MAAM,WAAW,GAAG,CAAC,KAAY,KAAI;IAEjC,MAAM,EACF,OAAO,EACP,WAAW,EACX,cAAc,GACjB,GAAG,KAAK;IAET,QACIA,KAAC,WAAW,EAAA,EAAA,QAAA,EAAA,CACRA,KAAC,UAAU,EAAA,EAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAE,OAAO,EAAA,YAAA,EAAc,cAAc,aAClEC,GAAA,CAAC,iBAAiB,0BAAc,EAChCA,GAAA,CAAC,cAAc,EAAA,EAAA,aAAA,EAAA,IAAA,EAAA,CAAc,EAE7BA,IAAC,gBAAgB,EAAA,EAAC,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAA,CAAG,CAAA,EAAA,CACjC,EAEbA,GAAA,CAAC,eAAe,cACX,WAAW,EAAA,CACE,CAAA,EAAA,CACR;AAEtB;;;;"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* IdleControl styles
|
|
5
|
+
*
|
|
6
|
+
* @author: exode <hello@exode.ru>
|
|
7
|
+
*/
|
|
8
|
+
const IdleWrapper = styled.div `
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
align-items: center;
|
|
12
|
+
gap: 16px;
|
|
13
|
+
`;
|
|
14
|
+
const IdleButton = styled.button `
|
|
15
|
+
position: relative;
|
|
16
|
+
width: 72px;
|
|
17
|
+
height: 72px;
|
|
18
|
+
display: inline-flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
border-radius: 36px;
|
|
22
|
+
border: 0;
|
|
23
|
+
background: var(--background_content, #ffffff);
|
|
24
|
+
color: color-mix(in srgb, var(--accent, #3f8ae0) 80%, white 20%);
|
|
25
|
+
cursor: pointer;
|
|
26
|
+
transition: transform 180ms ease, box-shadow 180ms ease, background-color 180ms ease;
|
|
27
|
+
|
|
28
|
+
&:hover {
|
|
29
|
+
transform: translateY(-2px);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&:active {
|
|
33
|
+
transform: translateY(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&:focus-visible {
|
|
37
|
+
outline: 2px solid color-mix(in srgb, var(--accent, #3f8ae0) 70%, transparent);
|
|
38
|
+
outline-offset: 4px;
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
const IdleButtonOutline = styled.span `
|
|
42
|
+
position: absolute;
|
|
43
|
+
inset: 0;
|
|
44
|
+
border-radius: 32px;
|
|
45
|
+
border: 1px solid color-mix(in srgb, var(--accent, #3f8ae0) 28%, transparent);
|
|
46
|
+
pointer-events: none;
|
|
47
|
+
`;
|
|
48
|
+
const IdleButtonGlow = styled.span `
|
|
49
|
+
position: absolute;
|
|
50
|
+
inset: 8px;
|
|
51
|
+
border-radius: 28px;
|
|
52
|
+
background: radial-gradient(
|
|
53
|
+
120% 120% at 50% 50%,
|
|
54
|
+
color-mix(in srgb, var(--accent, #3f8ae0) 24%, transparent) 0%,
|
|
55
|
+
color-mix(in srgb, var(--accent, #3f8ae0) 10%, transparent) 65%,
|
|
56
|
+
transparent 100%
|
|
57
|
+
);
|
|
58
|
+
filter: blur(4px);
|
|
59
|
+
pointer-events: none;
|
|
60
|
+
`;
|
|
61
|
+
const IdleInstruction = styled.div `
|
|
62
|
+
font-size: 14px;
|
|
63
|
+
text-align: center;
|
|
64
|
+
color: color-mix(in srgb, var(--text_primary, #0a0a0a) 60%, transparent);
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
export { IdleButton, IdleButtonGlow, IdleButtonOutline, IdleInstruction, IdleWrapper };
|
|
68
|
+
//# sourceMappingURL=IdleControl.styled.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IdleControl.styled.js","sources":["../../../src/components/IdleControl/IdleControl.styled.ts"],"sourcesContent":["/**\n * IdleControl styles\n *\n * @author: exode <hello@exode.ru>\n */\n\nimport styled from 'styled-components';\n\n\nexport const IdleWrapper = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n`;\n\nexport const IdleButton = styled.button`\n position: relative;\n width: 72px;\n height: 72px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 36px;\n border: 0;\n background: var(--background_content, #ffffff);\n color: color-mix(in srgb, var(--accent, #3f8ae0) 80%, white 20%);\n cursor: pointer;\n transition: transform 180ms ease, box-shadow 180ms ease, background-color 180ms ease;\n\n &:hover {\n transform: translateY(-2px);\n }\n\n &:active {\n transform: translateY(0);\n }\n\n &:focus-visible {\n outline: 2px solid color-mix(in srgb, var(--accent, #3f8ae0) 70%, transparent);\n outline-offset: 4px;\n }\n`;\n\nexport const IdleButtonOutline = styled.span`\n position: absolute;\n inset: 0;\n border-radius: 32px;\n border: 1px solid color-mix(in srgb, var(--accent, #3f8ae0) 28%, transparent);\n pointer-events: none;\n`;\n\nexport const IdleButtonGlow = styled.span`\n position: absolute;\n inset: 8px;\n border-radius: 28px;\n background: radial-gradient(\n 120% 120% at 50% 50%,\n color-mix(in srgb, var(--accent, #3f8ae0) 24%, transparent) 0%,\n color-mix(in srgb, var(--accent, #3f8ae0) 10%, transparent) 65%,\n transparent 100%\n );\n filter: blur(4px);\n pointer-events: none;\n`;\n\nexport const IdleInstruction = styled.div`\n font-size: 14px;\n text-align: center;\n color: color-mix(in srgb, var(--text_primary, #0a0a0a) 60%, transparent);\n`;\n"],"names":[],"mappings":";;AAAA;;;;AAIG;AAKI,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;AAO9B,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BhC,MAAM,iBAAiB,GAAG,MAAM,CAAC,IAAI,CAAA;;;;;;;AAQrC,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAA;;;;;;;;;;;;;AAclC,MAAM,eAAe,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { Icon16Microphone, Icon16Pause, Icon16Play, Icon28DeleteOutlineAndroid, Icon28SendOutline } from '@vkontakte/icons';
|
|
4
|
+
import { WaveformBars } from '../WaveformBars/WaveformBars.js';
|
|
5
|
+
import { Wrapper, CancelButton, Track, CircleButton, Waveform, Timer, SendButton, SpinnerSlot, Spinner } from './RecordingShell.styled.js';
|
|
6
|
+
|
|
7
|
+
const RecordingShell = (props) => {
|
|
8
|
+
const { isPaused, isPlaying, isSending, isRecording, audioLevels, hasRecording, playbackTime, previewLevels, recordingTime, stopAriaLabel, playAriaLabel, sendAriaLabel, startAriaLabel, cancelAriaLabel, playbackDuration, pausePlaybackAriaLabel, pauseRecordingAriaLabel, resumeRecordingAriaLabel, onSend, onCancel, formatTime, onRecordToggle, onTogglePlayback, } = props;
|
|
9
|
+
const { lastDuration } = props;
|
|
10
|
+
const isPreview = !isRecording || isPaused;
|
|
11
|
+
const waveformValues = isRecording ? audioLevels : (hasRecording ? previewLevels : audioLevels);
|
|
12
|
+
const effectiveDuration = recordingTime
|
|
13
|
+
|| playbackDuration
|
|
14
|
+
|| lastDuration
|
|
15
|
+
|| 1;
|
|
16
|
+
const progress = (isPreview && isPlaying)
|
|
17
|
+
? Math.min(1, playbackTime / effectiveDuration)
|
|
18
|
+
: 0;
|
|
19
|
+
const displayTime = useMemo(() => {
|
|
20
|
+
if (isPlaying) {
|
|
21
|
+
return formatTime(playbackTime);
|
|
22
|
+
}
|
|
23
|
+
if (isRecording && !isPaused) {
|
|
24
|
+
return formatTime(recordingTime);
|
|
25
|
+
}
|
|
26
|
+
if (isRecording && isPaused) {
|
|
27
|
+
return formatTime(recordingTime);
|
|
28
|
+
}
|
|
29
|
+
if (lastDuration > 0) {
|
|
30
|
+
return formatTime(lastDuration);
|
|
31
|
+
}
|
|
32
|
+
if (hasRecording) {
|
|
33
|
+
if (playbackDuration > 0.5) {
|
|
34
|
+
return formatTime(playbackDuration);
|
|
35
|
+
}
|
|
36
|
+
if (recordingTime > 0) {
|
|
37
|
+
return formatTime(recordingTime);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return formatTime(0);
|
|
41
|
+
}, [
|
|
42
|
+
isPaused,
|
|
43
|
+
isPlaying,
|
|
44
|
+
formatTime,
|
|
45
|
+
isRecording,
|
|
46
|
+
lastDuration,
|
|
47
|
+
hasRecording,
|
|
48
|
+
playbackTime,
|
|
49
|
+
recordingTime,
|
|
50
|
+
playbackDuration,
|
|
51
|
+
]);
|
|
52
|
+
const recordButtonDisabled = isSending;
|
|
53
|
+
const recordButtonIcon = !isRecording || isPaused
|
|
54
|
+
? jsx(Icon16Microphone, { width: 24, height: 24 })
|
|
55
|
+
: jsx(Icon16Pause, { width: 24, height: 24 });
|
|
56
|
+
const recordButtonVariant = !isRecording || isPaused ? 'resume' : 'record';
|
|
57
|
+
const recordButtonAria = !isRecording
|
|
58
|
+
? startAriaLabel
|
|
59
|
+
: isPaused
|
|
60
|
+
? resumeRecordingAriaLabel
|
|
61
|
+
: pauseRecordingAriaLabel || stopAriaLabel;
|
|
62
|
+
const canShowPlayback = !isRecording || isPaused;
|
|
63
|
+
const playbackDisabled = !hasRecording && !isRecording;
|
|
64
|
+
const playbackIcon = isPlaying
|
|
65
|
+
? jsx(Icon16Pause, { width: 24, height: 24 })
|
|
66
|
+
: jsx(Icon16Play, { width: 24, height: 24 });
|
|
67
|
+
const playbackAria = isPlaying ? pausePlaybackAriaLabel : playAriaLabel;
|
|
68
|
+
const sendDisabled = isSending || (!hasRecording && !isRecording);
|
|
69
|
+
return (jsxs(Wrapper, { children: [jsx(CancelButton, { type: "button", onClick: onCancel, "aria-label": cancelAriaLabel, children: jsx(Icon28DeleteOutlineAndroid, { fill: "var(--exode_red)", width: 24, height: 24, style: { minWidth: 24 } }) }), jsxs(Track, { children: [jsx(CircleButton, { type: "button", "$variant": recordButtonVariant, onClick: onRecordToggle, "aria-label": recordButtonAria, disabled: recordButtonDisabled, children: recordButtonIcon }), !canShowPlayback ? null : (jsx(CircleButton, { type: "button", "$variant": "play", onClick: onTogglePlayback, "aria-label": playbackAria, disabled: playbackDisabled, children: playbackIcon })), jsx(Waveform, { "$expanded": canShowPlayback, children: jsx(WaveformBars, { scale: 42, minHeight: 6, divider: 100, progress: progress, values: waveformValues, styleInterpolator: ({ normalized, isPlayed, progress: p }) => ({
|
|
70
|
+
height: `${12 + normalized / 2}px`,
|
|
71
|
+
maxHeight: '34px',
|
|
72
|
+
opacity: p > 0
|
|
73
|
+
? (isPlayed ? 0.80 : 0.22)
|
|
74
|
+
: 0.38 + Math.min(0.5, normalized / 120),
|
|
75
|
+
transition: 'opacity 80ms ease',
|
|
76
|
+
}) }) }), jsx(Timer, { children: displayTime })] }), jsx(SendButton, { type: "button", onClick: onSend, disabled: sendDisabled, "aria-label": sendAriaLabel, children: isSending ? (jsx(SpinnerSlot, { children: jsx(Spinner, {}) })) : (jsx(Icon28SendOutline, { width: 24, height: 24 })) })] }));
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export { RecordingShell };
|
|
80
|
+
//# sourceMappingURL=RecordingShell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RecordingShell.js","sources":["../../../src/components/RecordingShell/RecordingShell.tsx"],"sourcesContent":["/**\n * RecordingShell\n *\n * @author: exode <hello@exode.ru>\n */\n\nimport React, { useMemo } from 'react';\n\nimport {\n Icon16Microphone,\n Icon16Pause,\n Icon16Play,\n Icon28DeleteOutlineAndroid,\n Icon28SendOutline,\n} from '@vkontakte/icons';\n\nimport { WaveformBars } from '../WaveformBars/WaveformBars';\n\nimport {\n CancelButton,\n CircleButton,\n SendButton,\n Spinner,\n SpinnerSlot,\n Timer,\n Track,\n Waveform,\n Wrapper,\n} from './RecordingShell.styled';\n\n\ninterface Props {\n isRecording: boolean;\n isPaused: boolean;\n hasRecording: boolean;\n isPlaying: boolean;\n lastDuration: number;\n recordingTime: number;\n playbackTime: number;\n playbackDuration: number;\n audioLevels: number[];\n previewLevels: number[];\n isSending: boolean;\n cancelAriaLabel: string;\n stopAriaLabel: string;\n startAriaLabel: string;\n pauseRecordingAriaLabel: string;\n resumeRecordingAriaLabel: string;\n playAriaLabel: string;\n pausePlaybackAriaLabel: string;\n sendAriaLabel: string;\n onSend: () => void;\n onCancel: () => void;\n onRecordToggle: () => void;\n onTogglePlayback: () => void;\n formatTime: (seconds: number) => string;\n}\n\n\nconst RecordingShell = (props: Props) => {\n\n const {\n isPaused,\n isPlaying,\n isSending,\n isRecording,\n audioLevels,\n hasRecording,\n playbackTime,\n previewLevels,\n recordingTime,\n stopAriaLabel,\n playAriaLabel,\n sendAriaLabel,\n startAriaLabel,\n cancelAriaLabel,\n playbackDuration,\n pausePlaybackAriaLabel,\n pauseRecordingAriaLabel,\n resumeRecordingAriaLabel,\n onSend,\n onCancel,\n formatTime,\n onRecordToggle,\n onTogglePlayback,\n } = props;\n\n const { lastDuration } = props;\n\n const isPreview = !isRecording || isPaused;\n const waveformValues = isRecording ? audioLevels : (hasRecording ? previewLevels : audioLevels);\n\n const effectiveDuration = recordingTime\n || playbackDuration\n || lastDuration\n || 1;\n\n const progress = (isPreview && isPlaying)\n ? Math.min(1, playbackTime / effectiveDuration)\n : 0;\n\n const displayTime = useMemo(() => {\n if (isPlaying) {\n return formatTime(playbackTime);\n }\n\n if (isRecording && !isPaused) {\n return formatTime(recordingTime);\n }\n\n if (isRecording && isPaused) {\n return formatTime(recordingTime);\n }\n\n if (lastDuration > 0) {\n return formatTime(lastDuration);\n }\n\n if (hasRecording) {\n if (playbackDuration > 0.5) {\n return formatTime(playbackDuration);\n }\n\n if (recordingTime > 0) {\n return formatTime(recordingTime);\n }\n }\n\n return formatTime(0);\n }, [\n isPaused,\n isPlaying,\n formatTime,\n isRecording,\n lastDuration,\n hasRecording,\n playbackTime,\n recordingTime,\n playbackDuration,\n ]);\n\n const recordButtonDisabled = isSending;\n const recordButtonIcon = !isRecording || isPaused\n ? <Icon16Microphone width={24} height={24}/>\n : <Icon16Pause width={24} height={24}/>;\n const recordButtonVariant: 'record' | 'resume' = !isRecording || isPaused ? 'resume' : 'record';\n const recordButtonAria = !isRecording\n ? startAriaLabel\n : isPaused\n ? resumeRecordingAriaLabel\n : pauseRecordingAriaLabel || stopAriaLabel;\n\n const canShowPlayback = !isRecording || isPaused;\n const playbackDisabled = !hasRecording && !isRecording;\n const playbackIcon = isPlaying\n ? <Icon16Pause width={24} height={24}/>\n : <Icon16Play width={24} height={24}/>;\n const playbackAria = isPlaying ? pausePlaybackAriaLabel : playAriaLabel;\n\n const sendDisabled = isSending || (!hasRecording && !isRecording);\n\n return (\n <Wrapper>\n <CancelButton type=\"button\" onClick={onCancel} aria-label={cancelAriaLabel}>\n <Icon28DeleteOutlineAndroid fill=\"var(--exode_red)\" width={24} height={24} style={{ minWidth: 24 }}/>\n </CancelButton>\n\n <Track>\n <CircleButton type=\"button\"\n $variant={recordButtonVariant}\n onClick={onRecordToggle}\n aria-label={recordButtonAria}\n disabled={recordButtonDisabled}>\n {recordButtonIcon}\n </CircleButton>\n\n {!canShowPlayback ? null : (\n <CircleButton type=\"button\"\n $variant=\"play\"\n onClick={onTogglePlayback}\n aria-label={playbackAria}\n disabled={playbackDisabled}>\n {playbackIcon}\n </CircleButton>\n )}\n\n <Waveform $expanded={canShowPlayback}>\n <WaveformBars scale={42}\n minHeight={6}\n divider={100}\n progress={progress}\n values={waveformValues}\n styleInterpolator={({ normalized, isPlayed, progress: p }) => ({\n height: `${12 + normalized / 2}px`,\n maxHeight: '34px',\n opacity: p > 0\n ? (isPlayed ? 0.80 : 0.22)\n : 0.38 + Math.min(0.5, normalized / 120),\n transition: 'opacity 80ms ease',\n })}/>\n </Waveform>\n\n <Timer>\n {displayTime}\n </Timer>\n </Track>\n\n <SendButton type=\"button\" onClick={onSend} disabled={sendDisabled} aria-label={sendAriaLabel}>\n {isSending ? (\n <SpinnerSlot>\n <Spinner/>\n </SpinnerSlot>\n ) : (\n <Icon28SendOutline width={24} height={24}/>\n )}\n </SendButton>\n </Wrapper>\n );\n};\n\n\nexport { RecordingShell };\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RecordingShell styles
|
|
5
|
+
*
|
|
6
|
+
* @author: exode <hello@exode.ru>
|
|
7
|
+
*/
|
|
8
|
+
const Wrapper$1 = styled.div `
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
gap: 12px;
|
|
12
|
+
padding: 10px 14px;
|
|
13
|
+
border-radius: 9999px;
|
|
14
|
+
background: var(--background_hover, color-mix(in srgb, var(--text_primary, #0a0a0a) 6%, transparent));
|
|
15
|
+
`;
|
|
16
|
+
const CancelButton$1 = styled.button `
|
|
17
|
+
width: 38px;
|
|
18
|
+
height: 38px;
|
|
19
|
+
display: inline-flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
border-radius: 19px;
|
|
23
|
+
background: color-mix(in srgb, var(--exode_red) 10%, transparent);
|
|
24
|
+
border: 0;
|
|
25
|
+
flex-shrink: 0;
|
|
26
|
+
color: color-mix(in srgb, var(--text_primary, #0a0a0a) 54%, transparent);
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
transition: transform 160ms ease;
|
|
29
|
+
|
|
30
|
+
&:hover:not(:disabled) {
|
|
31
|
+
transform: translateY(-1px);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&:active:not(:disabled) {
|
|
35
|
+
transform: translateY(0);
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
const Track = styled.div `
|
|
39
|
+
flex: 1;
|
|
40
|
+
display: flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
gap: 12px;
|
|
43
|
+
padding: 8px 10px;
|
|
44
|
+
border-radius: 9999px;
|
|
45
|
+
background: color-mix(in srgb, var(--text_primary, #0a0a0a) 10%, transparent);
|
|
46
|
+
`;
|
|
47
|
+
const CircleButton = styled.button `
|
|
48
|
+
width: 38px;
|
|
49
|
+
height: 38px;
|
|
50
|
+
display: inline-flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
justify-content: center;
|
|
53
|
+
border-radius: 19px;
|
|
54
|
+
border: 0;
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
transition: transform 160ms ease, color 160ms ease, background-color 160ms ease, box-shadow 160ms ease;
|
|
57
|
+
flex-shrink: 0;
|
|
58
|
+
background: var(--background_content, #ffffff);
|
|
59
|
+
|
|
60
|
+
color: ${({ $variant }) => {
|
|
61
|
+
if ($variant === 'record') {
|
|
62
|
+
return 'var(--exode_red, #ed1c24)';
|
|
63
|
+
}
|
|
64
|
+
if ($variant === 'resume') {
|
|
65
|
+
return 'color-mix(in srgb, var(--accent, #3f8ae0) 86%, white 14%)';
|
|
66
|
+
}
|
|
67
|
+
return 'color-mix(in srgb, var(--text_primary, #0a0a0a) 80%, transparent)';
|
|
68
|
+
}};
|
|
69
|
+
|
|
70
|
+
&:hover:not(:disabled) {
|
|
71
|
+
transform: translateY(-1px);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
&:active:not(:disabled) {
|
|
75
|
+
transform: translateY(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
&:focus-visible {
|
|
79
|
+
outline: 2px solid color-mix(in srgb, var(--accent, #3f8ae0) 70%, transparent);
|
|
80
|
+
outline-offset: 2px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
&:disabled {
|
|
84
|
+
cursor: not-allowed;
|
|
85
|
+
opacity: 0.45;
|
|
86
|
+
transform: none;
|
|
87
|
+
}
|
|
88
|
+
`;
|
|
89
|
+
const Waveform = styled.div `
|
|
90
|
+
position: relative;
|
|
91
|
+
flex: ${({ $expanded }) => ($expanded ? 1.4 : 1)};
|
|
92
|
+
height: 34px;
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
`;
|
|
97
|
+
const Timer$1 = styled.span `
|
|
98
|
+
text-align: right;
|
|
99
|
+
font-size: 13px;
|
|
100
|
+
font-weight: 600;
|
|
101
|
+
color: color-mix(in srgb, var(--text_primary, #0a0a0a) 86%, transparent);
|
|
102
|
+
`;
|
|
103
|
+
const SendButton$1 = styled.button `
|
|
104
|
+
width: 40px;
|
|
105
|
+
height: 40px;
|
|
106
|
+
display: inline-flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
justify-content: center;
|
|
109
|
+
border-radius: 20px;
|
|
110
|
+
border: 0;
|
|
111
|
+
cursor: pointer;
|
|
112
|
+
background: var(--accent, #3f8ae0);
|
|
113
|
+
color: var(--white, #ffffff);
|
|
114
|
+
transition: transform 160ms ease, opacity 160ms ease;
|
|
115
|
+
|
|
116
|
+
&:hover:not(:disabled) {
|
|
117
|
+
transform: translateY(-1px);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
&:active:not(:disabled) {
|
|
121
|
+
transform: translateY(0);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
&:disabled {
|
|
125
|
+
opacity: 0.45;
|
|
126
|
+
cursor: not-allowed;
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
const SpinnerSlot = styled.div `
|
|
130
|
+
width: 22px;
|
|
131
|
+
height: 22px;
|
|
132
|
+
display: inline-flex;
|
|
133
|
+
align-items: center;
|
|
134
|
+
justify-content: center;
|
|
135
|
+
`;
|
|
136
|
+
const Spinner = styled.span `
|
|
137
|
+
width: 100%;
|
|
138
|
+
height: 100%;
|
|
139
|
+
display: inline-block;
|
|
140
|
+
box-sizing: border-box;
|
|
141
|
+
border-radius: 50%;
|
|
142
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
143
|
+
border-top-color: var(--white, #ffffff);
|
|
144
|
+
border-right-color: var(--white, #ffffff);
|
|
145
|
+
animation: audioRecorderSpinner 0.75s linear infinite;
|
|
146
|
+
|
|
147
|
+
@keyframes audioRecorderSpinner {
|
|
148
|
+
from {
|
|
149
|
+
transform: rotate(0deg);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
to {
|
|
153
|
+
transform: rotate(360deg);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
`;
|
|
157
|
+
|
|
158
|
+
export { CancelButton$1 as CancelButton, CircleButton, SendButton$1 as SendButton, Spinner, SpinnerSlot, Timer$1 as Timer, Track, Waveform, Wrapper$1 as Wrapper };
|
|
159
|
+
//# sourceMappingURL=RecordingShell.styled.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RecordingShell.styled.js","sources":["../../../src/components/RecordingShell/RecordingShell.styled.ts"],"sourcesContent":["/**\n * RecordingShell styles\n *\n * @author: exode <hello@exode.ru>\n */\n\nimport styled from 'styled-components';\n\n\nexport const Wrapper = styled.div`\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 14px;\n border-radius: 9999px;\n background: var(--background_hover, color-mix(in srgb, var(--text_primary, #0a0a0a) 6%, transparent));\n`;\n\nexport const CancelButton = styled.button`\n width: 38px;\n height: 38px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 19px;\n background: color-mix(in srgb, var(--exode_red) 10%, transparent);\n border: 0;\n flex-shrink: 0;\n color: color-mix(in srgb, var(--text_primary, #0a0a0a) 54%, transparent);\n cursor: pointer;\n transition: transform 160ms ease;\n\n &:hover:not(:disabled) {\n transform: translateY(-1px);\n }\n\n &:active:not(:disabled) {\n transform: translateY(0);\n }\n`;\n\nexport const Track = styled.div`\n flex: 1;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 10px;\n border-radius: 9999px;\n background: color-mix(in srgb, var(--text_primary, #0a0a0a) 10%, transparent);\n`;\n\nexport const CircleButton = styled.button<{ $variant: 'record' | 'resume' | 'play' }>`\n width: 38px;\n height: 38px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 19px;\n border: 0;\n cursor: pointer;\n transition: transform 160ms ease, color 160ms ease, background-color 160ms ease, box-shadow 160ms ease;\n flex-shrink: 0;\n background: var(--background_content, #ffffff);\n\n color: ${({ $variant }) => {\n if ($variant === 'record') {\n return 'var(--exode_red, #ed1c24)';\n }\n\n if ($variant === 'resume') {\n return 'color-mix(in srgb, var(--accent, #3f8ae0) 86%, white 14%)';\n }\n\n return 'color-mix(in srgb, var(--text_primary, #0a0a0a) 80%, transparent)';\n }};\n\n &:hover:not(:disabled) {\n transform: translateY(-1px);\n }\n\n &:active:not(:disabled) {\n transform: translateY(0);\n }\n\n &:focus-visible {\n outline: 2px solid color-mix(in srgb, var(--accent, #3f8ae0) 70%, transparent);\n outline-offset: 2px;\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: 0.45;\n transform: none;\n }\n`;\n\nexport const Waveform = styled.div<{ $expanded: boolean }>`\n position: relative;\n flex: ${({ $expanded }) => ($expanded ? 1.4 : 1)};\n height: 34px;\n display: flex;\n align-items: center;\n overflow: hidden;\n`;\n\nexport const Timer = styled.span`\n text-align: right;\n font-size: 13px;\n font-weight: 600;\n color: color-mix(in srgb, var(--text_primary, #0a0a0a) 86%, transparent);\n`;\n\nexport const SendButton = styled.button`\n width: 40px;\n height: 40px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 20px;\n border: 0;\n cursor: pointer;\n background: var(--accent, #3f8ae0);\n color: var(--white, #ffffff);\n transition: transform 160ms ease, opacity 160ms ease;\n\n &:hover:not(:disabled) {\n transform: translateY(-1px);\n }\n\n &:active:not(:disabled) {\n transform: translateY(0);\n }\n\n &:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n }\n`;\n\nexport const SpinnerSlot = styled.div`\n width: 22px;\n height: 22px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n`;\n\nexport const Spinner = styled.span`\n width: 100%;\n height: 100%;\n display: inline-block;\n box-sizing: border-box;\n border-radius: 50%;\n border: 2px solid rgba(255, 255, 255, 0.3);\n border-top-color: var(--white, #ffffff);\n border-right-color: var(--white, #ffffff);\n animation: audioRecorderSpinner 0.75s linear infinite;\n\n @keyframes audioRecorderSpinner {\n from {\n transform: rotate(0deg);\n }\n\n to {\n transform: rotate(360deg);\n }\n }\n`;\n"],"names":["Wrapper","CancelButton","Timer","SendButton"],"mappings":";;AAAA;;;;AAIG;AAKI,MAAMA,SAAO,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;AAS1B,MAAMC,cAAY,GAAG,MAAM,CAAC,MAAM,CAAA;;;;;;;;;;;;;;;;;;;;;;AAuBlC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;AAUxB,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAA4C;;;;;;;;;;;;;AAaxE,WAAA,EAAA,CAAC,EAAE,QAAQ,EAAE,KAAI;AACtB,IAAA,IAAI,QAAQ,KAAK,QAAQ,EAAE;AACvB,QAAA,OAAO,2BAA2B;IACtC;AAEA,IAAA,IAAI,QAAQ,KAAK,QAAQ,EAAE;AACvB,QAAA,OAAO,2DAA2D;IACtE;AAEA,IAAA,OAAO,mEAAmE;AAC9E,CAAC,CAAA;;;;;;;;;;;;;;;;;;;;;AAsBE,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAwB;;AAE9C,UAAA,EAAA,CAAC,EAAE,SAAS,EAAE,MAAM,SAAS,GAAG,GAAG,GAAG,CAAC,CAAC,CAAA;;;;;;AAO7C,MAAMC,OAAK,GAAG,MAAM,CAAC,IAAI,CAAA;;;;;;AAOzB,MAAMC,YAAU,GAAG,MAAM,CAAC,MAAM,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BhC,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;AAQ9B,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;"}
|