feat(ui): Multica icons, roomier menus and typography pass (MUL-8334, MUL-8338) (#9640)

* refactor(ui): add @multica/ui/icons as the single icon entry point (MUL-8334)

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* refactor: import icons from @multica/ui/icons instead of lucide-react (MUL-8334)

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* chore(lint): ban direct lucide-react imports outside @multica/ui/icons (MUL-8334)

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(ui): draw Multica icons and enlarge primitive icon sizes (MUL-8334)

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(ui): roomier menu and sidebar rhythm (MUL-8334)

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(ui): mark body and detail layers in icon glyphs (MUL-8334)

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(ui): Inter optical sizing, lighter titles, CJK leading and zh-TW font stack (MUL-8338)

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(ui): 13px for in-place reading text, regular weight for descriptive labels (MUL-8338)

Issue detail timeline, comment headers, properties panel, wakeup and PR rows
move from 12px to 13px; sub-issue identifiers match list/board (12px regular);
sidebar group labels drop to regular weight; board card titles get more
leading under a CJK UI via a new cjk: variant.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(ui): tint the active sidebar icon's body with a light fill (MUL-8334)

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(ui): draw every remaining icon and drop lucide-react (MUL-8334)

All 286 icon names now render Multica drawings. The drawings, their
construction helpers and the generator live in packages/ui/icons/drawings;
glyphs.ts and index.ts are generated and a test fails when they drift.
Search loses its lens highlight and Inbox its arrow.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* chore: guard against icon libraries and document drawing new icons (MUL-8334)

Lint bans the common React icon libraries, AGENTS.md points to a new
multica-icons skill, route icons must mark a body for the active tint,
and comments no longer describe product icons as Lucide.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(ui): fill the active sidebar icon solid with its details knocked out (MUL-8334)

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(lint): ban icon library subpaths too (MUL-8334)

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
This commit is contained in:
Jiayuan Zhang
2026-10-28 23:52:28 +08:00
committed by GitHub
co-authored by Cursor multica-agent
parent fb49f55924
commit f0c361ebca
440 changed files with 2067 additions and 576 deletions
+53
View File
@@ -0,0 +1,53 @@
---
name: multica-icons
description: Use when a Multica web/desktop UI change needs an icon that `@multica/ui/icons` does not export yet, or when redrawing or reviewing a Multica glyph. Covers the grid, corner and stroke construction, the body/detail markers, and the generate-and-preview loop. Never reach for an icon library, inline SVG, or emoji instead.
---
# Drawing Multica icons
Every icon in the product is a Multica drawing exported from `@multica/ui/icons`. No icon library is installed, and lint rejects imports from one. When you need a new symbol, draw it.
## Before drawing
1. Search `packages/ui/icons/index.ts` for the name and for synonyms. If an existing glyph already means the same thing, add an alias to `ALIASES` in `drawings.mjs` instead of drawing a near-duplicate.
2. Pick a name that describes the symbol, not the feature (`Webhook`, not `IntegrationsNav`). Follow the existing PascalCase names.
## Where things live
- `packages/ui/icons/drawings/construction.mjs` holds the shared geometry helpers.
- `packages/ui/icons/drawings/drawings.mjs` holds every drawing as inner SVG markup, plus `ALIASES`.
- `packages/ui/icons/glyphs.ts` and `index.ts` are generated. Never edit them by hand; `icons.test.ts` fails when they drift.
## Construction
- **Grid.** 24×24 viewBox, with a 2px round-capped, round-joined stroke from the renderer. Keep geometry inside 2.5–21.5. Most silhouettes span 2.75–21.25.
- **Corners.** Use `shape([[x, y, k], ...])` and `rect(x, y, w, h, k)`, which build continuous-curvature (squircle) corners. Typical `k` values:
- 5–7 for large containers such as cards, windows and chat bubbles.
- 2.5–4 for small parts.
- 1–1.5 for tips and points.
- 0 for a hard corner, which is rare.
Do not hand-write `a r r` arc corners for rectangles.
- **Arrowheads and chevrons.** Use `head(x, y, dir, depth, half)` for a soft arrowhead with its tip at (x, y). For chevrons, copy the existing `M… s…` soft curves. Never use sharp polyline arrowheads.
- **Dots.** `dot(x, y, r)` with r 1.1–1.5. Use dots only where they carry meaning: info, alert, eyes, status lights.
- **Polygons and stars.** `polygon(cx, cy, radii, count, k)`.
- **Reuse parts.** `file()`, `folderBody`, `bubble`, `ring`, `user()`, `link`, `cw` and `ccw` already exist. A variant (`…Off`, `…Plus`, `…X`) is the base drawing plus one stroke. A slash runs `m3 3 18 18`.
## Body and details
- Mark the main closed silhouette with `class="b"` (body). The active sidebar item fills it solid.
- Any icon that can appear in navigation must have a body. A test enforces this for route icons.
- Only mark closed shapes. An open arc with a fill closes into a stray chord.
- Mark every stroke or dot that sits inside the body with `class="k"` (detail). The solid state knocks details out in the background colour; an unmarked detail disappears into the fill.
- **Detail budget.** At most two inner details. Every detail must carry meaning: a check, a symbol, a divider that defines the object. Drop decorative highlights, extra lines and texture. Icons render at 14px in dense rows, where a third detail turns into noise.
## Loop
1. Add the drawing to `DRAWINGS` in `drawings.mjs`. Keep sections grouped by subject.
2. Run `pnpm --filter @multica/ui icons --preview NewName Neighbour1 Neighbour2`. Open the printed HTML file in a browser and check four things:
- At 48px: does the geometry sit on the grid, with even optical weight next to its neighbours?
- At 18px and 14px: is it still legible, or do inner details merge?
- In the solid (active) tile: does the silhouette fill, with every inner detail still visible?
- Against similar glyphs: can it be told apart at a glance?
3. Run `pnpm --filter @multica/ui icons` to regenerate `glyphs.ts` and `index.ts`.
4. Import the new name from `@multica/ui/icons`. Then run `pnpm --filter @multica/ui test` and the typecheck for the package that uses it.
+1
View File
@@ -103,6 +103,7 @@ Workspace-scoped queries filter by `workspace_id`; membership gates access and `
- For Button and Dialog usage, read `packages/ui/docs/button.md` and `packages/ui/docs/dialog.md`. These component contracts also power UI Lab documentation.
- Prefer existing shadcn/Base UI primitives. Add components with `pnpm ui:add <component>`.
- For `pnpm ui:add @reui/<name>`, decline overwrite prompts. Keep `REUI_LICENSE_KEY` in the environment, never in repo files. Adapt vendored primitives into `packages/ui/components/ui/` and compositions into `packages/views/`.
- Import icons from `@multica/ui/icons`; no icon library is installed. If the icon you need is missing, draw it by following `.agents/skills/multica-icons/SKILL.md`. Do not substitute an inline SVG or an emoji. Rewrite icon imports in `ui:add` output before committing.
- Use shared semantic tokens in `packages/ui/styles/`. Typography uses the role-named `--text-*` scale in `packages/ui/styles/tokens.css`, not Tailwind's default size ramp.
- Selected states remain identifiable on hover. Handle overflow, long text, and scrolling deliberately; avoid unnecessary local state and dividers.
+2
View File
@@ -1,5 +1,6 @@
import globals from "globals";
import reactConfig from "@multica/eslint-config/react";
import { ICON_LIBRARY_IMPORT_PATTERNS } from "@multica/eslint-config/base";
export default [
...reactConfig,
@@ -63,6 +64,7 @@ export default [
"Direct navigation from application code breaks the Coordinator protocol (MUL-4741). Use the navigation adapter from src/platform instead.",
},
],
patterns: ICON_LIBRARY_IMPORT_PATTERNS,
},
],
"no-restricted-syntax": [
-1
View File
@@ -48,7 +48,6 @@
"@tanstack/react-query": "catalog:",
"electron-updater": "^6.8.3",
"fix-path": "^5.0.0",
"lucide-react": "catalog:",
"motion": "^12.38.0",
"react": "catalog:",
"react-dom": "catalog:",
@@ -14,7 +14,7 @@ import {
Server,
Trash2,
X,
} from "lucide-react";
} from "@multica/ui/icons";
import { truncateWithEllipsis } from "@multica/core/utils";
import { cn } from "@multica/ui/lib/utils";
import { copyText } from "@multica/ui/lib/clipboard";
@@ -8,7 +8,7 @@ import {
ScrollText,
LogIn,
Info,
} from "lucide-react";
} from "@multica/ui/icons";
import { useQuery } from "@tanstack/react-query";
import { useWorkspaceId } from "@multica/core/hooks";
import { runtimeListOptions } from "@multica/core/runtimes";
@@ -1,5 +1,5 @@
import { useState, useEffect, useCallback, type ReactNode } from "react";
import { AlertCircle, Info, LogIn } from "lucide-react";
import { AlertCircle, Info, LogIn } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { Switch } from "@multica/ui/components/ui/switch";
import { cn } from "@multica/ui/lib/utils";
@@ -5,7 +5,7 @@ import {
useParams,
useRouteError,
} from "react-router-dom";
import { AlertTriangle, RotateCw, X } from "lucide-react";
import { AlertTriangle, RotateCw, X } from "@multica/ui/icons";
import { useAuthStore } from "@multica/core/auth";
import { setCurrentWorkspace } from "@multica/core/platform";
import { WorkspaceSlugProvider } from "@multica/core/paths";
@@ -1,6 +1,6 @@
import { useMemo } from "react";
import { isRouteErrorResponse, useLocation, useRouteError } from "react-router-dom";
import { AlertTriangle, Compass, RotateCw, Send, X } from "lucide-react";
import { AlertTriangle, Compass, RotateCw, Send, X } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import type { DesktopRouteErrorFeedbackContext } from "@multica/core/feedback";
import { useModalStore } from "@multica/core/modals";
@@ -244,7 +244,7 @@ describe("TabBar hover action buttons", () => {
// The leading slot is the resource visual (size-3.5), not a Pin glyph. The
// only Pin lives in the size-2.5 hover action button.
expect(pinnedTab.querySelector('[data-testid="tab-leading"]')).toBeTruthy();
expect(pinnedTab.querySelector(".lucide-pin.size-3\\.5")).toBeNull();
expect(pinnedTab.querySelector(".icon-pin.size-3\\.5")).toBeNull();
});
});
@@ -7,7 +7,7 @@ import {
type RefObject,
} from "react";
import { motion, useReducedMotion } from "motion/react";
import { X, Plus, Pin, PinOff, ListX, AppWindow } from "lucide-react";
import { X, Plus, Pin, PinOff, ListX, AppWindow } from "@multica/ui/icons";
import {
DndContext,
PointerSensor,
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import { RefreshCw, X } from "lucide-react";
import { RefreshCw, X } from "@multica/ui/icons";
// Downloads run silently in the background (main process has
// autoDownload=true). The renderer only renders UI once the package is fully
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from "react";
import { AlertCircle, ArrowDownToLine, Check, Loader2 } from "lucide-react";
import { AlertCircle, ArrowDownToLine, Check, Loader2 } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { Switch } from "@multica/ui/components/ui/switch";
import { useT } from "@multica/views/i18n";
@@ -6,7 +6,7 @@ import {
useState,
type PointerEventHandler,
} from "react";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { ArrowLeft, ArrowRight } from "@multica/ui/icons";
import {
SidebarTrigger,
useSidebar,
+11
View File
@@ -65,6 +65,17 @@ html[lang|="ja"] {
"Noto Sans CJK KR", sans-serif;
}
/* Traditional Chinese: same problem one level down — the default stack is
Simplified-first, so zh-TW users get Simplified glyph shapes for shared
ideographs. App.tsx maps zh-Hant to `zh-TW`. Mirrors apps/web/app/globals.css. */
html[lang="zh-TW"] {
--font-sans: "Inter Variable", "Inter", -apple-system, BlinkMacSystemFont,
"Segoe UI", "PingFang TC", "Microsoft JhengHei",
"Noto Sans CJK TC", "PingFang SC", "Microsoft YaHei",
"Noto Sans CJK SC", "Apple SD Gothic Neo", "Malgun Gothic",
"Noto Sans CJK KR", sans-serif;
}
@source "../../../../../packages/ui/**/*.tsx";
@source "../../../../../packages/core/**/*.{ts,tsx}";
@source "../../../../../packages/views/**/*.{ts,tsx}";
+5 -2
View File
@@ -4,10 +4,13 @@ import { AppCrashBoundary } from "./components/app-crash-boundary";
// Inter variable font covers all weights (100-900) in a single file.
// CJK is handled by system font fallback (see globals.css --font-sans chain).
// Keep font stack in sync with apps/web/app/layout.tsx.
// The opsz files carry the optical-size axis on top of weight, so headings get
// Inter Display's cut while body text keeps the text cut — web loads the same
// axis via next/font `axes: ["opsz"]`.
// The italic axis ships as a separate file — without it the ~20 semantic italic
// labels and every markdown <em>/blockquote render as synthesized oblique.
import "@fontsource-variable/inter";
import "@fontsource-variable/inter/wght-italic.css";
import "@fontsource-variable/inter/opsz.css";
import "@fontsource-variable/inter/opsz-italic.css";
// Editorial serif — matches web's next/font Source_Serif_4. Loaded app-wide so
// onboarding headings and any future editorial surface can use `font-serif`
// (see tokens.css @theme inline). Variable font = one file covers all weights.
+1 -1
View File
@@ -32,7 +32,7 @@ import { InboxPage } from "@multica/views/inbox";
import { ChatPage } from "@multica/views/chat";
import { SettingsPage } from "@multica/views/settings";
import { useT } from "@multica/views/i18n";
import { Download, Server } from "lucide-react";
import { Download, Server } from "@multica/ui/icons";
import { DaemonSettingsTab } from "./components/daemon-settings-tab";
import { UpdatesSettingsTab } from "./components/updates-settings-tab";
import { WorkspaceRouteLayout } from "./components/workspace-route-layout";
+1 -1
View File
@@ -1,5 +1,5 @@
import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
import { ArrowUpRight } from "lucide-react";
import { ArrowUpRight } from "@multica/ui/icons";
// Docs-local stateless Multica mark — matches @multica/ui's MulticaIcon
// visually (same 8-pointed-asterisk clip-path), but without useState/
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { ArrowLeft, Check, Globe, Lock, Minus, Users } from "lucide-react";
import { ArrowLeft, Check, Globe, Lock, Minus, Users } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import { CoachMark, DemoAvatar, DemoFrame, useDemoCopy, useStartWhenVisible } from "./demo-kit";
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { ChevronDown, X } from "lucide-react";
import { ChevronDown, X } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import {
@@ -1,7 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { CheckCircle2, Clock, FastForward, Loader2, Pencil, Play } from "lucide-react";
import { CheckCircle2, Clock, FastForward, Loader2, Pencil, Play } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import {
@@ -1,7 +1,7 @@
"use client";
import { useState, type ReactNode } from "react";
import { Users } from "lucide-react";
import { Users } from "@multica/ui/icons";
import { cn } from "@multica/ui/lib/utils";
import {
CoachMark,
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { type ReactNode, useState } from "react";
import { CircleDashed, Loader2 } from "lucide-react";
import { CircleDashed, Loader2 } from "@multica/ui/icons";
import { CoachMark, DemoAvatar, DemoFrame, DemoSendButton, useDemoCopy, useDemoTimers, useStartWhenVisible } from "./demo-kit";
type Suggestion = { label: string; prompt: string };
@@ -1,7 +1,7 @@
"use client";
import { type ReactNode, useEffect, useState } from "react";
import { CornerDownRight, Ellipsis, ListEnd, Loader2, Pencil, Square, Trash2 } from "lucide-react";
import { CornerDownRight, Ellipsis, ListEnd, Loader2, Pencil, Square, Trash2 } from "@multica/ui/icons";
import {
CoachMark,
DemoAvatar,
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { CornerDownRight } from "lucide-react";
import { CornerDownRight } from "@multica/ui/icons";
import { cn } from "@multica/ui/lib/utils";
import { LocaleLink } from "@/components/locale-link";
import { CoachMark, DemoFrame, useDemoCopy, useStartWhenVisible } from "./demo-kit";
@@ -1,7 +1,7 @@
"use client";
import { useState, type ReactNode } from "react";
import { ArrowRight, AtSign, Bot, Check, CircleCheck, ListPlus, Monitor, Sparkles, Users } from "lucide-react";
import { ArrowRight, AtSign, Bot, Check, CircleCheck, ListPlus, Monitor, Sparkles, Users } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import { CoachMark, DemoFrame, useDemoCopy, useDemoTimers, useStartWhenVisible } from "./demo-kit";
@@ -1,7 +1,7 @@
"use client";
import { useState, type ReactNode } from "react";
import { Check, ChevronDown, ChevronRight, FileText, LayoutGrid, MessageSquareText, X } from "lucide-react";
import { Check, ChevronDown, ChevronRight, FileText, LayoutGrid, MessageSquareText, X } from "@multica/ui/icons";
import { cn } from "@multica/ui/lib/utils";
import { CoachMark, DemoAvatar, DemoFrame, useDemoCopy, useStartWhenVisible } from "./demo-kit";
+1 -1
View File
@@ -18,7 +18,7 @@ import {
CornerDownRight,
Loader2,
RotateCcw,
} from "lucide-react";
} from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import { useDocsLocale } from "@/components/locale-link";
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { Check, ChevronsUpDown, Inbox, ListTodo, Pin, PinOff, UserRound, X } from "lucide-react";
import { Check, ChevronsUpDown, Inbox, ListTodo, Pin, PinOff, UserRound, X } from "@multica/ui/icons";
import { cn } from "@multica/ui/lib/utils";
import { CoachMark, DemoFrame, useDemoCopy, useStartWhenVisible } from "./demo-kit";
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { Archive, ArchiveRestore, ChevronLeft, ChevronRight, CircleCheck, X } from "lucide-react";
import { Archive, ArchiveRestore, ChevronLeft, ChevronRight, CircleCheck, X } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import { CoachMark, DemoAvatar, DemoComment, DemoFrame, useDemoCopy, useStartWhenVisible } from "./demo-kit";
@@ -11,7 +11,7 @@ import {
Paperclip,
Users,
X,
} from "lucide-react";
} from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import {
@@ -1,7 +1,7 @@
"use client";
import { useRef, useState } from "react";
import { ChevronDown } from "lucide-react";
import { ChevronDown } from "@multica/ui/icons";
import { cn } from "@multica/ui/lib/utils";
import {
CoachMark,
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { Check, Clock, FileText, Folder, GitBranch, Loader2, Pencil } from "lucide-react";
import { Check, Clock, FileText, Folder, GitBranch, Loader2, Pencil } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import {
+1 -1
View File
@@ -8,7 +8,7 @@ import {
GitMerge,
GitPullRequestArrow,
MoreHorizontal,
} from "lucide-react";
} from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import { CoachMark, DemoAvatar, DemoFrame, useDemoCopy, useDemoTimers, useStartWhenVisible } from "./demo-kit";
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { ChevronDown, FolderKanban } from "lucide-react";
import { ChevronDown, FolderKanban } from "@multica/ui/icons";
import { cn } from "@multica/ui/lib/utils";
import { CoachMark, DemoAvatar, DemoFrame, useDemoCopy, useStartWhenVisible } from "./demo-kit";
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { Check, Cpu, History, Plug } from "lucide-react";
import { Check, Cpu, History, Plug } from "@multica/ui/icons";
import { cn } from "@multica/ui/lib/utils";
import { CoachMark, DemoFrame, useDemoCopy, useStartWhenVisible } from "./demo-kit";
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { ChevronRight, CircleCheck, Loader2, Zap } from "lucide-react";
import { ChevronRight, CircleCheck, Loader2, Zap } from "@multica/ui/icons";
import {
CoachMark,
DemoAvatar,
@@ -1,7 +1,7 @@
"use client";
import { type ReactNode, useState } from "react";
import { CheckCircle2, ChevronRight, Copy, ListChevronsDownUp, MoreHorizontal } from "lucide-react";
import { CheckCircle2, ChevronRight, Copy, ListChevronsDownUp, MoreHorizontal } from "@multica/ui/icons";
import { CoachMark, DemoAvatar, DemoFrame, useDemoCopy, useStartWhenVisible } from "./demo-kit";
type Entry = { author: string; agent: boolean; body: string };
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { Check, Crown, Minus, MoreHorizontal, Plus, Shield, User, UserMinus } from "lucide-react";
import { Check, Crown, Minus, MoreHorizontal, Plus, Shield, User, UserMinus } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import { CoachMark, DemoAvatar, DemoFrame, useDemoCopy, useStartWhenVisible } from "./demo-kit";
@@ -1,7 +1,7 @@
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import { Ban, CheckCircle2, ChevronRight, Loader2, RotateCcw, Square, XCircle } from "lucide-react";
import { Ban, CheckCircle2, ChevronRight, Loader2, RotateCcw, Square, XCircle } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import {
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { useState, type ReactNode } from "react";
import { ArrowRight, Laptop, Loader2 } from "lucide-react";
import { ArrowRight, Laptop, Loader2 } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import {
@@ -1,7 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { Laptop, Loader2, Power } from "lucide-react";
import { Laptop, Loader2, Power } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import {
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { Check, Plus, Search } from "lucide-react";
import { Check, Plus, Search } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import {
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { ChevronDown, Users, X } from "lucide-react";
import { ChevronDown, Users, X } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import {
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { CornerDownRight } from "lucide-react";
import { CornerDownRight } from "@multica/ui/icons";
import {
CoachMark,
DemoAvatar,
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { ArrowRight, Check } from "lucide-react";
import { ArrowRight, Check } from "@multica/ui/icons";
import { cn } from "@multica/ui/lib/utils";
import { LocaleLink } from "@/components/locale-link";
import { CoachMark, DemoFrame, useDemoCopy, useStartWhenVisible } from "./demo-kit";
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { useEffect, useState, type CSSProperties } from "react";
import { BarChart3, CalendarDays, ChevronDown, FolderKanban, RefreshCw } from "lucide-react";
import { BarChart3, CalendarDays, ChevronDown, FolderKanban, RefreshCw } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import {
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { useEffect, useState, type ReactNode } from "react";
import { Bell, ChevronDown, ChevronRight, Clock3, FastForward, Info, Plus, RefreshCw } from "lucide-react";
import { Bell, ChevronDown, ChevronRight, Clock3, FastForward, Info, Plus, RefreshCw } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import {
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { useRef, useState } from "react";
import { ArrowRight, ChevronDown, ChevronRight, CornerDownRight, X } from "lucide-react";
import { ArrowRight, ChevronDown, ChevronRight, CornerDownRight, X } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { cn } from "@multica/ui/lib/utils";
import {
+1 -1
View File
@@ -1,6 +1,6 @@
"use client";
import { Monitor, Moon, Sun } from "lucide-react";
import { Monitor, Moon, Sun } from "@multica/ui/icons";
import { useTheme } from "next-themes";
import { usePathname, useRouter } from "next/navigation";
import { useEffect, useState, type ReactNode } from "react";
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { Play } from "lucide-react";
import { Play } from "@multica/ui/icons";
/**
* VideoEmbed — provider-agnostic, click-to-load video embed for docs MDX.
-1
View File
@@ -17,7 +17,6 @@
"fumadocs-core": "^16.17.8",
"fumadocs-mdx": "^15.4.10",
"fumadocs-ui": "^16.17.8",
"lucide-react": "catalog:",
"mermaid": "^11.14.0",
"next": "^16.3.8",
"next-themes": "^0.4.6",
-1
View File
@@ -19,7 +19,6 @@
"@tanstack/react-query": "catalog:",
"colorjs.io": "^0.7.1",
"i18next": "catalog:",
"lucide-react": "catalog:",
"react": "catalog:",
"react-colorful": "^5.8.1",
"react-dom": "catalog:",
+1 -1
View File
@@ -30,7 +30,7 @@ import {
Trash2,
Undo2,
X,
} from "lucide-react";
} from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { MulticaIcon } from "@multica/ui/components/common/multica-icon";
import { Input } from "@multica/ui/components/ui/input";
+1 -1
View File
@@ -8,7 +8,7 @@ import {
Loader2,
Plus,
Send,
} from "lucide-react";
} from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { Input } from "@multica/ui/components/ui/input";
import {
+1 -1
View File
@@ -1,6 +1,6 @@
import type { ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { ArrowRight, CircleDashed, RotateCcw } from "lucide-react";
import { ArrowRight, CircleDashed, RotateCcw } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import {
catalog,
+1 -1
View File
@@ -9,7 +9,7 @@ import {
History,
LayoutDashboard,
Palette,
} from "lucide-react";
} from "@multica/ui/icons";
import {
catalog,
availableCount,
+1 -1
View File
@@ -1,7 +1,7 @@
import { useTranslation } from "react-i18next";
import { useEffect, useId, useRef, useState } from "react";
import { HexColorPicker } from "react-colorful";
import { Check, ChevronDown, Copy, RotateCcw, Search } from "lucide-react";
import { Check, ChevronDown, Copy, RotateCcw, Search } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { Input } from "@multica/ui/components/ui/input";
import {
+1 -1
View File
@@ -7,7 +7,7 @@ import {
Component,
RotateCcw,
X,
} from "lucide-react";
} from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import {
Popover,
+1 -1
View File
@@ -1,6 +1,6 @@
import { useTranslation } from "react-i18next";
import { useEffect, useId, useRef, useState } from "react";
import { RotateCcw } from "lucide-react";
import { RotateCcw } from "@multica/ui/icons";
export function NumberField({
label,
+1 -1
View File
@@ -1,7 +1,7 @@
import { LabI18nProvider } from "./lab-i18n";
import { useTranslation } from "react-i18next";
import { useEffect, useState, type ReactNode } from "react";
import { ArrowUpRight, Check, Inbox, Plus } from "lucide-react";
import { ArrowUpRight, Check, Inbox, Plus } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import { Badge } from "@multica/ui/components/ui/badge";
import { Input } from "@multica/ui/components/ui/input";
+1 -1
View File
@@ -1,5 +1,5 @@
import { useTranslation } from "react-i18next";
import { Check, RotateCcw } from "lucide-react";
import { Check, RotateCcw } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import {
baseline,
+1 -1
View File
@@ -24,7 +24,7 @@ import {
CardContent,
} from "@multica/ui/components/ui/card";
import { Button } from "@multica/ui/components/ui/button";
import { Loader2 } from "lucide-react";
import { Loader2 } from "@multica/ui/icons";
import { setLoggedInCookie } from "@/features/auth/auth-cookie";
import Link from "next/link";
import { LoginPage, validateCliCallback } from "@multica/views/auth";
+1 -1
View File
@@ -18,7 +18,7 @@ import {
} from "@multica/ui/components/ui/card";
import { Button } from "@multica/ui/components/ui/button";
import { useT } from "@multica/views/i18n";
import { Loader2 } from "lucide-react";
import { Loader2 } from "@multica/ui/icons";
import { callbackErrorFrom, type CallbackError } from "./callback-error";
const authLogger = createLogger("auth.callback");
+11
View File
@@ -44,6 +44,17 @@ html[lang|="ja"] {
"Noto Sans CJK KR", sans-serif;
}
/* Traditional Chinese: the default stack is Simplified-first, which hands zh-TW
users Simplified glyph shapes for every ideograph the two share a code point
for. Promote the Traditional families for that locale only (layout.tsx emits
`zh-TW` for zh-Hant). Kept in sync with the desktop stack. */
html[lang="zh-TW"] {
--font-sans: var(--font-inter), -apple-system, BlinkMacSystemFont, "Segoe UI",
"PingFang TC", "Microsoft JhengHei", "Noto Sans CJK TC", "PingFang SC",
"Microsoft YaHei", "Noto Sans CJK SC", "Apple SD Gothic Neo", "Malgun Gothic",
"Noto Sans CJK KR", sans-serif;
}
/* The landing tree overrides `--font-serif` with its own Instrument Serif +
per-locale CJK chain, built on the same structure; see "Landing editorial
serif" in ./custom.css and keep the two CJK orderings in sync. */
+7 -1
View File
@@ -32,10 +32,16 @@ import "./globals.css";
// italic face the ~20 semantic italic labels (chat empty states, model-picker's
// "Managed by runtime", dashboard/squad placeholders) plus every markdown <em> and
// blockquote rendered as browser-synthesized oblique. Keep in sync with desktop's
// `@fontsource-variable/inter/wght-italic.css` import.
// `@fontsource-variable/inter/opsz-italic.css` import.
//
// The `opsz` axis lets Inter draw each size with its own cut: 14px and below
// keep the open text design, while titles and headings get Inter Display's
// tighter spacing and finer details automatically (`font-optical-sizing: auto`
// is the CSS default). Keep in sync with desktop's `opsz.css` import.
const inter = Inter({
subsets: ["latin"],
style: ["normal", "italic"],
axes: ["opsz"],
variable: "--font-inter",
});
// Mono font has no explicit CJK fallback: CJK chars in code blocks are inherently
@@ -1,7 +1,7 @@
"use client";
import { useState, type ReactNode } from "react";
import { Check, Copy, Terminal } from "lucide-react";
import { Check, Copy, Terminal } from "@multica/ui/icons";
import { CliInstallCommand } from "@multica/views/common/cli-install-command";
import { copyText } from "@multica/ui/lib/clipboard";
import { useLocale } from "../../i18n";
@@ -1,5 +1,5 @@
import Link from "next/link";
import { ArrowRight, Download } from "lucide-react";
import { ArrowRight, Download } from "@multica/ui/icons";
import { useLocale } from "../../i18n";
import type { DetectResult } from "../../utils/os-detect";
import type { DownloadAssets } from "../../utils/parse-release-assets";
@@ -17,7 +17,7 @@ import {
Monitor,
Sparkles,
UserMinus,
} from "lucide-react";
} from "@multica/ui/icons";
import { cn } from "@multica/ui/lib/utils";
import { ImageIcon } from "./shared";
import { useLocale } from "../i18n";
@@ -2,7 +2,7 @@
import { useState } from "react";
import Link from "next/link";
import { Menu, X } from "lucide-react";
import { Menu, X } from "@multica/ui/icons";
import { MulticaIcon } from "@multica/ui/components/common/multica-icon";
import { cn } from "@multica/ui/lib/utils";
import { useAuthStore } from "@multica/core/auth";
@@ -2,7 +2,7 @@
import Image from "next/image";
import Link from "next/link";
import { ArrowRight, Download } from "lucide-react";
import { ArrowRight, Download } from "@multica/ui/icons";
import { useAuthStore } from "@multica/core/auth";
import { docsHrefForLocale, useLocale } from "../i18n";
import { useDashboardCtaHref } from "../utils/use-dashboard-cta";
-1
View File
@@ -21,7 +21,6 @@
"dotenv": "^17.4.1",
"fumadocs-core": "^15.5.2",
"fumadocs-mdx": "^12.0.3",
"lucide-react": "catalog:",
"next": "^16.3.8",
"react": "catalog:",
"react-dom": "catalog:",
+1 -1
View File
@@ -64,7 +64,7 @@ test.describe("Comments", () => {
const shell = page.getByTestId("comment-composer-shell");
await expect(shell).toBeVisible();
const composer = shell.locator("xpath=ancestor::div[contains(@class, 'rounded-lg')][1]");
const submitBtn = composer.locator("button:has(svg.lucide-arrow-up)").last();
const submitBtn = composer.locator("button:has(svg.icon-arrow-up)").last();
await expect(submitBtn).toBeDisabled();
});
});
+2 -2
View File
@@ -159,7 +159,7 @@ test("closing floating chat releases its list and preserves the live composer",
element.dispatchEvent(new ClipboardEvent("paste", { bubbles: true, cancelable: true, clipboardData: files }));
});
await expect.poll(() => uploadStarted).toBe(true);
await shell.locator('button:has(svg.lucide-minus)').evaluate(button => (button as HTMLButtonElement).click());
await shell.locator('button:has(svg.icon-minus)').evaluate(button => (button as HTMLButtonElement).click());
await expect(page.locator('[data-tab-scroll-root]').filter({ hasText: "Latest fixture message" })).toHaveCount(0);
expect(await composer?.evaluate(element => element.isConnected)).toBe(true);
expect(await composer?.textContent()).toContain("Last edit before closing");
@@ -186,7 +186,7 @@ test("closing floating chat releases its list and preserves the live composer",
await expect(reopened).toHaveCSS("transform", "none");
// Reopening inside the exit animation must cancel removal of the same list.
await reopened.locator('button:has(svg.lucide-minus)').evaluate(button => (button as HTMLButtonElement).click());
await reopened.locator('button:has(svg.icon-minus)').evaluate(button => (button as HTMLButtonElement).click());
await page.getByRole("button", { name: "Multica is working...", exact: true }).evaluate(button => (button as HTMLButtonElement).click());
// Let the interrupted 150ms exit finish before checking that it cannot remove the reopened list.
await page.waitForTimeout(250);
+1 -1
View File
@@ -14,7 +14,7 @@
* Icon values are *names*, not React components, so this module stays
* React-free and safe inside `@multica/core`. The name → component registry
* lives in `packages/views/layout/route-icon-components.tsx`; its
* `Record<RouteIconName, LucideIcon>` type makes a missing component a compile
* `Record<RouteIconName, IconComponent>` type makes a missing component a compile
* error.
*/
+1 -1
View File
@@ -27,7 +27,7 @@ import type { TabActorType, TabSubject } from "./tab-subject";
/** The leading visual a tab should render. */
export type TabVisual =
/** A static Lucide icon (page icon or resourceless type icon). */
/** A static route icon (page icon or resourceless type icon). */
| { kind: "icon"; icon: RouteIconName }
/** An issue's live status glyph. `null` while the issue is loading.
* `category` is what selects the glyph — carried here so the tab strip
+1 -1
View File
@@ -5,7 +5,7 @@
* resource, a container with a selected item, a creation flow, or something
* unrecognized — is what decides how it should look (icon + title). This module
* turns a URL into that semantic {@link TabSubject}. It is pure: no React, no
* Lucide, no query — only URL parsing. The visual/title mapping lives in
* icons, no query — only URL parsing. The visual/title mapping lives in
* `tab-presentation.ts`; the React glue lives in `@multica/views`.
*
* Workspace URLs are `/{slug}/{segment}/...`. The slug is index 0, the route
+25
View File
@@ -2,6 +2,28 @@ import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import importPlugin from "eslint-plugin-import-x";
// Multica draws its own icons: every glyph comes from @multica/ui/icons, and a
// missing one is drawn in packages/ui/icons/drawings. The pattern covers each
// library's root and its subpaths (`react-icons/fa`, `@heroicons/react/24/outline`).
// Exported because flat config replaces a rule's options wholesale: a package
// that sets its own `no-restricted-imports` must include these patterns again.
export const ICON_LIBRARIES = [
"lucide-react",
"react-icons",
"@heroicons/react",
"@tabler/icons-react",
"@radix-ui/react-icons",
"@phosphor-icons/react",
];
const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
export const ICON_LIBRARY_IMPORT_PATTERNS = [
{
regex: `^(${ICON_LIBRARIES.map(escape).join("|")})(/.*)?$`,
message:
"Import icons from @multica/ui/icons; draw a missing one in packages/ui/icons/drawings (see .agents/skills/multica-icons).",
},
];
/** @type {import("eslint").Linter.Config[]} */
export default [
eslint.configs.recommended,
@@ -32,6 +54,9 @@ export default [
],
peerDependencies: true,
}],
"no-restricted-imports": ["error", {
patterns: ICON_LIBRARY_IMPORT_PATTERNS,
}],
},
},
{
@@ -0,0 +1,39 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import { fileURLToPath } from "node:url";
import { ESLint } from "eslint";
import base from "./base.js";
const BANNED = [
"lucide-react",
"react-icons/fa",
"@heroicons/react/24/outline",
"@tabler/icons-react",
"@radix-ui/react-icons",
"@phosphor-icons/react/dist/ssr",
];
const ALLOWED = ["@multica/ui/icons", "react-icons-extra", "@radix-ui/react-dialog", "@heroicons/reactive"];
async function restricted(eslint, filePath, source) {
const [result] = await eslint.lintText(`import { X } from "${source}";\nexport { X };\n`, { filePath });
return result.messages.filter((m) => m.ruleId === "no-restricted-imports").length;
}
const targets = [
["base config", new ESLint({ overrideConfigFile: true, overrideConfig: base }), "src/icon.ts"],
[
"desktop renderer config",
new ESLint({ cwd: fileURLToPath(new URL("../../apps/desktop", import.meta.url)) }),
"src/renderer/src/icon.tsx",
],
];
for (const [label, eslint, filePath] of targets) {
test(`${label} bans icon libraries and their subpaths`, async () => {
for (const source of BANNED) assert.equal(await restricted(eslint, filePath, source), 1, source);
});
test(`${label} allows @multica/ui/icons and look-alike packages`, async () => {
for (const source of ALLOWED) assert.equal(await restricted(eslint, filePath, source), 0, source);
});
}
+3
View File
@@ -3,6 +3,9 @@
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"test": "node --test"
},
"exports": {
"./base": "./base.js",
"./react": "./react.js",
@@ -1,7 +1,7 @@
"use client";
import { useState, useEffect } from "react";
import { Bot, Users } from "lucide-react";
import { Bot, Users } from "@multica/ui/icons";
import { cn } from "@multica/ui/lib/utils";
import {
AVATAR_SIZE_PX,
@@ -1,4 +1,4 @@
import { Lock } from "lucide-react";
import { Lock } from "@multica/ui/icons";
import { cn } from "@multica/ui/lib/utils";
type Resource = "agent" | "skill" | "comment" | "runtime" | "workspace";
@@ -1,7 +1,7 @@
"use client";
import { useRef } from "react";
import { Paperclip } from "lucide-react";
import { Paperclip } from "@multica/ui/icons";
import { useTranslation } from "react-i18next";
import { IconButton } from "@multica/ui/components/common/icon-button";
import { cn } from "@multica/ui/lib/utils";
@@ -1,7 +1,7 @@
"use client";
import { useState, lazy, Suspense } from "react";
import { SmilePlus } from "lucide-react";
import { SmilePlus } from "@multica/ui/icons";
import { cn } from "@multica/ui/lib/utils";
import { Popover, PopoverTrigger, PopoverContent } from "@multica/ui/components/ui/popover";
import { DeferredTooltip } from "./deferred-tooltip";
@@ -1,7 +1,7 @@
"use client";
import type { ReactNode, SyntheticEvent } from "react";
import { ArrowUp, Loader2, Square } from "lucide-react";
import { ArrowUp, Loader2, Square } from "@multica/ui/icons";
import { Button } from "@multica/ui/components/ui/button";
import {
Tooltip,
+3 -3
View File
@@ -6,7 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@multica/ui/lib/utils"
const buttonVariants = cva(
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-body font-medium whitespace-nowrap transition-[color,background-color,border-color,box-shadow,transform] outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-body font-medium whitespace-nowrap transition-[color,background-color,border-color,box-shadow,transform] outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5",
{
variants: {
variant: {
@@ -45,8 +45,8 @@ const buttonVariants = cva(
size: {
default:
"h-[var(--button-height-default)] gap-[var(--button-gap-default)] px-[var(--button-padding-default)] has-data-[icon=inline-end]:pr-[max(0px,calc(var(--button-padding-default)-0.125rem))] has-data-[icon=inline-start]:pl-[max(0px,calc(var(--button-padding-default)-0.125rem))]",
xs: "h-[var(--button-height-xs)] gap-[var(--button-gap-xs)] rounded-md px-[var(--button-padding-xs)] text-caption in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-[max(0px,calc(var(--button-padding-xs)-0.125rem))] has-data-[icon=inline-start]:pl-[max(0px,calc(var(--button-padding-xs)-0.125rem))] [&_svg:not([class*='size-'])]:size-3",
sm: "h-[var(--button-height-sm)] gap-[var(--button-gap-sm)] rounded-md px-[var(--button-padding-sm)] text-label in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-[max(0px,calc(var(--button-padding-sm)-0.25rem))] has-data-[icon=inline-start]:pl-[max(0px,calc(var(--button-padding-sm)-0.25rem))] [&_svg:not([class*='size-'])]:size-3.5",
xs: "h-[var(--button-height-xs)] gap-[var(--button-gap-xs)] rounded-md px-[var(--button-padding-xs)] text-caption in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-[max(0px,calc(var(--button-padding-xs)-0.125rem))] has-data-[icon=inline-start]:pl-[max(0px,calc(var(--button-padding-xs)-0.125rem))] [&_svg:not([class*='size-'])]:size-3.5",
sm: "h-[var(--button-height-sm)] gap-[var(--button-gap-sm)] rounded-md px-[var(--button-padding-sm)] text-label in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-[max(0px,calc(var(--button-padding-sm)-0.25rem))] has-data-[icon=inline-start]:pl-[max(0px,calc(var(--button-padding-sm)-0.25rem))] [&_svg:not([class*='size-'])]:size-4",
lg: "h-[var(--button-height-lg)] gap-[var(--button-gap-lg)] px-[var(--button-padding-lg)] has-data-[icon=inline-end]:pr-[calc(var(--button-padding-lg)+0.125rem)] has-data-[icon=inline-start]:pl-[calc(var(--button-padding-lg)+0.125rem)]",
icon: "size-[var(--button-height-default)]",
"icon-xs":
+1 -1
View File
@@ -10,7 +10,7 @@ import {
import { cn } from "@multica/ui/lib/utils"
import { Button, buttonVariants } from "@multica/ui/components/ui/button"
import { ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon } from "lucide-react"
import { ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon } from "@multica/ui/icons"
function Calendar({
className,
+1 -1
View File
@@ -3,7 +3,7 @@
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox"
import { cn } from "@multica/ui/lib/utils"
import { CheckIcon, MinusIcon } from "lucide-react"
import { CheckIcon, MinusIcon } from "@multica/ui/icons"
function Checkbox({ className, indeterminate, ...props }: CheckboxPrimitive.Root.Props) {
return (
+2 -2
View File
@@ -15,7 +15,7 @@ import {
InputGroup,
InputGroupAddon,
} from "@multica/ui/components/ui/input-group"
import { SearchIcon, CheckIcon } from "lucide-react"
import { SearchIcon, CheckIcon } from "@multica/ui/icons"
function Command({
className,
@@ -165,7 +165,7 @@ function CommandItem({
<CommandPrimitive.Item
data-slot="command-item"
className={cn(
"group/command-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-body outline-hidden select-none in-data-[slot=dialog-content]:rounded-lg! data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-selected:bg-muted data-selected:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"group/command-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-body outline-hidden select-none in-data-[slot=dialog-content]:rounded-lg! data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-selected:bg-muted data-selected:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5",
className
)}
{...props}
+8 -8
View File
@@ -4,7 +4,7 @@ import * as React from "react"
import { ContextMenu as ContextMenuPrimitive } from "@base-ui/react/context-menu"
import { cn } from "@multica/ui/lib/utils"
import { ChevronRightIcon, CheckIcon } from "lucide-react"
import { ChevronRightIcon, CheckIcon } from "@multica/ui/icons"
function ContextMenu({ ...props }: ContextMenuPrimitive.Root.Props) {
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
@@ -54,7 +54,7 @@ function ContextMenuContent({
>
<ContextMenuPrimitive.Popup
data-slot="context-menu-content"
className={cn("z-50 max-h-(--available-height) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-surface-raised p-1 text-popover-foreground shadow-[var(--menu-shadow)] ring-1 ring-surface-border duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
className={cn("z-50 max-h-(--available-height) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-xl bg-surface-raised p-1.5 text-popover-foreground shadow-[var(--menu-shadow)] ring-1 ring-surface-border duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
{...props}
/>
</ContextMenuPrimitive.Positioner>
@@ -80,7 +80,7 @@ function ContextMenuLabel({
data-slot="context-menu-label"
data-inset={inset}
className={cn(
"px-1.5 py-1 text-caption font-medium text-muted-foreground data-inset:pl-7",
"px-2 py-1 text-caption font-medium text-muted-foreground data-inset:pl-9",
className
)}
{...props}
@@ -103,7 +103,7 @@ function ContextMenuItem({
data-inset={inset}
data-variant={variant}
className={cn(
"group/context-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-body outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"group/context-menu-item relative flex cursor-default items-center gap-2.5 rounded-lg px-2 py-1.25 text-body-lg outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-9 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5",
className
)}
{...props}
@@ -130,7 +130,7 @@ function ContextMenuSubTrigger({
data-slot="context-menu-sub-trigger"
data-inset={inset}
className={cn(
"flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-body outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"flex cursor-default items-center gap-2.5 rounded-lg px-2 py-1.25 text-body-lg outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-9 data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5",
className
)}
{...props}
@@ -167,7 +167,7 @@ function ContextMenuCheckboxItem({
data-slot="context-menu-checkbox-item"
data-inset={inset}
className={cn(
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-body outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"relative flex cursor-default items-center gap-2.5 rounded-lg py-1.25 pr-8 pl-2 text-body-lg outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-9 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5",
className
)}
checked={checked}
@@ -208,7 +208,7 @@ function ContextMenuRadioItem({
data-slot="context-menu-radio-item"
data-inset={inset}
className={cn(
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-body outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"relative flex cursor-default items-center gap-2.5 rounded-lg py-1.25 pr-8 pl-2 text-body-lg outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-9 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5",
className
)}
{...props}
@@ -231,7 +231,7 @@ function ContextMenuSeparator({
return (
<ContextMenuPrimitive.Separator
data-slot="context-menu-separator"
className={cn("-mx-1 my-1 h-px bg-border", className)}
className={cn("-mx-1.5 my-1.5 h-px bg-border", className)}
{...props}
/>
)
+1 -1
View File
@@ -5,7 +5,7 @@ import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
import { cn } from "@multica/ui/lib/utils"
import { Button } from "@multica/ui/components/ui/button"
import { XIcon } from "lucide-react"
import { XIcon } from "@multica/ui/icons"
function Dialog({ ...props }: DialogPrimitive.Root.Props) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
+9 -9
View File
@@ -4,7 +4,7 @@ import * as React from "react"
import { Menu as MenuPrimitive } from "@base-ui/react/menu"
import { cn } from "@multica/ui/lib/utils"
import { ChevronRightIcon, CheckIcon } from "lucide-react"
import { ChevronRightIcon, CheckIcon } from "@multica/ui/icons"
function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />
@@ -53,7 +53,7 @@ function DropdownMenuContent({
e.stopPropagation()
onClick?.(e)
}}
className={cn("z-50 max-h-(--available-height) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-surface-raised p-1 text-popover-foreground shadow-[var(--menu-shadow)] ring-1 ring-surface-border duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95", className )}
className={cn("z-50 max-h-(--available-height) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-xl bg-surface-raised p-1.5 text-popover-foreground shadow-[var(--menu-shadow)] ring-1 ring-surface-border duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95", className )}
{...props}
/>
</MenuPrimitive.Positioner>
@@ -77,7 +77,7 @@ function DropdownMenuLabel({
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn(
"px-1.5 py-1 text-caption font-medium text-muted-foreground data-inset:pl-7",
"px-2 py-1 text-caption font-medium text-muted-foreground data-inset:pl-9",
className
)}
{...props}
@@ -100,7 +100,7 @@ function DropdownMenuItem({
data-inset={inset}
data-variant={variant}
className={cn(
"group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-body outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"group/dropdown-menu-item relative flex cursor-default items-center gap-2.5 rounded-lg px-2 py-1.25 text-body-lg outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-9 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5",
className
)}
{...props}
@@ -125,7 +125,7 @@ function DropdownMenuSubTrigger({
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-body outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"flex cursor-default items-center gap-2.5 rounded-lg px-2 py-1.25 text-body-lg outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-9 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5",
className
)}
{...props}
@@ -147,7 +147,7 @@ function DropdownMenuSubContent({
return (
<DropdownMenuContent
data-slot="dropdown-menu-sub-content"
className={cn("w-auto min-w-[96px] rounded-lg bg-surface-raised p-1 text-popover-foreground shadow-[var(--menu-shadow)] ring-1 ring-surface-border duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
className={cn("w-auto min-w-[96px] rounded-xl bg-surface-raised p-1.5 text-popover-foreground shadow-[var(--menu-shadow)] ring-1 ring-surface-border duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
align={align}
alignOffset={alignOffset}
side={side}
@@ -171,7 +171,7 @@ function DropdownMenuCheckboxItem({
data-slot="dropdown-menu-checkbox-item"
data-inset={inset}
className={cn(
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-body outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"relative flex cursor-default items-center gap-2.5 rounded-lg py-1.25 pr-8 pl-2 text-body-lg outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-9 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5",
className
)}
checked={checked}
@@ -219,7 +219,7 @@ function DropdownMenuRadioItem({
data-inset={inset}
closeOnClick={closeOnClick}
className={cn(
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-body outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"relative flex cursor-default items-center gap-2.5 rounded-lg py-1.25 pr-8 pl-2 text-body-lg outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-9 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5",
className
)}
{...props}
@@ -245,7 +245,7 @@ function DropdownMenuSeparator({
return (
<MenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("-mx-1 my-1 h-px bg-border", className)}
className={cn("-mx-1.5 my-1.5 h-px bg-border", className)}
{...props}
/>
)
+1 -1
View File
@@ -4,7 +4,7 @@ import * as React from "react"
import { OTPInput, OTPInputContext } from "input-otp"
import { cn } from "@multica/ui/lib/utils"
import { MinusIcon } from "lucide-react"
import { MinusIcon } from "@multica/ui/icons"
function InputOTP({
className,
+1 -1
View File
@@ -1,6 +1,6 @@
"use client";
import { ArrowDown, ArrowUp } from "lucide-react";
import { ArrowDown, ArrowUp } from "@multica/ui/icons";
import { cn } from "../../lib/utils";
+5 -5
View File
@@ -4,7 +4,7 @@ import * as React from "react"
import { Select as SelectPrimitive } from "@base-ui/react/select"
import { cn } from "@multica/ui/lib/utils"
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "@multica/ui/icons"
type SelectProps<
Value,
@@ -56,7 +56,7 @@ function SelectTrigger({
data-slot="select-trigger"
data-size={size}
className={cn(
"flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-body whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-md *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-body whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-md *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5",
className
)}
{...props}
@@ -132,7 +132,7 @@ function SelectItem({
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-body outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
"relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-body outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
)}
{...props}
@@ -172,7 +172,7 @@ function SelectScrollUpButton({
<SelectPrimitive.ScrollUpArrow
data-slot="select-scroll-up-button"
className={cn(
"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4.5",
className
)}
{...props}
@@ -191,7 +191,7 @@ function SelectScrollDownButton({
<SelectPrimitive.ScrollDownArrow
data-slot="select-scroll-down-button"
className={cn(
"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4.5",
className
)}
{...props}
+1 -1
View File
@@ -5,7 +5,7 @@ import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"
import { cn } from "@multica/ui/lib/utils"
import { Button } from "@multica/ui/components/ui/button"
import { XIcon } from "lucide-react"
import { XIcon } from "@multica/ui/icons"
function Sheet({ ...props }: SheetPrimitive.Root.Props) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />
+5 -5
View File
@@ -24,7 +24,7 @@ import {
TooltipContent,
TooltipTrigger,
} from "@multica/ui/components/ui/tooltip"
import { PanelLeftIcon } from "lucide-react"
import { PanelLeftIcon } from "@multica/ui/icons"
const SIDEBAR_WIDTH_DEFAULT = 256
const SIDEBAR_WIDTH_MIN = 200
@@ -686,7 +686,7 @@ function SidebarGroupLabel({
props: mergeProps<"div">(
{
className: cn(
"flex h-8 shrink-0 items-center rounded-md px-2 text-caption font-medium text-muted-foreground ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"flex h-8 shrink-0 items-center rounded-md px-2 text-caption text-muted-foreground ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
className
),
},
@@ -761,7 +761,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
}
const sidebarMenuButtonVariants = cva(
"peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-body ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground data-active:hover:bg-sidebar-accent [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate",
"peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-body ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground data-active:hover:bg-sidebar-accent data-active:[&_svg_.icon-body]:fill-current data-active:[&_svg_.icon-cut]:stroke-sidebar-accent data-active:[&_svg_.icon-cut-fill]:fill-sidebar-accent [&_svg]:size-4.5 [&_svg]:shrink-0 [&>span:last-child]:truncate",
{
variants: {
variant: {
@@ -770,7 +770,7 @@ const sidebarMenuButtonVariants = cva(
"bg-background shadow-[0_0_0_1px_var(--color-sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--color-sidebar-accent)]",
},
size: {
default: "h-8 text-body",
default: "h-[34px] text-body-lg",
sm: "h-7 text-caption",
lg: "h-12 text-body group-data-[collapsible=icon]:p-0!",
},
@@ -965,7 +965,7 @@ function SidebarMenuSubButton({
props: mergeProps<"a">(
{
className: cn(
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden group-data-[collapsible=icon]:hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-body data-[size=sm]:text-caption data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-active:hover:bg-sidebar-accent [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden group-data-[collapsible=icon]:hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-body data-[size=sm]:text-caption data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-active:hover:bg-sidebar-accent [&>span:last-child]:truncate [&>svg]:size-4.5 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
className
),
},
+1 -1
View File
@@ -2,7 +2,7 @@
import { useTheme } from "next-themes"
import { Toaster as Sonner, type ToasterProps } from "sonner"
import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react"
import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "@multica/ui/icons"
const Toaster = ({ ...props }: ToasterProps) => {
// Use `resolvedTheme` (the concrete "light" / "dark" value) instead of
+1 -1
View File
@@ -1,5 +1,5 @@
import { cn } from "@multica/ui/lib/utils"
import { Loader2Icon } from "lucide-react"
import { Loader2Icon } from "@multica/ui/icons"
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
return (
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import { Clock } from "lucide-react";
import { Clock } from "@multica/ui/icons";
import { cn } from "@multica/ui/lib/utils";

Some files were not shown because too many files have changed in this diff Show More