Sergei Garin 2024-07-03 21:20:17 +03:00 committed by GitHub
parent ee39fd7f53
commit 3de873f97c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 84 additions and 91 deletions

View File

@ -74,9 +74,11 @@ export const BUTTON_STYLES = twv.tv({
'group', 'group',
// we need to set the height to max-content to prevent the button from growing in flex containers // we need to set the height to max-content to prevent the button from growing in flex containers
'h-[max-content]', 'h-[max-content]',
// basic outline
'outline-offset-[1px] outline-transparent',
// buttons always have borders // buttons always have borders
// so keep them in mind when setting paddings // so keep them in mind when setting paddings
'border border-transparent', 'border-0.5 border-transparent',
// button reset styles // button reset styles
'whitespace-nowrap cursor-pointer select-none appearance-none', 'whitespace-nowrap cursor-pointer select-none appearance-none',
// Align the content by the center // Align the content by the center
@ -87,7 +89,7 @@ export const BUTTON_STYLES = twv.tv({
variants: { variants: {
isDisabled: { true: 'disabled:opacity-50 disabled:cursor-not-allowed' }, isDisabled: { true: 'disabled:opacity-50 disabled:cursor-not-allowed' },
isFocused: { isFocused: {
true: 'focus:outline-none focus-visible:outline focus-visible:outline-primary focus-visible:outline-offset-2', true: 'focus:outline-none focus-visible:outline-2 focus-visible:outline-black focus-visible:outline-offset-[-2px]',
}, },
isActive: { isActive: {
none: '', none: '',
@ -107,10 +109,10 @@ export const BUTTON_STYLES = twv.tv({
variant: 'body', variant: 'body',
color: 'custom', color: 'custom',
weight: 'semibold', weight: 'semibold',
className: 'flex px-[11px] py-[5px]', className: 'flex px-[11px] py-[5.5px]',
}), }),
content: 'gap-2', content: 'gap-2',
icon: 'mb-[-0.3cap]', icon: 'mb-[-0.1cap] h-4.5 w-4.5',
extraClickZone: 'after:inset-[-6px]', extraClickZone: 'after:inset-[-6px]',
}, },
medium: { medium: {
@ -118,9 +120,9 @@ export const BUTTON_STYLES = twv.tv({
variant: 'body', variant: 'body',
color: 'custom', color: 'custom',
weight: 'semibold', weight: 'semibold',
className: 'flex px-[9px] py-[3px]', className: 'flex px-[9px] py-[3.5px]',
}), }),
icon: 'mb-[-0.3cap]', icon: 'mb-[-0.1cap] h-4 w-4',
content: 'gap-2', content: 'gap-2',
extraClickZone: 'after:inset-[-8px]', extraClickZone: 'after:inset-[-8px]',
}, },
@ -129,9 +131,9 @@ export const BUTTON_STYLES = twv.tv({
variant: 'body', variant: 'body',
color: 'custom', color: 'custom',
weight: 'medium', weight: 'medium',
className: 'flex px-[7px] py-[1px]', className: 'flex px-[7px] py-[1.5px]',
}), }),
icon: 'mb-[-0.3cap]', icon: 'mb-[-0.1cap] h-3.5 w-3.5',
content: 'gap-1', content: 'gap-1',
extraClickZone: 'after:inset-[-10px]', extraClickZone: 'after:inset-[-10px]',
}, },
@ -140,9 +142,10 @@ export const BUTTON_STYLES = twv.tv({
variant: 'body', variant: 'body',
color: 'custom', color: 'custom',
weight: 'medium', weight: 'medium',
className: 'flex px-[5px] py-[1px]', disableLineHeightCompensation: true,
className: 'flex px-[5px] pt-[0.5px] pb-[2.5px]',
}), }),
icon: 'mb-[-0.3cap]', icon: 'mb-[-0.2cap] h-3 w-3',
content: 'gap-1', content: 'gap-1',
extraClickZone: 'after:inset-[-12px]', extraClickZone: 'after:inset-[-12px]',
}, },
@ -150,17 +153,24 @@ export const BUTTON_STYLES = twv.tv({
base: text.TEXT_STYLE({ base: text.TEXT_STYLE({
variant: 'body', variant: 'body',
color: 'custom', color: 'custom',
className: 'flex px-[3px] py-[0px]', className: 'flex px-[3px] pt-[0.5px] pb-[2.5px] leading-[16px]',
// we need to disable line height compensation for this size // we need to disable line height compensation for this size
// because otherwise the text will be too high in the button // because otherwise the text will be too high in the button
disableLineHeightCompensation: true, disableLineHeightCompensation: true,
}), }),
content: 'gap-0.5', content: 'gap-0.5',
icon: 'mb-[-0.1cap]',
extraClickZone: 'after:inset-[-12px]', extraClickZone: 'after:inset-[-12px]',
}, },
}, },
iconOnly: { iconOnly: {
true: { base: text.TEXT_STYLE({ disableLineHeightCompensation: true }), icon: 'mb-[unset]' }, true: {
base: text.TEXT_STYLE({
disableLineHeightCompensation: true,
className: 'border-0 outline-offset-[5px]',
}),
icon: 'mb-[unset]',
},
}, },
rounded: { rounded: {
full: 'rounded-full', full: 'rounded-full',
@ -173,10 +183,11 @@ export const BUTTON_STYLES = twv.tv({
xxxlarge: 'rounded-3xl', xxxlarge: 'rounded-3xl',
}, },
variant: { variant: {
custom: 'focus-visible:outline-offset-2', custom: '',
link: { link: {
base: 'inline-block px-0 py-0 rounded-sm text-primary/50 underline hover:text-primary border-none', base: 'inline-block px-0 py-0 rounded-sm text-primary/50 underline hover:text-primary border-0',
icon: 'h-[1.25cap] mt-[0.25cap]', content: 'gap-1.5',
icon: 'h-[1.25cap] w-[1.25cap] mt-[0.25cap]',
}, },
primary: 'bg-primary text-white hover:bg-primary/70', primary: 'bg-primary text-white hover:bg-primary/70',
tertiary: 'bg-accent text-white hover:bg-accent-dark', tertiary: 'bg-accent text-white hover:bg-accent-dark',
@ -184,17 +195,16 @@ export const BUTTON_STYLES = twv.tv({
delete: delete:
'bg-danger/80 hover:bg-danger text-white focus-visible:outline-danger focus-visible:bg-danger', 'bg-danger/80 hover:bg-danger text-white focus-visible:outline-danger focus-visible:bg-danger',
icon: { icon: {
base: 'border-0 opacity-80 hover:opacity-100 focus-visible:opacity-100 text-primary', base: 'opacity-80 hover:opacity-100 focus-visible:opacity-100',
wrapper: 'w-full h-full', wrapper: 'w-full h-full',
content: 'w-full h-full', content: 'w-full h-full',
extraClickZone: 'w-full h-full', extraClickZone: 'w-full h-full',
}, },
ghost: ghost:
'text-primary hover:text-primary/80 hover:bg-white focus-visible:text-primary/80 focus-visible:bg-white', 'text-primary hover:text-primary/80 hover:bg-white focus-visible:text-primary/80 focus-visible:bg-white',
submit: 'bg-invite text-white opacity-80 hover:opacity-100 focus-visible:outline-offset-2', submit: 'bg-invite text-white opacity-80 hover:opacity-100',
outline: outline: 'border-primary/40 text-primary hover:border-primary hover:bg-primary/5',
'border-primary/40 text-primary hover:border-primary focus-visible:outline-offset-2 hover:bg-primary/10', bar: 'border-primary/20 hover:bg-primary/5',
bar: 'rounded-full border-0.5 border-primary/20 transition-colors hover:bg-primary/10',
}, },
iconPosition: { iconPosition: {
start: { content: '' }, start: { content: '' },
@ -230,7 +240,7 @@ export const BUTTON_STYLES = twv.tv({
loader: 'absolute inset-0 flex items-center justify-center', loader: 'absolute inset-0 flex items-center justify-center',
content: 'flex items-center gap-[0.5em]', content: 'flex items-center gap-[0.5em]',
text: 'inline-flex items-center justify-center gap-1', text: 'inline-flex items-center justify-center gap-1',
icon: 'h-[2cap] flex-none aspect-square', icon: 'h-[1.906cap] w-[1.906cap] flex-none aspect-square flex items-center justify-center',
}, },
defaultVariants: { defaultVariants: {
isActive: 'none', isActive: 'none',
@ -243,42 +253,16 @@ export const BUTTON_STYLES = twv.tv({
showIconOnHover: false, showIconOnHover: false,
}, },
compoundVariants: [ compoundVariants: [
{ isFocused: true, iconOnly: true, class: 'focus-visible:outline-offset-3' }, { isFocused: true, iconOnly: true, class: 'focus-visible:outline-offset-[3px]' },
{ { size: 'custom', iconOnly: true, class: { icon: 'w-full h-full' } },
variant: 'link', { size: 'xxsmall', iconOnly: true, class: { base: 'p-0 rounded-full', icon: 'w-2.5 h-2.5' } },
isFocused: true, { size: 'xsmall', iconOnly: true, class: { base: 'p-0 rounded-full', icon: 'w-3 h-3' } },
class: 'focus-visible:outline-offset-1', { size: 'small', iconOnly: true, class: { base: 'p-0 rounded-full', icon: 'w-3.5 h-3.5' } },
}, { size: 'medium', iconOnly: true, class: { base: 'p-0 rounded-full', icon: 'w-4 h-4' } },
{ { size: 'large', iconOnly: true, class: { base: 'p-0 rounded-full', icon: 'w-4.5 h-4.5' } },
size: 'xxsmall', { size: 'hero', iconOnly: true, class: { base: 'p-0 rounded-full', icon: 'w-12 h-12' } },
iconOnly: true,
class: { base: 'p-0 rounded-full', icon: 'h-[1.25cap] -mt-[0.1cap]' }, { variant: 'link', isFocused: true, class: 'focus-visible:outline-offset-1' },
},
{
size: 'xsmall',
iconOnly: true,
class: { base: 'p-0 rounded-full', icon: 'h-[1.45cap] -mt-[0.1cap]' },
},
{
size: 'small',
iconOnly: true,
class: { base: 'p-0 rounded-full', icon: 'h-[1.65cap] -mt-[0.1cap]' },
},
{
size: 'medium',
iconOnly: true,
class: { base: 'p-0 rounded-full', icon: 'h-[2cap] -mt-[0.1cap]' },
},
{
size: 'large',
iconOnly: true,
class: { base: 'p-0 rounded-full', icon: 'h-[3.65cap]' },
},
{
size: 'hero',
class: { base: 'p-0 rounded-full', icon: 'h-[5.5cap]' },
iconOnly: true,
},
{ variant: 'link', size: 'xxsmall', class: 'font-medium' }, { variant: 'link', size: 'xxsmall', class: 'font-medium' },
{ variant: 'link', size: 'xsmall', class: 'font-medium' }, { variant: 'link', size: 'xsmall', class: 'font-medium' },
{ variant: 'link', size: 'small', class: 'font-medium' }, { variant: 'link', size: 'small', class: 'font-medium' },

View File

@ -36,7 +36,7 @@ export function CloseButton(props: CloseButtonProps) {
variant="icon" variant="icon"
className={values => className={values =>
tailwindMerge.twMerge( tailwindMerge.twMerge(
'h-3 w-3 bg-primary/30 hover:bg-red-500/80 focus-visible:bg-red-500/80 focus-visible:outline-offset-1', 'bg-primary/30 hover:bg-red-500/80 focus-visible:bg-red-500/80 focus-visible:outline-offset-1',
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
// @ts-expect-error ts fails to infer the type of the className prop // @ts-expect-error ts fails to infer the type of the className prop
typeof className === 'function' ? className(values) : className typeof className === 'function' ? className(values) : className
@ -44,7 +44,7 @@ export function CloseButton(props: CloseButtonProps) {
} }
tooltip={tooltip} tooltip={tooltip}
showIconOnHover showIconOnHover
size="custom" size="xsmall"
rounded="full" rounded="full"
extraClickZone="medium" extraClickZone="medium"
icon={icon} icon={icon}

View File

@ -27,11 +27,12 @@ export const TEXT_STYLE = twv.tv({
variants: { variants: {
color: { color: {
custom: '', custom: '',
primary: 'text-primary/90', primary: 'text-primary',
danger: 'text-danger', danger: 'text-danger',
success: 'text-share', success: 'text-share',
disabled: 'text-primary/30', disabled: 'text-primary/30',
invert: 'text-white/80', invert: 'text-white',
inherit: 'text-inherit',
}, },
font: { font: {
default: '', default: '',

View File

@ -44,7 +44,7 @@ export default function SvgMask(props: SvgMaskProps) {
...(invert ? { WebkitMaskComposite: 'exclude, exclude' } : {}), ...(invert ? { WebkitMaskComposite: 'exclude, exclude' } : {}),
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
}} }}
className={tailwindMerge.twMerge('inline-block size-max', className)} className={tailwindMerge.twMerge('inline-block h-max w-max', className)}
> >
{/* This is required for this component to have the right size. */} {/* This is required for this component to have the right size. */}
<img alt={alt} src={src} className="pointer-events-none opacity-0" draggable={false} /> <img alt={alt} src={src} className="pointer-events-none opacity-0" draggable={false} />

View File

@ -53,7 +53,7 @@ export default function Label(props: InternalLabelProps) {
typeof childrenRaw !== 'string' ? ( typeof childrenRaw !== 'string' ? (
childrenRaw childrenRaw
) : ( ) : (
<ariaComponents.Text truncate="1" className="max-w-24 text-inherit"> <ariaComponents.Text truncate="1" className="max-w-24" color="invert" variant="body">
{childrenRaw} {childrenRaw}
</ariaComponents.Text> </ariaComponents.Text>
) )
@ -75,7 +75,7 @@ export default function Label(props: InternalLabelProps) {
title={title} title={title}
disabled={isDisabled} disabled={isDisabled}
className={tailwindMerge.twMerge( className={tailwindMerge.twMerge(
'focus-child relative flex h-text items-center whitespace-nowrap rounded-inherit px-label-x transition-all selectable after:pointer-events-none after:absolute after:inset after:rounded-full', 'focus-child relative flex items-center whitespace-nowrap rounded-inherit px-[7px] opacity-75 transition-all after:pointer-events-none after:absolute after:inset after:rounded-full hover:opacity-100 focus:opacity-100',
active && 'active', active && 'active',
negated && 'after:border-2 after:border-delete', negated && 'after:border-2 after:border-delete',
className, className,

View File

@ -10,6 +10,7 @@ import * as modalProvider from '#/providers/ModalProvider'
import * as textProvider from '#/providers/TextProvider' import * as textProvider from '#/providers/TextProvider'
import * as aria from '#/components/aria' import * as aria from '#/components/aria'
import * as ariaComponents from '#/components/AriaComponents'
import Label from '#/components/dashboard/Label' import Label from '#/components/dashboard/Label'
import FocusArea from '#/components/styled/FocusArea' import FocusArea from '#/components/styled/FocusArea'
import FocusRing from '#/components/styled/FocusRing' import FocusRing from '#/components/styled/FocusRing'
@ -82,7 +83,7 @@ function Tags(props: InternalTagsProps) {
return ( return (
<div <div
data-testid="asset-search-tag-names" data-testid="asset-search-tag-names"
className="pointer-events-auto flex flex-wrap gap-2 whitespace-nowrap px-search-suggestions" className="pointer-events-auto flex flex-wrap gap-2 whitespace-nowrap px-1.5"
> >
{(isCloud ? AssetQuery.tagNames : AssetQuery.localTagNames).flatMap(entry => { {(isCloud ? AssetQuery.tagNames : AssetQuery.localTagNames).flatMap(entry => {
const [key, tag] = entry const [key, tag] = entry
@ -90,15 +91,17 @@ function Tags(props: InternalTagsProps) {
? [] ? []
: [ : [
<FocusRing key={key}> <FocusRing key={key}>
<aria.Button <ariaComponents.Button
className="h-text rounded-full bg-frame px-button-x transition-all hover:bg-selected-frame" variant="bar"
size="xsmall"
className="min-w-12"
onPress={() => { onPress={() => {
querySource.current = QuerySource.internal querySource.current = QuerySource.internal
setQuery(query.add({ [key]: [[]] })) setQuery(query.add({ [key]: [[]] }))
}} }}
> >
{tag + ':'} {tag + ':'}
</aria.Button> </ariaComponents.Button>
</FocusRing>, </FocusRing>,
] ]
})} })}
@ -280,7 +283,7 @@ export default function AssetSearchBar(props: AssetSearchBarProps) {
data-testid="asset-search-bar" data-testid="asset-search-bar"
{...aria.mergeProps<aria.LabelProps>()(innerProps, { {...aria.mergeProps<aria.LabelProps>()(innerProps, {
className: className:
'z-1 group relative flex h-row grow max-w-[60em] items-center gap-asset-search-bar rounded-full px-3 text-primary', 'z-1 group relative flex grow max-w-[60em] items-center gap-asset-search-bar rounded-full px-1.5 py-1 text-primary',
ref: rootRef, ref: rootRef,
onFocus: () => { onFocus: () => {
setAreSuggestionsVisible(true) setAreSuggestionsVisible(true)
@ -297,14 +300,17 @@ export default function AssetSearchBar(props: AssetSearchBarProps) {
> >
<div className="relative size-4 placeholder" /> <div className="relative size-4 placeholder" />
<div <div
className={tailwindMerge.twMerge( className={ariaComponents.DIALOG_BACKGROUND({
'pointer-events-none absolute left top z-1 flex w-full flex-col overflow-hidden rounded-default border-0.5 border-primary/20 -outline-offset-1 outline-primary transition-colors before:absolute before:inset before:backdrop-blur-default group-focus-within:outline group-focus-within:outline-2 hover:before:bg-frame', className: tailwindMerge.twMerge(
areSuggestionsVisible && 'before:bg-frame' 'absolute left-0 top-0 z-1 flex w-full flex-col overflow-hidden rounded-default border-0.5 border-primary/20 -outline-offset-1 outline-primary transition-colors',
)} areSuggestionsVisible ? '' : 'bg-transparent'
),
})}
> >
<div className="padding relative h-[30px]" /> <div className="h-[32px]" />
{areSuggestionsVisible && ( {areSuggestionsVisible && (
<div className="relative flex flex-col gap-search-suggestions"> <div className="relative mt-3 flex flex-col gap-3">
{/* Tags (`name:`, `modified:`, etc.) */} {/* Tags (`name:`, `modified:`, etc.) */}
<Tags <Tags
isCloud={isCloud} isCloud={isCloud}
@ -316,7 +322,7 @@ export default function AssetSearchBar(props: AssetSearchBarProps) {
{isCloud && labels.length !== 0 && ( {isCloud && labels.length !== 0 && (
<div <div
data-testid="asset-search-labels" data-testid="asset-search-labels"
className="pointer-events-auto flex gap-2 p-search-suggestions" className="pointer-events-auto flex gap-2 px-1.5"
> >
{[...labels] {[...labels]
.sort((a, b) => string.compareCaseInsensitive(a.value, b.value)) .sort((a, b) => string.compareCaseInsensitive(a.value, b.value))
@ -354,7 +360,7 @@ export default function AssetSearchBar(props: AssetSearchBarProps) {
</div> </div>
)} )}
{/* Suggestions */} {/* Suggestions */}
<div className="flex max-h-search-suggestions-list flex-col overflow-y-auto"> <div className="flex max-h-search-suggestions-list flex-col overflow-y-auto overflow-x-hidden pb-0.5 pl-0.5">
{suggestions.map((suggestion, index) => ( {suggestions.map((suggestion, index) => (
// This should not be a `<button>`, since `render()` may output a // This should not be a `<button>`, since `render()` may output a
// tree containing a button. // tree containing a button.
@ -367,8 +373,8 @@ export default function AssetSearchBar(props: AssetSearchBarProps) {
} }
}} }}
className={tailwindMerge.twMerge( className={tailwindMerge.twMerge(
'pointer-events-auto mx-search-suggestion cursor-pointer rounded-default px-search-suggestions py-search-suggestion-y text-left transition-colors last:mb-search-suggestion hover:bg-selected-frame', 'flex cursor-pointer rounded-l-default rounded-r-sm px-[7px] py-0.5 text-left transition-[background-color] hover:bg-primary/5',
selectedIndices.has(index) && 'bg-frame', selectedIndices.has(index) && 'bg-primary/10',
index === selectedIndex && 'bg-selected-frame' index === selectedIndex && 'bg-selected-frame'
)} )}
onPress={event => { onPress={event => {
@ -391,14 +397,19 @@ export default function AssetSearchBar(props: AssetSearchBarProps) {
} }
}} }}
> >
{suggestion.render()} <ariaComponents.Text variant="body" truncate="1" className="w-full">
{suggestion.render()}
</ariaComponents.Text>
</aria.Button> </aria.Button>
))} ))}
</div> </div>
</div> </div>
)} )}
</div> </div>
<SvgMask src={FindIcon} className="absolute left-3 z-1 text-primary/30" /> <SvgMask
src={FindIcon}
className="absolute left-2 top-[50%] z-1 mt-[1px] -translate-y-1/2 text-primary/40"
/>
<FocusRing placement="before"> <FocusRing placement="before">
<aria.SearchField <aria.SearchField
aria-label={getText('assetSearchFieldLabel')} aria-label={getText('assetSearchFieldLabel')}
@ -419,7 +430,7 @@ export default function AssetSearchBar(props: AssetSearchBarProps) {
: getText('remoteBackendSearchPlaceholder') : getText('remoteBackendSearchPlaceholder')
: getText('localBackendSearchPlaceholder') : getText('localBackendSearchPlaceholder')
} }
className="focus-child peer text relative z-1 w-full bg-transparent placeholder-primary/20" className="focus-child peer text relative z-1 w-full bg-transparent placeholder-primary/40"
onChange={event => { onChange={event => {
if (querySource.current !== QuerySource.internal) { if (querySource.current !== QuerySource.internal) {
querySource.current = QuerySource.typing querySource.current = QuerySource.typing

View File

@ -9,7 +9,6 @@ import * as backendHooks from '#/hooks/backendHooks'
import * as modalProvider from '#/providers/ModalProvider' import * as modalProvider from '#/providers/ModalProvider'
import * as textProvider from '#/providers/TextProvider' import * as textProvider from '#/providers/TextProvider'
import * as aria from '#/components/aria'
import * as ariaComponents from '#/components/AriaComponents' import * as ariaComponents from '#/components/AriaComponents'
import Label from '#/components/dashboard/Label' import Label from '#/components/dashboard/Label'
import Button from '#/components/styled/Button' import Button from '#/components/styled/Button'
@ -132,20 +131,18 @@ export default function Labels(props: LabelsProps) {
) )
})} })}
<ariaComponents.Button <ariaComponents.Button
size="custom" size="xsmall"
variant="bar" variant="outline"
className="px-2" className="pl-1 pr-2"
isActive={false} /* eslint-disable-next-line no-restricted-syntax */
icon={<img src={PlusIcon} alt="" className="ml-auto mt-[1px] size-[8px]" />}
onPress={event => { onPress={event => {
if (event.target instanceof HTMLElement) { if (event.target instanceof HTMLElement) {
setModal(<NewLabelModal backend={backend} eventTarget={event.target} />) setModal(<NewLabelModal backend={backend} eventTarget={event.target} />)
} }
}} }}
> >
{/* This is a non-standard-sized icon. */} {getText('newLabelButtonLabel')}
{/* eslint-disable-next-line no-restricted-syntax */}
<img src={PlusIcon} className="mr-[6px] size-[6px]" />
<aria.Text className="text-header">{getText('newLabelButtonLabel')}</aria.Text>
</ariaComponents.Button> </ariaComponents.Button>
</div> </div>
</div> </div>