feat(icons): extract shared workspace icon package

Create @workspace/icons as the single Hugeicons-backed Icon primitive and icon-data export surface, with size presets and inherited current color.

Migrate blocks and the web app to the shared package, remove duplicated direct Hugeicons dependencies and the blocks-local Icon component, and update the lockfile.
This commit is contained in:
Maofeng
2026-07-31 16:06:39 +08:00
parent 38d2defeb1
commit d364e5e39f
22 changed files with 120 additions and 49 deletions
+1 -2
View File
@@ -11,11 +11,10 @@
"@base-ui/react": "^1.6.0",
"@base-ui/utils": "^0.3.1",
"@floating-ui/utils": "^0.2.12",
"@hugeicons/core-free-icons": "^4.2.3",
"@hugeicons/react": "^1.1.9",
"@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",
@@ -1,5 +1,6 @@
import { Link } from "@tanstack/react-router"
import * as React from "react"
import { Icon } from "@workspace/icons"
import { useMessage } from "@workspace/i18n"
import {
Breadcrumb,
@@ -27,7 +28,6 @@ import {
type NavigationInfo,
useNavigationStack,
} from "../navigation"
import { Icon } from "../../components/icon"
import { layoutMessages } from "./messages"
const MAX_VISIBLE_ENTRIES = 3
@@ -1,4 +1,10 @@
import * as React from "react"
import {
BellIcon,
Icon,
MessageMultiple01Icon,
Search01Icon,
} from "@workspace/icons"
import { Button } from "@workspace/ui/components/button"
import { Kbd } from "@workspace/ui/components/kbd"
import { useIsTablet } from "@workspace/ui/hooks/use-breakpoint"
@@ -6,7 +12,6 @@ import { cn } from "@workspace/ui/lib/utils"
import { ChevronLeftIcon, ChevronRightIcon, SearchIcon } from "lucide-react"
import { ChatPopover, ChatPopoverTrigger, type ChatThread } from "../chats"
import { Icon } from "../../components/icon"
import {
MobileNavigationSheet,
MobileNavigationSheetTrigger,
@@ -18,12 +23,6 @@ import {
NotificationCenterTrigger,
type UseNotificationsOptions,
} from "../notifications"
import {
BellIcon,
MessageMultiple01Icon,
Search01Icon,
} from "@hugeicons/core-free-icons"
import { AppSidebar } from "./app-sidebar"
import {
headerActionIconClassName,
@@ -1,3 +1,4 @@
import { ReloadIcon } from "@workspace/icons"
import { useMessage } from "@workspace/i18n"
import { useRefresh } from "../../hooks/use-refresh"
import {
@@ -6,7 +7,6 @@ import {
TooltipTrigger,
} from "@workspace/ui/components/tooltip"
import { HeaderActionButton } from "./header-action-button"
import { ReloadIcon } from "@hugeicons/core-free-icons"
import { cn } from "@workspace/ui/lib/utils"
import { layoutMessages } from "./messages"
@@ -1,4 +1,4 @@
import { Icon, type IconData } from "../../components/icon"
import { Icon, type IconData } from "@workspace/icons"
import { Button, type ButtonProps } from "@workspace/ui/components/button"
import { cn } from "@workspace/ui/lib/utils"
@@ -1,4 +1,12 @@
import { formatForDisplay } from "@tanstack/react-hotkeys"
import {
Icon,
type IconData,
KeyboardIcon,
LogoutCircle02Icon,
ShieldKeyIcon,
SwatchIcon,
} from "@workspace/icons"
import { useMessage } from "@workspace/i18n"
import { Button, type ButtonProps } from "@workspace/ui/components/button"
import {
@@ -35,13 +43,6 @@ import {
HeaderActionButton,
} from "./header-action-button"
import { useOnScroll } from "../../hooks/use-on-scroll"
import { Icon, type IconData } from "../../components/icon"
import {
KeyboardIcon,
LogoutCircle02Icon,
ShieldKeyIcon,
SwatchIcon,
} from "@hugeicons/core-free-icons"
import { layoutMessages } from "./messages"
interface UserMenuItem {
@@ -1,12 +1,10 @@
import { Icon } from "@workspace/icons"
import {
DropdownMenuItem,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
} from "@workspace/ui/components/dropdown-menu"
import { Icon } from "../../components/icon"
import { resolveNavigationItemIcon } from "./item-icon"
import { NavigationBadge, NavigationLink } from "./navigation-button"
import { NavigationLabel } from "./navigation-label"
@@ -1,4 +1,4 @@
import type { IconData } from "../../components/icon"
import type { IconData } from "@workspace/icons"
import type { NavigationItem } from "./types"
@@ -1,4 +1,5 @@
import { useLocation } from "@tanstack/react-router"
import { Icon, Menu02Icon } from "@workspace/icons"
import { useMessage } from "@workspace/i18n"
import type { ButtonProps } from "@workspace/ui/components/button"
import { Separator } from "@workspace/ui/components/separator"
@@ -31,8 +32,6 @@ import {
type GetNavigationRouteState,
} from "./route-state"
import type { NavigationGroup } from "./types"
import { Icon } from "../../components/icon"
import { Menu02Icon } from "@hugeicons/core-free-icons"
const mobileNavigationSheetHandle = createSheetHandle<void>()
@@ -2,12 +2,12 @@ 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 { 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 { Icon, type IconData } from "../../components/icon"
import { NavigationLabel, TruncatedNavigationLabel } from "./navigation-label"
import type { NavigationLabelValue } from "./types"
@@ -1,7 +1,6 @@
import type { IconData } from "@workspace/icons"
import type { MessageDescriptor } from "@workspace/i18n"
import type { IconData } from "../../components/icon"
export type NavigationLabelValue = string | MessageDescriptor
export interface NavigationItem {
@@ -1,7 +1,7 @@
import { useLocation } from "@tanstack/react-router"
import * as React from "react"
import type { IconData } from "@workspace/icons"
import type { IconData } from "../../components/icon"
import { resolveNavigationItemIcon } from "./item-icon"
import type {
NavigationGroup,
@@ -1,5 +1,6 @@
import * as React from "react"
import { Link } from "@tanstack/react-router"
import { Icon } from "@workspace/icons"
import { useFormatters, useMessage } from "@workspace/i18n"
import {
Avatar,
@@ -17,7 +18,6 @@ import {
} from "@workspace/ui/components/item"
import { cn } from "@workspace/ui/lib/utils"
import { Icon } from "../../components/icon"
import { notificationMessages } from "./messages"
import type {
@@ -1,4 +1,4 @@
import type { IconData } from "../../components/icon"
import type { IconData } from "@workspace/icons"
export type NotificationTone =
| "destructive"
+22
View File
@@ -0,0 +1,22 @@
# @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
@@ -0,0 +1,22 @@
{
"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"
}
}
@@ -1,29 +1,32 @@
import { type HugeiconsIconProps, HugeiconsIcon } from "@hugeicons/react"
import { cn } from "@workspace/ui/lib/utils"
export * from "@hugeicons/core-free-icons"
export type IconData = HugeiconsIconProps["icon"]
export type IconSize = keyof typeof sizeValues
export type IconProps = Omit<HugeiconsIconProps, "icon"> & {
export type IconProps = Omit<HugeiconsIconProps, "icon" | "size"> & {
data: IconData
size?: "xs" | "sm" | "md" | "lg" | "xl"
size?: IconSize
}
const sizeClasses = {
xs: "size-3",
sm: "size-4",
md: "size-5",
lg: "size-6",
xl: "size-7",
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={cn(sizeClasses[size], className)}
className={className}
{...props}
/>
)
+17
View File
@@ -0,0 +1,17 @@
{
"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"]
}