Merge pull request #245 from numerique-gouv/feat/video-conference-ui
Enhance Conference UI
This commit is contained in:
BIN
src/backend/locale/en_US/LC_MESSAGES/django.mo
Normal file
BIN
src/backend/locale/en_US/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
BIN
src/backend/locale/fr_FR/LC_MESSAGES/django.mo
Normal file
BIN
src/backend/locale/fr_FR/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
@@ -93,6 +93,66 @@ const config: Config = {
|
||||
* This way we'll only add the things we need step by step and prevent using lots of differents things.
|
||||
*/
|
||||
...pandaPreset.theme.tokens,
|
||||
colors: defineTokens.colors({
|
||||
...pandaPreset.theme.tokens.colors,
|
||||
primaryDark: {
|
||||
50: { value: '#1B1B35' },
|
||||
100: { value: '#2B2B5B' },
|
||||
200: { value: '#3B3B81' },
|
||||
300: { value: '#4A4AA8' },
|
||||
400: { value: '#5A5ACE' },
|
||||
500: { value: '#6A6AF4' },
|
||||
600: { value: '#8585F6' },
|
||||
700: { value: '#CACAFB' },
|
||||
800: { value: '#E3E3FB' },
|
||||
900: { value: '#ECECFE' },
|
||||
950: { value: '#F5F5FE' },
|
||||
action: { value: '#C1C1FB' },
|
||||
},
|
||||
primary: {
|
||||
50: { value: '#F5F5FE' },
|
||||
100: { value: '#ECECFE' },
|
||||
200: { value: '#E3E3FB' },
|
||||
300: { value: '#CACAFB' },
|
||||
400: { value: '#8585F6' },
|
||||
500: { value: '#6A6AF4' },
|
||||
600: { value: '#313178' },
|
||||
700: { value: '#272747' },
|
||||
800: { value: '#000091' },
|
||||
900: { value: '#21213F' },
|
||||
950: { value: '#1B1B35' },
|
||||
action: { value: '#1212FF' },
|
||||
},
|
||||
greyscale: {
|
||||
'000': { value: '#FFFFFF' },
|
||||
50: { value: '#F6F6F6' },
|
||||
100: { value: '#EEEEEE' },
|
||||
200: { value: '#E5E5E5' },
|
||||
250: { value: '#DDDDDD' },
|
||||
300: { value: '#CECECE' },
|
||||
400: { value: '#929292' },
|
||||
500: { value: '#7C7C7C' },
|
||||
600: { value: '#666666' },
|
||||
700: { value: '#3A3A3A' },
|
||||
750: { value: '#353535' },
|
||||
800: { value: '#2A2A2A' },
|
||||
900: { value: '#242424' },
|
||||
950: { value: '#1E1E1E' },
|
||||
1000: { value: '#161616' },
|
||||
},
|
||||
error: {
|
||||
100: {value: '#391C1C'},
|
||||
200: {value: '#412121'},
|
||||
300: {value: '#642626'},
|
||||
400: {value: '#CE0500'},
|
||||
500: {value: '#F60700'},
|
||||
600: {value: '#FF5655'},
|
||||
700: {value: '#FFBDBD'},
|
||||
800: {value: '#FFDDDD'},
|
||||
900: {value: '#FFE9E9'},
|
||||
950: {value: '#FFF4F4'},
|
||||
}
|
||||
}),
|
||||
animations: {},
|
||||
blurs: {},
|
||||
/* just directly use values as tokens. This allows us to follow a specific design scale,
|
||||
@@ -194,7 +254,7 @@ const config: Config = {
|
||||
semanticTokens: defineSemanticTokens({
|
||||
colors: {
|
||||
default: {
|
||||
text: { value: '{colors.gray.900}' },
|
||||
text: { value: '{colors.greyscale.1000}' },
|
||||
bg: { value: 'white' },
|
||||
subtle: { value: '{colors.gray.100}' },
|
||||
'subtle-text': { value: '{colors.gray.600}' },
|
||||
|
||||
@@ -27,7 +27,7 @@ export const SoundTester = () => {
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
invisible
|
||||
variant="secondaryText"
|
||||
onPress={() => {
|
||||
audioRef?.current?.play()
|
||||
setIsPlaying(true)
|
||||
|
||||
@@ -185,8 +185,8 @@ export const IntroSlider = () => {
|
||||
<ButtonContainer>
|
||||
<ButtonVerticalCenter>
|
||||
<Button
|
||||
variant="greyscale"
|
||||
square
|
||||
invisible
|
||||
aria-label={t('previous.label')}
|
||||
tooltip={t('previous.tooltip')}
|
||||
onPress={() => setSlideIndex(slideIndex - 1)}
|
||||
@@ -221,8 +221,8 @@ export const IntroSlider = () => {
|
||||
<ButtonContainer>
|
||||
<ButtonVerticalCenter>
|
||||
<Button
|
||||
variant="greyscale"
|
||||
square
|
||||
invisible
|
||||
aria-label={t('next.label')}
|
||||
tooltip={t('next.tooltip')}
|
||||
onPress={() => setSlideIndex(slideIndex + 1)}
|
||||
|
||||
@@ -10,7 +10,6 @@ import { JoinMeetingDialog } from '../components/JoinMeetingDialog'
|
||||
import { ProConnectButton } from '@/components/ProConnectButton'
|
||||
import { useCreateRoom } from '@/features/rooms'
|
||||
import { usePersistentUserChoices } from '@livekit/components-react'
|
||||
import { menuItemRecipe } from '@/primitives/menuItemRecipe'
|
||||
import { RiAddLine, RiLink } from '@remixicon/react'
|
||||
import { LaterMeetingDialog } from '@/features/home/components/LaterMeetingDialog'
|
||||
import { IntroSlider } from '@/features/home/components/IntroSlider'
|
||||
@@ -18,6 +17,7 @@ import { MoreLink } from '@/features/home/components/MoreLink'
|
||||
import { ReactNode, useState } from 'react'
|
||||
|
||||
import { css } from '@/styled-system/css'
|
||||
import { menuRecipe } from '@/primitives/menuRecipe.ts'
|
||||
|
||||
const Columns = ({ children }: { children?: ReactNode }) => {
|
||||
return (
|
||||
@@ -173,7 +173,9 @@ export const Home = () => {
|
||||
</Button>
|
||||
<RACMenu>
|
||||
<MenuItem
|
||||
className={menuItemRecipe({ icon: true })}
|
||||
className={
|
||||
menuRecipe({ icon: true, variant: 'light' }).item
|
||||
}
|
||||
onAction={async () => {
|
||||
const slug = generateRoomId()
|
||||
createRoom({ slug, username }).then((data) =>
|
||||
@@ -188,7 +190,9 @@ export const Home = () => {
|
||||
{t('createMenu.instantOption')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className={menuItemRecipe({ icon: true })}
|
||||
className={
|
||||
menuRecipe({ icon: true, variant: 'light' }).item
|
||||
}
|
||||
onAction={() => {
|
||||
const slug = generateRoomId()
|
||||
createRoom({ slug, username }).then((data) =>
|
||||
@@ -207,8 +211,7 @@ export const Home = () => {
|
||||
)}
|
||||
<DialogTrigger>
|
||||
<Button
|
||||
variant="primary"
|
||||
outline
|
||||
variant="secondary"
|
||||
style={{
|
||||
height: !isLoggedIn ? '56px' : undefined, // Temporary, Align with ProConnect Button fixed height
|
||||
}}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { InviteDialog } from './InviteDialog'
|
||||
|
||||
import { VideoConference } from '../livekit/prefabs/VideoConference'
|
||||
import posthog from 'posthog-js'
|
||||
import { css } from '@/styled-system/css'
|
||||
|
||||
export const Conference = ({
|
||||
roomId,
|
||||
@@ -107,6 +108,9 @@ export const Conference = ({
|
||||
audio={userConfig.audioEnabled}
|
||||
video={userConfig.videoEnabled}
|
||||
connectOptions={connectOptions}
|
||||
className={css({
|
||||
backgroundColor: 'primaryDark.50 !important',
|
||||
})}
|
||||
>
|
||||
<VideoConference />
|
||||
{showInviteDialog && (
|
||||
|
||||
@@ -49,8 +49,6 @@ export const InviteDialog = ({
|
||||
}
|
||||
}, [isCopied])
|
||||
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
|
||||
return (
|
||||
<StyledRACDialog {...dialogProps}>
|
||||
{({ close }) => (
|
||||
@@ -65,7 +63,7 @@ export const InviteDialog = ({
|
||||
</Heading>
|
||||
<Div position="absolute" top="5" right="5">
|
||||
<Button
|
||||
invisible
|
||||
variant="greyscale"
|
||||
size="xs"
|
||||
onPress={() => {
|
||||
dialogProps.onClose?.()
|
||||
@@ -78,45 +76,24 @@ export const InviteDialog = ({
|
||||
</Div>
|
||||
<P>{t('shareDialog.description')}</P>
|
||||
<Button
|
||||
variant={isCopied ? 'success' : 'primary'}
|
||||
size="sm"
|
||||
variant={isCopied ? 'success' : 'tertiary'}
|
||||
fullWidth
|
||||
aria-label={t('shareDialog.copy')}
|
||||
style={{
|
||||
justifyContent: 'start',
|
||||
}}
|
||||
onPress={() => {
|
||||
navigator.clipboard.writeText(roomUrl)
|
||||
setIsCopied(true)
|
||||
}}
|
||||
onHoverChange={setIsHovered}
|
||||
data-attr="share-dialog-copy"
|
||||
>
|
||||
{isCopied ? (
|
||||
<>
|
||||
<RiCheckLine size={18} style={{ marginRight: '8px' }} />
|
||||
<RiCheckLine size={24} style={{ marginRight: '8px' }} />
|
||||
{t('shareDialog.copied')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RiFileCopyLine
|
||||
size={18}
|
||||
style={{ marginRight: '8px', minWidth: '18px' }}
|
||||
/>
|
||||
{isHovered ? (
|
||||
t('shareDialog.copy')
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
userSelect: 'none',
|
||||
textWrap: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{roomUrl.replace(/^https?:\/\//, '')}
|
||||
</div>
|
||||
)}
|
||||
<RiFileCopyLine size={24} style={{ marginRight: '8px' }} />
|
||||
{t('shareDialog.copyButton')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@@ -43,7 +43,7 @@ const ratingButtonRecipe = cva({
|
||||
variants: {
|
||||
selected: {
|
||||
true: {
|
||||
backgroundColor: '#1d4ed8',
|
||||
backgroundColor: 'primary.800',
|
||||
color: 'white',
|
||||
},
|
||||
false: {
|
||||
|
||||
@@ -151,7 +151,6 @@ export const Effects = () => {
|
||||
<HStack>
|
||||
<ToggleButton
|
||||
size={'sm'}
|
||||
legacyStyle
|
||||
aria-label={tooltipLabel(BlurRadius.LIGHT)}
|
||||
tooltip={tooltipLabel(BlurRadius.LIGHT)}
|
||||
isDisabled={processorPending}
|
||||
@@ -162,7 +161,6 @@ export const Effects = () => {
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
size={'sm'}
|
||||
legacyStyle
|
||||
aria-label={tooltipLabel(BlurRadius.NORMAL)}
|
||||
tooltip={tooltipLabel(BlurRadius.NORMAL)}
|
||||
isDisabled={processorPending}
|
||||
|
||||
@@ -68,6 +68,7 @@ const StyledSidePanel = ({
|
||||
>
|
||||
<Button
|
||||
invisible
|
||||
variant="greyscale"
|
||||
size="xs"
|
||||
onPress={onClose}
|
||||
aria-label={closeButtonTooltip}
|
||||
|
||||
@@ -106,7 +106,7 @@ export const ChatInput = ({
|
||||
/>
|
||||
<Button
|
||||
square
|
||||
invisible
|
||||
variant="secondaryText"
|
||||
size="sm"
|
||||
onPress={handleSubmit}
|
||||
isDisabled={isDisabled}
|
||||
|
||||
@@ -23,7 +23,7 @@ export const ChatToggle = () => {
|
||||
>
|
||||
<ToggleButton
|
||||
square
|
||||
legacyStyle
|
||||
variant="primaryTextDark"
|
||||
aria-label={t(tooltipLabel)}
|
||||
tooltip={t(tooltipLabel)}
|
||||
isSelected={isChatOpen}
|
||||
|
||||
@@ -24,7 +24,7 @@ export const HandToggle = () => {
|
||||
>
|
||||
<ToggleButton
|
||||
square
|
||||
legacyStyle
|
||||
variant="primaryDark"
|
||||
aria-label={t(tooltipLabel)}
|
||||
tooltip={t(tooltipLabel)}
|
||||
isSelected={isHandRaised}
|
||||
|
||||
@@ -16,7 +16,7 @@ export const OptionsButton = () => {
|
||||
<Menu>
|
||||
<Button
|
||||
square
|
||||
legacyStyle
|
||||
variant="primaryDark"
|
||||
aria-label={t('options.buttonLabel')}
|
||||
tooltip={t('options.buttonLabel')}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { menuItemRecipe } from '@/primitives/menuItemRecipe'
|
||||
import {
|
||||
RiAccountBoxLine,
|
||||
RiMegaphoneLine,
|
||||
@@ -10,6 +9,7 @@ import { Dispatch, SetStateAction } from 'react'
|
||||
import { DialogState } from './OptionsButton'
|
||||
import { Separator } from '@/primitives/Separator'
|
||||
import { useSidePanel } from '../../../hooks/useSidePanel'
|
||||
import { menuRecipe } from '@/primitives/menuRecipe.ts'
|
||||
|
||||
// @todo try refactoring it to use MenuList component
|
||||
export const OptionsMenuItems = ({
|
||||
@@ -29,7 +29,7 @@ export const OptionsMenuItems = ({
|
||||
<Section>
|
||||
<MenuItem
|
||||
onAction={() => toggleEffects()}
|
||||
className={menuItemRecipe({ icon: true })}
|
||||
className={menuRecipe({ icon: true }).item}
|
||||
>
|
||||
<RiAccountBoxLine size={20} />
|
||||
{t('effects')}
|
||||
@@ -40,13 +40,13 @@ export const OptionsMenuItems = ({
|
||||
<MenuItem
|
||||
href="https://grist.incubateur.net/o/docs/forms/1YrfNP1QSSy8p2gCxMFnSf/4"
|
||||
target="_blank"
|
||||
className={menuItemRecipe({ icon: true })}
|
||||
className={menuRecipe({ icon: true }).item}
|
||||
>
|
||||
<RiMegaphoneLine size={20} />
|
||||
{t('feedbacks')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className={menuItemRecipe({ icon: true })}
|
||||
className={menuRecipe({ icon: true }).item}
|
||||
onAction={() => onOpenDialog('settings')}
|
||||
>
|
||||
<RiSettings3Line size={20} />
|
||||
|
||||
@@ -69,7 +69,7 @@ export const HandRaisedListItem = ({
|
||||
</HStack>
|
||||
<Button
|
||||
square
|
||||
invisible
|
||||
variant="greyscale"
|
||||
size="sm"
|
||||
onPress={() => lowerHandParticipant(participant)}
|
||||
tooltip={t('participants.lowerParticipantHand', { name })}
|
||||
|
||||
@@ -63,7 +63,7 @@ const MicIndicator = ({ participant }: MicIndicatorProps) => {
|
||||
<>
|
||||
<Button
|
||||
square
|
||||
invisible
|
||||
variant="greyscale"
|
||||
size="sm"
|
||||
tooltip={
|
||||
isLocal(participant)
|
||||
|
||||
@@ -29,7 +29,7 @@ export const ParticipantsToggle = () => {
|
||||
>
|
||||
<ToggleButton
|
||||
square
|
||||
legacyStyle
|
||||
variant="primaryTextDark"
|
||||
aria-label={t(tooltipLabel)}
|
||||
tooltip={t(tooltipLabel)}
|
||||
isSelected={isParticipantsOpen}
|
||||
|
||||
@@ -26,7 +26,7 @@ export const ScreenShareToggle = (
|
||||
<ToggleButton
|
||||
isSelected={enabled}
|
||||
square
|
||||
legacyStyle
|
||||
variant="primaryDark"
|
||||
tooltip={t(tooltipLabel)}
|
||||
onPress={(e) =>
|
||||
buttonProps.onClick?.(
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
useTrackToggle,
|
||||
UseTrackToggleProps,
|
||||
} from '@livekit/components-react'
|
||||
import { HStack } from '@/styled-system/jsx'
|
||||
import { Button, Menu, MenuList } from '@/primitives'
|
||||
import {
|
||||
RemixiconComponentType,
|
||||
@@ -19,6 +18,7 @@ import { Track } from 'livekit-client'
|
||||
import { Shortcut } from '@/features/shortcuts/types'
|
||||
|
||||
import { ToggleDevice } from '@/features/rooms/livekit/components/controls/ToggleDevice.tsx'
|
||||
import { css } from '@/styled-system/css'
|
||||
|
||||
export type ToggleSource = Exclude<
|
||||
Track.Source,
|
||||
@@ -86,7 +86,12 @@ export const SelectToggleDevice = <T extends ToggleSource>({
|
||||
const selectLabel = t('choose', { keyPrefix: `join.${config.kind}` })
|
||||
|
||||
return (
|
||||
<HStack gap={0}>
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
gap: '1px',
|
||||
})}
|
||||
>
|
||||
<ToggleDevice {...trackProps} config={config} />
|
||||
<Menu>
|
||||
<Button
|
||||
@@ -94,6 +99,7 @@ export const SelectToggleDevice = <T extends ToggleSource>({
|
||||
aria-label={selectLabel}
|
||||
groupPosition="right"
|
||||
square
|
||||
variant={trackProps.enabled ? 'primaryDark' : 'error2'}
|
||||
>
|
||||
<RiArrowDownSLine />
|
||||
</Button>
|
||||
@@ -109,6 +115,6 @@ export const SelectToggleDevice = <T extends ToggleSource>({
|
||||
}}
|
||||
/>
|
||||
</Menu>
|
||||
</HStack>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,9 +57,9 @@ export const ToggleDevice = ({
|
||||
|
||||
return (
|
||||
<ToggleButton
|
||||
isSelected={enabled}
|
||||
variant={enabled ? undefined : 'danger'}
|
||||
toggledStyles={false}
|
||||
isSelected={!enabled}
|
||||
variant={enabled ? 'primaryDark' : 'error2'}
|
||||
shySelected
|
||||
onPress={() => toggle()}
|
||||
aria-label={toggleLabel}
|
||||
tooltip={toggleLabel}
|
||||
|
||||
@@ -103,54 +103,77 @@ export function ControlBar({
|
||||
return (
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
gap: '.5rem',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '.75rem',
|
||||
borderTop: '1px solid var(--lk-border-color)',
|
||||
maxHeight: 'var(--lk-control-bar-height)',
|
||||
height: '80px',
|
||||
position: 'absolute',
|
||||
backgroundColor: '#d1d5db',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
display: 'flex',
|
||||
})}
|
||||
>
|
||||
<SelectToggleDevice
|
||||
source={Track.Source.Microphone}
|
||||
onChange={microphoneOnChange}
|
||||
onDeviceError={(error) =>
|
||||
onDeviceError?.({ source: Track.Source.Microphone, error })
|
||||
}
|
||||
onActiveDeviceChange={(deviceId) =>
|
||||
saveAudioInputDeviceId(deviceId ?? '')
|
||||
}
|
||||
/>
|
||||
<SelectToggleDevice
|
||||
source={Track.Source.Camera}
|
||||
onChange={cameraOnChange}
|
||||
onDeviceError={(error) =>
|
||||
onDeviceError?.({ source: Track.Source.Camera, error })
|
||||
}
|
||||
onActiveDeviceChange={(deviceId) =>
|
||||
saveVideoInputDeviceId(deviceId ?? '')
|
||||
}
|
||||
/>
|
||||
{browserSupportsScreenSharing && (
|
||||
<ScreenShareToggle
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
gap: '.5rem',
|
||||
alignItems: 'center',
|
||||
lg: {
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
},
|
||||
})}
|
||||
>
|
||||
<SelectToggleDevice
|
||||
source={Track.Source.Microphone}
|
||||
onChange={microphoneOnChange}
|
||||
onDeviceError={(error) =>
|
||||
onDeviceError?.({ source: Track.Source.ScreenShare, error })
|
||||
onDeviceError?.({ source: Track.Source.Microphone, error })
|
||||
}
|
||||
onActiveDeviceChange={(deviceId) =>
|
||||
saveAudioInputDeviceId(deviceId ?? '')
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<HandToggle />
|
||||
<ChatToggle />
|
||||
<ParticipantsToggle />
|
||||
<OptionsButton />
|
||||
<LeaveButton />
|
||||
<StartMediaButton />
|
||||
<SelectToggleDevice
|
||||
source={Track.Source.Camera}
|
||||
onChange={cameraOnChange}
|
||||
onDeviceError={(error) =>
|
||||
onDeviceError?.({ source: Track.Source.Camera, error })
|
||||
}
|
||||
onActiveDeviceChange={(deviceId) =>
|
||||
saveVideoInputDeviceId(deviceId ?? '')
|
||||
}
|
||||
/>
|
||||
{browserSupportsScreenSharing && (
|
||||
<ScreenShareToggle
|
||||
onDeviceError={(error) =>
|
||||
onDeviceError?.({ source: Track.Source.ScreenShare, error })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<HandToggle />
|
||||
<OptionsButton />
|
||||
<LeaveButton />
|
||||
<StartMediaButton />
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
gap: '.5rem',
|
||||
alignItems: 'center',
|
||||
marginRight: '6.25rem',
|
||||
lg: {
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
},
|
||||
})}
|
||||
>
|
||||
<ChatToggle />
|
||||
<ParticipantsToggle />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -28,11 +28,7 @@ export const FeedbackRoute = () => {
|
||||
<VStack>
|
||||
<Heading>{t('feedback.heading')}</Heading>
|
||||
<HStack>
|
||||
<Button
|
||||
outline
|
||||
variant="primary"
|
||||
onPress={() => window.history.back()}
|
||||
>
|
||||
<Button variant="secondary" onPress={() => window.history.back()}>
|
||||
{t('feedback.back')}
|
||||
</Button>
|
||||
<Button variant="primary" onPress={() => setLocation('/')}>
|
||||
|
||||
@@ -10,7 +10,7 @@ export const SettingsButton = () => {
|
||||
<DialogTrigger>
|
||||
<Button
|
||||
square
|
||||
invisible
|
||||
variant="greyscale"
|
||||
aria-label={t('settingsButtonLabel')}
|
||||
tooltip={t('settingsButtonLabel')}
|
||||
>
|
||||
|
||||
@@ -68,7 +68,7 @@ export const AccountTab = ({ id, onOpenChange }: AccountTabProps) => {
|
||||
marginLeft: 'auto',
|
||||
})}
|
||||
>
|
||||
<Button onPress={handleOnCancel}>
|
||||
<Button variant="secondary" onPress={handleOnCancel}>
|
||||
{t('cancel', { ns: 'global' })}
|
||||
</Button>
|
||||
<Button variant={'primary'} onPress={handleOnSubmit}>
|
||||
|
||||
@@ -66,7 +66,7 @@ export const Header = () => {
|
||||
<Menu>
|
||||
<Button
|
||||
size="sm"
|
||||
invisible
|
||||
variant="greyscale"
|
||||
tooltip={t('loggedInUserTooltip')}
|
||||
tooltipType="delayed"
|
||||
>
|
||||
@@ -83,6 +83,7 @@ export const Header = () => {
|
||||
</span>
|
||||
</Button>
|
||||
<MenuList
|
||||
variant={'light'}
|
||||
items={[{ value: 'logout', label: t('logout') }]}
|
||||
onAction={(value) => {
|
||||
if (value === 'logout') {
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"leaveRoomPrompt": "This will make you leave the meeting.",
|
||||
"shareDialog": {
|
||||
"copy": "Copy the meeting link",
|
||||
"copyButton": "Copy link",
|
||||
"copied": "Link copied to clipboard",
|
||||
"heading": "Your meeting is ready",
|
||||
"description": "Share this link with people you want to invite to the meeting.",
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"leaveRoomPrompt": "Revenir à l'accueil vous fera quitter la réunion.",
|
||||
"shareDialog": {
|
||||
"copy": "Copier le lien de la réunion",
|
||||
"copyButton": "Copier le lien",
|
||||
"copied": "Lien copié dans le presse-papiers",
|
||||
"heading": "Votre réunion est prête",
|
||||
"description": "Partagez ce lien avec les personnes que vous souhaitez inviter à la réunion.",
|
||||
|
||||
@@ -112,6 +112,7 @@ export const Dialog = ({
|
||||
{!isAlert && (
|
||||
<Div position="absolute" top="5" right="5">
|
||||
<Button
|
||||
variant="greyscale"
|
||||
invisible
|
||||
size="xs"
|
||||
onPress={() => close()}
|
||||
|
||||
@@ -51,7 +51,7 @@ export const Form = ({
|
||||
{submitLabel}
|
||||
</Button>
|
||||
{!!onCancel && (
|
||||
<Button variant="primary" outline onPress={() => onCancel()}>
|
||||
<Button variant="secondary" onPress={() => onCancel()}>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { Menu, MenuProps, MenuItem } from 'react-aria-components'
|
||||
import { menuItemRecipe } from './menuItemRecipe'
|
||||
import { menuRecipe } from '@/primitives/menuRecipe.ts'
|
||||
import type { RecipeVariantProps } from '@/styled-system/types'
|
||||
|
||||
/**
|
||||
* render a Button primitive that shows a popover showing a list of pressable items
|
||||
@@ -14,11 +15,15 @@ export const MenuList = <T extends string | number = string>({
|
||||
onAction: (key: T) => void
|
||||
selectedItem?: T
|
||||
items: Array<string | { value: T; label: ReactNode }>
|
||||
} & MenuProps<unknown>) => {
|
||||
} & MenuProps<unknown> &
|
||||
RecipeVariantProps<typeof menuRecipe>) => {
|
||||
const [variantProps] = menuRecipe.splitVariantProps(menuProps)
|
||||
const classes = menuRecipe({ extraPadding: true, ...variantProps })
|
||||
return (
|
||||
<Menu
|
||||
selectionMode={selectedItem !== undefined ? 'single' : undefined}
|
||||
selectedKeys={selectedItem !== undefined ? [selectedItem] : undefined}
|
||||
className={classes.root}
|
||||
{...menuProps}
|
||||
>
|
||||
{items.map((item) => {
|
||||
@@ -26,7 +31,7 @@ export const MenuList = <T extends string | number = string>({
|
||||
const label = typeof item === 'string' ? item : item.label
|
||||
return (
|
||||
<MenuItem
|
||||
className={menuItemRecipe({ extraPadding: true })}
|
||||
className={classes.item}
|
||||
key={value}
|
||||
id={value as string}
|
||||
onAction={() => {
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from 'react-aria-components'
|
||||
import { Box } from './Box'
|
||||
import { StyledPopover } from './Popover'
|
||||
import { menuItemRecipe } from './menuItemRecipe'
|
||||
import { menuRecipe } from '@/primitives/menuRecipe.ts'
|
||||
|
||||
const StyledButton = styled(Button, {
|
||||
base: {
|
||||
@@ -75,7 +75,9 @@ export const Select = <T extends string | number>({
|
||||
<ListBox>
|
||||
{items.map((item) => (
|
||||
<ListBoxItem
|
||||
className={menuItemRecipe({ extraPadding: true })}
|
||||
className={
|
||||
menuRecipe({ extraPadding: true, variant: 'light' }).item
|
||||
}
|
||||
id={item.value}
|
||||
key={item.value}
|
||||
>
|
||||
|
||||
@@ -66,12 +66,8 @@ const StyledTab = styled(RACTab, {
|
||||
color: 'box.text',
|
||||
},
|
||||
'&[data-selected]': {
|
||||
backgroundColor: 'primary',
|
||||
backgroundColor: 'primary.800',
|
||||
color: 'white',
|
||||
'&[data-hovered]': {
|
||||
backgroundColor: 'primary',
|
||||
color: 'white',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -43,7 +43,7 @@ const StyledTooltip = styled(RACTooltip, {
|
||||
base: {
|
||||
boxShadow: '0 8px 20px rgba(0 0 0 / 0.1)',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: 'gray.800',
|
||||
backgroundColor: 'primaryDark.100',
|
||||
color: 'gray.100',
|
||||
forcedColorAdjust: 'none',
|
||||
outline: 'none',
|
||||
|
||||
@@ -12,25 +12,11 @@ export const buttonRecipe = cva({
|
||||
transition: 'background 200ms, outline 200ms, border-color 200ms',
|
||||
cursor: 'pointer',
|
||||
border: '1px solid transparent',
|
||||
color: 'colorPalette.text',
|
||||
backgroundColor: 'colorPalette',
|
||||
'&[data-hovered]': {
|
||||
backgroundColor: 'colorPalette.hover',
|
||||
},
|
||||
'&[data-pressed]': {
|
||||
backgroundColor: 'colorPalette.active',
|
||||
},
|
||||
'&[data-selected]': {
|
||||
backgroundColor: 'colorPalette.active',
|
||||
},
|
||||
'&[data-disabled]': {
|
||||
cursor: 'auto',
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
size: {
|
||||
default: {
|
||||
borderRadius: 8,
|
||||
borderRadius: 4,
|
||||
paddingX: '1',
|
||||
paddingY: '0.625',
|
||||
'--square-padding': '{spacing.0.625}',
|
||||
@@ -53,20 +39,137 @@ export const buttonRecipe = cva({
|
||||
},
|
||||
},
|
||||
variant: {
|
||||
default: {
|
||||
colorPalette: 'control',
|
||||
borderColor: 'control.subtle',
|
||||
},
|
||||
primary: {
|
||||
colorPalette: 'primary',
|
||||
backgroundColor: 'primary.800',
|
||||
color: 'white',
|
||||
'&[data-hovered]': {
|
||||
backgroundColor: 'primary.action',
|
||||
},
|
||||
'&[data-pressed]': {
|
||||
backgroundColor: 'primary.action',
|
||||
},
|
||||
'&[data-disabled]': {
|
||||
opacity: 0.3,
|
||||
backgroundColor: 'greyscale.100',
|
||||
color: 'greyscale.400',
|
||||
},
|
||||
},
|
||||
secondary: {
|
||||
backgroundColor: 'white',
|
||||
color: 'primary.800',
|
||||
borderColor: 'primary.800',
|
||||
'&[data-hovered]': {
|
||||
backgroundColor: 'greyscale.100',
|
||||
},
|
||||
'&[data-pressed]': {
|
||||
backgroundColor: 'greyscale.100',
|
||||
},
|
||||
},
|
||||
secondaryText: {
|
||||
backgroundColor: 'transparent',
|
||||
color: 'primary.800',
|
||||
'&[data-hovered]': {
|
||||
backgroundColor: 'greyscale.100',
|
||||
},
|
||||
'&[data-pressed]': {
|
||||
backgroundColor: 'greyscale.100',
|
||||
},
|
||||
'&[data-disabled]': {
|
||||
color: 'greyscale.400',
|
||||
},
|
||||
},
|
||||
tertiary: {
|
||||
backgroundColor: 'primary.100',
|
||||
color: 'primary.800',
|
||||
'&[data-hovered]': {
|
||||
backgroundColor: 'primary.300',
|
||||
},
|
||||
'&[data-pressed]': {
|
||||
backgroundColor: 'primary.300',
|
||||
},
|
||||
},
|
||||
primaryDark: {
|
||||
backgroundColor: 'primaryDark.100',
|
||||
color: 'white',
|
||||
'&[data-pressed]': {
|
||||
backgroundColor: 'primaryDark.900',
|
||||
color: 'primaryDark.100',
|
||||
},
|
||||
'&[data-hovered]': {
|
||||
backgroundColor: 'primaryDark.300',
|
||||
color: 'white',
|
||||
},
|
||||
'&[data-selected]': {
|
||||
backgroundColor: 'primaryDark.900 !important',
|
||||
color: 'primaryDark.100 !important',
|
||||
},
|
||||
},
|
||||
primaryTextDark: {
|
||||
backgroundColor: 'transparent',
|
||||
color: 'primaryDark.800',
|
||||
'&[data-hovered]': {
|
||||
backgroundColor: 'primaryDark.100',
|
||||
},
|
||||
'&[data-pressed]': {
|
||||
backgroundColor: 'primaryDark.700',
|
||||
color: 'primaryDark.100',
|
||||
},
|
||||
'&[data-selected]': {
|
||||
backgroundColor: 'primaryDark.700',
|
||||
color: 'primaryDark.100',
|
||||
},
|
||||
},
|
||||
greyscale: {
|
||||
backgroundColor: 'transparent',
|
||||
color: 'greyscale.400',
|
||||
'&[data-hovered]': {
|
||||
color: 'greyscale.800',
|
||||
},
|
||||
'&[data-pressed]': {
|
||||
color: 'greyscale.800',
|
||||
},
|
||||
'&[data-selected]': {
|
||||
color: 'greyscale.800',
|
||||
},
|
||||
'&[data-disabled]': {
|
||||
color: 'greyscale.200',
|
||||
},
|
||||
},
|
||||
danger: {
|
||||
backgroundColor: 'error.400',
|
||||
color: 'white',
|
||||
'&[data-hovered]': {
|
||||
backgroundColor: 'error.600',
|
||||
},
|
||||
'&[data-pressed]': {
|
||||
backgroundColor: 'error.700',
|
||||
color: 'error.200',
|
||||
},
|
||||
},
|
||||
error2: {
|
||||
backgroundColor: 'error.200',
|
||||
color: 'error.900',
|
||||
'&[data-hovered]': {
|
||||
backgroundColor: 'error.300',
|
||||
},
|
||||
'&[data-focused]': {
|
||||
backgroundColor: 'error.200',
|
||||
},
|
||||
'&[data-pressed]': {
|
||||
backgroundColor: 'error.900',
|
||||
color: 'error.100',
|
||||
},
|
||||
'&[data-selected]': {
|
||||
backgroundColor: 'error.900 !important',
|
||||
color: 'error.100 !important',
|
||||
},
|
||||
'&[data-disabled]': {
|
||||
backgroundColor: 'error.200',
|
||||
color: 'error.300',
|
||||
},
|
||||
},
|
||||
// @TODO: better handling of colors… this is a mess
|
||||
success: {
|
||||
colorPalette: 'success',
|
||||
borderColor: 'success.300',
|
||||
color: 'success.subtle-text',
|
||||
backgroundColor: 'success.subtle',
|
||||
'&[data-hovered]': {
|
||||
@@ -83,31 +186,6 @@ export const buttonRecipe = cva({
|
||||
color: 'primary !important',
|
||||
},
|
||||
},
|
||||
danger: {
|
||||
colorPalette: 'danger',
|
||||
borderColor: 'danger.600',
|
||||
color: 'danger.subtle-text',
|
||||
backgroundColor: 'danger.subtle',
|
||||
'&[data-hovered]': {
|
||||
backgroundColor: 'danger.200',
|
||||
},
|
||||
'&[data-pressed]': {
|
||||
backgroundColor: 'danger.subtle!',
|
||||
},
|
||||
},
|
||||
},
|
||||
outline: {
|
||||
true: {
|
||||
color: 'colorPalette',
|
||||
backgroundColor: 'transparent!',
|
||||
borderColor: 'currentcolor!',
|
||||
'&[data-hovered]': {
|
||||
backgroundColor: 'colorPalette.subtle!',
|
||||
},
|
||||
'&[data-pressed]': {
|
||||
backgroundColor: 'colorPalette.subtle!',
|
||||
},
|
||||
},
|
||||
},
|
||||
invisible: {
|
||||
true: {
|
||||
@@ -130,6 +208,10 @@ export const buttonRecipe = cva({
|
||||
width: 'full',
|
||||
},
|
||||
},
|
||||
// some toggle buttons make more sense without a "pushed button" style when selected because their content changes to mark the state
|
||||
shySelected: {
|
||||
true: {},
|
||||
},
|
||||
// if the button is next to other ones to make a "button group", tell where the button is to handle radius
|
||||
groupPosition: {
|
||||
left: {
|
||||
@@ -145,40 +227,21 @@ export const buttonRecipe = cva({
|
||||
borderRadius: 0,
|
||||
},
|
||||
},
|
||||
// some toggle buttons make more sense without a "pushed button" style when selected because their content changes to mark the state
|
||||
toggledStyles: {
|
||||
false: {
|
||||
'&[data-selected]': {
|
||||
backgroundColor: 'colorPalette',
|
||||
},
|
||||
},
|
||||
},
|
||||
legacyStyle: {
|
||||
true: {
|
||||
borderColor: 'gray.400',
|
||||
transition: 'border 200ms, background 200ms, color 200ms',
|
||||
'&[data-hovered]': {
|
||||
borderColor: 'gray.500',
|
||||
},
|
||||
'&[data-pressed]': {
|
||||
borderColor: 'gray.500',
|
||||
},
|
||||
'&[data-selected]': {
|
||||
backgroundColor: '#1d4ed8',
|
||||
color: 'white',
|
||||
borderColor: 'gray.500',
|
||||
'&[data-hovered]': {
|
||||
borderColor: '#6b7280',
|
||||
backgroundColor: '#1e40af',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
variant: 'primaryDark',
|
||||
shySelected: true,
|
||||
css: {
|
||||
'&[data-selected]': {
|
||||
backgroundColor: 'primaryDark.100',
|
||||
color: 'white',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
defaultVariants: {
|
||||
size: 'default',
|
||||
variant: 'default',
|
||||
outline: false,
|
||||
toggledStyles: true,
|
||||
variant: 'primary',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import { cva } from '@/styled-system/css'
|
||||
|
||||
/**
|
||||
* reusable styles for a menu item, select item, etc… to be used with panda `css()` or `styled()`
|
||||
*
|
||||
* these are in their own files because react hot refresh doesn't like exporting stuff
|
||||
* that aren't components in component files
|
||||
*/
|
||||
export const menuItemRecipe = cva({
|
||||
base: {
|
||||
paddingY: 0.125,
|
||||
paddingX: 0.5,
|
||||
textAlign: 'left',
|
||||
width: 'full',
|
||||
borderRadius: 4,
|
||||
cursor: 'pointer',
|
||||
color: 'box.text',
|
||||
border: '1px solid transparent',
|
||||
position: 'relative',
|
||||
'&[data-selected]': {
|
||||
'&::before': {
|
||||
content: '"✓"',
|
||||
position: 'absolute',
|
||||
top: '2px',
|
||||
left: '6px',
|
||||
},
|
||||
},
|
||||
'&[data-focused]': {
|
||||
color: 'primary.text',
|
||||
backgroundColor: 'primary',
|
||||
outline: 'none!',
|
||||
},
|
||||
'&[data-hovered]': {
|
||||
color: 'primary.text',
|
||||
backgroundColor: 'primary',
|
||||
outline: 'none!',
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
icon: {
|
||||
true: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '1rem',
|
||||
paddingY: '0.4rem',
|
||||
},
|
||||
},
|
||||
extraPadding: {
|
||||
true: {
|
||||
paddingLeft: 1.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
72
src/frontend/src/primitives/menuRecipe.ts
Normal file
72
src/frontend/src/primitives/menuRecipe.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { sva } from '@/styled-system/css'
|
||||
|
||||
export const menuRecipe = sva({
|
||||
slots: ['root', 'item'],
|
||||
base: {
|
||||
root: {},
|
||||
item: {
|
||||
paddingY: 0.125,
|
||||
paddingX: 0.5,
|
||||
textAlign: 'left',
|
||||
width: 'full',
|
||||
borderRadius: 4,
|
||||
cursor: 'pointer',
|
||||
color: 'box.text',
|
||||
border: '1px solid transparent',
|
||||
position: 'relative',
|
||||
'&[data-selected]': {
|
||||
'&::before': {
|
||||
content: '"✓"',
|
||||
position: 'absolute',
|
||||
top: '2px',
|
||||
left: '6px',
|
||||
},
|
||||
},
|
||||
'&[data-focused]': {
|
||||
color: 'primary.text',
|
||||
backgroundColor: 'primaryDark.100',
|
||||
outline: 'none!',
|
||||
},
|
||||
'&[data-hovered]': {
|
||||
color: 'primary.text',
|
||||
backgroundColor: 'primaryDark.100',
|
||||
outline: 'none!',
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
variant: {
|
||||
light: {
|
||||
item: {
|
||||
'&[data-focused]': {
|
||||
backgroundColor: 'primary.800',
|
||||
},
|
||||
'&[data-hovered]': {
|
||||
backgroundColor: 'primary.800',
|
||||
},
|
||||
},
|
||||
},
|
||||
dark: {},
|
||||
},
|
||||
extraPadding: {
|
||||
true: {
|
||||
item: {
|
||||
paddingLeft: 1.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
icon: {
|
||||
true: {
|
||||
item: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '1rem',
|
||||
paddingY: '0.4rem',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'dark',
|
||||
},
|
||||
})
|
||||
6059
src/frontend/yarn.lock
Normal file
6059
src/frontend/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user