mirror of
https://github.com/langgenius/dify.git
synced 2026-09-05 08:48:10 +08:00
fix(web): align workspace switcher states and semantics (#41713)
This commit is contained in:
parent
8f7cca3ae2
commit
642ad2fffe
@ -194,6 +194,27 @@ describe('WorkspaceCard', () => {
|
|||||||
expect(screen.getByRole('button', { name: workspaceMenuAccessibleName })).toBeInTheDocument()
|
expect(screen.getByRole('button', { name: workspaceMenuAccessibleName })).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('keeps full workspace names on the final interactive title owner', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
renderWorkspaceCard()
|
||||||
|
|
||||||
|
const trigger = screen.getByRole('button', { name: workspaceMenuAccessibleName })
|
||||||
|
expect(trigger).toHaveAttribute('title', 'Solar Studio')
|
||||||
|
expect(within(trigger).getByText('Solar Studio')).not.toHaveAttribute('title')
|
||||||
|
|
||||||
|
await user.click(trigger)
|
||||||
|
|
||||||
|
const panel = await screen.findByRole('dialog', { name: 'Solar Studio' })
|
||||||
|
const workspaceItem = within(panel).getByRole('button', {
|
||||||
|
name: 'Evan Workspace',
|
||||||
|
})
|
||||||
|
expect(workspaceItem).toHaveAttribute('title', 'Evan Workspace')
|
||||||
|
expect(within(workspaceItem).getByText('Evan Workspace')).not.toHaveAttribute('title')
|
||||||
|
expect(
|
||||||
|
within(panel).getByRole('button', { name: 'common.mainNav.workspace.settings' }),
|
||||||
|
).toHaveAttribute('title', 'common.mainNav.workspace.settings')
|
||||||
|
})
|
||||||
|
|
||||||
it('includes the visible workspace plan in the menu trigger accessible name', () => {
|
it('includes the visible workspace plan in the menu trigger accessible name', () => {
|
||||||
renderWorkspaceCard({ systemFeatures: { deployment_edition: 'CLOUD' } })
|
renderWorkspaceCard({ systemFeatures: { deployment_edition: 'CLOUD' } })
|
||||||
|
|
||||||
@ -308,8 +329,12 @@ describe('WorkspaceCard', () => {
|
|||||||
expect(
|
expect(
|
||||||
within(panel).getByRole('button', { name: 'common.mainNav.workspace.sort.openMenu' }),
|
within(panel).getByRole('button', { name: 'common.mainNav.workspace.sort.openMenu' }),
|
||||||
).toBeDisabled()
|
).toBeDisabled()
|
||||||
expect(within(panel).getByRole('button', { name: 'common.operation.search' })).toBeDisabled()
|
expect(within(panel).getByRole('button', { name: 'common.operation.search' })).toHaveAttribute(
|
||||||
|
'aria-disabled',
|
||||||
|
'true',
|
||||||
|
)
|
||||||
expect(panel.querySelector('[aria-busy="true"]')).toBeInTheDocument()
|
expect(panel.querySelector('[aria-busy="true"]')).toBeInTheDocument()
|
||||||
|
expect(within(panel).getByRole('status', { name: 'common.loading' })).toBeInTheDocument()
|
||||||
expect(within(panel).queryByRole('button', { name: 'Evan Workspace' })).not.toBeInTheDocument()
|
expect(within(panel).queryByRole('button', { name: 'Evan Workspace' })).not.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -390,32 +415,79 @@ describe('WorkspaceCard', () => {
|
|||||||
).toBeInTheDocument()
|
).toBeInTheDocument()
|
||||||
const workspaceItem = within(panel).getByRole('button', { name: 'Evan Workspace' })
|
const workspaceItem = within(panel).getByRole('button', { name: 'Evan Workspace' })
|
||||||
expect(workspaceItem).toBeInTheDocument()
|
expect(workspaceItem).toBeInTheDocument()
|
||||||
expect(workspaceItem.parentElement).toHaveClass('max-h-[240px]', 'overflow-y-auto')
|
const workspaceList = within(panel).getByRole('list', {
|
||||||
|
name: 'common.userProfile.workspace',
|
||||||
|
})
|
||||||
|
expect(within(workspaceList).getAllByRole('listitem')).toHaveLength(2)
|
||||||
|
expect(workspaceList.parentElement).toHaveClass('max-h-[240px]', 'overflow-y-auto')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('filters workspace switcher options from the search action', async () => {
|
it('filters workspace switcher options from the search action', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
renderWorkspaceCard()
|
renderWorkspaceCard()
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: workspaceMenuAccessibleName }))
|
await user.click(screen.getByRole('button', { name: workspaceMenuAccessibleName }))
|
||||||
fireEvent.click(await screen.findByRole('button', { name: 'common.operation.search' }))
|
const searchTrigger = await screen.findByRole('button', { name: 'common.operation.search' })
|
||||||
|
expect(searchTrigger).toHaveAttribute('aria-expanded', 'false')
|
||||||
|
|
||||||
|
await user.click(searchTrigger)
|
||||||
|
|
||||||
expect(screen.getByText('common.userProfile.workspace')).toBeInTheDocument()
|
expect(screen.getByText('common.userProfile.workspace')).toBeInTheDocument()
|
||||||
expect(
|
expect(
|
||||||
screen.getByRole('button', { name: 'common.mainNav.workspace.sort.openMenu' }),
|
screen.getByRole('button', { name: 'common.mainNav.workspace.sort.openMenu' }),
|
||||||
).toBeInTheDocument()
|
).toBeInTheDocument()
|
||||||
expect(screen.getByRole('button', { name: 'common.operation.search' })).toHaveClass(
|
expect(searchTrigger).toHaveAttribute('aria-expanded', 'true')
|
||||||
'bg-state-base-hover',
|
const controlledPanelId = searchTrigger.getAttribute('aria-controls')
|
||||||
)
|
expect(controlledPanelId).toBeTruthy()
|
||||||
|
|
||||||
fireEvent.change(screen.getByPlaceholderText('common.mainNav.workspace.searchPlaceholder'), {
|
const searchInput = screen.getByPlaceholderText('common.mainNav.workspace.searchPlaceholder')
|
||||||
target: { value: 'evan' },
|
expect(document.getElementById(controlledPanelId!)).toContainElement(searchInput)
|
||||||
})
|
expect(searchInput).toHaveFocus()
|
||||||
|
await user.type(searchInput, 'evan')
|
||||||
|
|
||||||
const panel = screen.getByRole('dialog', { name: 'Solar Studio' })
|
const panel = screen.getByRole('dialog', { name: 'Solar Studio' })
|
||||||
expect(within(panel).getByRole('button', { name: 'Evan Workspace' })).toBeInTheDocument()
|
expect(within(panel).getByRole('button', { name: 'Evan Workspace' })).toBeInTheDocument()
|
||||||
expect(within(panel).queryByRole('button', { name: 'Solar Studio' })).not.toBeInTheDocument()
|
expect(within(panel).queryByRole('button', { name: 'Solar Studio' })).not.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('announces an empty workspace search result', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
renderWorkspaceCard()
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: workspaceMenuAccessibleName }))
|
||||||
|
await user.click(await screen.findByRole('button', { name: 'common.operation.search' }))
|
||||||
|
await user.type(
|
||||||
|
screen.getByPlaceholderText('common.mainNav.workspace.searchPlaceholder'),
|
||||||
|
'missing',
|
||||||
|
)
|
||||||
|
|
||||||
|
const panel = screen.getByRole('dialog', { name: 'Solar Studio' })
|
||||||
|
expect(within(panel).getByRole('status')).toHaveTextContent(
|
||||||
|
'common.mainNav.workspace.noResults',
|
||||||
|
)
|
||||||
|
expect(
|
||||||
|
within(panel).queryByRole('list', { name: 'common.userProfile.workspace' }),
|
||||||
|
).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('exposes only the current workspace as current', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
renderWorkspaceCard()
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: workspaceMenuAccessibleName }))
|
||||||
|
|
||||||
|
const panel = await screen.findByRole('dialog', { name: 'Solar Studio' })
|
||||||
|
const workspaceList = within(panel).getByRole('list', {
|
||||||
|
name: 'common.userProfile.workspace',
|
||||||
|
})
|
||||||
|
expect(
|
||||||
|
within(workspaceList).getByRole('button', { name: 'Solar Studio', current: true }),
|
||||||
|
).toBeInTheDocument()
|
||||||
|
expect(
|
||||||
|
within(workspaceList).getByRole('button', { name: 'Evan Workspace' }),
|
||||||
|
).not.toHaveAttribute('aria-current')
|
||||||
|
})
|
||||||
|
|
||||||
it('sorts workspaces by last opened and can sort by created time', async () => {
|
it('sorts workspaces by last opened and can sort by created time', async () => {
|
||||||
mockWorkspaces = [
|
mockWorkspaces = [
|
||||||
{
|
{
|
||||||
@ -451,7 +523,10 @@ describe('WorkspaceCard', () => {
|
|||||||
fireEvent.click(screen.getByRole('button', { name: workspaceMenuAccessibleName }))
|
fireEvent.click(screen.getByRole('button', { name: workspaceMenuAccessibleName }))
|
||||||
|
|
||||||
const panel = await screen.findByRole('dialog', { name: 'Solar Studio' })
|
const panel = await screen.findByRole('dialog', { name: 'Solar Studio' })
|
||||||
const defaultWorkspaceOptions = within(panel)
|
const workspaceList = within(panel).getByRole('list', {
|
||||||
|
name: 'common.userProfile.workspace',
|
||||||
|
})
|
||||||
|
const defaultWorkspaceOptions = within(workspaceList)
|
||||||
.getAllByRole('button')
|
.getAllByRole('button')
|
||||||
.map((item) => item.getAttribute('title'))
|
.map((item) => item.getAttribute('title'))
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@ -476,7 +551,7 @@ describe('WorkspaceCard', () => {
|
|||||||
screen.getByRole('menuitemradio', { name: 'common.mainNav.workspace.sort.createdTime' }),
|
screen.getByRole('menuitemradio', { name: 'common.mainNav.workspace.sort.createdTime' }),
|
||||||
)
|
)
|
||||||
|
|
||||||
const createdTimeWorkspaceOptions = within(panel)
|
const createdTimeWorkspaceOptions = within(workspaceList)
|
||||||
.getAllByRole('button')
|
.getAllByRole('button')
|
||||||
.map((item) => item.getAttribute('title'))
|
.map((item) => item.getAttribute('title'))
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@ -488,6 +563,39 @@ describe('WorkspaceCard', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('closes the nested sort menu before the workspace popover on Escape', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
renderWorkspaceCard()
|
||||||
|
|
||||||
|
const workspaceTrigger = screen.getByRole('button', { name: workspaceMenuAccessibleName })
|
||||||
|
await user.click(workspaceTrigger)
|
||||||
|
const panel = await screen.findByRole('dialog', { name: 'Solar Studio' })
|
||||||
|
const sortTrigger = within(panel).getByRole('button', {
|
||||||
|
name: 'common.mainNav.workspace.sort.openMenu',
|
||||||
|
})
|
||||||
|
await user.click(sortTrigger)
|
||||||
|
expect(
|
||||||
|
await screen.findByRole('menuitemradio', {
|
||||||
|
name: 'common.mainNav.workspace.sort.lastOpened',
|
||||||
|
}),
|
||||||
|
).toBeInTheDocument()
|
||||||
|
|
||||||
|
await user.keyboard('{Escape}')
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByRole('menuitemradio', {
|
||||||
|
name: 'common.mainNav.workspace.sort.lastOpened',
|
||||||
|
}),
|
||||||
|
).not.toBeInTheDocument()
|
||||||
|
expect(panel).toBeInTheDocument()
|
||||||
|
expect(sortTrigger).toHaveFocus()
|
||||||
|
|
||||||
|
await user.keyboard('{Escape}')
|
||||||
|
|
||||||
|
await waitFor(() => expect(panel).not.toBeInTheDocument())
|
||||||
|
expect(workspaceTrigger).toHaveFocus()
|
||||||
|
})
|
||||||
|
|
||||||
it('opens account settings from workspace menu actions', async () => {
|
it('opens account settings from workspace menu actions', async () => {
|
||||||
renderWorkspaceCard()
|
renderWorkspaceCard()
|
||||||
|
|
||||||
@ -527,6 +635,20 @@ describe('WorkspaceCard', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('closes the popover without switching when the current workspace is selected', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
renderWorkspaceCard()
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: workspaceMenuAccessibleName }))
|
||||||
|
const panel = await screen.findByRole('dialog', { name: 'Solar Studio' })
|
||||||
|
await user.click(within(panel).getByRole('button', { name: 'Solar Studio', current: true }))
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.queryByRole('dialog', { name: 'Solar Studio' })).not.toBeInTheDocument(),
|
||||||
|
)
|
||||||
|
expect(mockSwitchWorkspace).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
it('keeps workspace settings visible for dataset operators without member management permission', async () => {
|
it('keeps workspace settings visible for dataset operators without member management permission', async () => {
|
||||||
mockCurrentWorkspaceQuery({
|
mockCurrentWorkspaceQuery({
|
||||||
...currentWorkspaceValue,
|
...currentWorkspaceValue,
|
||||||
|
|||||||
@ -0,0 +1,62 @@
|
|||||||
|
import type { TenantListItemResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||||
|
import { page, userEvent } from 'vite-plus/test/browser'
|
||||||
|
import { render } from 'vitest-browser-react'
|
||||||
|
import { WorkspaceSwitcher } from '../workspace-switcher'
|
||||||
|
|
||||||
|
const workspaces: TenantListItemResponse[] = [
|
||||||
|
{
|
||||||
|
id: 'workspace-current',
|
||||||
|
name: 'Solar Studio',
|
||||||
|
plan: 'sandbox',
|
||||||
|
status: 'normal',
|
||||||
|
created_at: 2,
|
||||||
|
current: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'workspace-other',
|
||||||
|
name: 'Solar Studio',
|
||||||
|
plan: 'team',
|
||||||
|
status: 'normal',
|
||||||
|
created_at: 1,
|
||||||
|
current: false,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
describe('WorkspaceSwitcher visual states', () => {
|
||||||
|
it('keeps current, hover, and focus-visible as independent states', async () => {
|
||||||
|
// Chromium owns the rendered hover/focus-visible output that happy-dom cannot reproduce.
|
||||||
|
const screen = await render(
|
||||||
|
<WorkspaceSwitcher workspaces={workspaces} isPending={false} onSwitchWorkspace={vi.fn()} />,
|
||||||
|
)
|
||||||
|
const workspaceList = screen.getByRole('list', { name: 'common.userProfile.workspace' })
|
||||||
|
const workspaceItems = workspaceList.getByRole('listitem')
|
||||||
|
const current = workspaceItems.nth(0).getByRole('button')
|
||||||
|
const other = workspaceItems.nth(1).getByRole('button')
|
||||||
|
const captureList = () => page.screenshot({ element: workspaceList, save: false })
|
||||||
|
|
||||||
|
const currentResting = await page.screenshot({ element: current, save: false })
|
||||||
|
const otherResting = await page.screenshot({ element: other, save: false })
|
||||||
|
expect(currentResting).not.toBe(otherResting)
|
||||||
|
|
||||||
|
const resting = await captureList()
|
||||||
|
|
||||||
|
await other.hover()
|
||||||
|
const hover = await captureList()
|
||||||
|
expect(hover).not.toBe(resting)
|
||||||
|
|
||||||
|
await userEvent.unhover(other)
|
||||||
|
await current.hover()
|
||||||
|
const currentHover = await captureList()
|
||||||
|
expect(currentHover).not.toBe(resting)
|
||||||
|
expect(currentHover).not.toBe(hover)
|
||||||
|
|
||||||
|
await userEvent.unhover(current)
|
||||||
|
await userEvent.tab()
|
||||||
|
await userEvent.tab()
|
||||||
|
await userEvent.tab()
|
||||||
|
expect(current.element()).toHaveFocus()
|
||||||
|
const focusVisible = await captureList()
|
||||||
|
expect(focusVisible).not.toBe(resting)
|
||||||
|
expect(focusVisible).not.toBe(currentHover)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -126,10 +126,7 @@ function WorkspaceCardTrigger({
|
|||||||
</span>
|
</span>
|
||||||
<div className="min-w-0 grow">
|
<div className="min-w-0 grow">
|
||||||
<div className="flex min-w-0 items-center gap-1 pr-0.5">
|
<div className="flex min-w-0 items-center gap-1 pr-0.5">
|
||||||
<span
|
<span className="max-w-30 min-w-0 shrink truncate system-sm-medium text-text-primary">
|
||||||
className="max-w-30 min-w-0 shrink truncate system-sm-medium text-text-primary"
|
|
||||||
title={name}
|
|
||||||
>
|
|
||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
{showStatus && <span className="flex shrink-0 items-center">{status}</span>}
|
{showStatus && <span className="flex shrink-0 items-center">{status}</span>}
|
||||||
@ -183,8 +180,8 @@ function WorkspaceMenuHeader({
|
|||||||
name: string
|
name: string
|
||||||
status: ReactNode
|
status: ReactNode
|
||||||
showInviteMembers: boolean
|
showInviteMembers: boolean
|
||||||
settingsLabel: ReactNode
|
settingsLabel: string
|
||||||
inviteMembersLabel: ReactNode
|
inviteMembersLabel: string
|
||||||
onOpenSettings: () => void
|
onOpenSettings: () => void
|
||||||
onInviteMembers: () => void
|
onInviteMembers: () => void
|
||||||
}) {
|
}) {
|
||||||
@ -205,6 +202,7 @@ function WorkspaceMenuHeader({
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
title={settingsLabel}
|
||||||
className="flex h-8 w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-1 text-left outline-hidden hover:bg-state-base-hover focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
|
className="flex h-8 w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-1 text-left outline-hidden hover:bg-state-base-hover focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
|
||||||
onClick={onOpenSettings}
|
onClick={onOpenSettings}
|
||||||
>
|
>
|
||||||
@ -218,6 +216,7 @@ function WorkspaceMenuHeader({
|
|||||||
{showInviteMembers && (
|
{showInviteMembers && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
title={inviteMembersLabel}
|
||||||
className="flex h-8 w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-1 text-left outline-hidden hover:bg-state-base-hover focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
|
className="flex h-8 w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-1 text-left outline-hidden hover:bg-state-base-hover focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
|
||||||
onClick={onInviteMembers}
|
onClick={onInviteMembers}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
|
||||||
export function WorkspaceMenuItemContent({
|
export function WorkspaceMenuItemContent({
|
||||||
icon,
|
icon,
|
||||||
label,
|
label,
|
||||||
trailing,
|
trailing,
|
||||||
|
trailingClassName,
|
||||||
}: {
|
}: {
|
||||||
icon: ReactNode
|
icon: ReactNode
|
||||||
label: ReactNode
|
label: ReactNode
|
||||||
trailing?: ReactNode
|
trailing?: ReactNode
|
||||||
|
trailingClassName?: string
|
||||||
}) {
|
}) {
|
||||||
const labelTitle = typeof label === 'string' ? label : undefined
|
|
||||||
const showTrailing = trailing !== undefined && trailing !== null
|
const showTrailing = trailing !== undefined && trailing !== null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -20,14 +22,15 @@ export function WorkspaceMenuItemContent({
|
|||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span className="min-w-0 flex-1 truncate text-left system-md-regular text-text-secondary">
|
||||||
className="min-w-0 flex-1 truncate text-left system-md-regular text-text-secondary"
|
|
||||||
title={labelTitle}
|
|
||||||
>
|
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
{showTrailing && (
|
{showTrailing && (
|
||||||
<span className="flex h-4 w-4 shrink-0 items-center justify-center">{trailing}</span>
|
<span
|
||||||
|
className={cn('flex h-4 w-4 shrink-0 items-center justify-center', trailingClassName)}
|
||||||
|
>
|
||||||
|
{trailing}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import type { TenantListItemResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
import type { TenantListItemResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from '@langgenius/dify-ui/collapsible'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@ -10,14 +11,13 @@ import {
|
|||||||
DropdownMenuRadioItemIndicator,
|
DropdownMenuRadioItemIndicator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@langgenius/dify-ui/dropdown-menu'
|
} from '@langgenius/dify-ui/dropdown-menu'
|
||||||
import { useMemo, useState } from 'react'
|
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||||
|
import { useId, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { SearchInput } from '@/app/components/base/search-input'
|
import { SearchInput } from '@/app/components/base/search-input'
|
||||||
import { WorkspaceAvatar } from '@/app/components/base/workspace-avatar'
|
import { WorkspaceAvatar } from '@/app/components/base/workspace-avatar'
|
||||||
import { WorkspaceMenuItemContent } from './workspace-menu-content'
|
import { WorkspaceMenuItemContent } from './workspace-menu-content'
|
||||||
|
|
||||||
const workspaceSwitchActionButtonClassName =
|
|
||||||
'flex shrink-0 items-center justify-center rounded-md p-0.5 text-text-tertiary outline-hidden hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid disabled:cursor-not-allowed disabled:text-text-disabled disabled:hover:bg-transparent disabled:hover:text-text-disabled'
|
|
||||||
const workspaceSwitchActionIconWrapClassName = 'flex size-5 shrink-0 items-center justify-center'
|
const workspaceSwitchActionIconWrapClassName = 'flex size-5 shrink-0 items-center justify-center'
|
||||||
const workspaceSwitchActionIconClassName = 'size-3.5 shrink-0'
|
const workspaceSwitchActionIconClassName = 'size-3.5 shrink-0'
|
||||||
const workspaceSwitchListClassName = 'max-h-[240px] overflow-y-auto overscroll-contain scroll-py-1'
|
const workspaceSwitchListClassName = 'max-h-[240px] overflow-y-auto overscroll-contain scroll-py-1'
|
||||||
@ -31,19 +31,20 @@ const getWorkspaceLastOpenedAt = (workspace: TenantListItemResponse) =>
|
|||||||
|
|
||||||
function WorkspaceSwitchControls({
|
function WorkspaceSwitchControls({
|
||||||
disabled,
|
disabled,
|
||||||
|
labelId,
|
||||||
searchText,
|
searchText,
|
||||||
sort,
|
sort,
|
||||||
onSearchTextChange,
|
onSearchTextChange,
|
||||||
onSortChange,
|
onSortChange,
|
||||||
}: {
|
}: {
|
||||||
disabled: boolean
|
disabled: boolean
|
||||||
|
labelId: string
|
||||||
searchText: string
|
searchText: string
|
||||||
sort: WorkspaceSort
|
sort: WorkspaceSort
|
||||||
onSearchTextChange: (value: string) => void
|
onSearchTextChange: (value: string) => void
|
||||||
onSortChange: (value: WorkspaceSort) => void
|
onSortChange: (value: WorkspaceSort) => void
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [searchVisible, setSearchVisible] = useState(false)
|
|
||||||
const [sortMenuOpen, setSortMenuOpen] = useState(false)
|
const [sortMenuOpen, setSortMenuOpen] = useState(false)
|
||||||
const sortMenuLabel = t(($) => $[workspaceSwitchI18nKey('mainNav.workspace.sort.openMenu')], {
|
const sortMenuLabel = t(($) => $[workspaceSwitchI18nKey('mainNav.workspace.sort.openMenu')], {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
@ -64,26 +65,28 @@ function WorkspaceSwitchControls({
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Collapsible>
|
||||||
<div className="flex items-start gap-0.5 py-1 pr-2 pl-3">
|
<div className="flex items-start gap-0.5 py-1 pr-2 pl-3">
|
||||||
<div className="flex min-w-0 flex-1 items-center justify-center py-1">
|
<div className="flex min-w-0 flex-1 items-center justify-center py-1">
|
||||||
<span className="min-w-0 flex-1 truncate system-xs-medium-uppercase text-text-tertiary">
|
<span
|
||||||
|
id={labelId}
|
||||||
|
className="min-w-0 flex-1 truncate system-xs-medium-uppercase text-text-tertiary"
|
||||||
|
>
|
||||||
{t(($) => $['userProfile.workspace'], { ns: 'common' })}
|
{t(($) => $['userProfile.workspace'], { ns: 'common' })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu open={sortMenuOpen} onOpenChange={setSortMenuOpen}>
|
<DropdownMenu open={sortMenuOpen} onOpenChange={setSortMenuOpen}>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
aria-label={sortMenuLabel}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={cn(
|
className="data-popup-open:bg-state-base-hover data-popup-open:text-text-secondary"
|
||||||
workspaceSwitchActionButtonClassName,
|
render={
|
||||||
'data-popup-open:bg-state-base-hover data-popup-open:text-text-secondary',
|
<IconButton aria-label={sortMenuLabel}>
|
||||||
)}
|
<span aria-hidden className={workspaceSwitchActionIconWrapClassName}>
|
||||||
>
|
<span className={cn('i-ri-sort-desc', workspaceSwitchActionIconClassName)} />
|
||||||
<span aria-hidden className={workspaceSwitchActionIconWrapClassName}>
|
</span>
|
||||||
<span className={cn('i-ri-sort-desc', workspaceSwitchActionIconClassName)} />
|
</IconButton>
|
||||||
</span>
|
}
|
||||||
</DropdownMenuTrigger>
|
/>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
sideOffset={4}
|
sideOffset={4}
|
||||||
@ -113,22 +116,19 @@ function WorkspaceSwitchControls({
|
|||||||
</DropdownMenuRadioGroup>
|
</DropdownMenuRadioGroup>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<button
|
<CollapsibleTrigger
|
||||||
type="button"
|
|
||||||
aria-label={t(($) => $['operation.search'], { ns: 'common' })}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={cn(
|
className="size-6 min-h-0 w-6 justify-center gap-0 rounded-md p-0.5 data-panel-open:bg-state-base-hover data-panel-open:text-text-secondary"
|
||||||
workspaceSwitchActionButtonClassName,
|
render={
|
||||||
searchVisible && 'bg-state-base-hover text-text-secondary',
|
<IconButton aria-label={t(($) => $['operation.search'], { ns: 'common' })}>
|
||||||
)}
|
<span aria-hidden className={workspaceSwitchActionIconWrapClassName}>
|
||||||
onClick={() => setSearchVisible((visible) => !visible)}
|
<span className={cn('i-ri-search-line', workspaceSwitchActionIconClassName)} />
|
||||||
>
|
</span>
|
||||||
<span aria-hidden className={workspaceSwitchActionIconWrapClassName}>
|
</IconButton>
|
||||||
<span className={cn('i-ri-search-line', workspaceSwitchActionIconClassName)} />
|
}
|
||||||
</span>
|
/>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
{searchVisible && (
|
<CollapsiblePanel>
|
||||||
<div className="px-2 pb-2">
|
<div className="px-2 pb-2">
|
||||||
<SearchInput
|
<SearchInput
|
||||||
value={searchText}
|
value={searchText}
|
||||||
@ -140,8 +140,8 @@ function WorkspaceSwitchControls({
|
|||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</CollapsiblePanel>
|
||||||
</>
|
</Collapsible>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,6 +156,8 @@ export function WorkspaceSwitcher({
|
|||||||
isPending,
|
isPending,
|
||||||
onSwitchWorkspace,
|
onSwitchWorkspace,
|
||||||
}: WorkspaceSwitcherProps) {
|
}: WorkspaceSwitcherProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const workspaceListLabelId = useId()
|
||||||
const [workspaceSearchText, setWorkspaceSearchText] = useState('')
|
const [workspaceSearchText, setWorkspaceSearchText] = useState('')
|
||||||
const [workspaceSort, setWorkspaceSort] = useState<WorkspaceSort>('lastOpened')
|
const [workspaceSort, setWorkspaceSort] = useState<WorkspaceSort>('lastOpened')
|
||||||
const displayedWorkspaces = useMemo(() => {
|
const displayedWorkspaces = useMemo(() => {
|
||||||
@ -183,6 +185,7 @@ export function WorkspaceSwitcher({
|
|||||||
<div className="p-1 pb-2">
|
<div className="p-1 pb-2">
|
||||||
<WorkspaceSwitchControls
|
<WorkspaceSwitchControls
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
|
labelId={workspaceListLabelId}
|
||||||
searchText={workspaceSearchText}
|
searchText={workspaceSearchText}
|
||||||
sort={workspaceSort}
|
sort={workspaceSort}
|
||||||
onSearchTextChange={setWorkspaceSearchText}
|
onSearchTextChange={setWorkspaceSearchText}
|
||||||
@ -190,39 +193,54 @@ export function WorkspaceSwitcher({
|
|||||||
/>
|
/>
|
||||||
<div aria-busy={isPending} className={workspaceSwitchListClassName}>
|
<div aria-busy={isPending} className={workspaceSwitchListClassName}>
|
||||||
{isPending ? (
|
{isPending ? (
|
||||||
<div aria-hidden className="flex h-8 items-center justify-center">
|
<div
|
||||||
<span className="i-ri-loader-2-line size-4 animate-spin text-text-tertiary motion-reduce:animate-none" />
|
role="status"
|
||||||
|
aria-label={t(($) => $.loading, { ns: 'common' })}
|
||||||
|
className="flex h-8 items-center justify-center"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className="i-ri-loader-2-line size-4 animate-spin text-text-tertiary motion-reduce:animate-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : displayedWorkspaces.length === 0 ? (
|
||||||
|
<div
|
||||||
|
role="status"
|
||||||
|
className="flex h-8 items-center px-3 system-xs-regular text-text-tertiary"
|
||||||
|
>
|
||||||
|
{t(($) => $[workspaceSwitchI18nKey('mainNav.workspace.noResults')], {
|
||||||
|
ns: 'common',
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
displayedWorkspaces.map((workspace) => {
|
<ul aria-labelledby={workspaceListLabelId}>
|
||||||
const workspaceName = getWorkspaceName(workspace)
|
{displayedWorkspaces.map((workspace) => {
|
||||||
|
const workspaceName = getWorkspaceName(workspace)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<li key={workspace.id}>
|
||||||
type="button"
|
<button
|
||||||
key={workspace.id}
|
type="button"
|
||||||
aria-current={workspace.current ? 'true' : undefined}
|
aria-current={workspace.current ? 'true' : undefined}
|
||||||
title={workspaceName}
|
title={workspaceName}
|
||||||
className={cn(
|
className="group flex h-8 w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-1 text-left outline-hidden hover:bg-state-base-hover focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
|
||||||
'flex h-8 w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-1 text-left outline-hidden hover:bg-state-base-hover focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid',
|
onClick={() => {
|
||||||
workspace.current && 'bg-state-base-hover',
|
onSwitchWorkspace(workspace.id)
|
||||||
)}
|
}}
|
||||||
onClick={() => {
|
>
|
||||||
onSwitchWorkspace(workspace.id)
|
<WorkspaceMenuItemContent
|
||||||
}}
|
icon={<WorkspaceAvatar name={workspaceName} size="xs" />}
|
||||||
>
|
label={workspaceName}
|
||||||
<WorkspaceMenuItemContent
|
trailingClassName="hidden group-aria-current:flex"
|
||||||
icon={<WorkspaceAvatar name={workspaceName} size="xs" />}
|
trailing={
|
||||||
label={workspaceName}
|
<span aria-hidden className="i-ri-check-line h-4 w-4 text-text-accent" />
|
||||||
trailing={
|
}
|
||||||
workspace.current ? (
|
/>
|
||||||
<span aria-hidden className="i-ri-check-line h-4 w-4 text-text-accent" />
|
</button>
|
||||||
) : undefined
|
</li>
|
||||||
}
|
)
|
||||||
/>
|
})}
|
||||||
</button>
|
</ul>
|
||||||
)
|
|
||||||
})
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} رصيد",
|
"mainNav.workspace.credits": "{{count}} رصيد",
|
||||||
"mainNav.workspace.creditsUnit": "أرصدة",
|
"mainNav.workspace.creditsUnit": "أرصدة",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "لم يتم العثور على مساحات عمل",
|
||||||
"mainNav.workspace.openMenu": "فتح قائمة مساحة العمل",
|
"mainNav.workspace.openMenu": "فتح قائمة مساحة العمل",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} Credits",
|
"mainNav.workspace.credits": "{{count}} Credits",
|
||||||
"mainNav.workspace.creditsUnit": "Credits",
|
"mainNav.workspace.creditsUnit": "Credits",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "Keine Arbeitsbereiche gefunden",
|
||||||
"mainNav.workspace.openMenu": "Workspace-Menü öffnen",
|
"mainNav.workspace.openMenu": "Workspace-Menü öffnen",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} credits",
|
"mainNav.workspace.credits": "{{count}} credits",
|
||||||
"mainNav.workspace.creditsUnit": "credits",
|
"mainNav.workspace.creditsUnit": "credits",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "No workspaces found",
|
||||||
"mainNav.workspace.openMenu": "Open workspace menu",
|
"mainNav.workspace.openMenu": "Open workspace menu",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} créditos",
|
"mainNav.workspace.credits": "{{count}} créditos",
|
||||||
"mainNav.workspace.creditsUnit": "créditos",
|
"mainNav.workspace.creditsUnit": "créditos",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "No se encontraron espacios de trabajo",
|
||||||
"mainNav.workspace.openMenu": "Abrir menú del espacio de trabajo",
|
"mainNav.workspace.openMenu": "Abrir menú del espacio de trabajo",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} اعتبار",
|
"mainNav.workspace.credits": "{{count}} اعتبار",
|
||||||
"mainNav.workspace.creditsUnit": "اعتبار",
|
"mainNav.workspace.creditsUnit": "اعتبار",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "هیچ فضای کاری یافت نشد",
|
||||||
"mainNav.workspace.openMenu": "باز کردن منوی فضای کاری",
|
"mainNav.workspace.openMenu": "باز کردن منوی فضای کاری",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} crédits",
|
"mainNav.workspace.credits": "{{count}} crédits",
|
||||||
"mainNav.workspace.creditsUnit": "crédits",
|
"mainNav.workspace.creditsUnit": "crédits",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "Aucun espace de travail trouvé",
|
||||||
"mainNav.workspace.openMenu": "Ouvrir le menu de l’espace de travail",
|
"mainNav.workspace.openMenu": "Ouvrir le menu de l’espace de travail",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} क्रेडिट",
|
"mainNav.workspace.credits": "{{count}} क्रेडिट",
|
||||||
"mainNav.workspace.creditsUnit": "क्रेडिट",
|
"mainNav.workspace.creditsUnit": "क्रेडिट",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "कोई वर्कस्पेस नहीं मिला",
|
||||||
"mainNav.workspace.openMenu": "वर्कस्पेस मेनू खोलें",
|
"mainNav.workspace.openMenu": "वर्कस्पेस मेनू खोलें",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} kredit",
|
"mainNav.workspace.credits": "{{count}} kredit",
|
||||||
"mainNav.workspace.creditsUnit": "kredit",
|
"mainNav.workspace.creditsUnit": "kredit",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "Tidak ada workspace yang ditemukan",
|
||||||
"mainNav.workspace.openMenu": "Buka menu workspace",
|
"mainNav.workspace.openMenu": "Buka menu workspace",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} crediti",
|
"mainNav.workspace.credits": "{{count}} crediti",
|
||||||
"mainNav.workspace.creditsUnit": "crediti",
|
"mainNav.workspace.creditsUnit": "crediti",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "Nessun workspace trovato",
|
||||||
"mainNav.workspace.openMenu": "Apri menu workspace",
|
"mainNav.workspace.openMenu": "Apri menu workspace",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} クレジット",
|
"mainNav.workspace.credits": "{{count}} クレジット",
|
||||||
"mainNav.workspace.creditsUnit": "クレジット",
|
"mainNav.workspace.creditsUnit": "クレジット",
|
||||||
"mainNav.workspace.inviteMembers": "メンバーを招待",
|
"mainNav.workspace.inviteMembers": "メンバーを招待",
|
||||||
|
"mainNav.workspace.noResults": "ワークスペースが見つかりません",
|
||||||
"mainNav.workspace.openMenu": "ワークスペースメニューを開く",
|
"mainNav.workspace.openMenu": "ワークスペースメニューを開く",
|
||||||
"mainNav.workspace.searchPlaceholder": "ワークスペースを検索",
|
"mainNav.workspace.searchPlaceholder": "ワークスペースを検索",
|
||||||
"mainNav.workspace.settings": "ワークスペース設定",
|
"mainNav.workspace.settings": "ワークスペース設定",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} 크레딧",
|
"mainNav.workspace.credits": "{{count}} 크레딧",
|
||||||
"mainNav.workspace.creditsUnit": "크레딧",
|
"mainNav.workspace.creditsUnit": "크레딧",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "워크스페이스를 찾을 수 없습니다",
|
||||||
"mainNav.workspace.openMenu": "워크스페이스 메뉴 열기",
|
"mainNav.workspace.openMenu": "워크스페이스 메뉴 열기",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} ເຄຣດິດ",
|
"mainNav.workspace.credits": "{{count}} ເຄຣດິດ",
|
||||||
"mainNav.workspace.creditsUnit": "ເຄຣດິດ",
|
"mainNav.workspace.creditsUnit": "ເຄຣດິດ",
|
||||||
"mainNav.workspace.inviteMembers": "ເຊີນສະມາຊິກ",
|
"mainNav.workspace.inviteMembers": "ເຊີນສະມາຊິກ",
|
||||||
|
"mainNav.workspace.noResults": "ບໍ່ພົບພື້ນທີ່ເຮັດວຽກ",
|
||||||
"mainNav.workspace.openMenu": "ເປີດເມນູພື້ນທີ່ເຮັດວຽກ",
|
"mainNav.workspace.openMenu": "ເປີດເມນູພື້ນທີ່ເຮັດວຽກ",
|
||||||
"mainNav.workspace.searchPlaceholder": "ຄົ້ນຫາພື້ນທີ່ເຮັດວຽກ",
|
"mainNav.workspace.searchPlaceholder": "ຄົ້ນຫາພື້ນທີ່ເຮັດວຽກ",
|
||||||
"mainNav.workspace.settings": "ການຕັ້ງຄ່າພື້ນທີ່ເຮັດວຽກ",
|
"mainNav.workspace.settings": "ການຕັ້ງຄ່າພື້ນທີ່ເຮັດວຽກ",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} credits",
|
"mainNav.workspace.credits": "{{count}} credits",
|
||||||
"mainNav.workspace.creditsUnit": "credits",
|
"mainNav.workspace.creditsUnit": "credits",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "Geen werkruimtes gevonden",
|
||||||
"mainNav.workspace.openMenu": "Werkruimtemenu openen",
|
"mainNav.workspace.openMenu": "Werkruimtemenu openen",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} kredytów",
|
"mainNav.workspace.credits": "{{count}} kredytów",
|
||||||
"mainNav.workspace.creditsUnit": "kredyty",
|
"mainNav.workspace.creditsUnit": "kredyty",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "Nie znaleziono obszarów roboczych",
|
||||||
"mainNav.workspace.openMenu": "Otwórz menu obszaru roboczego",
|
"mainNav.workspace.openMenu": "Otwórz menu obszaru roboczego",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} créditos",
|
"mainNav.workspace.credits": "{{count}} créditos",
|
||||||
"mainNav.workspace.creditsUnit": "créditos",
|
"mainNav.workspace.creditsUnit": "créditos",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "Nenhum workspace encontrado",
|
||||||
"mainNav.workspace.openMenu": "Abrir menu do workspace",
|
"mainNav.workspace.openMenu": "Abrir menu do workspace",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} credite",
|
"mainNav.workspace.credits": "{{count}} credite",
|
||||||
"mainNav.workspace.creditsUnit": "credite",
|
"mainNav.workspace.creditsUnit": "credite",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "Nu s-au găsit spații de lucru",
|
||||||
"mainNav.workspace.openMenu": "Deschide meniul spațiului de lucru",
|
"mainNav.workspace.openMenu": "Deschide meniul spațiului de lucru",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} кредитов",
|
"mainNav.workspace.credits": "{{count}} кредитов",
|
||||||
"mainNav.workspace.creditsUnit": "кредиты",
|
"mainNav.workspace.creditsUnit": "кредиты",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "Рабочие пространства не найдены",
|
||||||
"mainNav.workspace.openMenu": "Открыть меню рабочей области",
|
"mainNav.workspace.openMenu": "Открыть меню рабочей области",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} kreditov",
|
"mainNav.workspace.credits": "{{count}} kreditov",
|
||||||
"mainNav.workspace.creditsUnit": "krediti",
|
"mainNav.workspace.creditsUnit": "krediti",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "Ni najdenih delovnih prostorov",
|
||||||
"mainNav.workspace.openMenu": "Odpri meni delovnega prostora",
|
"mainNav.workspace.openMenu": "Odpri meni delovnega prostora",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} เครดิต",
|
"mainNav.workspace.credits": "{{count}} เครดิต",
|
||||||
"mainNav.workspace.creditsUnit": "เครดิต",
|
"mainNav.workspace.creditsUnit": "เครดิต",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "ไม่พบพื้นที่ทำงาน",
|
||||||
"mainNav.workspace.openMenu": "เปิดเมนูพื้นที่ทำงาน",
|
"mainNav.workspace.openMenu": "เปิดเมนูพื้นที่ทำงาน",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} kredi",
|
"mainNav.workspace.credits": "{{count}} kredi",
|
||||||
"mainNav.workspace.creditsUnit": "kredi",
|
"mainNav.workspace.creditsUnit": "kredi",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "Çalışma alanı bulunamadı",
|
||||||
"mainNav.workspace.openMenu": "Çalışma alanı menüsünü aç",
|
"mainNav.workspace.openMenu": "Çalışma alanı menüsünü aç",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} кредитів",
|
"mainNav.workspace.credits": "{{count}} кредитів",
|
||||||
"mainNav.workspace.creditsUnit": "кредити",
|
"mainNav.workspace.creditsUnit": "кредити",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "Робочі простори не знайдено",
|
||||||
"mainNav.workspace.openMenu": "Відкрити меню робочого простору",
|
"mainNav.workspace.openMenu": "Відкрити меню робочого простору",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} tín dụng",
|
"mainNav.workspace.credits": "{{count}} tín dụng",
|
||||||
"mainNav.workspace.creditsUnit": "tín dụng",
|
"mainNav.workspace.creditsUnit": "tín dụng",
|
||||||
"mainNav.workspace.inviteMembers": "Invite members",
|
"mainNav.workspace.inviteMembers": "Invite members",
|
||||||
|
"mainNav.workspace.noResults": "Không tìm thấy không gian làm việc",
|
||||||
"mainNav.workspace.openMenu": "Mở menu workspace",
|
"mainNav.workspace.openMenu": "Mở menu workspace",
|
||||||
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
"mainNav.workspace.searchPlaceholder": "Search workspace",
|
||||||
"mainNav.workspace.settings": "Workspace settings",
|
"mainNav.workspace.settings": "Workspace settings",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} 消息额度",
|
"mainNav.workspace.credits": "{{count}} 消息额度",
|
||||||
"mainNav.workspace.creditsUnit": "消息额度",
|
"mainNav.workspace.creditsUnit": "消息额度",
|
||||||
"mainNav.workspace.inviteMembers": "邀请成员",
|
"mainNav.workspace.inviteMembers": "邀请成员",
|
||||||
|
"mainNav.workspace.noResults": "未找到工作空间",
|
||||||
"mainNav.workspace.openMenu": "打开工作空间菜单",
|
"mainNav.workspace.openMenu": "打开工作空间菜单",
|
||||||
"mainNav.workspace.searchPlaceholder": "搜索工作空间",
|
"mainNav.workspace.searchPlaceholder": "搜索工作空间",
|
||||||
"mainNav.workspace.settings": "工作空间设置",
|
"mainNav.workspace.settings": "工作空间设置",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"mainNav.workspace.credits": "{{count}} 點額度",
|
"mainNav.workspace.credits": "{{count}} 點額度",
|
||||||
"mainNav.workspace.creditsUnit": "點額度",
|
"mainNav.workspace.creditsUnit": "點額度",
|
||||||
"mainNav.workspace.inviteMembers": "邀請成員",
|
"mainNav.workspace.inviteMembers": "邀請成員",
|
||||||
|
"mainNav.workspace.noResults": "找不到工作區",
|
||||||
"mainNav.workspace.openMenu": "開啟工作區選單",
|
"mainNav.workspace.openMenu": "開啟工作區選單",
|
||||||
"mainNav.workspace.searchPlaceholder": "搜尋工作區",
|
"mainNav.workspace.searchPlaceholder": "搜尋工作區",
|
||||||
"mainNav.workspace.settings": "工作區設定",
|
"mainNav.workspace.settings": "工作區設定",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user