Compare commits

..

15 Commits

Author SHA1 Message Date
lebaudantoine 076d84a5bc 📱(frontend) enhance control bar responsiveness for better UX
Implemented collapsible advanced options to maintain usability on narrow screens
following GMeet's UX pattern. Dialog and popover components were chosen
based on GMeet choices, though this introduces potential accessibility concerns
that should be addressed in future iterations.

Current implementation uses JS for breakpoint handling due to challenges with
Panda CSS's pure CSS approach. This workaround was necessary to resolve a
persistent issue where the popover remained open after window expansion beyond
750px, even after the lateral menu trigger was removed from view.

Technical debt note: Code needs refinement, particularly around breakpoint
management and component architecture. Prioritized shipping over perfection to
meet immediate responsive design needs.
2025-01-27 22:51:42 +01:00
Nathan Vasse 96c18fc627 (front) add switch camera on mobile
We want to have a more simple control bar and give the user to switch
its back / front camera from the responsive menu.
2025-01-21 15:37:59 +01:00
Nathan Vasse c380ff5e1d (front) option to hide menu on SelectToggleDevice
On mobile we want to be able to hide the chevron and menu.
2025-01-21 15:37:59 +01:00
lebaudantoine 6587863afb 💄(frontend) use dark theme for menus in the videoconference
Inspired by GMeet. Actually, these menus are horrible to work with.
This is clearly some technical debt. I fixed the styles, but not the
code, we should refactor them to make easy to chose between two
variant, a light and a dark one.
2025-01-21 11:53:12 +01:00
lebaudantoine a06daad33d 💄(frontend) fix screen-share control alignment
Oopsie, due to its (too) big icon, the height of this control was 4px bigger
than other buttons. Fixed that minor issue.
2025-01-21 11:53:12 +01:00
renovate[bot] 10c3c67d53 ⬆️(dependencies) update js dependencies 2025-01-21 11:20:29 +01:00
lebaudantoine fa85ef5690 📌(frontend) block upgrade to react v19
Many dependencies are not supported react v19 yet.
Avoid renovate to bump react to its latest major version.
2025-01-21 11:20:29 +01:00
lebaudantoine cc676cf71b ⬆️(frontend) bump react-aria-related dependencies
No major / breaking change in these few releases.
Quickly tested all features, everything seems alright
2025-01-20 19:31:55 +01:00
lebaudantoine 90df8fe5b4 ⬆️(frontend) bump react-query-related dependencies
No major / breaking change in these few releases.
Quickly tested all features, everything seems alright
2025-01-20 19:31:55 +01:00
lebaudantoine 90cfcd31c8 ⬆️(frontend) bump LiveKit-related dependencies
No major / breaking change in these few releases.
Quickly tested all features, everything seems alright
2025-01-20 19:31:55 +01:00
renovate[bot] 0ed3416ce2 ⬆️(dependencies) update python dependencies 2025-01-20 09:35:06 +01:00
lebaudantoine e47401246f ✏️(frontend) fix feedback-s
Feedback is a mass noun.
Spotted by @bvhme
2025-01-18 22:11:35 +01:00
lebaudantoine a518179efb ⬆️(frontend) bump vite to v6.0.7
Updates build configuration to support new major version.
This might introduce some breaking changes.

