Improve sidebar view

This commit is contained in:
Sergey Garin 2024-05-23 12:08:42 +03:00
parent 53f04b9fa1
commit 18d88baaaa
9 changed files with 64 additions and 64 deletions

View File

@ -16,8 +16,8 @@ import SvgMask from '#/components/SvgMask'
/** Props for a {@link Button}. */
export type ButtonProps =
| (BaseButtonProps & Omit<aria.ButtonProps, 'onPress'> & PropsWithoutHref)
| (BaseButtonProps & Omit<aria.LinkProps, 'onPress'> & PropsWithHref)
| (BaseButtonProps & Omit<aria.ButtonProps, 'children' | 'onPress'> & PropsWithoutHref)
| (BaseButtonProps & Omit<aria.LinkProps, 'children' | 'onPress'> & PropsWithHref)
/**
* Props for a button with an href.
@ -53,6 +53,8 @@ export interface BaseButtonProps extends Omit<twv.VariantProps<typeof BUTTON_STY
*/
readonly onPress?: (event: aria.PressEvent) => Promise<void> | void
readonly children?: React.ReactNode
readonly testId?: string
}
@ -69,7 +71,7 @@ export const BUTTON_STYLES = twv.tv({
custom: '',
hero: 'px-8 py-4 text-lg',
large: 'px-6 py-3 text-base',
medium: 'px-4 py-2 text-sm',
medium: { base: 'px-[7px] py-[3px] text-xs', text: 'pt-[1px] pb-[3px] leading-5' },
small: 'px-3 py-1 text-xs',
xsmall: 'px-2 py-1 text-xs',
xxsmall: 'px-1.5 py-0.5 text-xs',
@ -88,15 +90,16 @@ export const BUTTON_STYLES = twv.tv({
link: 'inline-flex px-0 py-0 rounded-sm text-primary/50 underline hover:text-primary focus-visible:outline-offset-0',
primary: 'bg-primary text-white hover:bg-primary/70 focus-visible:outline-offset-2',
tertiary: 'bg-share text-white hover:bg-share/90 focus-visible:outline-offset-2',
cancel: 'bg-selected-frame opacity-80 hover:opacity-100 focus-visible:outline-offset-2',
delete: 'bg-delete text-white focus-visible:outline-offset-2',
cancel: 'bg-white/50 hover:bg-white focus-visible:outline-offset-2',
delete: 'bg-danger text-white focus-visible:outline-offset-2',
icon: {
base: 'opacity-70 hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-offset-0',
base: 'opacity-80 hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-offset-0',
wrapper: 'w-full h-full',
content: 'w-full h-full',
icon: 'w-fit h-fit',
},
ghost:
'opacity-80 hover:opacity-100 hover:bg-white focus-visible:opacity-100 focus-visible:bg-white',
submit: 'bg-invite text-white opacity-80 hover:opacity-100 focus-visible:outline-offset-2',
outline:
'border-primary/40 text-primary font-bold hover:border-primary/90 focus-visible:outline-offset-2',
@ -114,7 +117,8 @@ export const BUTTON_STYLES = twv.tv({
wrapper: 'relative block',
loader:
'animate-appear-delayed absolute inset-0 flex items-center justify-center duration-1000',
content: 'flex items-center gap-[0.5em] delay-1000 duration-0',
content: 'flex items-center gap-[0.75em] delay-1000 duration-0',
text: '',
icon: 'h-[1.5em] flex-none',
},
defaultVariants: {
@ -194,6 +198,7 @@ export const Button = React.forwardRef(function Button(
loader,
extraClickZone,
icon: iconClasses,
text,
} = BUTTON_STYLES({
isDisabled,
loading: isLoading,
@ -219,7 +224,7 @@ export const Button = React.forwardRef(function Button(
return (
<>
{icon != null && <SvgMask src={icon} className={iconClasses()} />}
<>{children}</>
<span className={text()}>{children}</span>
</>
)
}

View File

@ -14,16 +14,23 @@ const STYLES = twv.tv({
base: 'flex w-full flex-auto',
variants: {
wrap: { true: 'flex-wrap' },
direction: { column: 'flex-col justify-center', row: 'flex-row items-center' },
direction: { column: 'flex-col', row: 'flex-row' },
gap: {
custom: '',
large: 'gap-3.5',
medium: 'gap-2',
small: 'gap-1.5',
xsmall: 'gap-1',
xxsmall: 'gap-0.5',
none: 'gap-0',
},
align: { start: 'justify-start', center: 'justify-center', end: 'justify-end' },
},
compoundVariants: [
{ direction: 'column', align: 'start', class: 'items-start' },
{ direction: 'column', align: 'center', class: 'items-center' },
{ direction: 'column', align: 'end', class: 'items-end' },
],
})
/**

View File

@ -38,11 +38,11 @@ export function PaywallDialog(props: PaywallDialogProps) {
<div className="flex flex-col">
<components.PaywallLock feature={feature} className="mb-2" />
<p className="text-base text-primary/60">{getText(descriptionTextId)}</p>
<p className="text-base text-primary/70">{getText(descriptionTextId)}</p>
<components.PaywallBulletPoints bulletPointsTextId={bulletPointsTextId} className="my-4" />
<upgradeButton.UpgradeButton feature={feature} rounding="xlarge" className="mt-2" />
<upgradeButton.UpgradeButton feature={feature} rounded="xlarge" className="mt-2" />
</div>
</ariaComponents.Dialog>
)

View File

@ -40,7 +40,7 @@ export function PaywallBulletPoints(props: PaywallBulletPointsProps) {
return (
<ul
className={tw.twMerge(
'm-0 flex w-full list-inside list-none flex-col gap-1 text-base',
'm-0 flex w-full list-inside list-none flex-col gap-1 text-base text-primary/70',
className
)}
>

View File

@ -1,10 +1,8 @@
/** @file A styled button representing a tab on a sidebar. */
import * as React from 'react'
import * as aria from '#/components/aria'
import StatelessSpinner, * as statelessSpinner from '#/components/StatelessSpinner'
import SvgMask from '#/components/SvgMask'
import UnstyledButton from '#/components/UnstyledButton'
import type * as aria from '#/components/aria'
import * as ariaComponent from '#/components/AriaComponents'
// ========================
// === SidebarTabButton ===
@ -20,38 +18,23 @@ export interface SidebarTabButtonProps {
readonly icon: string
readonly label: string
readonly onPress: (event: aria.PressEvent) => void
readonly isPending?: boolean
}
/** A styled button representing a tab on a sidebar. */
export default function SidebarTabButton(props: SidebarTabButtonProps) {
const {
isDisabled = false,
autoFocus = false,
active = false,
icon,
label,
onPress,
isPending = false,
} = props
const { isDisabled = false, active = false, icon, label, onPress } = props
return (
<UnstyledButton
autoFocus={autoFocus}
<ariaComponent.Button
variant="ghost"
size="medium"
onPress={onPress}
isDisabled={isDisabled}
className={`relative rounded-full ${active ? 'focus-default' : ''}`}
icon={icon}
rounded="full"
className={active ? 'bg-white opacity-100' : ''}
>
<div
className={`button icon-with-text h-row px-button-x transition-colors selectable hover:bg-selected-frame ${active ? 'disabled bg-selected-frame active' : ''}`}
>
{active && isPending ? (
<StatelessSpinner state={statelessSpinner.SpinnerState.loadingMedium} size={16} />
) : (
<SvgMask src={icon} />
)}
<aria.Text className="text">{label}</aria.Text>
</div>
</UnstyledButton>
{label}
</ariaComponent.Button>
)
}

View File

@ -7,6 +7,7 @@ import * as modalProvider from '#/providers/ModalProvider'
import * as textProvider from '#/providers/TextProvider'
import * as aria from '#/components/aria'
import * as ariaComponents from '#/components/AriaComponents'
import SettingsSection from '#/components/styled/settings/SettingsSection'
import UnstyledButton from '#/components/UnstyledButton'
@ -31,8 +32,10 @@ export default function DeleteUserAccountSettingsSection() {
className="flex flex-col items-start gap-settings-section-header rounded-2.5xl border-2 border-danger px-[1rem] pb-[0.9375rem] pt-[0.5625rem]"
>
<div className="flex gap-buttons">
<UnstyledButton
className="button bg-danger px-delete-user-account-button-x text-inversed opacity-full hover:opacity-full"
<ariaComponents.Button
variant="delete"
size="medium"
rounded="full"
onPress={() => {
setModal(
<ConfirmDeleteUserModal
@ -44,11 +47,9 @@ export default function DeleteUserAccountSettingsSection() {
)
}}
>
<aria.Text className="text inline-block">
{getText('deleteUserAccountButtonLabel')}
</aria.Text>
</UnstyledButton>
<aria.Text className="text my-auto">{getText('deleteUserAccountWarning')}</aria.Text>
{getText('deleteUserAccountButtonLabel')}
</ariaComponents.Button>
<aria.Text className="text-md my-auto">{getText('deleteUserAccountWarning')}</aria.Text>
</div>
</SettingsSection>
)

View File

@ -19,7 +19,7 @@ export default function MembersSettingsTabBar() {
return (
<HorizontalMenuBar>
<ariaComponents.DialogTrigger>
<ariaComponents.Button variant="cancel" rounded="full" size="small">
<ariaComponents.Button variant="cancel" rounded="full" size="medium">
{getText('inviteMembers')}
</ariaComponents.Button>

View File

@ -221,7 +221,7 @@ export function UserGroupsSettingsTabContent() {
feature="userGroupsFull"
variant="cancel"
size="medium"
rounding="full"
rounded="full"
iconPosition="end"
tooltip={getText('userGroupsPaywallMessage')}
>
@ -231,7 +231,7 @@ export function UserGroupsSettingsTabContent() {
<div className="flex items-center gap-2">
<ariaComponents.Button
variant="cancel"
rounding="full"
rounded="full"
size="medium"
onPress={event => {
const placeholderId = backendModule.newPlaceholderUserGroupId()

View File

@ -12,6 +12,7 @@ import * as textProvider from '#/providers/TextProvider'
import SettingsTab from '#/layouts/Settings/SettingsTab'
import * as aria from '#/components/aria'
import * as ariaComponents from '#/components/AriaComponents'
import FocusArea from '#/components/styled/FocusArea'
import SidebarTabButton from '#/components/styled/SidebarTabButton'
@ -133,19 +134,22 @@ export default function SettingsSidebar(props: SettingsSidebarProps) {
>
{section.name}
</aria.Header>
{section.tabs.map(tab => (
<SidebarTabButton
key={tab.settingsTab}
isDisabled={(tab.organizationOnly ?? false) && !isUserInOrganization}
id={tab.settingsTab}
icon={tab.icon}
label={tab.name}
active={tab.settingsTab === settingsTab}
onPress={() => {
setSettingsTab(tab.settingsTab)
}}
/>
))}
<ariaComponents.ButtonGroup gap="xxsmall" direction="column" align="start">
{section.tabs.map(tab => (
<SidebarTabButton
key={tab.settingsTab}
isDisabled={(tab.organizationOnly ?? false) && !isUserInOrganization}
id={tab.settingsTab}
icon={tab.icon}
label={tab.name}
active={tab.settingsTab === settingsTab}
onPress={() => {
setSettingsTab(tab.settingsTab)
}}
/>
))}
</ariaComponents.ButtonGroup>
</div>
))}
</div>