refactor(icons): migrate workspace to Icones

This commit is contained in:
Maofeng
2026-09-20 15:52:22 +08:00
parent 1fc56b6982
commit ea00a71112
95 changed files with 1409 additions and 725 deletions
+4 -8
View File
@@ -11,16 +11,14 @@
"@base-ui/react": "^1.6.0",
"@base-ui/utils": "^0.3.1",
"@floating-ui/utils": "^0.2.12",
"@icones/react": "^0.0.1",
"@lexical/react": "^0.48.0",
"@tanstack/react-hotkeys": "^0.10.0",
"@tanstack/react-query": "^5.101.4",
"@tanstack/react-router": "^1.170.18",
"@workspace/icons": "workspace:*",
"@workspace/i18n": "workspace:*",
"@workspace/ui": "workspace:*",
"cmdk": "^1.1.1",
"lexical": "^0.48.0",
"lucide-react": "^1.27.0",
"react": "^19.2.6",
"react-dom": "^19.2.6"
},
@@ -46,6 +44,7 @@
"./layout": "./src/blocks/layout/index.ts",
"./layout/locales": "./src/blocks/layout/locales/catalogs.ts",
"./layout/locales/*": "./src/blocks/layout/locales/*.ts",
"./layouts/vega": "./src/blocks/layouts/vega/index.tsx",
"./media": "./src/blocks/media/index.ts",
"./media/locales": "./src/blocks/media/locales/catalogs.ts",
"./media/locales/*": "./src/blocks/media/locales/*.ts",
@@ -54,9 +53,6 @@
"./navigation/locales/*": "./src/blocks/navigation/locales/*.ts",
"./notifications": "./src/blocks/notifications/index.ts",
"./notifications/locales": "./src/blocks/notifications/locales/catalogs.ts",
"./notifications/locales/*": "./src/blocks/notifications/locales/*.ts",
"./search": "./src/blocks/search/index.ts",
"./search/locales": "./src/blocks/search/locales/catalogs.ts",
"./search/locales/*": "./src/blocks/search/locales/*.ts"
"./notifications/locales/*": "./src/blocks/notifications/locales/*.ts"
}
}
}
@@ -1,4 +1,4 @@
import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react"
import { MonitorIcon, MoonIcon, SunIcon } from "@workspace/ui/components/icon"
import { LocalizedText, useMessage } from "@workspace/i18n"
import {
Select,
@@ -1,4 +1,4 @@
import { MoonIcon, SunIcon } from "lucide-react"
import { MoonIcon, SunIcon } from "@workspace/ui/components/icon"
import { LocalizedText } from "@workspace/i18n"
import { useUiState } from "./ui-state"
import { Badge } from "@workspace/ui/components/badge"
@@ -2,7 +2,7 @@ import React from "react"
import { Button } from "@workspace/ui/components/button"
import { useUiState } from "./ui-state"
import { Icon, Moon02Icon, Sun03Icon } from "@workspace/icons"
import { Icon } from "@icones/react"
import { cn } from "@workspace/ui/lib/utils"
type ThemeToggleButtonProps = Omit<
@@ -50,7 +50,7 @@ export function ThemeToggleButton({
>
<Icon
strokeWidth={2.5}
data={resolvedTheme === "dark" ? Sun03Icon : Moon02Icon}
name={resolvedTheme === "dark" ? "huge:sun-03" : "huge:moon-02"}
/>
</Button>
)
@@ -27,7 +27,7 @@ import {
SendHorizontalIcon,
SmileIcon,
XIcon,
} from "lucide-react"
} from "@workspace/ui/components/icon"
import { customerServiceEmojiGroups, customerServiceEmojis } from "./emojis"
import { chatMessages } from "./messages"
@@ -6,7 +6,7 @@ import {
} from "@workspace/ui/components/avatar"
import { Input } from "@workspace/ui/components/input"
import { cn } from "@workspace/ui/lib/utils"
import { SearchIcon } from "lucide-react"
import { SearchIcon } from "@workspace/ui/components/icon"
import { chatMessages } from "./messages"
import type { ChatConversation } from "./types"
@@ -34,7 +34,7 @@ import {
ImageIcon,
LinkIcon,
MessageCircleIcon,
} from "lucide-react"
} from "@workspace/ui/components/icon"
import { chatMessages } from "./messages"
import type {
@@ -10,7 +10,11 @@ import {
} from "@workspace/ui/components/empty"
import { useIsMobile } from "@workspace/ui/hooks/use-breakpoint"
import { cn } from "@workspace/ui/lib/utils"
import { ArrowLeftIcon, InfoIcon, MessageCircleIcon } from "lucide-react"
import {
ArrowLeftIcon,
InfoIcon,
MessageCircleIcon,
} from "@workspace/ui/components/icon"
import { ChatComposer } from "./chat-composer"
import {
@@ -1,6 +1,6 @@
import { Link } from "@tanstack/react-router"
import * as React from "react"
import { Icon } from "@workspace/icons"
import { Icon } from "@icones/react"
import { useMessage } from "@workspace/i18n"
import {
Breadcrumb,
@@ -102,24 +102,24 @@ function useVisibleEntryCount(entries: readonly BreadcrumbEntry[]) {
const breadcrumb = breadcrumbRef.current
const list = listRef.current
const measurement = measurementRef.current
const measurementElement = measurementRef.current
if (!breadcrumb || !list || !measurement) {
if (!breadcrumb || !list || !measurementElement) {
return
}
const updateVisibleCount = () => {
const entryWidths = Array.from(
measurement.querySelectorAll<HTMLElement>(
measurementElement.querySelectorAll<HTMLElement>(
"[data-breadcrumb-measure-entry]"
)
).map((element) => element.getBoundingClientRect().width)
const menuWidth =
measurement
measurementElement
.querySelector<HTMLElement>("[data-breadcrumb-measure-menu]")
?.getBoundingClientRect().width ?? 0
const separatorWidth =
measurement
measurementElement
.querySelector<HTMLElement>("[data-breadcrumb-measure-separator]")
?.getBoundingClientRect().width ?? 0
const listStyle = window.getComputedStyle(list)
@@ -169,7 +169,7 @@ function useVisibleEntryCount(entries: readonly BreadcrumbEntry[]) {
const observer = new ResizeObserver(updateVisibleCount)
observer.observe(breadcrumb)
observer.observe(measurement)
observer.observe(measurementElement)
return () => observer.disconnect()
}, [entries, entriesKey, maximumVisibleCount])
@@ -190,7 +190,7 @@ function useVisibleEntryCount(entries: readonly BreadcrumbEntry[]) {
function BreadcrumbEntryContent({ entry }: { entry: BreadcrumbEntry }) {
return (
<>
{entry.icon && <Icon data={entry.icon} className="size-4 shrink-0" />}
{entry.icon && <Icon name={entry.icon} className="size-4 shrink-0" />}
<NavigationLabel label={entry.label} />
</>
)
@@ -326,7 +326,7 @@ function BreadcrumbMenu({ entries }: { entries: readonly BreadcrumbEntry[] }) {
const content = (
<>
{entry.icon && (
<Icon data={entry.icon} className="size-4 shrink-0" />
<Icon name={entry.icon} className="size-4 shrink-0" />
)}
<NavigationLabel label={entry.label} />
</>
@@ -409,7 +409,7 @@ export function AppBreadcrumb() {
<BreadcrumbPage className="inline-flex min-w-0 items-center gap-1.5 text-primary">
{item.entry.icon && (
<Icon
data={item.entry.icon}
name={item.entry.icon}
className="size-4 shrink-0"
/>
)}
@@ -440,7 +440,7 @@ export function AppBreadcrumb() {
className="inline-flex items-center gap-1.5 font-normal"
>
{entry.icon && (
<Icon data={entry.icon} className="size-4 shrink-0" />
<Icon name={entry.icon} className="size-4 shrink-0" />
)}
<NavigationLabel label={entry.label} />
</span>
@@ -1,11 +1,10 @@
import * as React from "react"
import { ChevronLeftIcon, ChevronRightIcon, SearchIcon } from "lucide-react"
import {
BellIcon,
Icon,
MessageMultiple01Icon,
Search01Icon,
} from "@workspace/icons"
ChevronLeftIcon,
ChevronRightIcon,
SearchIcon,
} from "@workspace/ui/components/icon"
import { Icon } from "@icones/react"
import { Button } from "@workspace/ui/components/button"
import { Kbd } from "@workspace/ui/components/kbd"
import { useIsTablet } from "@workspace/ui/hooks/use-breakpoint"
@@ -153,12 +152,14 @@ function AppHeader({ actions }: { actions?: React.ReactNode }) {
<GlobalSearchTrigger />
{actions}
<NotificationCenterTrigger
render={<HeaderActionButton icon={BellIcon} showIndicator />}
render={
<HeaderActionButton icon="huge:notification-01" showIndicator />
}
/>
<ChatThreadTrigger
render={
<HeaderActionButton
icon={MessageMultiple01Icon}
icon="huge:message-multiple-01"
showIndicator
/>
}
@@ -201,7 +202,7 @@ function GlobalSearchTrigger() {
variant="secondary"
>
<Icon
data={Search01Icon}
name="huge:search-01"
className={cn(
"pointer-event-none size-5 transition-all not-mobile:hidden",
headerActionIconClassName
@@ -1,4 +1,3 @@
import { ReloadIcon } from "@workspace/icons"
import { useMessage } from "@workspace/i18n"
import { useRefresh } from "../../hooks/use-refresh"
import {
@@ -36,7 +35,7 @@ export function AppQueryIndicator({ className }: { className?: string }) {
aria-label={refreshLabel}
className={cn("[&_svg]:size-4.5", className)}
disabled={!canRefresh}
icon={ReloadIcon}
icon="huge:reload"
onClick={refresh}
/>
}
@@ -1,4 +1,4 @@
import { Icon, type IconData } from "@workspace/icons"
import { Icon, type IconName } from "@icones/react"
import { Button, type ButtonProps } from "@workspace/ui/components/button"
import { cn } from "@workspace/ui/lib/utils"
@@ -6,7 +6,7 @@ export const headerActionIconClassName =
"transition-all transform origin-center group-hover/button:scale-105"
type HeaderActionButtonProps = ButtonProps & {
icon?: IconData
icon?: IconName<"huge">
showIndicator?: boolean
}
@@ -20,7 +20,7 @@ export function HeaderActionButton({
return (
<Button variant="ghost" size="icon-lg" className={cn(className)} {...props}>
{icon ? (
<Icon data={icon} className={cn("size-5", headerActionIconClassName)} />
<Icon name={icon} className={cn("size-5", headerActionIconClassName)} />
) : (
children
)}
@@ -1,12 +1,5 @@
import { formatForDisplay } from "@tanstack/react-hotkeys"
import {
Icon,
type IconData,
KeyboardIcon,
LogoutCircle02Icon,
ShieldKeyIcon,
SwatchIcon,
} from "@workspace/icons"
import { Icon, type IconName } from "@icones/react"
import { useMessage } from "@workspace/i18n"
import { Button, type ButtonProps } from "@workspace/ui/components/button"
import {
@@ -49,7 +42,7 @@ interface UserMenuItem {
id: string
action?: NavigationAction
shortcut?: string
icon: IconData
icon: IconName<"huge">
label: string
}
@@ -137,18 +130,18 @@ function UserMenuContent({ onItemSelect }: { onItemSelect: VoidFunction }) {
{
id: "appearance",
action: navigationActions.appearance,
icon: SwatchIcon,
icon: "huge:swatch",
label: appearanceLabel,
shortcut: "Mod+,",
},
{
id: "keybindings",
icon: KeyboardIcon,
icon: "huge:keyboard",
label: keybindingsLabel,
},
{
id: "account-password",
icon: ShieldKeyIcon,
icon: "huge:shield-key",
label: accountPasswordLabel,
shortcut: "Mod+P",
},
@@ -172,14 +165,14 @@ function UserMenuContent({ onItemSelect }: { onItemSelect: VoidFunction }) {
className="w-full justify-start font-normal"
size="lg"
variant="ghost"
nativeButton={item.action ? true : false}
nativeButton={Boolean(item.action)}
render={item.action ? undefined : <a />}
onClick={onItemSelect}
{...(item.action
? getNavigationCommandProps(item.action)
: undefined)}
>
<Icon data={item.icon} size="sm" />
<Icon name={item.icon} size="sm" />
{item.label}
{item.shortcut && <MenuShortcut shortcut={item.shortcut} />}
</Button>
@@ -189,7 +182,7 @@ function UserMenuContent({ onItemSelect }: { onItemSelect: VoidFunction }) {
</div>
<div className="rounded-b-md border-t p-2">
<Button className="w-full font-normal" variant="destructive" size="lg">
<Icon data={LogoutCircle02Icon} size="sm" />
<Icon name="huge:logout-circle-02" size="sm" />
{logoutLabel}
<MenuShortcutSequence shortcuts={["Q", "Q", "Q"]} />
</Button>
@@ -0,0 +1,156 @@
import { Icon } from "@icones/react";
import { Button } from "@workspace/ui/components/button";
import { cn } from "@workspace/ui/lib/utils";
import React from "react";
type Props = {
children: React.ReactNode
className?: string
}
/*
+------------------+
| header |
+-------+-+--------+
| | | |
| aside | | main |
| | | |
+-------+-+--------+
^
|
gap
*/
export function Layout({ children, className }: Props) {
return (
<div
className={cn(
"isolate",
"[--app-header-height:--spacing(18)]",
"[--app-aside-width:--spacing(56)]",
"[--app-split-gap:--spacing(4)]",
className,
)}
data-slot="root"
>
<header className="z-10 sticky top-0 h-0" data-slot="header">
<div className="h-(--app-header-height)">
header
</div>
<div className="border-t h-0 absolute inset-x-0 top-(--app-header-height)" />
</header>
<div className="flex gap-(--app-split-gap)">
<aside
className="w-(--app-aside-width) h-svh pt-(--app-header-height) shrink-0"
data-slot="aside"
>
<div className="flex flex-col items-stretch h-full py-4">
<Navigation items={groups} />
<Navigation className="mt-auto" items={[links[1]]} />
</div>
</aside>
<main className="pt-(--app-header-height)" data-slot="main">
<div className="p-4">
{children}
</div>
</main>
</div>
</div>
)
}
const links = [
{ key: "inbox", label: "收件箱", icon: <Icon name="tabler:inbox" /> },
{ key: "starred", label: "已加星标", icon: <Icon name="tabler:star-filled" /> },
{ key: "sent", label: "已发送", icon: <Icon name="tabler:send" /> },
{ key: "drafts", label: "草稿", icon: <Icon name="tabler:edit" /> },
{ key: "deleted", label: "已删除", icon: <Icon name="tabler:trash" /> },
{ key: "sent-mail", label: "已发邮件", icon: <Icon name="tabler:send" /> },
]
const groups = [
{
key: "group-1",
label: "group 1",
items: [...links],
},
{
key: "group-2",
label: "group 2",
items: [...links],
},
...links.slice(2, 4),
]
type NavItem = { key: string; label: string; icon: React.ReactNode }
type NavGroup = { key: string; label: string; items: Array<NavItem> }
type NavItems = Array<NavGroup | NavItem>
function isGroup(item: NavGroup | NavItem): item is NavGroup {
return (item as NavGroup).items !== undefined
}
function Navigation({ items, className }: { items: NavItems; className?: string }) {
return (
<ul className={className}>
{items.map((item, index) => {
if (isGroup(item)) {
const previousItem = index > 0 ? items[index - 1] : undefined
return (
<li
key={item.key}
className={cn(
"not-last:mb-4",
previousItem && isGroup(previousItem)
? "mt-0"
: "not-first:mt-4",
)}
>
<span className="text-xs px-2.5 text-muted-foreground">{item.label}</span>
<ul>
{item.items.map(subItem => (
<li key={subItem.key}>
<NavigationItem
label={subItem.label}
icon={subItem.icon}
current={item.key === "drafts"}
/>
</li>
))}
</ul>
</li>
);
}
return (
<li key={item.key}>
<NavigationItem
label={item.label}
icon={item.icon}
current={item.key === "drafts"}
/>
</li>
);
})}
</ul>
)
}
function NavigationItem({ label, icon, current }: {
label: string
icon: React.ReactNode
current: boolean
}) {
return (
<Button
variant="ghost"
aria-current={current ? "page" : undefined}
className="flex w-full justify-start rounded-l-none rounded-r-full aria-[current=page]:text-red-600 aria-[current=page]:bg-current/10 [&_svg:not([class*='size-'])]:size-5"
size="lg"
>
{icon}
{label}
</Button>
)
}
@@ -11,7 +11,7 @@ import {
PencilIcon,
Trash2Icon,
UploadIcon,
} from "lucide-react"
} from "@workspace/ui/components/icon"
import { useTranslate } from "@workspace/i18n"
import {
ContextMenu,
@@ -49,6 +49,10 @@ export interface MediaContextMenuProps {
onUpload: (target: MediaStorageTarget) => void
}
function runAfterClose(action: VoidFunction) {
window.setTimeout(action)
}
export function MediaContextMenu({
asset,
children,
@@ -73,8 +77,6 @@ export function MediaContextMenu({
const cloudTargets = storageTargets.filter(
(target) => target.kind === "cloud"
)
const runAfterClose = (action: VoidFunction) => window.setTimeout(action)
const copy = async (text: string, successMessage: string) => {
try {
await navigator.clipboard.writeText(text)
@@ -5,7 +5,7 @@ import {
HeartIcon,
ImageIcon,
Trash2Icon,
} from "lucide-react"
} from "@workspace/ui/components/icon"
import { useTranslate } from "@workspace/i18n"
import { Button } from "@workspace/ui/components/button"
import {
@@ -1,4 +1,4 @@
import { CheckIcon, FilmIcon, HeartIcon } from "lucide-react"
import { CheckIcon, FilmIcon, HeartIcon } from "@workspace/ui/components/icon"
import { useTranslate } from "@workspace/i18n"
import { cn } from "@workspace/ui/lib/utils"
@@ -7,7 +7,7 @@ import {
LoaderCircleIcon,
SearchIcon,
UploadIcon,
} from "lucide-react"
} from "@workspace/ui/components/icon"
import { useTranslate } from "@workspace/i18n"
import {
AlertDialog,
@@ -57,7 +57,6 @@ import type {
MediaStorageTarget,
} from "./types"
import {
defaultMediaReference,
getMediaFolderPath,
isMediaFile,
mediaKindForFile,
@@ -67,7 +66,6 @@ import {
const PAGE_SIZE = 40
const EMPTY_ASSETS: readonly MediaAsset[] = []
const EMPTY_FOLDERS: readonly MediaFolder[] = []
const DEFAULT_STORAGE_TARGET: MediaStorageTarget = {
id: "local",
kind: "local",
@@ -216,15 +214,7 @@ export function MediaLibrary({
} finally {
if (assetRequestId.current === requestId) setIsLoadingAssets(false)
}
}, [
adapter,
allowedKinds,
allowedKindsKey,
deferredKeyword,
filter,
folder,
t,
])
}, [adapter, allowedKinds, deferredKeyword, filter, folder, t])
React.useEffect(() => {
void refreshFolders()
+1 -1
View File
@@ -12,7 +12,7 @@ import {
PlusIcon,
Trash2Icon,
UnlinkIcon,
} from "lucide-react"
} from "@workspace/ui/components/icon"
import { useTranslate } from "@workspace/i18n"
import { Button } from "@workspace/ui/components/button"
import {
@@ -1,4 +1,4 @@
import { Icon } from "@workspace/icons"
import { Icon } from "@icones/react"
import {
DropdownMenuItem,
DropdownMenuSub,
@@ -23,7 +23,7 @@ export function SidebarFlyoutMenuItems({
return items.map((item) => {
const icon = resolveNavigationItemIcon(item)
const iconElement = icon ? (
<Icon data={icon} className="size-5 shrink-0" />
<Icon name={icon} className="size-5 shrink-0" />
) : null
const routeState = getRouteState(item.to)
@@ -1,10 +1,10 @@
import type { IconData } from "@workspace/icons"
import type { IconName } from "@icones/react"
import type { NavigationItem } from "./types"
export function resolveNavigationItemIcon(
item: NavigationItem,
visibleByDefault = false
): IconData | undefined {
): IconName<"huge"> | undefined {
return (item.showIcon ?? visibleByDefault) ? item.icon : undefined
}
@@ -1,5 +1,6 @@
import { useLocation } from "@tanstack/react-router"
import { Icon, Menu02Icon } from "@workspace/icons"
import { Fragment } from "react"
import { Icon } from "@icones/react"
import { useMessage } from "@workspace/i18n"
import type { ButtonProps } from "@workspace/ui/components/button"
import { Separator } from "@workspace/ui/components/separator"
@@ -13,8 +14,8 @@ import {
SheetTrigger,
} from "@workspace/ui/components/sheet"
import { useOnBreakpoint } from "@workspace/ui/hooks/use-breakpoint"
import { cn, separate } from "@workspace/ui/lib/utils"
import { ChevronRightIcon } from "lucide-react"
import { cn } from "@workspace/ui/lib/utils"
import { ChevronRightIcon } from "@workspace/ui/components/icon"
import { HeaderActionButton } from "../layout/header-action-button"
import { navigationMessages } from "./messages"
@@ -50,7 +51,7 @@ export function MobileNavigationSheetTrigger({
}
>
<Icon
data={Menu02Icon}
name="huge:menu-02"
strokeWidth={2.25}
className="size-5 [&_path]:transition-all group-hover/button:[&_path]:not-first:not-last:opacity-40 [&_path]:first:opacity-40 group-hover/button:[&_path]:first:translate-x-1 group-hover/button:[&_path]:first:opacity-100 [&_path]:last:opacity-60 group-hover/button:[&_path]:last:translate-x-2 group-hover/button:[&_path]:last:opacity-100"
/>
@@ -136,14 +137,13 @@ function FlatNavigationGroupList({
<NavigationLabel label={group.label} />
</div>
<div className="rounded-md ring ring-sidebar-border">
{separate({
items: group.items,
iterator: (item) => {
const routeState = getRouteState(item.to)
{group.items.map((item, index) => {
const routeState = getRouteState(item.to)
return (
return (
<Fragment key={item.id}>
{index > 0 && <Separator className="ms-12 me-3 w-auto!" />}
<NavigationButton
key={`item-${item.id}`}
label={item.label}
layout="inline"
icon={resolveNavigationItemIcon(
@@ -167,14 +167,8 @@ function FlatNavigationGroupList({
</>
}
/>
)
},
separator: (index) => (
<Separator
key={`separator-${index}`}
className="ms-12 me-3 w-auto!"
/>
),
</Fragment>
)
})}
</div>
</div>
@@ -2,11 +2,11 @@ import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render"
import { Link } from "@tanstack/react-router"
import * as React from "react"
import { Icon, type IconData } from "@workspace/icons"
import { Icon, type IconName } from "@icones/react"
import { Badge } from "@workspace/ui/components/badge"
import { useRippleRef } from "@workspace/ui/hooks/use-ripple"
import { cn } from "@workspace/ui/lib/utils"
import { ChevronRightIcon } from "lucide-react"
import { ChevronRightIcon } from "@workspace/ui/components/icon"
import { NavigationLabel, TruncatedNavigationLabel } from "./navigation-label"
import type { NavigationLabelValue } from "./types"
@@ -15,7 +15,7 @@ export interface NavigationButtonProps extends Omit<
useRender.ComponentProps<"button", {}>,
"className" | "style"
> {
icon?: IconData
icon?: IconName<"huge">
isActive: boolean
isCurrent: boolean
label: NavigationLabelValue
@@ -104,7 +104,7 @@ export function NavigationButton({
: undefined
)}
>
<Icon data={icon} />
<Icon name={icon} />
</span>
)}
<span
@@ -1,4 +1,4 @@
import type { IconData } from "@workspace/icons"
import type { IconName } from "@icones/react"
import type { MessageDescriptor } from "@workspace/i18n"
export type NavigationLabelValue = string | MessageDescriptor
@@ -7,7 +7,7 @@ export interface NavigationItem {
id: string
to?: string
label: NavigationLabelValue
icon?: IconData
icon?: IconName<"huge">
showIcon?: boolean
items?: readonly NavigationItem[]
}
@@ -1,6 +1,6 @@
import { useLocation } from "@tanstack/react-router"
import * as React from "react"
import type { IconData } from "@workspace/icons"
import type { IconName } from "@icones/react"
import { resolveNavigationItemIcon } from "./item-icon"
import type {
@@ -13,7 +13,7 @@ export type NavigationKey = NavigationItem["id"]
export interface NavigationInfo {
id: NavigationKey
icon?: IconData
icon?: IconName<"huge">
items?: readonly NavigationInfo[]
label: NavigationLabelValue
to?: string
@@ -40,26 +40,25 @@ function normalizePathname(pathname: string) {
return pathname.replace(/\/+$/, "")
}
function createInfoItems(
items: readonly NavigationItem[],
showIconByDefault: boolean
): readonly NavigationInfo[] {
return items.map((item) => ({
id: item.id,
icon: resolveNavigationItemIcon(item, showIconByDefault),
items: item.items?.length ? createInfoItems(item.items, false) : undefined,
label: item.label,
to: item.to,
}))
}
function createNavigationIndex(
groups: readonly NavigationGroup[]
): NavigationIndex {
const recordsByKey = new Map<NavigationKey, NavigationRecord>()
const recordsByPathname = new Map<string, NavigationRecord>()
const createInfoItems = (
items: readonly NavigationItem[],
showIconByDefault: boolean
): readonly NavigationInfo[] =>
items.map((item) => ({
id: item.id,
icon: resolveNavigationItemIcon(item, showIconByDefault),
items: item.items?.length
? createInfoItems(item.items, false)
: undefined,
label: item.label,
to: item.to,
}))
const indexInfoItems = (
items: readonly NavigationInfo[],
parentStack: readonly NavigationInfo[]
@@ -1,5 +1,5 @@
import * as React from "react"
import { CheckCheckIcon } from "lucide-react"
import { CheckCheckIcon } from "@workspace/ui/components/icon"
import { useMessage } from "@workspace/i18n"
import { Button } from "@workspace/ui/components/button"
import {
@@ -1,6 +1,6 @@
import * as React from "react"
import { Link } from "@tanstack/react-router"
import { Icon } from "@workspace/icons"
import { Icon } from "@icones/react"
import { useFormatters, useMessage } from "@workspace/i18n"
import {
Avatar,
@@ -109,7 +109,7 @@ function NotificationItemMedia({ media }: { media: NotificationMedia }) {
ICON_TONE_CLASSES[media.tone ?? "neutral"]
)}
>
<Icon data={media.icon} size="lg" />
<Icon name={media.icon} size="lg" />
</span>
)
}
@@ -1,4 +1,4 @@
import type { IconData } from "@workspace/icons"
import type { IconName } from "@icones/react"
export type NotificationTone =
| "destructive"
@@ -15,7 +15,7 @@ export interface NotificationAvatarMedia {
}
export interface NotificationIconMedia {
icon: IconData
icon: IconName<"huge">
kind: "icon"
tone?: NotificationTone
}
+2 -2
View File
@@ -26,9 +26,9 @@
"@lingui/core": "^6",
"@lingui/react": "^6",
"@vitejs/plugin-react": "^6",
"@workspace/ui": "workspace:*",
"ansi-to-react": "^6.2.6",
"cac": "^7.0.0",
"lucide-react": "^1.27.0",
"open": "^11.0.0",
"react": "^19.2.6",
"react-dom": "^19.2.6",
@@ -43,4 +43,4 @@
"typescript": "~6",
"vitest": "^4"
}
}
}
@@ -3,7 +3,7 @@ import {
ChevronUpIcon,
TerminalIcon,
XIcon,
} from "lucide-react"
} from "@workspace/ui/components/icon"
import { AnsiText } from "./ansi-text"
import { useDevtoolLocalization } from "./devtool-localization"
@@ -4,7 +4,7 @@ import {
AlignLeftIcon,
AlignRightIcon,
SettingsIcon,
} from "lucide-react"
} from "@workspace/ui/components/icon"
import type {
I18nDevtoolPanelPlacement,
@@ -35,14 +35,8 @@ const panelPlacements = [
] as const
const interfaceLocaleLabels: Record<I18nDevtoolLocale, string> = {
de: "Deutsch",
en: "English",
es: "Español",
fr: "Français",
ja: "日本語",
ko: "한국어",
"en-US": "English",
"zh-Hans": "简体中文",
"zh-Hant": "繁體中文",
}
export function DevtoolSettings({
@@ -1,4 +1,4 @@
import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react"
import { MonitorIcon, MoonIcon, SunIcon } from "@workspace/ui/components/icon"
import type { I18nDevtoolTheme } from "./devtool-preferences"
import { useDevtoolLocalization } from "./devtool-localization"
@@ -1,4 +1,4 @@
import { LanguagesIcon } from "lucide-react"
import { LanguagesIcon } from "@workspace/ui/components/icon"
import { useDevtoolLocalization } from "./devtool-localization"
import { useFloatingTrigger } from "./use-floating-trigger"
+6 -1
View File
@@ -1,5 +1,9 @@
import * as React from "react"
import { Maximize2Icon, Minimize2Icon, XIcon } from "lucide-react"
import {
Maximize2Icon,
Minimize2Icon,
XIcon,
} from "@workspace/ui/components/icon"
import { createPortal } from "react-dom"
import { useLocale, useLocales } from "../runtime"
@@ -25,6 +29,7 @@ import { DevtoolSettings } from "./devtool-settings"
import { I18nDevtoolTrigger } from "./i18n-devtool-trigger"
import { MessagePanel } from "./message-panel"
import { MessageRepositoryProvider } from "./message-repository"
// oxlint-disable-next-line import/no-unassigned-import -- Loads the Devtool stylesheet.
import "./i18n-devtool.css"
export interface I18nDevtoolProps {
+32 -34
View File
@@ -4,7 +4,7 @@ import {
ChevronDownIcon,
CopyIcon,
TriangleAlertIcon,
} from "lucide-react"
} from "@workspace/ui/components/icon"
import { useDevtoolLocalization } from "./devtool-localization"
import type { DevtoolMessage, MessageOrigin } from "./types"
@@ -53,6 +53,35 @@ async function writeClipboard(value: string) {
}
}
function handleMenuKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
if (!["ArrowDown", "ArrowUp", "End", "Home"].includes(event.key)) {
return
}
event.preventDefault()
const items = Array.from(
event.currentTarget.querySelectorAll<HTMLButtonElement>(
'[role="menuitem"]:not(:disabled)'
)
)
if (items.length === 0) return
const currentIndex = items.indexOf(
document.activeElement as HTMLButtonElement
)
const nextIndex =
event.key === "Home"
? 0
: event.key === "End"
? items.length - 1
: event.key === "ArrowUp"
? (currentIndex - 1 + items.length) % items.length
: (currentIndex + 1) % items.length
items[nextIndex]?.focus()
}
export function MessageCopyMenu({
message,
translation,
@@ -132,37 +161,6 @@ export function MessageCopyMenu({
}
}
const handleMenuKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (!["ArrowDown", "ArrowUp", "End", "Home"].includes(event.key)) {
return
}
event.preventDefault()
const items = Array.from(
event.currentTarget.querySelectorAll<HTMLButtonElement>(
'[role="menuitem"]:not(:disabled)'
)
)
if (items.length === 0) {
return
}
const currentIndex = items.indexOf(
document.activeElement as HTMLButtonElement
)
const nextIndex =
event.key === "Home"
? 0
: event.key === "End"
? items.length - 1
: event.key === "ArrowUp"
? (currentIndex - 1 + items.length) % items.length
: (currentIndex + 1) % items.length
items[nextIndex]?.focus()
}
return (
<div className="i18n-message-copy" ref={rootRef}>
<button
@@ -236,10 +234,10 @@ export function MessageCopyMenu({
<span className="i18n-message-copy__label">
{messages.messagePanel.sourceLocations}
</span>
{message.origins.map((origin, index) => (
{message.origins.map((origin) => (
<button
type="button"
key={`${getOriginLocation(origin)}:${index}`}
key={getOriginLocation(origin)}
role="menuitem"
title={getOriginLocation(origin)}
onClick={() => void copy(getOriginLocation(origin))}
@@ -1,5 +1,9 @@
import * as React from "react"
import { ChevronDownIcon, FolderIcon, PackageIcon } from "lucide-react"
import {
ChevronDownIcon,
FolderIcon,
PackageIcon,
} from "@workspace/ui/components/icon"
import { useDevtoolLocalization } from "./devtool-localization"
import type { DevtoolMessage, DevtoolMessagePackage } from "./types"
@@ -137,19 +141,20 @@ function NamespaceBranch({
packageName: string
selectedKey?: string
}) {
const sortedNodes = Array.from(nodes.values())
sortedNodes.sort((left, right) => left.segment.localeCompare(right.segment))
return (
<ul className="i18n-message-tree__branch">
{Array.from(nodes.values())
.sort((left, right) => left.segment.localeCompare(right.segment))
.map((node) => (
<NamespaceNode
key={node.path}
node={node}
packageName={packageName}
selectedKey={selectedKey}
onSelect={onSelect}
/>
))}
{sortedNodes.map((node) => (
<NamespaceNode
key={node.path}
node={node}
packageName={packageName}
selectedKey={selectedKey}
onSelect={onSelect}
/>
))}
</ul>
)
}
+6 -3
View File
@@ -6,7 +6,7 @@ import {
RotateCcwIcon,
SaveIcon,
SearchIcon,
} from "lucide-react"
} from "@workspace/ui/components/icon"
import { useDevtoolLocalization } from "./devtool-localization"
import { formatMessageOrigin, MessageCopyMenu } from "./message-copy-menu"
@@ -20,6 +20,7 @@ import type {
DevtoolMessagePackage,
MessageRepository,
} from "./types"
// oxlint-disable-next-line import/no-unassigned-import -- Loads the message panel stylesheet.
import "./message-panel.css"
type LoadState =
@@ -392,17 +393,19 @@ export function MessagePanel({
}
}
return Array.from(groups, ([packageName, group]) => ({
const packageGroups = Array.from(groups, ([packageName, group]) => ({
kind: group.kind,
messages: group.messages,
missingCount: group.messages.filter((message) => message.missing).length,
packageName,
})).sort(
}))
packageGroups.sort(
(left, right) =>
Number(right.kind === "application") -
Number(left.kind === "application") ||
left.packageName.localeCompare(right.packageName)
)
return packageGroups
}, [missingOnly, query, state, visibleMessages])
const navigationModel = React.useMemo(
() => createMessageNavigationModel(visiblePackageGroups),
-22
View File
@@ -1,22 +0,0 @@
# @workspace/icons
Workspace icon primitives backed by Hugeicons.
## Usage
```tsx
import { Icon, Search01Icon } from "@workspace/icons"
export function SearchButton() {
return <Icon data={Search01Icon} size="lg" />
}
```
`Icon` defaults to the `md` size and inherits the current text color. A
Tailwind size class supplied through `className` overrides the size preset:
```tsx
<Icon data={Search01Icon} className="size-8 text-primary" />
```
Available presets are `xs`, `sm`, `md`, `lg`, and `xl`.
-22
View File
@@ -1,22 +0,0 @@
{
"name": "@workspace/icons",
"version": "0.0.0",
"type": "module",
"private": true,
"sideEffects": false,
"scripts": {
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@hugeicons/core-free-icons": "^4.2.3",
"@hugeicons/react": "^1.1.9",
"react": "^19.2.6"
},
"devDependencies": {
"@types/react": "^19",
"typescript": "~6"
},
"exports": {
".": "./src/index.tsx"
}
}
-33
View File
@@ -1,33 +0,0 @@
import { type HugeiconsIconProps, HugeiconsIcon } from "@hugeicons/react"
export * from "@hugeicons/core-free-icons"
export type IconData = HugeiconsIconProps["icon"]
export type IconSize = keyof typeof sizeValues
export type IconProps = Omit<HugeiconsIconProps, "icon" | "size"> & {
data: IconData
size?: IconSize
}
const sizeValues = {
xs: 12,
sm: 16,
md: 20,
lg: 24,
xl: 28,
}
export function Icon({ data, className, size = "md", ...props }: IconProps) {
return (
<HugeiconsIcon
icon={data}
size={sizeValues[size]}
color="currentColor"
aria-hidden="true"
strokeWidth={1.75}
className={className}
{...props}
/>
)
}
-17
View File
@@ -1,17 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"paths": {
"@workspace/icons": ["./src/index.tsx"]
}
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
+1 -2
View File
@@ -19,7 +19,6 @@
"@workspace/i18n": "workspace:*",
"@workspace/ui": "workspace:*",
"lexical": "^0.48.0",
"lucide-react": "^1.27.0",
"react": "^19.2.6",
"react-dom": "^19.2.6"
},
@@ -38,4 +37,4 @@
"./locales": "./src/locales/catalogs.ts",
"./locales/*": "./src/locales/*.ts"
}
}
}
+6 -1
View File
@@ -3,7 +3,12 @@
import * as React from "react"
import type { ComponentType, ReactElement, ReactNode } from "react"
import type { Klass, LexicalNode } from "lexical"
import { AlignLeft, CaseSensitive, Pilcrow, Plus } from "lucide-react"
import {
AlignLeft,
CaseSensitive,
Pilcrow,
Plus,
} from "@workspace/ui/components/icon"
import {
boldAction,
+1 -1
View File
@@ -1,4 +1,4 @@
import { Check, ChevronDown } from "lucide-react"
import { Check, ChevronDown } from "@workspace/ui/components/icon"
import { Button } from "@workspace/ui/components/button"
import {
DropdownMenu,
+6 -1
View File
@@ -1,4 +1,9 @@
import { AlignCenter, AlignJustify, AlignLeft, AlignRight } from "lucide-react"
import {
AlignCenter,
AlignJustify,
AlignLeft,
AlignRight,
} from "@workspace/ui/components/icon"
import {
$getSelection,
$isRangeSelection,
+7 -1
View File
@@ -8,7 +8,13 @@ import {
} from "@lexical/rich-text"
import type { HeadingTagType } from "@lexical/rich-text"
import { $setBlocksType } from "@lexical/selection"
import { Heading1, Heading2, Heading3, Pilcrow, Quote } from "lucide-react"
import {
Heading1,
Heading2,
Heading3,
Pilcrow,
Quote,
} from "@workspace/ui/components/icon"
import { $createParagraphNode, $getSelection, $isRangeSelection } from "lexical"
import type { LexicalActionDefinition } from "../types"
import { lexicalMessages } from "../messages"
@@ -1,7 +1,7 @@
import { TOGGLE_LINK_COMMAND } from "@lexical/link"
import { REMOVE_LIST_COMMAND } from "@lexical/list"
import { $patchStyleText, $setBlocksType } from "@lexical/selection"
import { Eraser } from "lucide-react"
import { Eraser } from "@workspace/ui/components/icon"
import { $createParagraphNode, $getSelection, $isRangeSelection } from "lexical"
import type { LexicalActionDefinition } from "../types"
import { lexicalMessages } from "../messages"
@@ -2,7 +2,7 @@ import {
$getSelectionStyleValueForProperty,
$patchStyleText,
} from "@lexical/selection"
import { Baseline, Pipette } from "lucide-react"
import { Baseline, Pipette } from "@workspace/ui/components/icon"
import { $getSelection, $isRangeSelection, $setSelection } from "lexical"
import type { LexicalEditor, RangeSelection } from "lexical"
import { useRef, useState } from "react"
+1 -1
View File
@@ -1,4 +1,4 @@
import { CalendarDays } from "lucide-react"
import { CalendarDays } from "@workspace/ui/components/icon"
import { Button } from "@workspace/ui/components/button"
import { DropdownMenuItem } from "@workspace/ui/components/dropdown-menu"
import { cn } from "@workspace/ui/lib/utils"
+1 -1
View File
@@ -2,7 +2,7 @@ import {
$getSelectionStyleValueForProperty,
$patchStyleText,
} from "@lexical/selection"
import { ALargeSmall, ChevronDown } from "lucide-react"
import { ALargeSmall, ChevronDown } from "@workspace/ui/components/icon"
import { $getSelection, $isRangeSelection } from "lexical"
import { Button } from "@workspace/ui/components/button"
import {
+1 -1
View File
@@ -1,5 +1,5 @@
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin"
import { Redo2, Undo2 } from "lucide-react"
import { Redo2, Undo2 } from "@workspace/ui/components/icon"
import { REDO_COMMAND, UNDO_COMMAND } from "lexical"
import type { LexicalActionDefinition } from "../types"
import { lexicalMessages } from "../messages"
@@ -3,7 +3,7 @@ import {
HorizontalRuleNode,
} from "@lexical/extension"
import { $insertNodeToNearestRoot } from "@lexical/utils"
import { Minus } from "lucide-react"
import { Minus } from "@workspace/ui/components/icon"
import { $getSelection, $isRangeSelection } from "lexical"
import type { LexicalActionDefinition } from "../types"
import { lexicalMessages } from "../messages"
+1 -1
View File
@@ -1,4 +1,4 @@
import { IndentDecrease, IndentIncrease } from "lucide-react"
import { IndentDecrease, IndentIncrease } from "@workspace/ui/components/icon"
import { INDENT_CONTENT_COMMAND, OUTDENT_CONTENT_COMMAND } from "lexical"
import type { LexicalActionDefinition } from "../types"
import { lexicalMessages } from "../messages"
+1 -1
View File
@@ -1,6 +1,6 @@
import { $isLinkNode, LinkNode, TOGGLE_LINK_COMMAND } from "@lexical/link"
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin"
import { Link } from "lucide-react"
import { Link } from "@workspace/ui/components/icon"
import { $findMatchingParent, $getSelection, $isRangeSelection } from "lexical"
import { Button } from "@workspace/ui/components/button"
import {
+1 -1
View File
@@ -9,7 +9,7 @@ import {
} from "@lexical/list"
import { CheckListPlugin } from "@lexical/react/LexicalCheckListPlugin"
import { ListPlugin } from "@lexical/react/LexicalListPlugin"
import { List, ListChecks, ListOrdered } from "lucide-react"
import { List, ListChecks, ListOrdered } from "@workspace/ui/components/icon"
import { $getSelection, $isRangeSelection } from "lexical"
import type { LexicalCommand, LexicalEditor } from "lexical"
import type { LexicalActionDefinition, LexicalActionName } from "../types"
+1 -1
View File
@@ -1,4 +1,4 @@
import { ImageIcon, VideoIcon } from "lucide-react"
import { ImageIcon, VideoIcon } from "@workspace/ui/components/icon"
import { $insertNodes } from "lexical"
import {
+1 -1
View File
@@ -8,7 +8,7 @@ import {
Subscript,
Superscript,
Underline,
} from "lucide-react"
} from "@workspace/ui/components/icon"
import { $getSelection, $isRangeSelection, FORMAT_TEXT_COMMAND } from "lexical"
import type { TextFormatType } from "lexical"
import type { LexicalActionDefinition, LexicalActionName } from "../types"
+6 -1
View File
@@ -22,7 +22,12 @@ import type {
Spread,
} from "lexical"
import { cn } from "@workspace/ui/lib/utils"
import { AlignCenter, AlignLeft, AlignRight, Trash2 } from "lucide-react"
import {
AlignCenter,
AlignLeft,
AlignRight,
Trash2,
} from "@workspace/ui/components/icon"
import { useTranslate } from "@workspace/i18n"
import { ImageResizer } from "../components/image-resizer"
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react"
import { Trash2 } from "lucide-react"
import { Trash2 } from "@workspace/ui/components/icon"
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
import {
$getNearestNodeFromDOMNode,
@@ -9,7 +9,7 @@ import {
} from "react"
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
import { DraggableBlockPlugin_EXPERIMENTAL } from "@lexical/react/LexicalDraggableBlockPlugin"
import { GripVertical } from "lucide-react"
import { GripVertical } from "@workspace/ui/components/icon"
import { $getRoot, $isParagraphNode } from "lexical"
import { useLexicalMessage } from "../i18n"
@@ -1,7 +1,7 @@
import { useEffect, useId, useRef, useState } from "react"
import { $isLinkNode, $toggleLink } from "@lexical/link"
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
import { Check, Pencil, Trash2 } from "lucide-react"
import { Check, Pencil, Trash2 } from "@workspace/ui/components/icon"
import {
$findMatchingParent,
$getNearestNodeFromDOMNode,
+2 -2
View File
@@ -1,6 +1,6 @@
import type { ComponentType, ReactNode } from "react"
import type { Klass, LexicalEditor, LexicalNode } from "lexical"
import type { LucideIcon } from "lucide-react"
import type { IconComponent } from "@workspace/ui/components/icon"
import type { LexicalActionState } from "./context"
import type { LexicalEmbedDefinition } from "./embed"
import type { LexicalMessage } from "./i18n"
@@ -54,7 +54,7 @@ export interface LexicalActionDefinition<Value = string> {
name: string
label: LexicalMessage
hidden?: boolean
icon?: LucideIcon
icon?: IconComponent
group?: LexicalActionGroup
execute: (context: LexicalActionContext, value?: Value) => void
isActive?: (context: LexicalActionContext) => boolean
+2 -2
View File
@@ -10,6 +10,7 @@
"@base-ui/react": "^1.6.0",
"@base-ui/utils": "^0.3.1",
"@fontsource-variable/inter": "^5.3.0",
"@icones/react": "^0.0.1",
"@shadcn/react": "^0.2.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -17,7 +18,6 @@
"date-fns": "^4.4.0",
"embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2",
"lucide-react": "^1.27.0",
"react": "^19.2.6",
"react-day-picker": "^10.0.1",
"react-dom": "^19.2.6",
@@ -44,4 +44,4 @@
"./components/*": "./src/components/*.tsx",
"./hooks/*": "./src/hooks/*.ts"
}
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion"
import { cn } from "@workspace/ui/lib/utils"
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"
import { ChevronDownIcon, ChevronUpIcon } from "@workspace/ui/components/icon"
function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
return (
+4 -1
View File
@@ -3,7 +3,10 @@ import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render"
import { cn } from "@workspace/ui/lib/utils"
import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"
import {
ChevronRightIcon,
MoreHorizontalIcon,
} from "@workspace/ui/components/icon"
import { LocalizedText, uiMessages, useMessage } from "./i18n"
function Breadcrumb({
+184 -159
View File
@@ -14,7 +14,68 @@ import {
ChevronLeftIcon,
ChevronRightIcon,
ChevronDownIcon,
} from "lucide-react"
} from "@workspace/ui/components/icon"
type CalendarComponents = NonNullable<
React.ComponentProps<typeof DayPicker>["components"]
>
const CalendarLocaleContext = React.createContext<string | undefined>(undefined)
const CalendarRoot: NonNullable<CalendarComponents["Root"]> = ({
className: rootClassName,
rootRef,
...rootProps
}) => (
<div
data-slot="calendar"
ref={rootRef}
className={cn(rootClassName)}
{...rootProps}
/>
)
const CalendarChevron: NonNullable<CalendarComponents["Chevron"]> = ({
className: chevronClassName,
orientation,
...chevronProps
}) => {
if (orientation === "left") {
return (
<ChevronLeftIcon
className={cn("size-4 rtl:rotate-180", chevronClassName)}
{...chevronProps}
/>
)
}
if (orientation === "right") {
return (
<ChevronRightIcon
className={cn("size-4 rtl:rotate-180", chevronClassName)}
{...chevronProps}
/>
)
}
return (
<ChevronDownIcon
className={cn("size-4", chevronClassName)}
{...chevronProps}
/>
)
}
const CalendarWeekNumber: NonNullable<CalendarComponents["WeekNumber"]> = ({
children,
...weekNumberProps
}) => (
<td {...weekNumberProps}>
<div className="flex size-(--cell-size) items-center justify-center text-center">
{children}
</div>
</td>
)
function Calendar({
className,
@@ -37,162 +98,126 @@ function Calendar({
const localeCode = locale?.code ?? contextLocale
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn(
"group/calendar bg-background p-3 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(8)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className
)}
captionLayout={captionLayout}
locale={locale}
lang={lang ?? localeCode}
formatters={{
formatMonthDropdown: (date) =>
date.toLocaleString(localeCode, { month: "short" }),
...formatters,
}}
classNames={{
root: cn("w-fit", defaultClassNames.root),
months: cn(
"relative flex flex-col gap-4 md:flex-row",
defaultClassNames.months
),
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
nav: cn(
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
defaultClassNames.nav
),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
defaultClassNames.button_previous
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
defaultClassNames.button_next
),
month_caption: cn(
"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
defaultClassNames.month_caption
),
dropdowns: cn(
"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
defaultClassNames.dropdowns
),
dropdown_root: cn(
"relative rounded-(--cell-radius)",
defaultClassNames.dropdown_root
),
dropdown: cn(
"absolute inset-0 bg-popover opacity-0",
defaultClassNames.dropdown
),
caption_label: cn(
"font-medium select-none",
captionLayout === "label"
? "text-sm"
: "flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
defaultClassNames.caption_label
),
month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
"flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none",
defaultClassNames.weekday
),
week: cn("mt-2 flex w-full", defaultClassNames.week),
week_number_header: cn(
"w-(--cell-size) select-none",
defaultClassNames.week_number_header
),
week_number: cn(
"text-[0.8rem] text-muted-foreground select-none",
defaultClassNames.week_number
),
day: cn(
"group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-e-(--cell-radius)",
props.showWeekNumber
? "[&:nth-child(2)[data-selected=true]_button]:rounded-s-(--cell-radius)"
: "[&:first-child[data-selected=true]_button]:rounded-s-(--cell-radius)",
defaultClassNames.day
),
range_start: cn(
"relative isolate z-0 rounded-s-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:end-0 after:w-4 after:bg-muted",
defaultClassNames.range_start
),
range_middle: cn("rounded-none", defaultClassNames.range_middle),
range_end: cn(
"relative isolate z-0 rounded-e-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:start-0 after:w-4 after:bg-muted",
defaultClassNames.range_end
),
today: cn(
"rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none",
defaultClassNames.today
),
outside: cn(
"text-muted-foreground aria-selected:text-muted-foreground",
defaultClassNames.outside
),
disabled: cn(
"text-muted-foreground opacity-50",
defaultClassNames.disabled
),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames,
}}
components={{
Root: ({ className, rootRef, ...props }) => {
return (
<div
data-slot="calendar"
ref={rootRef}
className={cn(className)}
{...props}
/>
)
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
return (
<ChevronLeftIcon
className={cn("size-4 rtl:rotate-180", className)}
{...props}
/>
)
}
if (orientation === "right") {
return (
<ChevronRightIcon
className={cn("size-4 rtl:rotate-180", className)}
{...props}
/>
)
}
return (
<ChevronDownIcon className={cn("size-4", className)} {...props} />
)
},
DayButton: ({ ...props }) => (
<CalendarDayButton locale={localeCode} {...props} />
),
WeekNumber: ({ children, ...props }) => {
return (
<td {...props}>
<div className="flex size-(--cell-size) items-center justify-center text-center">
{children}
</div>
</td>
)
},
...components,
}}
{...props}
/>
<CalendarLocaleContext.Provider value={localeCode}>
<DayPicker
showOutsideDays={showOutsideDays}
className={cn(
"group/calendar bg-background p-3 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(8)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className
)}
captionLayout={captionLayout}
locale={locale}
lang={lang ?? localeCode}
formatters={{
formatMonthDropdown: (date) =>
date.toLocaleString(localeCode, { month: "short" }),
...formatters,
}}
classNames={{
root: cn("w-fit", defaultClassNames.root),
months: cn(
"relative flex flex-col gap-4 md:flex-row",
defaultClassNames.months
),
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
nav: cn(
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
defaultClassNames.nav
),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
defaultClassNames.button_previous
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
defaultClassNames.button_next
),
month_caption: cn(
"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
defaultClassNames.month_caption
),
dropdowns: cn(
"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
defaultClassNames.dropdowns
),
dropdown_root: cn(
"relative rounded-(--cell-radius)",
defaultClassNames.dropdown_root
),
dropdown: cn(
"absolute inset-0 bg-popover opacity-0",
defaultClassNames.dropdown
),
caption_label: cn(
"font-medium select-none",
captionLayout === "label"
? "text-sm"
: "flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
defaultClassNames.caption_label
),
month_grid: cn(
"w-full border-collapse",
defaultClassNames.month_grid
),
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
"flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none",
defaultClassNames.weekday
),
week: cn("mt-2 flex w-full", defaultClassNames.week),
week_number_header: cn(
"w-(--cell-size) select-none",
defaultClassNames.week_number_header
),
week_number: cn(
"text-[0.8rem] text-muted-foreground select-none",
defaultClassNames.week_number
),
day: cn(
"group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-e-(--cell-radius)",
props.showWeekNumber
? "[&:nth-child(2)[data-selected=true]_button]:rounded-s-(--cell-radius)"
: "[&:first-child[data-selected=true]_button]:rounded-s-(--cell-radius)",
defaultClassNames.day
),
range_start: cn(
"relative isolate z-0 rounded-s-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:end-0 after:w-4 after:bg-muted",
defaultClassNames.range_start
),
range_middle: cn("rounded-none", defaultClassNames.range_middle),
range_end: cn(
"relative isolate z-0 rounded-e-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:start-0 after:w-4 after:bg-muted",
defaultClassNames.range_end
),
today: cn(
"rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none",
defaultClassNames.today
),
outside: cn(
"text-muted-foreground aria-selected:text-muted-foreground",
defaultClassNames.outside
),
disabled: cn(
"text-muted-foreground opacity-50",
defaultClassNames.disabled
),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames,
}}
components={{
Root: CalendarRoot,
Chevron: CalendarChevron,
DayButton: CalendarDayButton,
WeekNumber: CalendarWeekNumber,
...components,
}}
{...props}
/>
</CalendarLocaleContext.Provider>
)
}
@@ -200,10 +225,10 @@ function CalendarDayButton({
className,
day,
modifiers,
locale,
...props
}: React.ComponentProps<typeof DayButton> & { locale?: string }) {
}: React.ComponentProps<typeof DayButton>) {
const defaultClassNames = getDefaultClassNames()
const locale = React.useContext(CalendarLocaleContext)
const ref = React.useRef<HTMLButtonElement>(null)
React.useEffect(() => {
+37 -18
View File
@@ -5,7 +5,10 @@ import useEmblaCarousel, {
import { cn } from "@workspace/ui/lib/utils"
import { Button } from "@workspace/ui/components/button"
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
import {
ChevronLeftIcon,
ChevronRightIcon,
} from "@workspace/ui/components/icon"
import { LocalizedText, uiMessages } from "./i18n"
type CarouselApi = UseEmblaCarouselType[1]
@@ -60,10 +63,10 @@ function Carousel({
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) return
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
const onSelect = React.useCallback((carouselApi: CarouselApi) => {
if (!carouselApi) return
setCanScrollPrev(carouselApi.canScrollPrev())
setCanScrollNext(carouselApi.canScrollNext())
}, [])
const scrollPrev = React.useCallback(() => {
@@ -103,20 +106,36 @@ function Carousel({
}
}, [api, onSelect])
const contextValue = React.useMemo<CarouselContextProps>(
() => ({
carouselRef,
api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
plugins,
setApi,
}),
[
api,
canScrollNext,
canScrollPrev,
carouselRef,
opts,
orientation,
plugins,
scrollNext,
scrollPrev,
setApi,
]
)
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}
>
<CarouselContext.Provider value={contextValue}>
<div
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
+1 -1
View File
@@ -3,7 +3,7 @@
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox"
import { cn } from "@workspace/ui/lib/utils"
import { CheckIcon } from "lucide-react"
import { CheckIcon } from "@workspace/ui/components/icon"
function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
return (
+5 -1
View File
@@ -11,7 +11,11 @@ import {
InputGroupButton,
InputGroupInput,
} from "@workspace/ui/components/input-group"
import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react"
import {
ChevronDownIcon,
XIcon,
CheckIcon,
} from "@workspace/ui/components/icon"
const Combobox = ComboboxPrimitive.Root
+1 -1
View File
@@ -13,7 +13,7 @@ import {
InputGroup,
InputGroupAddon,
} from "@workspace/ui/components/input-group"
import { SearchIcon, CheckIcon } from "lucide-react"
import { SearchIcon, CheckIcon } from "@workspace/ui/components/icon"
function Command({
className,
+1 -1
View File
@@ -4,7 +4,7 @@ import * as React from "react"
import { ContextMenu as ContextMenuPrimitive } from "@base-ui/react/context-menu"
import { cn } from "@workspace/ui/lib/utils"
import { ChevronRightIcon, CheckIcon } from "lucide-react"
import { ChevronRightIcon, CheckIcon } from "@workspace/ui/components/icon"
function ContextMenu({ ...props }: ContextMenuPrimitive.Root.Props) {
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
+1 -1
View File
@@ -5,7 +5,7 @@ import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
import { cn } from "@workspace/ui/lib/utils"
import { Button } from "@workspace/ui/components/button"
import { XIcon } from "lucide-react"
import { XIcon } from "@workspace/ui/components/icon"
import { LocalizedText, uiMessages } from "./i18n"
type DialogHandle<Payload> = DialogPrimitive.Handle<Payload>
+1 -1
View File
@@ -9,7 +9,7 @@ import {
getAnchoredSurfaceSideOffset,
} from "@workspace/ui/components/anchored-surface"
import { cn } from "@workspace/ui/lib/utils"
import { ChevronRightIcon, CheckIcon } from "lucide-react"
import { ChevronRightIcon, CheckIcon } from "@workspace/ui/components/icon"
import { useRippleRef } from "../hooks/use-ripple"
function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
+229
View File
@@ -0,0 +1,229 @@
import * as React from "react"
import { Icon, type IconName, type IconProps } from "@icones/react"
export type IconComponentProps = Omit<
Extract<IconProps, { name: string }>,
"name"
>
export type IconComponent = React.ComponentType<IconComponentProps>
function createIcon(name: IconName<"lucide">): IconComponent {
const Component = (props: IconComponentProps) => (
<Icon name={name} size={24} strokeWidth={2} {...props} />
)
Component.displayName = `Icon(${name})`
return Component
}
export const ALargeSmall = createIcon("lucide:a-large-small")
export const AlignCenter = createIcon("lucide:text-align-center")
export const AlignCenterIcon = AlignCenter
export const AlignJustify = createIcon("lucide:text-align-justify")
export const AlignLeft = createIcon("lucide:text-align-start")
export const AlignLeftIcon = AlignLeft
export const AlignRight = createIcon("lucide:text-align-end")
export const AlignRightIcon = AlignRight
export const AppWindowIcon = createIcon("lucide:app-window")
export const ArrowDownIcon = createIcon("lucide:arrow-down")
export const ArrowLeftIcon = createIcon("lucide:arrow-left")
export const Baseline = createIcon("lucide:baseline")
export const Bold = createIcon("lucide:bold")
export const CalendarDays = createIcon("lucide:calendar-days")
export const CaseLower = createIcon("lucide:case-lower")
export const CaseSensitive = createIcon("lucide:case-sensitive")
export const CaseUpper = createIcon("lucide:case-upper")
export const Check = createIcon("lucide:check")
export const CheckIcon = Check
export const CheckCheckIcon = createIcon("lucide:check-check")
export const ChevronDown = createIcon("lucide:chevron-down")
export const ChevronDownIcon = ChevronDown
export const ChevronLeftIcon = createIcon("lucide:chevron-left")
export const ChevronRightIcon = createIcon("lucide:chevron-right")
export const ChevronUpIcon = createIcon("lucide:chevron-up")
export const CircleAlertIcon = createIcon("lucide:circle-alert")
export const CircleCheckIcon = createIcon("lucide:circle-check")
export const ClipboardCopyIcon = createIcon("lucide:clipboard-copy")
export const CloudIcon = createIcon("lucide:cloud")
export const CloudUploadIcon = createIcon("lucide:cloud-upload")
export const CopyIcon = createIcon("lucide:copy")
export const CornerDownLeftIcon = createIcon("lucide:corner-down-left")
export const Eraser = createIcon("lucide:eraser")
export const ExternalLinkIcon = createIcon("lucide:external-link")
export const EyeIcon = createIcon("lucide:eye")
export const EyeOffIcon = createIcon("lucide:eye-off")
export const FileCode2Icon = createIcon("lucide:file-code-corner")
export const FileTextIcon = createIcon("lucide:file-text")
export const FilmIcon = createIcon("lucide:film")
export const FolderIcon = createIcon("lucide:folder")
export const FolderInputIcon = createIcon("lucide:folder-input")
export const FolderOpenIcon = createIcon("lucide:folder-open")
export const FolderPlusIcon = createIcon("lucide:folder-plus")
export const GlobeIcon = createIcon("lucide:globe")
export const GripVertical = createIcon("lucide:grip-vertical")
export const Heading1 = createIcon("lucide:heading-1")
export const Heading2 = createIcon("lucide:heading-2")
export const Heading3 = createIcon("lucide:heading-3")
export const HeartIcon = createIcon("lucide:heart")
export const HistoryIcon = createIcon("lucide:rotate-ccw-clock")
export const ImageIcon = createIcon("lucide:image")
export const ImagePlusIcon = createIcon("lucide:image-plus")
export const ImagesIcon = createIcon("lucide:images")
export const IndentDecrease = createIcon("lucide:list-indent-decrease")
export const IndentIncrease = createIcon("lucide:list-indent-increase")
export const InfoIcon = createIcon("lucide:info")
export const Italic = createIcon("lucide:italic")
export const LanguagesIcon = createIcon("lucide:languages")
export const LibraryIcon = createIcon("lucide:library")
export const Link = createIcon("lucide:link")
export const LinkIcon = Link
export const List = createIcon("lucide:list")
export const ListChecks = createIcon("lucide:list-checks")
export const ListOrdered = createIcon("lucide:list-ordered")
export const LoaderCircleIcon = createIcon("lucide:loader-circle")
export const Loader2Icon = LoaderCircleIcon
export const Maximize2Icon = createIcon("lucide:maximize-2")
export const MessageCircleIcon = createIcon("lucide:message-circle")
export const Minimize2Icon = createIcon("lucide:minimize-2")
export const Minus = createIcon("lucide:minus")
export const MinusIcon = Minus
export const MonitorIcon = createIcon("lucide:monitor")
export const MoonIcon = createIcon("lucide:moon")
export const MoreHorizontalIcon = createIcon("lucide:ellipsis")
export const OctagonXIcon = createIcon("lucide:octagon-x")
export const PackageIcon = createIcon("lucide:package")
export const PanelLeftIcon = createIcon("lucide:panel-left")
export const PaperclipIcon = createIcon("lucide:paperclip")
export const Pencil = createIcon("lucide:pencil")
export const PencilIcon = Pencil
export const Pilcrow = createIcon("lucide:pilcrow")
export const Pipette = createIcon("lucide:pipette")
export const Plus = createIcon("lucide:plus")
export const PlusIcon = Plus
export const Quote = createIcon("lucide:quote")
export const Redo2 = createIcon("lucide:redo-2")
export const RotateCcwIcon = createIcon("lucide:rotate-ccw")
export const SaveIcon = createIcon("lucide:save")
export const SearchIcon = createIcon("lucide:search")
export const SendHorizontalIcon = createIcon("lucide:send-horizontal")
export const SettingsIcon = createIcon("lucide:settings")
export const SmileIcon = createIcon("lucide:face-slightly-smiling")
export const Strikethrough = createIcon("lucide:strikethrough")
export const Subscript = createIcon("lucide:subscript")
export const SunIcon = createIcon("lucide:sun")
export const Superscript = createIcon("lucide:superscript")
export const TelescopeIcon = createIcon("lucide:telescope")
export const TerminalIcon = createIcon("lucide:terminal")
export const Trash2 = createIcon("lucide:trash")
export const Trash2Icon = Trash2
export const TriangleAlertIcon = createIcon("lucide:triangle-alert")
export const Underline = createIcon("lucide:underline")
export const Undo2 = createIcon("lucide:undo-2")
export const UnlinkIcon = createIcon("lucide:unlink")
export const UploadIcon = createIcon("lucide:upload")
export const VideoIcon = createIcon("lucide:video")
export const XIcon = createIcon("lucide:x")
// Literal references let @icones/vite emit assets for compatibility components.
export function IconAssetCatalog() {
return (
<>
<Icon name="lucide:a-large-small" />
<Icon name="lucide:text-align-center" />
<Icon name="lucide:text-align-justify" />
<Icon name="lucide:text-align-start" />
<Icon name="lucide:text-align-end" />
<Icon name="lucide:app-window" />
<Icon name="lucide:arrow-down" />
<Icon name="lucide:arrow-left" />
<Icon name="lucide:baseline" />
<Icon name="lucide:bold" />
<Icon name="lucide:calendar-days" />
<Icon name="lucide:case-lower" />
<Icon name="lucide:case-sensitive" />
<Icon name="lucide:case-upper" />
<Icon name="lucide:check" />
<Icon name="lucide:check-check" />
<Icon name="lucide:chevron-down" />
<Icon name="lucide:chevron-left" />
<Icon name="lucide:chevron-right" />
<Icon name="lucide:chevron-up" />
<Icon name="lucide:circle-alert" />
<Icon name="lucide:circle-check" />
<Icon name="lucide:clipboard-copy" />
<Icon name="lucide:cloud" />
<Icon name="lucide:cloud-upload" />
<Icon name="lucide:copy" />
<Icon name="lucide:corner-down-left" />
<Icon name="lucide:eraser" />
<Icon name="lucide:external-link" />
<Icon name="lucide:eye" />
<Icon name="lucide:eye-off" />
<Icon name="lucide:file-code-corner" />
<Icon name="lucide:file-text" />
<Icon name="lucide:film" />
<Icon name="lucide:folder" />
<Icon name="lucide:folder-input" />
<Icon name="lucide:folder-open" />
<Icon name="lucide:folder-plus" />
<Icon name="lucide:globe" />
<Icon name="lucide:grip-vertical" />
<Icon name="lucide:heading-1" />
<Icon name="lucide:heading-2" />
<Icon name="lucide:heading-3" />
<Icon name="lucide:heart" />
<Icon name="lucide:rotate-ccw-clock" />
<Icon name="lucide:image" />
<Icon name="lucide:image-plus" />
<Icon name="lucide:images" />
<Icon name="lucide:list-indent-decrease" />
<Icon name="lucide:list-indent-increase" />
<Icon name="lucide:info" />
<Icon name="lucide:italic" />
<Icon name="lucide:languages" />
<Icon name="lucide:library" />
<Icon name="lucide:link" />
<Icon name="lucide:list" />
<Icon name="lucide:list-checks" />
<Icon name="lucide:list-ordered" />
<Icon name="lucide:loader-circle" />
<Icon name="lucide:maximize-2" />
<Icon name="lucide:message-circle" />
<Icon name="lucide:minimize-2" />
<Icon name="lucide:minus" />
<Icon name="lucide:monitor" />
<Icon name="lucide:moon" />
<Icon name="lucide:ellipsis" />
<Icon name="lucide:octagon-x" />
<Icon name="lucide:package" />
<Icon name="lucide:panel-left" />
<Icon name="lucide:paperclip" />
<Icon name="lucide:pencil" />
<Icon name="lucide:pilcrow" />
<Icon name="lucide:pipette" />
<Icon name="lucide:plus" />
<Icon name="lucide:quote" />
<Icon name="lucide:redo-2" />
<Icon name="lucide:rotate-ccw" />
<Icon name="lucide:save" />
<Icon name="lucide:search" />
<Icon name="lucide:send-horizontal" />
<Icon name="lucide:settings" />
<Icon name="lucide:face-slightly-smiling" />
<Icon name="lucide:strikethrough" />
<Icon name="lucide:subscript" />
<Icon name="lucide:sun" />
<Icon name="lucide:superscript" />
<Icon name="lucide:telescope" />
<Icon name="lucide:terminal" />
<Icon name="lucide:trash" />
<Icon name="lucide:triangle-alert" />
<Icon name="lucide:underline" />
<Icon name="lucide:undo-2" />
<Icon name="lucide:unlink" />
<Icon name="lucide:upload" />
<Icon name="lucide:video" />
<Icon name="lucide:x" />
</>
)
}
+1 -1
View File
@@ -2,7 +2,7 @@ import * as React from "react"
import { OTPInput, OTPInputContext } from "input-otp"
import { cn } from "@workspace/ui/lib/utils"
import { MinusIcon } from "lucide-react"
import { MinusIcon } from "@workspace/ui/components/icon"
function InputOTP({
className,
+1 -1
View File
@@ -18,7 +18,7 @@ import {
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@workspace/ui/components/dropdown-menu"
import { CheckIcon } from "lucide-react"
import { CheckIcon } from "@workspace/ui/components/icon"
function Menubar({ className, ...props }: MenubarPrimitive.Props) {
return (
@@ -8,7 +8,7 @@ import {
import { cn } from "@workspace/ui/lib/utils"
import { Button } from "@workspace/ui/components/button"
import { ArrowDownIcon } from "lucide-react"
import { ArrowDownIcon } from "@workspace/ui/components/icon"
function MessageScrollerProvider(
props: React.ComponentProps<typeof MessageScrollerPrimitive.Provider>
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from "react"
import { cn } from "@workspace/ui/lib/utils"
import { ChevronDownIcon } from "lucide-react"
import { ChevronDownIcon } from "@workspace/ui/components/icon"
type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & {
size?: "sm" | "default"
@@ -2,7 +2,7 @@ import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/naviga
import { cva } from "class-variance-authority"
import { cn } from "@workspace/ui/lib/utils"
import { ChevronDownIcon } from "lucide-react"
import { ChevronDownIcon } from "@workspace/ui/components/icon"
function NavigationMenu({
align = "start",
+1 -1
View File
@@ -6,7 +6,7 @@ import {
ChevronLeftIcon,
ChevronRightIcon,
MoreHorizontalIcon,
} from "lucide-react"
} from "@workspace/ui/components/icon"
import { LocalizedText, uiMessages, useMessage } from "./i18n"
function Pagination({
+5 -1
View File
@@ -4,7 +4,11 @@ import * as React from "react"
import { Select as SelectPrimitive } from "@base-ui/react/select"
import { cn } from "@workspace/ui/lib/utils"
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
import {
ChevronDownIcon,
CheckIcon,
ChevronUpIcon,
} from "@workspace/ui/components/icon"
const Select = SelectPrimitive.Root
+1 -1
View File
@@ -5,7 +5,7 @@ import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"
import { cn } from "@workspace/ui/lib/utils"
import { Button } from "@workspace/ui/components/button"
import { XIcon } from "lucide-react"
import { XIcon } from "@workspace/ui/components/icon"
import { LocalizedText, uiMessages } from "./i18n"
function createSheetHandle<Payload>(): SheetPrimitive.Handle<Payload> {
+4 -2
View File
@@ -21,7 +21,7 @@ import {
TooltipContent,
TooltipTrigger,
} from "@workspace/ui/components/tooltip"
import { PanelLeftIcon } from "lucide-react"
import { PanelLeftIcon } from "@workspace/ui/components/icon"
import { LocalizedText, uiMessages, useMessage } from "./i18n"
const SIDEBAR_COOKIE_NAME = "sidebar_state"
@@ -89,7 +89,9 @@ function SidebarProvider({
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
return isMobile
? setOpenMobile((currentOpen) => !currentOpen)
: setOpen((currentOpen) => !currentOpen)
}, [isMobile, setOpen, setOpenMobile])
// Adds a keyboard shortcut to toggle the sidebar.
+5 -2
View File
@@ -1,8 +1,11 @@
import { cn } from "@workspace/ui/lib/utils"
import { Loader2Icon } from "lucide-react"
import {
Loader2Icon,
type IconComponentProps,
} from "@workspace/ui/components/icon"
import { uiMessages, useMessage } from "./i18n"
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
function Spinner({ className, ...props }: IconComponentProps) {
const loadingLabel = useMessage(uiMessages.loading)
return (
+5 -3
View File
@@ -10,10 +10,12 @@ import {
TriangleAlertIcon,
OctagonXIcon,
Loader2Icon,
} from "lucide-react"
} from "@workspace/ui/components/icon"
import { uiMessages, useMessage } from "./i18n"
const toast = ToastPrimitive.createToastManager()
const defaultToastActionRender = <Button variant="outline" size="sm" />
const defaultToastCloseRender = <Button variant="ghost" size="icon-sm" />
function ToastProvider({ ...props }: ToastPrimitive.Provider.Props) {
return <ToastPrimitive.Provider {...props} />
@@ -101,7 +103,7 @@ function ToastDescription({
function ToastAction({
className,
render = <Button variant="outline" size="sm" />,
render = defaultToastActionRender,
...props
}: ToastPrimitive.Action.Props) {
return (
@@ -118,7 +120,7 @@ function ToastClose({
"aria-label": ariaLabel,
className,
children,
render = <Button variant="ghost" size="icon-sm" />,
render = defaultToastCloseRender,
...props
}: ToastPrimitive.Close.Props) {
const closeLabel = useMessage(uiMessages.closeToast)