mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 01:16:45 +08:00
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:
co-authored by
Cursor
multica-agent
parent
fb49f55924
commit
f0c361ebca
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}";
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,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,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,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";
|
||||
|
||||
|
||||
@@ -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,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 {
|
||||
|
||||
@@ -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,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,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,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,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,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,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,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,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,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,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,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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
History,
|
||||
LayoutDashboard,
|
||||
Palette,
|
||||
} from "lucide-react";
|
||||
} from "@multica/ui/icons";
|
||||
import {
|
||||
catalog,
|
||||
availableCount,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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,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,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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { ArrowDown, ArrowUp } from "lucide-react";
|
||||
import { ArrowDown, ArrowUp } from "@multica/ui/icons";
|
||||
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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
|
||||
),
|
||||
},
|
||||
|
||||
@@ -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,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,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
Reference in New Issue
Block a user