mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 13:02:07 +03:00
Add tooltips (#9899)
- Dashboard side of #9828 - Add tooltips for elements rendered by the Dashboard # Important Notes - The UI for the tooltips can be changed at any time.
This commit is contained in:
parent
720d32cbe3
commit
35571f64ba
@ -194,52 +194,52 @@ export function locateAssetLabels(page: test.Locator | test.Page) {
|
|||||||
|
|
||||||
/** Find a toggle for the "Name" column (if any) on the current page. */
|
/** Find a toggle for the "Name" column (if any) on the current page. */
|
||||||
export function locateNameColumnToggle(page: test.Locator | test.Page) {
|
export function locateNameColumnToggle(page: test.Locator | test.Page) {
|
||||||
return page.getByAltText(/^(?:Show|Hide) Name$/)
|
return page.getByAltText('Name')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a toggle for the "Modified" column (if any) on the current page. */
|
/** Find a toggle for the "Modified" column (if any) on the current page. */
|
||||||
export function locateModifiedColumnToggle(page: test.Locator | test.Page) {
|
export function locateModifiedColumnToggle(page: test.Locator | test.Page) {
|
||||||
return page.getByAltText(/^(?:Show|Hide) Modified date column$/)
|
return page.getByAltText('Modified')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a toggle for the "Shared with" column (if any) on the current page. */
|
/** Find a toggle for the "Shared with" column (if any) on the current page. */
|
||||||
export function locateSharedWithColumnToggle(page: test.Locator | test.Page) {
|
export function locateSharedWithColumnToggle(page: test.Locator | test.Page) {
|
||||||
return page.getByAltText(/^(?:Show|Hide) Shared with column$/)
|
return page.getByAltText('Shared With')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a toggle for the "Labels" column (if any) on the current page. */
|
/** Find a toggle for the "Labels" column (if any) on the current page. */
|
||||||
export function locateLabelsColumnToggle(page: test.Locator | test.Page) {
|
export function locateLabelsColumnToggle(page: test.Locator | test.Page) {
|
||||||
return page.getByAltText(/^(?:Show|Hide) Labels column$/)
|
return page.getByAltText('Labels')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a toggle for the "Accessed by projects" column (if any) on the current page. */
|
/** Find a toggle for the "Accessed by projects" column (if any) on the current page. */
|
||||||
export function locateAccessedByProjectsColumnToggle(page: test.Locator | test.Page) {
|
export function locateAccessedByProjectsColumnToggle(page: test.Locator | test.Page) {
|
||||||
return page.getByAltText(/^(?:Show|Hide) Accessed by projects column$/)
|
return page.getByAltText('Accessed By Projects')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a toggle for the "Accessed data" column (if any) on the current page. */
|
/** Find a toggle for the "Accessed data" column (if any) on the current page. */
|
||||||
export function locateAccessedDataColumnToggle(page: test.Locator | test.Page) {
|
export function locateAccessedDataColumnToggle(page: test.Locator | test.Page) {
|
||||||
return page.getByAltText(/^(?:Show|Hide) Accessed data column$/)
|
return page.getByAltText('Accessed Data')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a toggle for the "Docs" column (if any) on the current page. */
|
/** Find a toggle for the "Docs" column (if any) on the current page. */
|
||||||
export function locateDocsColumnToggle(page: test.Locator | test.Page) {
|
export function locateDocsColumnToggle(page: test.Locator | test.Page) {
|
||||||
return page.getByAltText(/^(?:Show|Hide) Docs column$/)
|
return page.getByAltText('Docs')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a button for the "Recent" category (if any) on the current page. */
|
/** Find a button for the "Recent" category (if any) on the current page. */
|
||||||
export function locateRecentCategory(page: test.Locator | test.Page) {
|
export function locateRecentCategory(page: test.Locator | test.Page) {
|
||||||
return page.getByLabel('Go To Recent category')
|
return page.getByLabel('Recent').locator('visible=true')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a button for the "Home" category (if any) on the current page. */
|
/** Find a button for the "Home" category (if any) on the current page. */
|
||||||
export function locateHomeCategory(page: test.Locator | test.Page) {
|
export function locateHomeCategory(page: test.Locator | test.Page) {
|
||||||
return page.getByLabel('Go To Home category')
|
return page.getByLabel('Home').locator('visible=true')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a button for the "Trash" category (if any) on the current page. */
|
/** Find a button for the "Trash" category (if any) on the current page. */
|
||||||
export function locateTrashCategory(page: test.Locator | test.Page) {
|
export function locateTrashCategory(page: test.Locator | test.Page) {
|
||||||
return page.getByLabel('Go To Trash category')
|
return page.getByLabel('Trash').locator('visible=true')
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Context menu buttons ===
|
// === Context menu buttons ===
|
||||||
@ -363,30 +363,27 @@ export function locateUpgradeButton(page: test.Locator | test.Page) {
|
|||||||
|
|
||||||
/** Find a "new folder" icon (if any) on the current page. */
|
/** Find a "new folder" icon (if any) on the current page. */
|
||||||
export function locateNewFolderIcon(page: test.Locator | test.Page) {
|
export function locateNewFolderIcon(page: test.Locator | test.Page) {
|
||||||
return page.getByAltText('New Folder')
|
return page.getByRole('button').filter({ has: page.getByAltText('New Folder') })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a "new secret" icon (if any) on the current page. */
|
/** Find a "new secret" icon (if any) on the current page. */
|
||||||
export function locateNewSecretIcon(page: test.Locator | test.Page) {
|
export function locateNewSecretIcon(page: test.Locator | test.Page) {
|
||||||
return page.getByAltText('New Secret')
|
return page.getByRole('button').filter({ has: page.getByAltText('New Secret') })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a "upload files" icon (if any) on the current page. */
|
/** Find an "upload files" icon (if any) on the current page. */
|
||||||
export function locateUploadFilesIcon(page: test.Locator | test.Page) {
|
export function locateUploadFilesIcon(page: test.Locator | test.Page) {
|
||||||
return page.getByAltText('Upload Files')
|
return page.getByRole('button').filter({ has: page.getByAltText('Import') })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a "download files" icon (if any) on the current page. */
|
/** Find a "download files" icon (if any) on the current page. */
|
||||||
export function locateDownloadFilesIcon(page: test.Locator | test.Page) {
|
export function locateDownloadFilesIcon(page: test.Locator | test.Page) {
|
||||||
return page.getByAltText('Download Files')
|
return page.getByRole('button').filter({ has: page.getByAltText('Export') })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find an icon to open or close the asset panel (if any) on the current page. */
|
/** Find an icon to open or close the asset panel (if any) on the current page. */
|
||||||
export function locateAssetPanelIcon(page: test.Locator | test.Page) {
|
export function locateAssetPanelIcon(page: test.Locator | test.Page) {
|
||||||
return page
|
return page.getByAltText('Asset Panel').locator('visible=true')
|
||||||
.getByAltText('Open Asset Panel')
|
|
||||||
.or(page.getByAltText('Close Asset Panel'))
|
|
||||||
.locator('visible=true')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a list of tags in the search bar (if any) on the current page. */
|
/** Find a list of tags in the search bar (if any) on the current page. */
|
||||||
@ -423,22 +420,22 @@ export function locateSortDescendingIcon(page: test.Locator | test.Page) {
|
|||||||
|
|
||||||
/** Find a "home page" icon (if any) on the current page. */
|
/** Find a "home page" icon (if any) on the current page. */
|
||||||
export function locateHomePageIcon(page: test.Locator | test.Page) {
|
export function locateHomePageIcon(page: test.Locator | test.Page) {
|
||||||
return page.getByAltText('Home tab')
|
return page.getByRole('button').filter({ has: page.getByAltText('Home') })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a "drive page" icon (if any) on the current page. */
|
/** Find a "drive page" icon (if any) on the current page. */
|
||||||
export function locateDrivePageIcon(page: test.Locator | test.Page) {
|
export function locateDrivePageIcon(page: test.Locator | test.Page) {
|
||||||
return page.getByAltText('Drive tab')
|
return page.getByRole('button').filter({ has: page.getByAltText('Catalog') })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find an "editor page" icon (if any) on the current page. */
|
/** Find an "editor page" icon (if any) on the current page. */
|
||||||
export function locateEditorPageIcon(page: test.Locator | test.Page) {
|
export function locateEditorPageIcon(page: test.Locator | test.Page) {
|
||||||
return page.getByAltText('Project tab')
|
return page.getByRole('button').filter({ has: page.getByAltText('Graph Editor') })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a "settings page" icon (if any) on the current page. */
|
/** Find a "settings page" icon (if any) on the current page. */
|
||||||
export function locateSettingsPageIcon(page: test.Locator | test.Page) {
|
export function locateSettingsPageIcon(page: test.Locator | test.Page) {
|
||||||
return page.getByAltText('Settings tab')
|
return page.getByRole('button').filter({ has: page.getByAltText('Settings') })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a "name" column heading (if any) on the current page. */
|
/** Find a "name" column heading (if any) on the current page. */
|
||||||
|
@ -7,6 +7,7 @@ import * as tailwindMerge from 'tailwind-merge'
|
|||||||
import * as focusHooks from '#/hooks/focusHooks'
|
import * as focusHooks from '#/hooks/focusHooks'
|
||||||
|
|
||||||
import * as aria from '#/components/aria'
|
import * as aria from '#/components/aria'
|
||||||
|
import * as ariaComponents from '#/components/AriaComponents'
|
||||||
import Spinner, * as spinnerModule from '#/components/Spinner'
|
import Spinner, * as spinnerModule from '#/components/Spinner'
|
||||||
import SvgMask from '#/components/SvgMask'
|
import SvgMask from '#/components/SvgMask'
|
||||||
|
|
||||||
@ -16,6 +17,8 @@ import SvgMask from '#/components/SvgMask'
|
|||||||
|
|
||||||
/** Props for a {@link Button}. */
|
/** Props for a {@link Button}. */
|
||||||
export interface ButtonProps extends Readonly<aria.ButtonProps> {
|
export interface ButtonProps extends Readonly<aria.ButtonProps> {
|
||||||
|
/** Falls back to `aria-label`. Pass `false` to explicitly disable the tooltip. */
|
||||||
|
readonly tooltip?: React.ReactNode
|
||||||
readonly loading?: boolean
|
readonly loading?: boolean
|
||||||
readonly variant: 'cancel' | 'delete' | 'icon' | 'submit'
|
readonly variant: 'cancel' | 'delete' | 'icon' | 'submit'
|
||||||
readonly icon?: string
|
readonly icon?: string
|
||||||
@ -46,9 +49,11 @@ const CLASSES_FOR_VARIANT: Record<ButtonProps['variant'], string> = {
|
|||||||
|
|
||||||
/** A button allows a user to perform an action, with mouse, touch, and keyboard interactions. */
|
/** A button allows a user to perform an action, with mouse, touch, and keyboard interactions. */
|
||||||
export function Button(props: ButtonProps) {
|
export function Button(props: ButtonProps) {
|
||||||
const { className, children, variant, icon, loading = false, ...ariaButtonProps } = props
|
const { tooltip, className, children, variant, icon, loading = false, ...ariaButtonProps } = props
|
||||||
const focusChildProps = focusHooks.useFocusChild()
|
const focusChildProps = focusHooks.useFocusChild()
|
||||||
|
|
||||||
|
const tooltipElement = tooltip === false ? null : tooltip ?? ariaButtonProps['aria-label']
|
||||||
|
|
||||||
const classes = clsx(
|
const classes = clsx(
|
||||||
DEFAULT_CLASSES,
|
DEFAULT_CLASSES,
|
||||||
DISABLED_CLASSES,
|
DISABLED_CLASSES,
|
||||||
@ -71,7 +76,7 @@ export function Button(props: ButtonProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const button = (
|
||||||
<aria.Button
|
<aria.Button
|
||||||
{...aria.mergeProps<aria.ButtonProps>()(ariaButtonProps, focusChildProps, {
|
{...aria.mergeProps<aria.ButtonProps>()(ariaButtonProps, focusChildProps, {
|
||||||
className: values =>
|
className: values =>
|
||||||
@ -84,4 +89,13 @@ export function Button(props: ButtonProps) {
|
|||||||
{childrenFactory()}
|
{childrenFactory()}
|
||||||
</aria.Button>
|
</aria.Button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return tooltipElement == null ? (
|
||||||
|
button
|
||||||
|
) : (
|
||||||
|
<ariaComponents.TooltipTrigger>
|
||||||
|
{button}
|
||||||
|
<ariaComponents.Tooltip>{tooltipElement}</ariaComponents.Tooltip>
|
||||||
|
</ariaComponents.TooltipTrigger>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import * as portal from '#/components/Portal'
|
|||||||
// =================
|
// =================
|
||||||
|
|
||||||
const DEFAULT_CLASSES =
|
const DEFAULT_CLASSES =
|
||||||
'flex bg-frame backdrop-blur-default text-primary p-2 rounded-default shadow-soft text-xs'
|
'flex bg-frame outline outline-2 outline-primary backdrop-blur-default text-primary px-2 leading-cozy min-h-6 rounded-default shadow-soft text-xs'
|
||||||
const DEFAULT_CONTAINER_PADDING = 4
|
const DEFAULT_CONTAINER_PADDING = 4
|
||||||
const DEFAULT_OFFSET = 4
|
const DEFAULT_OFFSET = 4
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
/** @file File containing SVG icon definitions. */
|
/** @file File containing SVG icon definitions. */
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
|
import * as tailwindMerge from 'tailwind-merge'
|
||||||
|
|
||||||
// ===============
|
// ===============
|
||||||
// === SvgMask ===
|
// === SvgMask ===
|
||||||
// ===============
|
// ===============
|
||||||
@ -20,19 +22,16 @@ export interface SvgMaskProps {
|
|||||||
// underlying `div`.
|
// underlying `div`.
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
readonly className?: string | undefined
|
readonly className?: string | undefined
|
||||||
readonly onClick?: (event: React.MouseEvent) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Use an SVG as a mask. This lets the SVG use the text color (`currentColor`). */
|
/** Use an SVG as a mask. This lets the SVG use the text color (`currentColor`). */
|
||||||
export default function SvgMask(props: SvgMaskProps) {
|
export default function SvgMask(props: SvgMaskProps) {
|
||||||
const { invert = false, alt, src, title, style, color, className, onClick } = props
|
const { invert = false, alt, src, style, color, className } = props
|
||||||
const urlSrc = `url(${JSON.stringify(src)})`
|
const urlSrc = `url(${JSON.stringify(src)})`
|
||||||
const mask = invert ? `${urlSrc}, linear-gradient(white 0 0)` : urlSrc
|
const mask = invert ? `${urlSrc}, linear-gradient(white 0 0)` : urlSrc
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
{...(onClick == null ? {} : { role: 'button' })}
|
|
||||||
title={title}
|
|
||||||
style={{
|
style={{
|
||||||
...(style ?? {}),
|
...(style ?? {}),
|
||||||
backgroundColor: color ?? 'currentcolor',
|
backgroundColor: color ?? 'currentcolor',
|
||||||
@ -50,10 +49,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={`inline-block ${onClick != null ? 'cursor-pointer' : ''} ${
|
className={tailwindMerge.twMerge('inline-block h-max w-max', className)}
|
||||||
className ?? 'h-max w-max'
|
|
||||||
}`}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
>
|
||||||
{/* 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="transparent" draggable={false} />
|
<img alt={alt} src={src} className="transparent" draggable={false} />
|
||||||
|
@ -4,6 +4,7 @@ import * as React from 'react'
|
|||||||
import * as focusHooks from '#/hooks/focusHooks'
|
import * as focusHooks from '#/hooks/focusHooks'
|
||||||
|
|
||||||
import * as aria from '#/components/aria'
|
import * as aria from '#/components/aria'
|
||||||
|
import * as ariaComponents from '#/components/AriaComponents'
|
||||||
import type * as focusRing from '#/components/styled/FocusRing'
|
import type * as focusRing from '#/components/styled/FocusRing'
|
||||||
import FocusRing from '#/components/styled/FocusRing'
|
import FocusRing from '#/components/styled/FocusRing'
|
||||||
|
|
||||||
@ -15,6 +16,8 @@ import FocusRing from '#/components/styled/FocusRing'
|
|||||||
export interface UnstyledButtonProps extends Readonly<React.PropsWithChildren> {
|
export interface UnstyledButtonProps extends Readonly<React.PropsWithChildren> {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
readonly 'aria-label'?: string
|
readonly 'aria-label'?: string
|
||||||
|
/** Falls back to `aria-label`. Pass `false` to explicitly disable the tooltip. */
|
||||||
|
readonly tooltip?: React.ReactNode
|
||||||
readonly focusRingPlacement?: focusRing.FocusRingPlacement
|
readonly focusRingPlacement?: focusRing.FocusRingPlacement
|
||||||
readonly autoFocus?: boolean
|
readonly autoFocus?: boolean
|
||||||
/** When `true`, the button is not clickable. */
|
/** When `true`, the button is not clickable. */
|
||||||
@ -26,10 +29,12 @@ export interface UnstyledButtonProps extends Readonly<React.PropsWithChildren> {
|
|||||||
|
|
||||||
/** An unstyled button with a focus ring and focus movement behavior. */
|
/** An unstyled button with a focus ring and focus movement behavior. */
|
||||||
function UnstyledButton(props: UnstyledButtonProps, ref: React.ForwardedRef<HTMLButtonElement>) {
|
function UnstyledButton(props: UnstyledButtonProps, ref: React.ForwardedRef<HTMLButtonElement>) {
|
||||||
const { focusRingPlacement, children, ...buttonProps } = props
|
const { tooltip, focusRingPlacement, children, ...buttonProps } = props
|
||||||
const focusChildProps = focusHooks.useFocusChild()
|
const focusChildProps = focusHooks.useFocusChild()
|
||||||
|
|
||||||
return (
|
const tooltipElement = tooltip === false ? null : tooltip ?? buttonProps['aria-label']
|
||||||
|
|
||||||
|
const button = (
|
||||||
<FocusRing {...(focusRingPlacement == null ? {} : { placement: focusRingPlacement })}>
|
<FocusRing {...(focusRingPlacement == null ? {} : { placement: focusRingPlacement })}>
|
||||||
<aria.Button
|
<aria.Button
|
||||||
{...aria.mergeProps<aria.ButtonProps & React.RefAttributes<HTMLButtonElement>>()(
|
{...aria.mergeProps<aria.ButtonProps & React.RefAttributes<HTMLButtonElement>>()(
|
||||||
@ -42,6 +47,15 @@ function UnstyledButton(props: UnstyledButtonProps, ref: React.ForwardedRef<HTML
|
|||||||
</aria.Button>
|
</aria.Button>
|
||||||
</FocusRing>
|
</FocusRing>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return tooltipElement == null ? (
|
||||||
|
button
|
||||||
|
) : (
|
||||||
|
<ariaComponents.TooltipTrigger>
|
||||||
|
{button}
|
||||||
|
<ariaComponents.Tooltip>{tooltipElement}</ariaComponents.Tooltip>
|
||||||
|
</ariaComponents.TooltipTrigger>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default React.forwardRef(UnstyledButton)
|
export default React.forwardRef(UnstyledButton)
|
||||||
|
@ -17,6 +17,7 @@ import AssetListEventType from '#/events/AssetListEventType'
|
|||||||
|
|
||||||
import type * as column from '#/components/dashboard/column'
|
import type * as column from '#/components/dashboard/column'
|
||||||
import EditableSpan from '#/components/EditableSpan'
|
import EditableSpan from '#/components/EditableSpan'
|
||||||
|
import Button from '#/components/styled/Button'
|
||||||
import SvgMask from '#/components/SvgMask'
|
import SvgMask from '#/components/SvgMask'
|
||||||
|
|
||||||
import * as backendModule from '#/services/Backend'
|
import * as backendModule from '#/services/Backend'
|
||||||
@ -158,14 +159,13 @@ export default function DirectoryNameColumn(props: DirectoryNameColumnProps) {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SvgMask
|
<Button
|
||||||
src={FolderArrowIcon}
|
image={FolderArrowIcon}
|
||||||
alt={item.children == null ? getText('expand') : getText('collapse')}
|
alt={item.children == null ? getText('expand') : getText('collapse')}
|
||||||
className={`m-name-column-icon hidden size-icon cursor-pointer transition-transform duration-arrow group-hover:inline-block ${
|
className={`m-name-column-icon hidden size-icon cursor-pointer transition-transform duration-arrow group-hover:inline-block ${
|
||||||
item.children != null ? 'rotate-90' : ''
|
item.children != null ? 'rotate-90' : ''
|
||||||
}`}
|
}`}
|
||||||
onClick={event => {
|
onPress={() => {
|
||||||
event.stopPropagation()
|
|
||||||
doToggleDirectoryExpansion(asset.id, item.key, asset.title)
|
doToggleDirectoryExpansion(asset.id, item.key, asset.title)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -8,7 +8,7 @@ import * as textProvider from '#/providers/TextProvider'
|
|||||||
import * as aria from '#/components/aria'
|
import * as aria from '#/components/aria'
|
||||||
import type * as column from '#/components/dashboard/column'
|
import type * as column from '#/components/dashboard/column'
|
||||||
import * as columnUtils from '#/components/dashboard/column/columnUtils'
|
import * as columnUtils from '#/components/dashboard/column/columnUtils'
|
||||||
import SvgMask from '#/components/SvgMask'
|
import Button from '#/components/styled/Button'
|
||||||
|
|
||||||
/** A heading for the "Accessed by projects" column. */
|
/** A heading for the "Accessed by projects" column. */
|
||||||
export default function AccessedByProjectsColumnHeading(props: column.AssetColumnHeadingProps) {
|
export default function AccessedByProjectsColumnHeading(props: column.AssetColumnHeadingProps) {
|
||||||
@ -18,12 +18,12 @@ export default function AccessedByProjectsColumnHeading(props: column.AssetColum
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-drive-table-heading w-full items-center gap-icon-with-text">
|
<div className="flex h-drive-table-heading w-full items-center gap-icon-with-text">
|
||||||
<SvgMask
|
<Button
|
||||||
src={AccessedByProjectsIcon}
|
active
|
||||||
|
image={AccessedByProjectsIcon}
|
||||||
className="size-icon"
|
className="size-icon"
|
||||||
title={getText('accessedByProjectsColumnHide')}
|
alt={getText('accessedByProjectsColumnHide')}
|
||||||
onClick={event => {
|
onPress={() => {
|
||||||
event.stopPropagation()
|
|
||||||
hideColumn(columnUtils.Column.accessedByProjects)
|
hideColumn(columnUtils.Column.accessedByProjects)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -8,7 +8,7 @@ import * as textProvider from '#/providers/TextProvider'
|
|||||||
import * as aria from '#/components/aria'
|
import * as aria from '#/components/aria'
|
||||||
import type * as column from '#/components/dashboard/column'
|
import type * as column from '#/components/dashboard/column'
|
||||||
import * as columnUtils from '#/components/dashboard/column/columnUtils'
|
import * as columnUtils from '#/components/dashboard/column/columnUtils'
|
||||||
import SvgMask from '#/components/SvgMask'
|
import Button from '#/components/styled/Button'
|
||||||
|
|
||||||
/** A heading for the "Accessed data" column. */
|
/** A heading for the "Accessed data" column. */
|
||||||
export default function AccessedDataColumnHeading(props: column.AssetColumnHeadingProps) {
|
export default function AccessedDataColumnHeading(props: column.AssetColumnHeadingProps) {
|
||||||
@ -18,12 +18,12 @@ export default function AccessedDataColumnHeading(props: column.AssetColumnHeadi
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-drive-table-heading w-full items-center gap-icon-with-text">
|
<div className="flex h-drive-table-heading w-full items-center gap-icon-with-text">
|
||||||
<SvgMask
|
<Button
|
||||||
src={AccessedDataIcon}
|
active
|
||||||
|
image={AccessedDataIcon}
|
||||||
className="size-icon"
|
className="size-icon"
|
||||||
title={getText('accessedDataColumnHide')}
|
alt={getText('accessedDataColumnHide')}
|
||||||
onClick={event => {
|
onPress={() => {
|
||||||
event.stopPropagation()
|
|
||||||
hideColumn(columnUtils.Column.accessedData)
|
hideColumn(columnUtils.Column.accessedData)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -8,7 +8,7 @@ import * as textProvider from '#/providers/TextProvider'
|
|||||||
import * as aria from '#/components/aria'
|
import * as aria from '#/components/aria'
|
||||||
import type * as column from '#/components/dashboard/column'
|
import type * as column from '#/components/dashboard/column'
|
||||||
import * as columnUtils from '#/components/dashboard/column/columnUtils'
|
import * as columnUtils from '#/components/dashboard/column/columnUtils'
|
||||||
import SvgMask from '#/components/SvgMask'
|
import Button from '#/components/styled/Button'
|
||||||
|
|
||||||
/** A heading for the "Docs" column. */
|
/** A heading for the "Docs" column. */
|
||||||
export default function DocsColumnHeading(props: column.AssetColumnHeadingProps) {
|
export default function DocsColumnHeading(props: column.AssetColumnHeadingProps) {
|
||||||
@ -18,12 +18,12 @@ export default function DocsColumnHeading(props: column.AssetColumnHeadingProps)
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-drive-table-heading w-full items-center gap-icon-with-text">
|
<div className="flex h-drive-table-heading w-full items-center gap-icon-with-text">
|
||||||
<SvgMask
|
<Button
|
||||||
src={DocsIcon}
|
active
|
||||||
|
image={DocsIcon}
|
||||||
className="size-icon"
|
className="size-icon"
|
||||||
title={getText('docsColumnHide')}
|
alt={getText('docsColumnHide')}
|
||||||
onClick={event => {
|
onPress={() => {
|
||||||
event.stopPropagation()
|
|
||||||
hideColumn(columnUtils.Column.docs)
|
hideColumn(columnUtils.Column.docs)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -8,7 +8,7 @@ import * as textProvider from '#/providers/TextProvider'
|
|||||||
import * as aria from '#/components/aria'
|
import * as aria from '#/components/aria'
|
||||||
import type * as column from '#/components/dashboard/column'
|
import type * as column from '#/components/dashboard/column'
|
||||||
import * as columnUtils from '#/components/dashboard/column/columnUtils'
|
import * as columnUtils from '#/components/dashboard/column/columnUtils'
|
||||||
import SvgMask from '#/components/SvgMask'
|
import Button from '#/components/styled/Button'
|
||||||
|
|
||||||
/** A heading for the "Labels" column. */
|
/** A heading for the "Labels" column. */
|
||||||
export default function LabelsColumnHeading(props: column.AssetColumnHeadingProps) {
|
export default function LabelsColumnHeading(props: column.AssetColumnHeadingProps) {
|
||||||
@ -18,12 +18,12 @@ export default function LabelsColumnHeading(props: column.AssetColumnHeadingProp
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-drive-table-heading w-full items-center gap-icon-with-text">
|
<div className="flex h-drive-table-heading w-full items-center gap-icon-with-text">
|
||||||
<SvgMask
|
<Button
|
||||||
src={TagIcon}
|
active
|
||||||
|
image={TagIcon}
|
||||||
className="size-icon"
|
className="size-icon"
|
||||||
title={getText('labelsColumnHide')}
|
alt={getText('labelsColumnHide')}
|
||||||
onClick={event => {
|
onPress={() => {
|
||||||
event.stopPropagation()
|
|
||||||
hideColumn(columnUtils.Column.labels)
|
hideColumn(columnUtils.Column.labels)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -9,7 +9,7 @@ import * as textProvider from '#/providers/TextProvider'
|
|||||||
import * as aria from '#/components/aria'
|
import * as aria from '#/components/aria'
|
||||||
import type * as column from '#/components/dashboard/column'
|
import type * as column from '#/components/dashboard/column'
|
||||||
import * as columnUtils from '#/components/dashboard/column/columnUtils'
|
import * as columnUtils from '#/components/dashboard/column/columnUtils'
|
||||||
import SvgMask from '#/components/SvgMask'
|
import Button from '#/components/styled/Button'
|
||||||
import UnstyledButton from '#/components/UnstyledButton'
|
import UnstyledButton from '#/components/UnstyledButton'
|
||||||
|
|
||||||
import * as sorting from '#/utilities/sorting'
|
import * as sorting from '#/utilities/sorting'
|
||||||
@ -23,7 +23,7 @@ export default function ModifiedColumnHeading(props: column.AssetColumnHeadingPr
|
|||||||
const isDescending = sortInfo?.direction === sorting.SortDirection.descending
|
const isDescending = sortInfo?.direction === sorting.SortDirection.descending
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UnstyledButton
|
<div
|
||||||
aria-label={
|
aria-label={
|
||||||
!isSortActive
|
!isSortActive
|
||||||
? getText('sortByModificationDate')
|
? getText('sortByModificationDate')
|
||||||
@ -32,34 +32,38 @@ export default function ModifiedColumnHeading(props: column.AssetColumnHeadingPr
|
|||||||
: getText('sortByModificationDateDescending')
|
: getText('sortByModificationDateDescending')
|
||||||
}
|
}
|
||||||
className="group flex h-drive-table-heading w-full cursor-pointer items-center gap-icon-with-text"
|
className="group flex h-drive-table-heading w-full cursor-pointer items-center gap-icon-with-text"
|
||||||
onPress={() => {
|
|
||||||
const nextDirection = isSortActive
|
|
||||||
? sorting.nextSortDirection(sortInfo.direction)
|
|
||||||
: sorting.SortDirection.ascending
|
|
||||||
if (nextDirection == null) {
|
|
||||||
setSortInfo(null)
|
|
||||||
} else {
|
|
||||||
setSortInfo({ field: columnUtils.Column.modified, direction: nextDirection })
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<SvgMask
|
<Button
|
||||||
src={TimeIcon}
|
active
|
||||||
|
image={TimeIcon}
|
||||||
className="size-icon"
|
className="size-icon"
|
||||||
title={getText('modifiedColumnHide')}
|
alt={getText('modifiedColumnHide')}
|
||||||
onClick={event => {
|
onPress={() => {
|
||||||
event.stopPropagation()
|
|
||||||
hideColumn(columnUtils.Column.modified)
|
hideColumn(columnUtils.Column.modified)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<aria.Text className="text-header">{getText('modifiedColumnName')}</aria.Text>
|
<UnstyledButton
|
||||||
<img
|
className="flex grow gap-icon-with-text"
|
||||||
alt={isDescending ? getText('sortDescending') : getText('sortAscending')}
|
onPress={() => {
|
||||||
src={SortAscendingIcon}
|
const nextDirection = isSortActive
|
||||||
className={`transition-all duration-arrow ${
|
? sorting.nextSortDirection(sortInfo.direction)
|
||||||
isSortActive ? 'selectable active' : 'transparent group-hover:selectable'
|
: sorting.SortDirection.ascending
|
||||||
} ${isDescending ? 'rotate-180' : ''}`}
|
if (nextDirection == null) {
|
||||||
/>
|
setSortInfo(null)
|
||||||
</UnstyledButton>
|
} else {
|
||||||
|
setSortInfo({ field: columnUtils.Column.modified, direction: nextDirection })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<aria.Text className="text-header">{getText('modifiedColumnName')}</aria.Text>
|
||||||
|
<img
|
||||||
|
alt={isDescending ? getText('sortDescending') : getText('sortAscending')}
|
||||||
|
src={SortAscendingIcon}
|
||||||
|
className={`transition-all duration-arrow ${
|
||||||
|
isSortActive ? 'selectable active' : 'transparent group-hover:selectable'
|
||||||
|
} ${isDescending ? 'rotate-180' : ''}`}
|
||||||
|
/>
|
||||||
|
</UnstyledButton>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import * as textProvider from '#/providers/TextProvider'
|
|||||||
import * as aria from '#/components/aria'
|
import * as aria from '#/components/aria'
|
||||||
import type * as column from '#/components/dashboard/column'
|
import type * as column from '#/components/dashboard/column'
|
||||||
import * as columnUtils from '#/components/dashboard/column/columnUtils'
|
import * as columnUtils from '#/components/dashboard/column/columnUtils'
|
||||||
import SvgMask from '#/components/SvgMask'
|
import Button from '#/components/styled/Button'
|
||||||
|
|
||||||
/** A heading for the "Shared with" column. */
|
/** A heading for the "Shared with" column. */
|
||||||
export default function SharedWithColumnHeading(props: column.AssetColumnHeadingProps) {
|
export default function SharedWithColumnHeading(props: column.AssetColumnHeadingProps) {
|
||||||
@ -18,12 +18,12 @@ export default function SharedWithColumnHeading(props: column.AssetColumnHeading
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-drive-table-heading w-full items-center gap-icon-with-text">
|
<div className="flex h-drive-table-heading w-full items-center gap-icon-with-text">
|
||||||
<SvgMask
|
<Button
|
||||||
src={PeopleIcon}
|
active
|
||||||
|
image={PeopleIcon}
|
||||||
className="size-icon"
|
className="size-icon"
|
||||||
title={getText('sharedWithColumnHide')}
|
alt={getText('sharedWithColumnHide')}
|
||||||
onClick={event => {
|
onPress={() => {
|
||||||
event.stopPropagation()
|
|
||||||
hideColumn(columnUtils.Column.sharedWith)
|
hideColumn(columnUtils.Column.sharedWith)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
/** @file A styled button. */
|
/** @file A styled button. */
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
|
import * as tailwindMerge from 'tailwind-merge'
|
||||||
|
|
||||||
import * as focusHooks from '#/hooks/focusHooks'
|
import * as focusHooks from '#/hooks/focusHooks'
|
||||||
|
|
||||||
import * as aria from '#/components/aria'
|
import * as aria from '#/components/aria'
|
||||||
|
import * as ariaComponents from '#/components/AriaComponents'
|
||||||
import FocusRing from '#/components/styled/FocusRing'
|
import FocusRing from '#/components/styled/FocusRing'
|
||||||
import SvgMask from '#/components/SvgMask'
|
import SvgMask from '#/components/SvgMask'
|
||||||
|
|
||||||
@ -13,6 +16,8 @@ import SvgMask from '#/components/SvgMask'
|
|||||||
|
|
||||||
/** Props for a {@link Button}. */
|
/** Props for a {@link Button}. */
|
||||||
export interface ButtonProps {
|
export interface ButtonProps {
|
||||||
|
/** Falls back to `aria-label`. Pass `false` to explicitly disable the tooltip. */
|
||||||
|
readonly tooltip?: React.ReactNode
|
||||||
readonly autoFocus?: boolean
|
readonly autoFocus?: boolean
|
||||||
/** When `true`, the button is not faded out even when not hovered. */
|
/** When `true`, the button is not faded out even when not hovered. */
|
||||||
readonly active?: boolean
|
readonly active?: boolean
|
||||||
@ -25,7 +30,10 @@ export interface ButtonProps {
|
|||||||
readonly alt?: string
|
readonly alt?: string
|
||||||
/** A title that is only shown when `disabled` is `true`. */
|
/** A title that is only shown when `disabled` is `true`. */
|
||||||
readonly error?: string | null
|
readonly error?: string | null
|
||||||
|
/** Class names for the icon itself. */
|
||||||
readonly className?: string
|
readonly className?: string
|
||||||
|
/** Extra class names for the `button` element wrapping the icon.
|
||||||
|
* This is useful for things like positioning the entire button (e.g. `absolute`). */
|
||||||
readonly buttonClassName?: string
|
readonly buttonClassName?: string
|
||||||
readonly onPress: (event: aria.PressEvent) => void
|
readonly onPress: (event: aria.PressEvent) => void
|
||||||
}
|
}
|
||||||
@ -33,31 +41,31 @@ export interface ButtonProps {
|
|||||||
/** A styled button. */
|
/** A styled button. */
|
||||||
function Button(props: ButtonProps, ref: React.ForwardedRef<HTMLButtonElement>) {
|
function Button(props: ButtonProps, ref: React.ForwardedRef<HTMLButtonElement>) {
|
||||||
const {
|
const {
|
||||||
|
tooltip,
|
||||||
active = false,
|
active = false,
|
||||||
softDisabled = false,
|
softDisabled = false,
|
||||||
image,
|
image,
|
||||||
error,
|
error,
|
||||||
alt,
|
alt,
|
||||||
className,
|
className,
|
||||||
buttonClassName = '',
|
buttonClassName,
|
||||||
...buttonProps
|
...buttonProps
|
||||||
} = props
|
} = props
|
||||||
const { isDisabled = false } = buttonProps
|
const { isDisabled = false } = buttonProps
|
||||||
const focusChildProps = focusHooks.useFocusChild()
|
const focusChildProps = focusHooks.useFocusChild()
|
||||||
|
|
||||||
return (
|
const tooltipElement = tooltip === false ? null : tooltip ?? alt
|
||||||
|
|
||||||
|
const button = (
|
||||||
<FocusRing placement="after">
|
<FocusRing placement="after">
|
||||||
<aria.Button
|
<aria.Button
|
||||||
{...aria.mergeProps<aria.ButtonProps>()(
|
{...aria.mergeProps<aria.ButtonProps>()(buttonProps, focusChildProps, {
|
||||||
buttonProps,
|
ref,
|
||||||
focusChildProps,
|
className: tailwindMerge.twMerge(
|
||||||
{
|
'relative after:pointer-events-none after:absolute after:inset-button-focus-ring-inset after:rounded-button-focus-ring',
|
||||||
ref,
|
buttonClassName
|
||||||
className:
|
),
|
||||||
'relative after:pointer-events-none after:absolute after:inset-button-focus-ring-inset after:rounded-button-focus-ring',
|
})}
|
||||||
},
|
|
||||||
{ className: buttonClassName }
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`group flex selectable ${isDisabled || softDisabled ? 'disabled' : ''} ${active ? 'active' : ''}`}
|
className={`group flex selectable ${isDisabled || softDisabled ? 'disabled' : ''} ${active ? 'active' : ''}`}
|
||||||
@ -72,6 +80,15 @@ function Button(props: ButtonProps, ref: React.ForwardedRef<HTMLButtonElement>)
|
|||||||
</aria.Button>
|
</aria.Button>
|
||||||
</FocusRing>
|
</FocusRing>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return tooltipElement == null ? (
|
||||||
|
button
|
||||||
|
) : (
|
||||||
|
<ariaComponents.TooltipTrigger>
|
||||||
|
{button}
|
||||||
|
<ariaComponents.Tooltip>{tooltipElement}</ariaComponents.Tooltip>
|
||||||
|
</ariaComponents.TooltipTrigger>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default React.forwardRef(Button)
|
export default React.forwardRef(Button)
|
||||||
|
@ -6,9 +6,11 @@ import EyeIcon from 'enso-assets/eye.svg'
|
|||||||
|
|
||||||
import * as focusHooks from '#/hooks/focusHooks'
|
import * as focusHooks from '#/hooks/focusHooks'
|
||||||
|
|
||||||
|
import * as textProvider from '#/providers/TextProvider'
|
||||||
|
|
||||||
import * as aria from '#/components/aria'
|
import * as aria from '#/components/aria'
|
||||||
|
import Button from '#/components/styled/Button'
|
||||||
import FocusRing from '#/components/styled/FocusRing'
|
import FocusRing from '#/components/styled/FocusRing'
|
||||||
import SvgMask from '#/components/SvgMask'
|
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// === SettingsInput ===
|
// === SettingsInput ===
|
||||||
@ -27,6 +29,7 @@ export interface SettingsInputProps {
|
|||||||
function SettingsInput(props: SettingsInputProps, ref: React.ForwardedRef<HTMLInputElement>) {
|
function SettingsInput(props: SettingsInputProps, ref: React.ForwardedRef<HTMLInputElement>) {
|
||||||
const { type, placeholder, autoComplete, onChange, onSubmit } = props
|
const { type, placeholder, autoComplete, onChange, onSubmit } = props
|
||||||
const focusChildProps = focusHooks.useFocusChild()
|
const focusChildProps = focusHooks.useFocusChild()
|
||||||
|
const { getText } = textProvider.useText()
|
||||||
// This is SAFE. The value of this context is never a `SlottedContext`.
|
// This is SAFE. The value of this context is never a `SlottedContext`.
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
const inputProps = (React.useContext(aria.InputContext) ?? null) as aria.InputProps | null
|
const inputProps = (React.useContext(aria.InputContext) ?? null) as aria.InputProps | null
|
||||||
@ -86,10 +89,12 @@ function SettingsInput(props: SettingsInputProps, ref: React.ForwardedRef<HTMLIn
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{type === 'password' && (
|
{type === 'password' && (
|
||||||
<SvgMask
|
<Button
|
||||||
src={isShowingPassword ? EyeIcon : EyeCrossedIcon}
|
active
|
||||||
className="absolute right-2 top-1 cursor-pointer rounded-full"
|
image={isShowingPassword ? EyeIcon : EyeCrossedIcon}
|
||||||
onClick={() => {
|
alt={isShowingPassword ? getText('hidePassword') : getText('showPassword')}
|
||||||
|
buttonClassName="absolute right-2 top-1 cursor-pointer rounded-full size-icon"
|
||||||
|
onPress={() => {
|
||||||
setIsShowingPassword(show => !show)
|
setIsShowingPassword(show => !show)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -96,8 +96,9 @@ function CategorySwitcherItem(props: InternalCategorySwitcherItemProps) {
|
|||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
>
|
>
|
||||||
<UnstyledButton
|
<UnstyledButton
|
||||||
aria-label={getText(buttonTextId)}
|
tooltip={false}
|
||||||
className={`rounded-inherit ${isCurrent ? 'focus-default' : ''}`}
|
className={`rounded-inherit ${isCurrent ? 'focus-default' : ''}`}
|
||||||
|
aria-label={getText(buttonTextId)}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
@ -10,9 +10,9 @@ import * as textProvider from '#/providers/TextProvider'
|
|||||||
import * as aria from '#/components/aria'
|
import * as aria from '#/components/aria'
|
||||||
import Label from '#/components/dashboard/Label'
|
import Label from '#/components/dashboard/Label'
|
||||||
import * as labelUtils from '#/components/dashboard/Label/labelUtils'
|
import * as labelUtils from '#/components/dashboard/Label/labelUtils'
|
||||||
|
import Button from '#/components/styled/Button'
|
||||||
import FocusArea from '#/components/styled/FocusArea'
|
import FocusArea from '#/components/styled/FocusArea'
|
||||||
import FocusRing from '#/components/styled/FocusRing'
|
import FocusRing from '#/components/styled/FocusRing'
|
||||||
import SvgMask from '#/components/SvgMask'
|
|
||||||
|
|
||||||
import ConfirmDeleteModal from '#/modals/ConfirmDeleteModal'
|
import ConfirmDeleteModal from '#/modals/ConfirmDeleteModal'
|
||||||
import DragModal from '#/modals/DragModal'
|
import DragModal from '#/modals/DragModal'
|
||||||
@ -127,8 +127,11 @@ export default function Labels(props: LabelsProps) {
|
|||||||
</Label>
|
</Label>
|
||||||
{!newLabelNames.has(label.value) && (
|
{!newLabelNames.has(label.value) && (
|
||||||
<FocusRing placement="after">
|
<FocusRing placement="after">
|
||||||
<aria.Button
|
<Button
|
||||||
className="relative flex after:absolute after:inset-button-focus-ring-inset after:rounded-button-focus-ring"
|
active
|
||||||
|
image={Trash2Icon}
|
||||||
|
alt={getText('delete')}
|
||||||
|
className="relative flex size-icon text-delete transition-all transparent after:absolute after:inset-button-focus-ring-inset after:rounded-button-focus-ring group-has-[[data-focus-visible]]:active group-hover:active"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setModal(
|
setModal(
|
||||||
<ConfirmDeleteModal
|
<ConfirmDeleteModal
|
||||||
@ -139,13 +142,7 @@ export default function Labels(props: LabelsProps) {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<SvgMask
|
|
||||||
src={Trash2Icon}
|
|
||||||
alt={getText('delete')}
|
|
||||||
className="size-icon text-delete transition-all transparent group-has-[[data-focus-visible]]:active group-hover:active"
|
|
||||||
/>
|
|
||||||
</aria.Button>
|
|
||||||
</FocusRing>
|
</FocusRing>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,17 +37,15 @@ interface PageUIData {
|
|||||||
readonly page: Page
|
readonly page: Page
|
||||||
readonly icon: string
|
readonly icon: string
|
||||||
readonly altId: Extract<text.TextId, `${Page}PageAltText`>
|
readonly altId: Extract<text.TextId, `${Page}PageAltText`>
|
||||||
readonly tooltipId: Extract<text.TextId, `${Page}PageTooltip`>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PAGE_DATA: PageUIData[] = [
|
const PAGE_DATA: PageUIData[] = [
|
||||||
{ page: Page.home, icon: HomeIcon, altId: 'homePageAltText', tooltipId: 'homePageTooltip' },
|
{ page: Page.home, icon: HomeIcon, altId: 'homePageAltText' },
|
||||||
{ page: Page.drive, icon: DriveIcon, altId: 'drivePageAltText', tooltipId: 'drivePageTooltip' },
|
{ page: Page.drive, icon: DriveIcon, altId: 'drivePageAltText' },
|
||||||
{
|
{
|
||||||
page: Page.editor,
|
page: Page.editor,
|
||||||
icon: NetworkIcon,
|
icon: NetworkIcon,
|
||||||
altId: 'editorPageAltText',
|
altId: 'editorPageAltText',
|
||||||
tooltipId: 'editorPageTooltip',
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -87,16 +85,16 @@ export default function PageSwitcher(props: PageSwitcherProps) {
|
|||||||
{...innerProps}
|
{...innerProps}
|
||||||
>
|
>
|
||||||
{PAGE_DATA.map(pageData => {
|
{PAGE_DATA.map(pageData => {
|
||||||
|
const error = ERRORS[pageData.page]
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
key={pageData.page}
|
key={pageData.page}
|
||||||
aria-label={getText(pageData.tooltipId)}
|
|
||||||
alt={getText(pageData.altId)}
|
alt={getText(pageData.altId)}
|
||||||
image={pageData.icon}
|
image={pageData.icon}
|
||||||
active={page === pageData.page}
|
active={page === pageData.page}
|
||||||
softDisabled={page === pageData.page}
|
softDisabled={page === pageData.page}
|
||||||
isDisabled={pageData.page === Page.editor && isEditorDisabled}
|
isDisabled={pageData.page === Page.editor && isEditorDisabled}
|
||||||
error={ERRORS[pageData.page]}
|
error={error == null ? null : getText(error)}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setPage(pageData.page)
|
setPage(pageData.page)
|
||||||
}}
|
}}
|
||||||
|
@ -75,6 +75,7 @@ export default function UserBar(props: UserBarProps) {
|
|||||||
<Button
|
<Button
|
||||||
active={isHelpChatOpen}
|
active={isHelpChatOpen}
|
||||||
image={ChatIcon}
|
image={ChatIcon}
|
||||||
|
alt={getText('chatButtonAltText')}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setIsHelpChatOpen(!isHelpChatOpen)
|
setIsHelpChatOpen(!isHelpChatOpen)
|
||||||
}}
|
}}
|
||||||
@ -82,6 +83,7 @@ export default function UserBar(props: UserBarProps) {
|
|||||||
{shouldShowInviteButton && (
|
{shouldShowInviteButton && (
|
||||||
<UnstyledButton
|
<UnstyledButton
|
||||||
className="text my-auto rounded-full bg-share px-button-x text-inversed"
|
className="text my-auto rounded-full bg-share px-button-x text-inversed"
|
||||||
|
aria-label={getText('inviteButtonAltText')}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setModal(<InviteUsersModal />)
|
setModal(<InviteUsersModal />)
|
||||||
}}
|
}}
|
||||||
@ -92,6 +94,7 @@ export default function UserBar(props: UserBarProps) {
|
|||||||
{shouldShowShareButton && (
|
{shouldShowShareButton && (
|
||||||
<UnstyledButton
|
<UnstyledButton
|
||||||
className="text my-auto rounded-full bg-share px-button-x text-inversed"
|
className="text my-auto rounded-full bg-share px-button-x text-inversed"
|
||||||
|
aria-label={getText('shareButtonAltText')}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setModal(
|
setModal(
|
||||||
<ManagePermissionsModal
|
<ManagePermissionsModal
|
||||||
@ -109,6 +112,7 @@ export default function UserBar(props: UserBarProps) {
|
|||||||
)}
|
)}
|
||||||
<UnstyledButton
|
<UnstyledButton
|
||||||
className="flex size-profile-picture select-none items-center overflow-clip rounded-full"
|
className="flex size-profile-picture select-none items-center overflow-clip rounded-full"
|
||||||
|
aria-label={getText('userMenuAltText')}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
updateModal(oldModal =>
|
updateModal(oldModal =>
|
||||||
oldModal?.type === UserMenu ? null : (
|
oldModal?.type === UserMenu ? null : (
|
||||||
|
@ -11,10 +11,10 @@ import * as textProvider from '#/providers/TextProvider'
|
|||||||
|
|
||||||
import * as aria from '#/components/aria'
|
import * as aria from '#/components/aria'
|
||||||
import Modal from '#/components/Modal'
|
import Modal from '#/components/Modal'
|
||||||
|
import Button from '#/components/styled/Button'
|
||||||
import ButtonRow from '#/components/styled/ButtonRow'
|
import ButtonRow from '#/components/styled/ButtonRow'
|
||||||
import FocusArea from '#/components/styled/FocusArea'
|
import FocusArea from '#/components/styled/FocusArea'
|
||||||
import FocusRing from '#/components/styled/FocusRing'
|
import FocusRing from '#/components/styled/FocusRing'
|
||||||
import SvgMask from '#/components/SvgMask'
|
|
||||||
import UnstyledButton from '#/components/UnstyledButton'
|
import UnstyledButton from '#/components/UnstyledButton'
|
||||||
|
|
||||||
import type * as backend from '#/services/Backend'
|
import type * as backend from '#/services/Backend'
|
||||||
@ -110,10 +110,10 @@ export default function UpsertSecretModal(props: UpsertSecretModalProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FocusRing>
|
</FocusRing>
|
||||||
<SvgMask
|
<Button
|
||||||
src={isShowingValue ? EyeIcon : EyeCrossedIcon}
|
image={isShowingValue ? EyeIcon : EyeCrossedIcon}
|
||||||
className="absolute right-2 top-1 cursor-pointer rounded-full"
|
className="absolute right-2 top-1 cursor-pointer rounded-full"
|
||||||
onClick={() => {
|
onPress={() => {
|
||||||
setIsShowingValue(show => !show)
|
setIsShowingValue(show => !show)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -222,11 +222,11 @@
|
|||||||
"homeCategory": "Home",
|
"homeCategory": "Home",
|
||||||
"rootCategory": "Root",
|
"rootCategory": "Root",
|
||||||
"trashCategory": "Trash",
|
"trashCategory": "Trash",
|
||||||
"recentCategoryButtonLabel": "Go to Recent category",
|
"recentCategoryButtonLabel": "Recent",
|
||||||
"draftsCategoryButtonLabel": "Go to Drafts category",
|
"draftsCategoryButtonLabel": "Drafts",
|
||||||
"homeCategoryButtonLabel": "Go to Home category",
|
"homeCategoryButtonLabel": "Home",
|
||||||
"rootCategoryButtonLabel": "Go to Root category",
|
"rootCategoryButtonLabel": "Root",
|
||||||
"trashCategoryButtonLabel": "Go to Trash category",
|
"trashCategoryButtonLabel": "Trash",
|
||||||
"recentCategoryDropZoneLabel": "Move to Recent category",
|
"recentCategoryDropZoneLabel": "Move to Recent category",
|
||||||
"draftsCategoryDropZoneLabel": "Move to Drafts category",
|
"draftsCategoryDropZoneLabel": "Move to Drafts category",
|
||||||
"homeCategoryDropZoneLabel": "Move to Home category",
|
"homeCategoryDropZoneLabel": "Move to Home category",
|
||||||
@ -235,8 +235,8 @@
|
|||||||
|
|
||||||
"newFolder": "New Folder",
|
"newFolder": "New Folder",
|
||||||
"newProject": "New Project",
|
"newProject": "New Project",
|
||||||
"uploadFiles": "Upload Files",
|
"uploadFiles": "Import",
|
||||||
"downloadFiles": "Download Files",
|
"downloadFiles": "Export",
|
||||||
"newDataLink": "New Data Link",
|
"newDataLink": "New Data Link",
|
||||||
"newSecret": "New Secret",
|
"newSecret": "New Secret",
|
||||||
"newLabel": "New Label",
|
"newLabel": "New Label",
|
||||||
@ -260,8 +260,8 @@
|
|||||||
"sortByTimestamp": "Sort by timestamp",
|
"sortByTimestamp": "Sort by timestamp",
|
||||||
"sortByTimestampDescending": "Sort by timestamp descending",
|
"sortByTimestampDescending": "Sort by timestamp descending",
|
||||||
"stopSortingByTimestamp": "Stop sorting by timestamp",
|
"stopSortingByTimestamp": "Stop sorting by timestamp",
|
||||||
"closeAssetPanel": "Close Asset Panel",
|
"closeAssetPanel": "Asset Panel",
|
||||||
"openAssetPanel": "Open Asset Panel",
|
"openAssetPanel": "Asset Panel",
|
||||||
"confirmEdit": "Confirm Edit",
|
"confirmEdit": "Confirm Edit",
|
||||||
"cancelEdit": "Cancel Edit",
|
"cancelEdit": "Cancel Edit",
|
||||||
"loadingAppMessage": "Logging in to Enso...",
|
"loadingAppMessage": "Logging in to Enso...",
|
||||||
@ -320,6 +320,8 @@
|
|||||||
"noVersionsFound": "No versions found",
|
"noVersionsFound": "No versions found",
|
||||||
"latestIndicator": "(Latest)",
|
"latestIndicator": "(Latest)",
|
||||||
"noDateSelected": "No date selected",
|
"noDateSelected": "No date selected",
|
||||||
|
"hidePassword": "Hide password",
|
||||||
|
"showPassword": "Show password",
|
||||||
|
|
||||||
"deleteLabelActionText": "delete the label '$0'",
|
"deleteLabelActionText": "delete the label '$0'",
|
||||||
"deleteSelectedAssetActionText": "delete '$0'",
|
"deleteSelectedAssetActionText": "delete '$0'",
|
||||||
@ -392,14 +394,14 @@
|
|||||||
"newsItemCommunityServer": "Join our community server",
|
"newsItemCommunityServer": "Join our community server",
|
||||||
"newsItemCommunityServerDescription": "Chat with our team and other Enso users.",
|
"newsItemCommunityServerDescription": "Chat with our team and other Enso users.",
|
||||||
|
|
||||||
"homePageAltText": "Home tab",
|
"chatButtonAltText": "Chat",
|
||||||
"drivePageAltText": "Drive tab",
|
"inviteButtonAltText": "Invite others to try Enso",
|
||||||
"editorPageAltText": "Project tab",
|
"shareButtonAltText": "Share",
|
||||||
"settingsPageAltText": "Settings tab",
|
"userMenuAltText": "User Settings",
|
||||||
"homePageTooltip": "Go to homepage",
|
"homePageAltText": "Home",
|
||||||
"drivePageTooltip": "Go to drive",
|
"drivePageAltText": "Catalog",
|
||||||
"editorPageTooltip": "Go to project",
|
"editorPageAltText": "Graph Editor",
|
||||||
"settingsPageTooltip": "Go to settings",
|
"settingsPageAltText": "Settings",
|
||||||
|
|
||||||
"soloPlanName": "Solo",
|
"soloPlanName": "Solo",
|
||||||
"teamPlanName": "Team",
|
"teamPlanName": "Team",
|
||||||
@ -476,20 +478,20 @@
|
|||||||
"pasteAllShortcut": "Paste All",
|
"pasteAllShortcut": "Paste All",
|
||||||
"deleteLabelShortcut": "Delete Label",
|
"deleteLabelShortcut": "Delete Label",
|
||||||
|
|
||||||
"nameColumnShow": "Show Name column",
|
"nameColumnShow": "Name",
|
||||||
"nameColumnHide": "Hide Name column",
|
"nameColumnHide": "Name",
|
||||||
"modifiedColumnShow": "Show Modified date column",
|
"modifiedColumnShow": "Modified",
|
||||||
"modifiedColumnHide": "Hide Modified date column",
|
"modifiedColumnHide": "Modified",
|
||||||
"sharedWithColumnShow": "Show Shared with column",
|
"sharedWithColumnShow": "Shared With",
|
||||||
"sharedWithColumnHide": "Hide Shared with column",
|
"sharedWithColumnHide": "Shared With",
|
||||||
"labelsColumnShow": "Show Labels column",
|
"labelsColumnShow": "Labels",
|
||||||
"labelsColumnHide": "Hide Labels column",
|
"labelsColumnHide": "Labels",
|
||||||
"accessedByProjectsColumnShow": "Show Accessed by projects column",
|
"accessedByProjectsColumnShow": "Accessed By Projects",
|
||||||
"accessedByProjectsColumnHide": "Hide Accessed by projects column",
|
"accessedByProjectsColumnHide": "Accessed By Projects",
|
||||||
"accessedDataColumnShow": "Show Accessed data column",
|
"accessedDataColumnShow": "Accessed Data",
|
||||||
"accessedDataColumnHide": "Hide Accessed data column",
|
"accessedDataColumnHide": "Accessed Data",
|
||||||
"docsColumnShow": "Show Docs column",
|
"docsColumnShow": "Docs",
|
||||||
"docsColumnHide": "Hide Doc columns",
|
"docsColumnHide": "Docs",
|
||||||
|
|
||||||
"activityLog": "Activity Log",
|
"activityLog": "Activity Log",
|
||||||
"startDate": "Start Date",
|
"startDate": "Start Date",
|
||||||
|
Loading…
Reference in New Issue
Block a user