♻️(frontend) extract recording row layout in reusable component

Now that screen recording and transcription share the same UI presentation,
extract the row logic into a reusable component to avoid code duplication and
improve code maintainability.
This commit is contained in:
lebaudantoine
2025-12-31 22:53:33 +01:00
committed by aleb_the_flash
parent 398ef1ae8a
commit 57a7523cc4
3 changed files with 104 additions and 175 deletions

View File

@@ -0,0 +1,60 @@
import { css } from '@/styled-system/css'
import { ReactNode } from 'react'
type RowPosition = 'first' | 'middle' | 'last' | 'single'
const BORDER_RADIUS_MAP: Record<RowPosition, string> = {
first: '4px 4px 0 0',
middle: '0',
last: '0 0 4px 4px',
single: '4px',
} as const
interface RowWrapperProps {
iconName: string
children: ReactNode
position?: RowPosition
}
export const RowWrapper = ({
iconName,
children,
position = 'middle',
}: RowWrapperProps) => {
return (
<div
style={{
borderRadius: BORDER_RADIUS_MAP[position],
}}
className={css({
width: '100%',
background: 'gray.100',
padding: '8px',
display: 'flex',
marginTop: '4px',
})}
>
<div
className={css({
flex: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
})}
>
{/* fixme - doesn't handle properly material-symbols */}
<span className="material-icons">{iconName}</span>
</div>
<div
className={css({
flex: 5,
display: 'flex',
alignItems: 'center',
gap: '0.25rem',
})}
>
{children}
</div>
</div>
)
}

View File

