Merge pull request #245 from numerique-gouv/feat/video-conference-ui

Enhance Conference UI
This commit is contained in:
NathanVss
2024-11-28 11:49:00 +01:00
committed by GitHub
39 changed files with 6461 additions and 246 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -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}' },

View File

@@ -27,7 +27,7 @@ export const SoundTester = () => {
return (
<>
<Button
invisible
variant="secondaryText"
onPress={() => {
audioRef?.current?.play()
setIsPlaying(true)

View File

@@ -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)}

View File

@@ -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
}}

View File

@@ -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 && (

View File

@@ -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>

View File

@@ -43,7 +43,7 @@ const ratingButtonRecipe = cva({
variants: {
selected: {
true: {
backgroundColor: '#1d4ed8',
backgroundColor: 'primary.800',
color: 'white',
},
false: {

View File

@@ -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}

View File

@@ -68,6 +68,7 @@ const StyledSidePanel = ({
>
<Button
invisible
variant="greyscale"
size="xs"
onPress={onClose}
aria-label={closeButtonTooltip}

View File

@@ -106,7 +106,7 @@ export const ChatInput = ({
/>
<Button
square
invisible
variant="secondaryText"
size="sm"
onPress={handleSubmit}
isDisabled={isDisabled}

View File

@@ -23,7 +23,7 @@ export const ChatToggle = () => {
>
<ToggleButton
square
legacyStyle
variant="primaryTextDark"
aria-label={t(tooltipLabel)}
tooltip={t(tooltipLabel)}
isSelected={isChatOpen}

View File

@@ -24,7 +24,7 @@ export const HandToggle = () => {
>
<ToggleButton
square
legacyStyle
variant="primaryDark"
aria-label={t(tooltipLabel)}
tooltip={t(tooltipLabel)}
isSelected={isHandRaised}

View File

@@ -16,7 +16,7 @@ export const OptionsButton = () => {
<Menu>
<Button
square
legacyStyle
variant="primaryDark"
aria-label={t('options.buttonLabel')}
tooltip={t('options.buttonLabel')}
>

View File

@@ -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} />

View File

@@ -69,7 +69,7 @@ export const HandRaisedListItem = ({
</HStack>
<Button
square
invisible
variant="greyscale"
size="sm"
onPress={() => lowerHandParticipant(participant)}
tooltip={t('participants.lowerParticipantHand', { name })}

View File

@@ -63,7 +63,7 @@ const MicIndicator = ({ participant }: MicIndicatorProps) => {
<>
<Button
square
invisible
variant="greyscale"
size="sm"
tooltip={
isLocal(participant)

View File

@@ -29,7 +29,7 @@ export const ParticipantsToggle = () => {
>
<ToggleButton
square
legacyStyle
variant="primaryTextDark"
aria-label={t(tooltipLabel)}
tooltip={t(tooltipLabel)}
isSelected={isParticipantsOpen}

View File

@@ -26,7 +26,7 @@ export const ScreenShareToggle = (
<ToggleButton
isSelected={enabled}
square
legacyStyle
variant="primaryDark"
tooltip={t(tooltipLabel)}
onPress={(e) =>
buttonProps.onClick?.(

View File

@@ -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>
)
}

View File

@@ -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}

View File

@@ -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>
)
}

View File

@@ -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('/')}>

View File

@@ -10,7 +10,7 @@ export const SettingsButton = () => {
<DialogTrigger>
<Button
square
invisible
variant="greyscale"
aria-label={t('settingsButtonLabel')}
tooltip={t('settingsButtonLabel')}
>

View File

@@ -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}>

View File

@@ -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') {

View File

@@ -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.",

View File

@@ -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.",

View File

@@ -112,6 +112,7 @@ export const Dialog = ({
{!isAlert && (
<Div position="absolute" top="5" right="5">
<Button
variant="greyscale"
invisible
size="xs"
onPress={() => close()}

View File

@@ -51,7 +51,7 @@ export const Form = ({
{submitLabel}
</Button>
{!!onCancel && (
<Button variant="primary" outline onPress={() => onCancel()}>
<Button variant="secondary" onPress={() => onCancel()}>
{t('cancel')}
</Button>
)}

View File

@@ -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={() => {

View File

@@ -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}
>

View File

@@ -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',
},
},
},
},

View File

@@ -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',

View File

@@ -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',
},
})

View File

@@ -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,
},
},
},
})

View 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

File diff suppressed because it is too large Load Diff