I've read their migration guide, everything seems okay.
2025-01-17 18:23:48 +01:00
lebaudantoine f17471dae5 🚨(frontend) resolve key prop warning in ParticipantListItem
Key prop was incorrectly passed down as a regular prop to ParticipantListItem
instead of being used at the array mapping level. Key is a special React prop
for list rendering and cannot be accessed as a component prop.
2025-01-16 18:04:20 +01:00
lebaudantoine 36ea44befc 🚨(frontend) update deprecated react-aria Section components
Section was deprecated in Novembre 2024 release, It has been replaced
by specific components for each parent collection component,
e.g. MenuSection.
2025-01-16 18:04:20 +01:00
25 changed files with 2507 additions and 1869 deletions
+1 -1
View File
@@ -14,7 +14,7 @@
"groupName": "ignored js dependencies",
"matchManagers": ["npm"],
"matchPackageNames": [
"eslint"
"eslint", "react", "react-dom", "@types/react-dom", "@types/react", "react-i18next"
]
}
]
+6 -6
View File
@@ -25,7 +25,7 @@ license = { file = "LICENSE" }
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"boto3==1.35.97",
"boto3==1.36.2",
"Brotli==1.1.0",
"brevo-python==1.1.2",
"celery[redis]==5.4.0",
@@ -48,11 +48,11 @@ dependencies = [
"june-analytics-python==2.3.0",
"markdown==3.7",
"nested-multipart-parser==1.5.0",
"psycopg[binary]==3.2.3",
"psycopg[binary]==3.2.4",
"PyJWT==2.10.1",
"python-frontmatter==1.1.0",
"requests==2.32.3",
"sentry-sdk==2.19.2",
"sentry-sdk==2.20.0",
"url-normalize==1.4.3",
"WeasyPrint>=60.2",
"whitenoise==6.8.2",
@@ -74,7 +74,7 @@ dev = [
"freezegun==1.5.1",
"ipdb==0.13.13",
"ipython==8.31.0",
"pyfakefs==5.7.3",
"pyfakefs==5.7.4",
"pylint-django==2.6.1",
"pylint==3.3.3",
"pytest-cov==6.0.0",
@@ -82,8 +82,8 @@ dev = [
"pytest==8.3.4",
"pytest-icdiff==0.9",
"pytest-xdist==3.6.1",
"responses==0.25.5",
"ruff==0.9.1",
"responses==0.25.6",
"ruff==0.9.2",
"types-requests==2.32.0.20241016",
]
+2139 -1760
View File
File diff suppressed because it is too large Load Diff
+28 -28
View File
@@ -13,23 +13,23 @@
"check": "prettier --check ./src"
},
"dependencies": {
"@livekit/components-react": "2.6.9",
"@livekit/components-react": "2.8.0",
"@livekit/components-styles": "1.1.4",
"@livekit/track-processors": "0.3.2",
"@pandacss/preset-panda": "0.48.0",
"@react-aria/toast": "3.0.0-beta.18",
"@remixicon/react": "4.5.0",
"@tanstack/react-query": "5.61.3",
"@livekit/track-processors": "0.3.3",
"@pandacss/preset-panda": "0.51.1",
"@react-aria/toast": "3.0.0-beta.19",
"@remixicon/react": "4.6.0",
"@tanstack/react-query": "5.64.2",
"crisp-sdk-web": "1.0.25",
"hoofd": "1.7.1",
"i18next": "24.0.2",
"i18next-browser-languagedetector": "8.0.0",
"i18next-parser": "9.0.2",
"hoofd": "1.7.3",
"i18next": "24.2.1",
"i18next-browser-languagedetector": "8.0.2",
"i18next-parser": "9.1.0",
"i18next-resources-to-backend": "1.2.1",
"livekit-client": "2.6.3",
"posthog-js": "1.188.0",
"livekit-client": "2.8.1",
"posthog-js": "1.207.0",
"react": "18.3.1",
"react-aria-components": "1.5.0",
"react-aria-components": "1.6.0",
"react-dom": "18.3.1",
"react-i18next": "15.1.1",
"use-sound": "4.0.3",
@@ -37,24 +37,24 @@
"wouter": "3.3.5"
},
"devDependencies": {
"@pandacss/dev": "0.48.0",
"@tanstack/eslint-plugin-query": "5.61.3",
"@tanstack/react-query-devtools": "5.61.3",
"@types/node": "22.9.3",
"@pandacss/dev": "0.51.1",
"@tanstack/eslint-plugin-query": "5.64.2",
"@tanstack/react-query-devtools": "5.64.2",
"@types/node": "22.10.7",
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
"@typescript-eslint/eslint-plugin": "8.15.0",
"@typescript-eslint/parser": "8.15.0",
"@vitejs/plugin-react": "4.3.3",
"@typescript-eslint/eslint-plugin": "8.21.0",
"@typescript-eslint/parser": "8.21.0",
"@vitejs/plugin-react": "4.3.4",
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-config-prettier": "10.0.1",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-react-hooks": "5.0.0",
"eslint-plugin-react-refresh": "0.4.14",
"postcss": "8.4.49",
"prettier": "3.3.3",
"typescript": "5.7.2",
"vite": "5.4.11",
"vite-tsconfig-paths": "5.1.3"
"eslint-plugin-react-hooks": "5.1.0",
"eslint-plugin-react-refresh": "0.4.18",
"postcss": "8.5.1",
"prettier": "3.4.2",
"typescript": "5.7.3",
"vite": "6.0.10",
"vite-tsconfig-paths": "5.1.4"
}
}
@@ -0,0 +1,123 @@
import { Button } from '@/primitives'
import { useMediaDeviceSelect } from '@livekit/components-react'
import { RiCameraSwitchLine } from '@remixicon/react'
import { useEffect, useState } from 'react'
import { ButtonProps } from 'react-aria-components'
import { useTranslation } from 'react-i18next'
enum FacingMode {
USER = 'user',
ENVIRONMENT = 'environment',
}
export const CameraSwitchButton = (props: Partial<ButtonProps>) => {
const { t } = useTranslation('rooms')
const { devices, activeDeviceId, setActiveMediaDevice } =
useMediaDeviceSelect({
kind: 'videoinput',
requestPermissions: true,
})
// getCapabilities type is not available.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const getDeviceFacingMode = (device: any): string[] => {
if (!device.getCapabilities) {
return []
}
const capabilities = device.getCapabilities()
if (!capabilities) {
return []
}
if (typeof capabilities.facingMode !== 'object') {
return []
}
return capabilities.facingMode
}
const detectCurrentFacingMode = (): FacingMode | null => {
if (!activeDeviceId) {
return null
}
const activeDevice = devices.find(
(device) => device.deviceId === activeDeviceId
)
if (!activeDevice) {
return null
}
const facingMode = getDeviceFacingMode(activeDevice)
if (facingMode.indexOf(FacingMode.USER) >= 0) {
return FacingMode.USER
}
if (facingMode.indexOf(FacingMode.ENVIRONMENT) >= 0) {
return FacingMode.ENVIRONMENT
}
return null
}
const guessCurrentFacingMode = () => {
const facingMode = detectCurrentFacingMode()
if (facingMode) {
return facingMode
}
// We consider by default if we have no clue that the user camera is used.
return FacingMode.USER
}
const [facingMode, setFacingMode] = useState<FacingMode | null>()
/**
* Before setting the initial value of facingMode we need to wait for devices to
* be loaded ( because in detectCurrentFacingMode we need to find the active device
* in the devices list ), which is not the case at first render.
*/
useEffect(() => {
if (devices.length == 0) {
return
}
if (facingMode) {
return
}
setFacingMode(guessCurrentFacingMode())
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [devices])
const getUserDevice = (
facingMode: FacingMode
): MediaDeviceInfo | undefined => {
return devices.find((device) => {
return getDeviceFacingMode(device).indexOf(facingMode) >= 0
})
}
const toggle = () => {
let target: FacingMode
if (facingMode === FacingMode.USER) {
target = FacingMode.ENVIRONMENT
} else {
target = FacingMode.USER
}
const device = getUserDevice(target)
if (device) {
setActiveMediaDevice(device.deviceId)
setFacingMode(target)
} else {
console.error('Cannot get user device with facingMode ' + target)
}
}
return (
<Button
onPress={(e) => {
toggle()
props.onPress?.(e)
}}
variant="primaryTextDark"
aria-label={t('options.items.switchCamera')}
tooltip={t('options.items.switchCamera')}
description={true}
>
<RiCameraSwitchLine size={20} />
</Button>
)
}
@@ -8,7 +8,7 @@ export const OptionsButton = () => {
return (
<>
<Menu>
<Menu variant="dark">
<Button
square
variant="primaryDark"
@@ -26,7 +26,7 @@ export const OptionsMenuItems = () => {
<MenuSection>
<MenuItem
onAction={() => toggleEffects()}
className={menuRecipe({ icon: true }).item}
className={menuRecipe({ icon: true, variant: 'dark' }).item}
>
<RiAccountBoxLine size={20} />
{t('effects')}
@@ -37,13 +37,13 @@ export const OptionsMenuItems = () => {
<MenuItem
href={GRIST_FORM}
target="_blank"
className={menuRecipe({ icon: true }).item}
className={menuRecipe({ icon: true, variant: 'dark' }).item}
>
<RiMegaphoneLine size={20} />
{t('feedbacks')}
{t('feedback')}
</MenuItem>
<MenuItem
className={menuRecipe({ icon: true }).item}
className={menuRecipe({ icon: true, variant: 'dark' }).item}
onAction={() => setDialogOpen(true)}
>
<RiSettings3Line size={20} />
@@ -46,9 +46,9 @@ export const ScreenShareToggle = ({
{...props}
>
<Div position="relative">
<RiRectangleLine size={28} />
<RiRectangleLine size={24} />
<Icon
size={16}
size={14}
style={{
position: 'absolute',
top: '50%',
@@ -67,10 +67,14 @@ type SelectToggleDeviceProps<T extends ToggleSource> =
UseTrackToggleProps<T> & {
onActiveDeviceChange: (deviceId: string) => void
source: SelectToggleSource
hideMenu?: boolean
variant?: 'dark' | 'light'
}
export const SelectToggleDevice = <T extends ToggleSource>({
onActiveDeviceChange,
hideMenu,
variant = 'light',
...props
}: SelectToggleDeviceProps<T>) => {
const config = selectToggleDeviceConfig[props.source]
@@ -92,29 +96,42 @@ export const SelectToggleDevice = <T extends ToggleSource>({
gap: '1px',
})}
>
<ToggleDevice {...trackProps} config={config} />
<Menu>
<Button
tooltip={selectLabel}
aria-label={selectLabel}
groupPosition="right"
square
variant={trackProps.enabled ? 'primaryDark' : 'error2'}
>
<RiArrowDownSLine />
</Button>
<MenuList
items={devices.map((d) => ({
value: d.deviceId,
label: d.label,
}))}
selectedItem={activeDeviceId}
onAction={(value) => {
setActiveMediaDevice(value as string)
onActiveDeviceChange(value as string)
}}
/>
</Menu>
<ToggleDevice
{...trackProps}
config={config}
toggleButtonProps={{
...(hideMenu
? {
groupPosition: undefined,
}
: {}),
}}
/>
{!hideMenu && (
<Menu variant={variant}>
<Button
tooltip={selectLabel}
aria-label={selectLabel}
groupPosition="right"
square
variant={trackProps.enabled ? 'primaryDark' : 'error2'}
>
<RiArrowDownSLine />
</Button>
<MenuList
items={devices.map((d) => ({
value: d.deviceId,
label: d.label,
}))}
selectedItem={activeDeviceId}
onAction={(value) => {
setActiveMediaDevice(value as string)
onActiveDeviceChange(value as string)
}}
variant={variant}
/>
</Menu>
)}
</div>
)
}
@@ -7,17 +7,20 @@ import { SelectToggleDeviceConfig } from './SelectToggleDevice'
import useLongPress from '@/features/shortcuts/useLongPress'
import { ActiveSpeaker } from '@/features/rooms/components/ActiveSpeaker'
import { useIsSpeaking, useLocalParticipant } from '@livekit/components-react'
import { ToggleButtonProps } from '@/primitives/ToggleButton'
export type ToggleDeviceProps = {
enabled: boolean
toggle: () => void
config: SelectToggleDeviceConfig
toggleButtonProps?: Partial<ToggleButtonProps>
}
export const ToggleDevice = ({
config,
enabled,
toggle,
toggleButtonProps,
}: ToggleDeviceProps) => {
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
@@ -64,6 +67,7 @@ export const ToggleDevice = ({
aria-label={toggleLabel}
tooltip={toggleLabel}
groupPosition="left"
{...toggleButtonProps}
>
<Icon />
</ToggleButton>
@@ -7,7 +7,7 @@ import { css } from '@/styled-system/css'
import { ToggleButtonProps } from '@/primitives/ToggleButton'
export const TranscriptToggle = ({
variant = 'primaryDark',
variant = 'primaryTextDark',
onPress,
...props
}: ToggleButtonProps) => {
@@ -8,10 +8,8 @@ import { HandToggle } from '../../components/controls/HandToggle'
import { ScreenShareToggle } from '../../components/controls/ScreenShareToggle'
import { OptionsButton } from '../../components/controls/Options/OptionsButton'
import { StartMediaButton } from '../../components/controls/StartMediaButton'
import { ChatToggle } from '../../components/controls/ChatToggle'
import { ParticipantsToggle } from '../../components/controls/Participants/ParticipantsToggle'
import { SupportToggle } from '../../components/controls/SupportToggle'
import { TranscriptToggle } from '../../components/controls/TranscriptToggle'
import { MoreOptions } from './MoreOptions'
import { useRef } from 'react'
export function DesktopControlBar({
onDeviceError,
@@ -21,9 +19,11 @@ export function DesktopControlBar({
saveVideoInputDeviceId,
}: ControlBarAuxProps) {
const browserSupportsScreenSharing = supportsScreenSharing()
const desktopControlBarEl = useRef<HTMLDivElement>(null)
return (
<>
<div
ref={desktopControlBarEl}
className={css({
width: '100vw',
display: 'flex',
@@ -62,6 +62,7 @@ export function DesktopControlBar({
onActiveDeviceChange={(deviceId) =>
saveAudioInputDeviceId(deviceId ?? '')
}
variant="dark"
/>
<SelectToggleDevice
source={Track.Source.Camera}
@@ -72,6 +73,7 @@ export function DesktopControlBar({
onActiveDeviceChange={(deviceId) =>
saveVideoInputDeviceId(deviceId ?? '')
}
variant="dark"
/>
{browserSupportsScreenSharing && (
<ScreenShareToggle
@@ -85,21 +87,7 @@ export function DesktopControlBar({
<LeaveButton />
<StartMediaButton />
</div>
<div
className={css({
display: 'flex',
justifyContent: 'flex-end',
flex: '1 1 33%',
alignItems: 'center',
gap: '0.5rem',
paddingRight: '0.25rem',
})}
>
<ChatToggle />
<ParticipantsToggle />
<TranscriptToggle />
<SupportToggle />
</div>
<MoreOptions parentElement={desktopControlBarEl} />
</div>
</>
)
@@ -23,6 +23,7 @@ import { LinkButton } from '@/primitives'
import { useSettingsDialog } from '../../components/controls/SettingsDialogContext'
import { ResponsiveMenu } from './ResponsiveMenu'
import { TranscriptToggle } from '../../components/controls/TranscriptToggle'
import { CameraSwitchButton } from '../../components/controls/CameraSwitchButton'
export function MobileControlBar({
onDeviceError,
@@ -55,7 +56,7 @@ export function MobileControlBar({
className={css({
display: 'flex',
justifyContent: 'space-between',
width: '422px',
width: '330px',
})}
>
<LeaveButton />
@@ -68,6 +69,7 @@ export function MobileControlBar({
onActiveDeviceChange={(deviceId) =>
saveAudioInputDeviceId(deviceId ?? '')
}
hideMenu={true}
/>
<SelectToggleDevice
source={Track.Source.Camera}
@@ -78,6 +80,7 @@ export function MobileControlBar({
onActiveDeviceChange={(deviceId) =>
saveVideoInputDeviceId(deviceId ?? '')
}
hideMenu={true}
/>
<HandToggle />
<Button
@@ -154,8 +157,8 @@ export function MobileControlBar({
<LinkButton
href="https://grist.incubateur.net/o/docs/forms/1YrfNP1QSSy8p2gCxMFnSf/4"
variant="primaryTextDark"
tooltip={t('options.items.feedbacks')}
aria-label={t('options.items.feedbacks')}
tooltip={t('options.items.feedback')}
aria-label={t('options.items.feedback')}
description={true}
target="_blank"
onPress={() => setIsMenuOpened(false)}
@@ -174,6 +177,7 @@ export function MobileControlBar({
>
<RiSettings3Line size={20} />
</Button>
<CameraSwitchButton onPress={() => setIsMenuOpened(false)} />
</div>
</div>
</ResponsiveMenu>
@@ -0,0 +1,87 @@
import { css } from '@/styled-system/css'
import { ChatToggle } from '../../components/controls/ChatToggle'
import { ParticipantsToggle } from '../../components/controls/Participants/ParticipantsToggle'
import { SupportToggle } from '../../components/controls/SupportToggle'
import { TranscriptToggle } from '../../components/controls/TranscriptToggle'
import { useSize } from '../../hooks/useResizeObserver'
import { useState, RefObject } from 'react'
import { Dialog, DialogTrigger, Popover } from 'react-aria-components'
import { Button } from '@/primitives'
import { ToggleButtonProps } from '@/primitives/ToggleButton'
import { RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
const CONTROL_BAR_BREAKPOINT = 750
const NavigationControls = ({ onPress }: Partial<ToggleButtonProps>) => (
<>
<ChatToggle onPress={onPress} />
<ParticipantsToggle onPress={onPress} />
<TranscriptToggle onPress={onPress} />
<SupportToggle onPress={onPress} />
</>
)
export const LateralMenu = () => {
const { t } = useTranslation('rooms')
const [isOpen, setIsOpen] = useState(false)
const handlePress = () => setIsOpen(!isOpen)
const handleClose = () => setIsOpen(false)
return (
<DialogTrigger isOpen={isOpen} onOpenChange={setIsOpen}>
<Button
square
variant="secondaryDark"
aria-label={t('controls.moreOptions')}
tooltip={t('controls.moreOptions')}
onPress={handlePress}
>
{isOpen ? <RiArrowDownSLine /> : <RiArrowUpSLine />}
</Button>
<Popover>
<Dialog
className={css({
width: '65px',
backgroundColor: 'primaryDark.50',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
borderRadius: '4px',
paddingTop: '10px',
gap: '0.5rem',
})}
>
<NavigationControls onPress={handleClose} />
</Dialog>
</Popover>
</DialogTrigger>
)
}
export const MoreOptions = ({
parentElement,
}: {
parentElement: RefObject<HTMLDivElement>
}) => {
const { width: parentWidth } = useSize(parentElement)
return (
<div
className={css({
display: 'flex',
justifyContent: 'flex-end',
flex: '1 1 33%',
alignItems: 'center',
gap: '0.5rem',
paddingRight: '0.25rem',
})}
>
{parentWidth > CONTROL_BAR_BREAKPOINT ? (
<NavigationControls />
) : (
<LateralMenu />
)}
</div>
)
}
+5 -3
View File
@@ -75,15 +75,17 @@
"open": "",
"closed": ""
},
"support": ""
"support": "",
"moreOptions": ""
},
"options": {
"buttonLabel": "",
"items": {
"feedbacks": "",
"feedback": "",
"settings": "",
"username": "",
"effects": ""
"effects": "",
"switchCamera": ""
}
},
"effects": {
+5 -3
View File
@@ -74,15 +74,17 @@
"open": "Hide AI assistant",
"closed": "Show AI assistant"
},
"support": "Support"
"support": "Support",
"moreOptions": "More options"
},
"options": {
"buttonLabel": "More Options",
"items": {
"feedbacks": "Give us feedbacks",
"feedback": "Give us feedback",
"settings": "Settings",
"username": "Update Your Name",
"effects": "Apply effects"
"effects": "Apply effects",
"switchCamera": "Switch camera"
}
},
"effects": {
+5 -3
View File
@@ -74,15 +74,17 @@
"open": "Masquer l'assistant IA",
"closed": "Afficher l'assistant IA"
},
"support": "Support"
"support": "Support",
"moreOptions": "Plus d'options"
},
"options": {
"buttonLabel": "Plus d'options",
"items": {
"feedbacks": "Partager votre avis",
"feedback": "Partager votre avis",
"settings": "Paramètres",
"username": "Choisir votre nom",
"effects": "Appliquer des effets"
"effects": "Appliquer des effets",
"switchCamera": "Changer de caméra"
}
},
"effects": {
+6 -2
View File
@@ -30,7 +30,7 @@ const box = cva({
},
},
variant: {
default: {
light: {
borderWidth: '1px',
borderStyle: 'solid',
borderColor: 'box.border',
@@ -46,6 +46,10 @@ const box = cva({
backgroundColor: 'box.bg',
color: 'control.text',
},
dark: {
backgroundColor: 'primaryDark.50',
borderColord: 'primaryDark.50',
},
},
size: {
default: {
@@ -57,7 +61,7 @@ const box = cva({
},
},
defaultVariants: {
variant: 'default',
variant: 'light',
size: 'default',
},
})
+3 -1
View File
@@ -8,15 +8,17 @@ import { Box } from './Box'
*/
export const Menu = ({
children,
variant = 'light',
}: {
children: [trigger: ReactNode, menu: ReactNode]
variant?: 'dark' | 'light'
}) => {
const [trigger, menu] = children
return (
<MenuTrigger>
{trigger}
<StyledPopover>
<Box size="sm" type="popover">
<Box size="sm" type="popover" variant={variant}>
{menu}
</Box>
</StyledPopover>
+6 -1
View File
@@ -10,6 +10,7 @@ export const MenuList = <T extends string | number = string>({
onAction,
selectedItem,
items = [],
variant = 'light',
...menuProps
}: {
onAction: (key: T) => void
@@ -18,7 +19,11 @@ export const MenuList = <T extends string | number = string>({
} & MenuProps<unknown> &
RecipeVariantProps<typeof menuRecipe>) => {
const [variantProps] = menuRecipe.splitVariantProps(menuProps)
const classes = menuRecipe({ extraPadding: true, ...variantProps })
const classes = menuRecipe({
extraPadding: true,
variant: variant,
...variantProps,
})
return (
<Menu
selectionMode={selectedItem !== undefined ? 'single' : undefined}
+1 -1
View File
@@ -4,7 +4,7 @@ import { Separator as RACSeparator } from 'react-aria-components'
export const Separator = styled(RACSeparator, {
base: {
height: '1px',
background: 'gray.400',
background: 'primaryDark.200',
margin: '4px 0',
},
})
@@ -109,6 +109,21 @@ export const buttonRecipe = cva({
color: 'primaryDark.100 !important',
},
},
secondaryDark: {
backgroundColor: 'primaryDark.50',
color: 'white',
'&[data-pressed]': {
backgroundColor: 'primaryDark.200',
},
'&[data-hovered]': {
backgroundColor: 'primaryDark.100',
color: 'white',
},
'&[data-selected]': {
backgroundColor: 'primaryDark.700 !important',
color: 'primaryDark.100 !important',
},
},
primaryTextDark: {
backgroundColor: 'transparent',
color: 'white',
+6 -2
View File
@@ -46,7 +46,11 @@ export const menuRecipe = sva({
},
},
},
dark: {},
dark: {
item: {
color: 'white',
},
},
},
extraPadding: {
true: {
@@ -67,6 +71,6 @@ export const menuRecipe = sva({
},
},
defaultVariants: {
variant: 'dark',
variant: 'light',
},
})
+1 -1
View File
@@ -14,7 +14,7 @@
"build": "npm run build-mjml-to-html && npm run build-html-to-plain-text"
},
"volta": {
"node": "22.11.0"
"node": "22.13.0"
},
"repository": "https://github.com/numerique-gouv/meet",
"author": "DINUM",
+4 -4
View File
@@ -9,15 +9,15 @@ dependencies = [
"pydantic-settings>=2.1.0",
"celery==5.4.0",
"redis==5.2.1",
"minio==7.2.14",
"openai==1.59.7",
"minio==7.2.15",
"openai==1.59.8",
"requests==2.32.3",
"sentry-sdk[fastapi, celery]==2.19.2",
"sentry-sdk[fastapi, celery]==2.20.0",
]
[project.optional-dependencies]
dev = [
"ruff==0.9.1",
"ruff==0.9.2",
]
[build-system]