@@ -27,6 +27,7 @@ import { useConfig } from '@/api/useConfig'
import { FeatureFlags } from '@/features/analytics/enums' import { FeatureFlags } from '@/features/analytics/enums'
import { NoAccessView } from './NoAccessView' import { NoAccessView } from './NoAccessView'
import { HStack, VStack } from '@/styled-system/jsx' import { HStack, VStack } from '@/styled-system/jsx'
import { RowWrapper } from './RowWrapper'
import { Checkbox } from '@/primitives/Checkbox' import { Checkbox } from '@/primitives/Checkbox'
import { useTranscriptionLanguage } from '@/features/settings' import { useTranscriptionLanguage } from '@/features/settings'
@@ -188,63 +189,12 @@ export const ScreenRecordingSidePanel = () => {
</Text> </Text>
</VStack> </VStack>
<VStack gap={0} marginBottom={40}> <VStack gap={0} marginBottom={40}>
<div <RowWrapper iconName="cloud_download" position="first">
className={css({ <Text variant="sm">{t('details.destination')}</Text>
width: '100%', </RowWrapper>
background: 'gray.100', <RowWrapper iconName="mail" position="last">
borderRadius: '4px 4px 0 0', <Text variant="sm">{t('details.receiver')}</Text>
paddingLeft: '4px', </RowWrapper>
padding: '8px',
display: 'flex',
})}
>
<div
className={css({
flex: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
})}
>
<span className="material-icons">cloud_download</span>
</div>
<div
className={css({
flex: 5,
})}
>
<Text variant="sm">{t('details.destination')}</Text>
</div>
</div>
<div
className={css({
width: '100%',
background: 'gray.100',
borderRadius: '0 0 4px 4px',
paddingLeft: '4px',
padding: '8px',
display: 'flex',
marginTop: '4px',
})}
>
<div
className={css({
flex: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
})}
>
<span className="material-icons">mail</span>
</div>
<div
className={css({
flex: 5,
})}
>
<Text variant="sm">{t('details.receiver')}</Text>
</div>
</div>
<div className={css({ height: '15px' })} /> <div className={css({ height: '15px' })} />

View File

@@ -25,7 +25,7 @@ import posthog from 'posthog-js'
import { useSnapshot } from 'valtio/index' import { useSnapshot } from 'valtio/index'
import { Spinner } from '@/primitives/Spinner' import { Spinner } from '@/primitives/Spinner'
import { useConfig } from '@/api/useConfig' import { useConfig } from '@/api/useConfig'
import { HStack, VStack } from '@/styled-system/jsx' import { VStack } from '@/styled-system/jsx'
import { Checkbox } from '@/primitives/Checkbox.tsx' import { Checkbox } from '@/primitives/Checkbox.tsx'
import { import {
@@ -34,6 +34,7 @@ import {
useTranscriptionLanguage, useTranscriptionLanguage,
} from '@/features/settings' } from '@/features/settings'
import { NoAccessView } from './NoAccessView' import { NoAccessView } from './NoAccessView'
import { RowWrapper } from './RowWrapper'
export const TranscriptSidePanel = () => { export const TranscriptSidePanel = () => {
const { data } = useConfig() const { data } = useConfig()
@@ -214,123 +215,41 @@ export const TranscriptSidePanel = () => {
</Text> </Text>
</VStack> </VStack>
<VStack gap={0} marginBottom={40}> <VStack gap={0} marginBottom={40}>
<div <RowWrapper iconName="article" position="first">
className={css({ <Text variant="sm">
width: '100%', {data?.transcription_destination ? (
// border: '1px solid black', <>
background: 'gray.100', {t('details.destination')}{' '}
borderRadius: '4px 4px 0 0', <A
paddingLeft: '4px', href={data.transcription_destination}
padding: '8px', target="_blank"
display: 'flex', rel="noopener noreferrer"
})} >
> {data.transcription_destination.replace('https://', '')}
<div </A>
className={css({ </>
flex: 1, ) : (
display: 'flex', t('details.destinationUnknown')
justifyContent: 'center', )}
alignItems: 'center', </Text>
})} </RowWrapper>
> <RowWrapper iconName="mail">
<span className="material-icons">article</span> <Text variant="sm">{t('details.receiver')}</Text>
</div> </RowWrapper>
<div <RowWrapper iconName="language" position="last">
className={css({ <Text variant="sm">{t('details.language')}</Text>
flex: 5, <Text variant="sm">
})} <Button
> variant="text"
<Text variant="sm"> size="xs"
{data?.transcription_destination ? ( onPress={() =>
<> openSettingsDialog(SettingsDialogExtendedKey.TRANSCRIPTION)
{t('details.destination')}{' '} }
<A >
href={data.transcription_destination} {selectedLanguageLabel}
target="_blank" </Button>
rel="noopener noreferrer" </Text>
> </RowWrapper>
{data.transcription_destination.replace('https://', '')}
</A>
</>
) : (
t('details.destinationUnknown')
)}
</Text>
</div>
</div>
<div
className={css({
width: '100%',
// border: '1px solid black',
background: 'gray.100',
paddingLeft: '4px',
padding: '8px',
display: 'flex',
marginTop: '4px',
})}
>
<div
className={css({
flex: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
})}
>
<span className="material-icons">mail</span>
</div>
<div
className={css({
flex: 5,
})}
>
<Text variant="sm">{t('details.receiver')}</Text>
</div>
</div>
<div
className={css({
width: '100%',
background: 'gray.100',
borderRadius: '0 0 4px 4px',
paddingLeft: '4px',
padding: '8px',
display: 'flex',
marginTop: '4px',
})}
>
<div
className={css({
flex: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
})}
>
<span className="material-icons">language</span>
</div>
<div
className={css({
flex: 5,
display: 'flex',
alignItems: 'center',
gap: '0.25rem',
})}
>
<Text variant="sm">{t('details.language')}</Text>
<Text variant="sm">
<Button
variant="text"
size="xs"
onPress={() =>
openSettingsDialog(SettingsDialogExtendedKey.TRANSCRIPTION)
}
>
{selectedLanguageLabel}
</Button>
</Text>
</div>
</div>
<div className={css({ height: '15px' })} /> <div className={css({ height: '15px' })} />
<div <div