@squadbase/vantage 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -10,6 +10,6 @@ import 'react';
10
10
  */
11
11
 
12
12
  /** Framework version, kept in sync with package.json at build time. */
13
- declare const VERSION = "0.2.0";
13
+ declare const VERSION = "0.2.2";
14
14
 
15
15
  export { VERSION };
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ function definePage(config) {
6
6
  }
7
7
 
8
8
  // src/index.ts
9
- var VERSION = "0.2.0";
9
+ var VERSION = "0.2.2";
10
10
 
11
11
  export { VERSION, definePage };
12
12
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/runtime/define-page.ts","../src/index.ts"],"names":[],"mappings":";;;AAyCO,SAAS,WAAW,MAAA,EAAgC;AACzD,EAAA,OAAO,MAAA;AACT;;;AChCO,IAAM,OAAA,GAAU","file":"index.js","sourcesContent":["import type { ComponentType, ReactNode } from \"react\"\n\n/**\n * Optional per-page configuration. The only required part of a page module is\n * its default-exported component; `definePage` covers everything else.\n */\nexport interface PageConfig {\n /** Document title applied when this route is active. */\n title?: string\n /** Description metadata for the page. */\n description?: string\n /**\n * Short label for navigation built from `useRoutes()`. Defaults to `title`,\n * which is often too long for a nav (\"Overview · Acme Analytics\").\n */\n navLabel?: string\n /**\n * Component rendered while the route is loading.\n * (Reserved — not yet applied to Vantage's code-split routes in the prototype.)\n */\n pendingComponent?: ComponentType\n /**\n * Component rendered when the route throws.\n * (Reserved — not yet applied to Vantage's code-split routes in the prototype.)\n */\n errorComponent?: ComponentType<{ error: unknown }>\n /**\n * Validate/transform the URL search params for this route.\n * (Reserved — not yet applied to Vantage's code-split routes in the prototype.)\n */\n validateSearch?: (search: Record<string, unknown>) => Record<string, unknown>\n}\n\n/**\n * Declare optional page configuration.\n *\n * ```tsx\n * export const page = definePage({ title: \"Monthly Analysis\" })\n * export default function MonthlyAnalysis() { return <div/> }\n * ```\n */\nexport function definePage(config: PageConfig): PageConfig {\n return config\n}\n\n/** Convenience type for a Vantage page component. */\nexport type PageComponent = ComponentType & { (): ReactNode }\n","/**\n * `@squadbase/vantage` — public runtime surface.\n *\n * The only import most pages need. Server state lives in\n * `@squadbase/vantage/query`, navigation in `@squadbase/vantage/router`,\n * UI in `@squadbase/vantage/ui`, and API types in `@squadbase/vantage/server`.\n */\nexport { definePage } from \"./runtime/define-page\"\nexport type { PageConfig, PageComponent } from \"./runtime/define-page\"\n\n/** Framework version, kept in sync with package.json at build time. */\nexport const VERSION = \"0.2.0\"\n"]}
1
+ {"version":3,"sources":["../src/runtime/define-page.ts","../src/index.ts"],"names":[],"mappings":";;;AAyCO,SAAS,WAAW,MAAA,EAAgC;AACzD,EAAA,OAAO,MAAA;AACT;;;AChCO,IAAM,OAAA,GAAU","file":"index.js","sourcesContent":["import type { ComponentType, ReactNode } from \"react\"\n\n/**\n * Optional per-page configuration. The only required part of a page module is\n * its default-exported component; `definePage` covers everything else.\n */\nexport interface PageConfig {\n /** Document title applied when this route is active. */\n title?: string\n /** Description metadata for the page. */\n description?: string\n /**\n * Short label for navigation built from `useRoutes()`. Defaults to `title`,\n * which is often too long for a nav (\"Overview · Acme Analytics\").\n */\n navLabel?: string\n /**\n * Component rendered while the route is loading.\n * (Reserved — not yet applied to Vantage's code-split routes in the prototype.)\n */\n pendingComponent?: ComponentType\n /**\n * Component rendered when the route throws.\n * (Reserved — not yet applied to Vantage's code-split routes in the prototype.)\n */\n errorComponent?: ComponentType<{ error: unknown }>\n /**\n * Validate/transform the URL search params for this route.\n * (Reserved — not yet applied to Vantage's code-split routes in the prototype.)\n */\n validateSearch?: (search: Record<string, unknown>) => Record<string, unknown>\n}\n\n/**\n * Declare optional page configuration.\n *\n * ```tsx\n * export const page = definePage({ title: \"Monthly Analysis\" })\n * export default function MonthlyAnalysis() { return <div/> }\n * ```\n */\nexport function definePage(config: PageConfig): PageConfig {\n return config\n}\n\n/** Convenience type for a Vantage page component. */\nexport type PageComponent = ComponentType & { (): ReactNode }\n","/**\n * `@squadbase/vantage` — public runtime surface.\n *\n * The only import most pages need. Server state lives in\n * `@squadbase/vantage/query`, navigation in `@squadbase/vantage/router`,\n * UI in `@squadbase/vantage/ui`, and API types in `@squadbase/vantage/server`.\n */\nexport { definePage } from \"./runtime/define-page\"\nexport type { PageConfig, PageComponent } from \"./runtime/define-page\"\n\n/** Framework version, kept in sync with package.json at build time. */\nexport const VERSION = \"0.2.2\"\n"]}
@@ -67,7 +67,7 @@ interface SearchStateOptions {
67
67
  *
68
68
  * ```tsx
69
69
  * const [region, setRegion] = useSearchParam("region", "all")
70
- * <SegmentedControl value={region} onValueChange={setRegion} options={REGIONS} />
70
+ * <SegmentedControl value={region} onChange={setRegion} options={REGIONS} />
71
71
  * ```
72
72
  *
73
73
  * Writing the default value (or `null`) drops the key from the URL.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/router/routes.ts","../../src/router/search.ts","../../src/router/index.ts"],"names":["TanstackLink","useParamsBase","useSearch","useSearchBase","useNavigate","useNavigateBase"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Route-introspection hooks. The route list comes from the generated manifest,\n * so a nav built on `useRoutes()` picks up a new page file the moment it is\n * created — no hand-maintained link array.\n */\nimport { useRouterState } from \"@tanstack/react-router\"\nimport { findRoute, getRoutes, type RouteInfo } from \"./registry\"\n\n/**\n * Every page route in the app, in the framework's scan order (shallow before\n * deep, static before dynamic, alphabetical) — the order a nav wants.\n *\n * ```tsx\n * // Dynamic routes have no single URL, so a nav lists only static ones.\n * const routes = useRoutes().filter((route) => !route.dynamic)\n * routes.map((route) => <Link key={route.path} to={route.to}>{route.label}</Link>)\n * ```\n */\nexport function useRoutes(): readonly RouteInfo[] {\n return getRoutes()\n}\n\n/**\n * The route currently rendered, or `undefined` on the 404 route (no page\n * matched). Use it for breadcrumbs or a page header that mirrors `definePage`.\n */\nexport function useCurrentRoute(): RouteInfo | undefined {\n // Selecting the path string (not the match object) keeps the subscription\n // stable — the component re-renders on navigation, not on every router tick.\n const path = useRouterState({\n select: (state) => {\n const matches = state.matches\n const leaf = matches[matches.length - 1] as\n | { staticData?: { routePath?: string } }\n | undefined\n return leaf?.staticData?.routePath\n },\n })\n return path ? findRoute(path) : undefined\n}\n","/**\n * URL-backed component state. A dashboard's filters belong in the URL: the view\n * survives a reload and can be pasted to a colleague. These hooks read and write\n * a single search-param key with a `useState`-shaped API.\n */\nimport { useCallback } from \"react\"\nimport { useNavigate, useSearch } from \"@tanstack/react-router\"\n\nexport interface SearchStateOptions {\n /**\n * Replace the current history entry instead of pushing a new one.\n * Defaults to `true` so dragging a slider doesn't fill the back button.\n */\n replace?: boolean\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nfunction useRawSearch(): Record<string, unknown> {\n return useSearch({ strict: false }) as any\n}\n\n/**\n * Writer for one key. Passing `undefined` removes the key so defaults never\n * show up in the URL.\n */\nfunction useSearchWriter(key: string, options: SearchStateOptions): (next: unknown) => void {\n const navigate = useNavigate() as any\n const replace = options.replace ?? true\n return useCallback(\n (next: unknown) => {\n // No `to`: TanStack keeps the current route and its path params, so this\n // works unchanged on a dynamic route like `/sales/$customerId`.\n navigate({\n search: (prev: Record<string, unknown>) => {\n const merged = { ...prev }\n if (next === undefined) delete merged[key]\n else merged[key] = next\n return merged\n },\n replace,\n })\n },\n [navigate, key, replace],\n )\n}\n\n/**\n * A string search param, as `useState`. The value is always a string —\n * TanStack parses `?year=2024` into a number, and this hook normalizes it back.\n *\n * ```tsx\n * const [region, setRegion] = useSearchParam(\"region\", \"all\")\n * <SegmentedControl value={region} onValueChange={setRegion} options={REGIONS} />\n * ```\n *\n * Writing the default value (or `null`) drops the key from the URL.\n */\nexport function useSearchParam(\n key: string,\n defaultValue = \"\",\n options: SearchStateOptions = {},\n): [string, (value: string | null) => void] {\n const search = useRawSearch()\n const write = useSearchWriter(key, options)\n\n const raw = search[key]\n const value = raw === undefined || raw === null ? defaultValue : String(raw)\n\n const set = useCallback(\n (next: string | null) => {\n write(next === null || next === defaultValue ? undefined : next)\n },\n [write, defaultValue],\n )\n\n return [value, set]\n}\n\n/**\n * Any JSON-serializable search param, as `useState` — arrays for a multi-select,\n * an object for a whole filter bar. Supports a functional update.\n *\n * ```tsx\n * const [segments, setSegments] = useSearchState<string[]>(\"segments\", [])\n * setSegments((prev) => [...prev, \"enterprise\"])\n * ```\n *\n * Writing a value deep-equal to the default drops the key from the URL.\n */\nexport function useSearchState<T>(\n key: string,\n defaultValue: T,\n options: SearchStateOptions = {},\n): [T, (value: T | ((prev: T) => T)) => void] {\n const search = useRawSearch()\n const write = useSearchWriter(key, options)\n\n const raw = search[key]\n const value = raw === undefined || raw === null ? defaultValue : (raw as T)\n\n const set = useCallback(\n (next: T | ((prev: T) => T)) => {\n const resolved = typeof next === \"function\" ? (next as (prev: T) => T)(value) : next\n // Structural comparison: the parsed value is plain JSON, so stringify is\n // both sufficient and cheap at filter sizes.\n const isDefault = JSON.stringify(resolved) === JSON.stringify(defaultValue)\n write(isDefault ? undefined : resolved)\n },\n [write, value, defaultValue],\n )\n\n return [value, set]\n}\n","/**\n * Vantage's thin router API. Users depend on `@squadbase/vantage/router`\n * instead of `@tanstack/react-router`, so the underlying router can evolve\n * without touching user code. Only the ergonomic, SPA-relevant surface is\n * exposed; typed-route generics are intentionally relaxed to plain strings.\n */\nimport {\n Link as TanstackLink,\n Outlet,\n useNavigate as useNavigateBase,\n useParams as useParamsBase,\n useSearch as useSearchBase,\n useRouterState,\n redirect,\n notFound,\n} from \"@tanstack/react-router\"\nimport { createElement, type ReactNode } from \"react\"\n\nexport { Outlet, useRouterState, redirect, notFound }\n\nexport { useRoutes, useCurrentRoute } from \"./routes\"\nexport type { RouteInfo } from \"./registry\"\n\nexport { useSearchParam, useSearchState } from \"./search\"\nexport type { SearchStateOptions } from \"./search\"\n\nexport interface LinkProps {\n to: string\n params?: Record<string, string>\n search?: Record<string, unknown>\n hash?: string\n replace?: boolean\n className?: string\n activeClassName?: string\n children?: ReactNode\n [key: string]: unknown\n}\n\n/** Client-side navigation link. */\nexport function Link({ activeClassName, className, ...props }: LinkProps): ReactNode {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const anyLink = TanstackLink as any\n return createElement(anyLink, {\n ...props,\n className,\n activeProps: activeClassName ? { className: `${className ?? \"\"} ${activeClassName}`.trim() } : undefined,\n })\n}\n\n/** Route params for the current route (e.g. `{ customerId: \"42\" }`). */\nexport function useParams(): Record<string, string> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return useParamsBase({ strict: false }) as any\n}\n\n/** Validated/raw search params for the current route. */\nexport function useSearch(): Record<string, unknown> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return useSearchBase({ strict: false }) as any\n}\n\n/** Imperative navigation. `navigate({ to: \"/sales/42\" })`. */\nexport function useNavigate(): (opts: { to: string; params?: Record<string, string>; search?: Record<string, unknown>; replace?: boolean }) => void {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return useNavigateBase() as any\n}\n"]}
1
+ {"version":3,"sources":["../../src/router/routes.ts","../../src/router/search.ts","../../src/router/index.ts"],"names":["TanstackLink","useParamsBase","useSearch","useSearchBase","useNavigate","useNavigateBase"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Route-introspection hooks. The route list comes from the generated manifest,\n * so a nav built on `useRoutes()` picks up a new page file the moment it is\n * created — no hand-maintained link array.\n */\nimport { useRouterState } from \"@tanstack/react-router\"\nimport { findRoute, getRoutes, type RouteInfo } from \"./registry\"\n\n/**\n * Every page route in the app, in the framework's scan order (shallow before\n * deep, static before dynamic, alphabetical) — the order a nav wants.\n *\n * ```tsx\n * // Dynamic routes have no single URL, so a nav lists only static ones.\n * const routes = useRoutes().filter((route) => !route.dynamic)\n * routes.map((route) => <Link key={route.path} to={route.to}>{route.label}</Link>)\n * ```\n */\nexport function useRoutes(): readonly RouteInfo[] {\n return getRoutes()\n}\n\n/**\n * The route currently rendered, or `undefined` on the 404 route (no page\n * matched). Use it for breadcrumbs or a page header that mirrors `definePage`.\n */\nexport function useCurrentRoute(): RouteInfo | undefined {\n // Selecting the path string (not the match object) keeps the subscription\n // stable — the component re-renders on navigation, not on every router tick.\n const path = useRouterState({\n select: (state) => {\n const matches = state.matches\n const leaf = matches[matches.length - 1] as\n | { staticData?: { routePath?: string } }\n | undefined\n return leaf?.staticData?.routePath\n },\n })\n return path ? findRoute(path) : undefined\n}\n","/**\n * URL-backed component state. A dashboard's filters belong in the URL: the view\n * survives a reload and can be pasted to a colleague. These hooks read and write\n * a single search-param key with a `useState`-shaped API.\n */\nimport { useCallback } from \"react\"\nimport { useNavigate, useSearch } from \"@tanstack/react-router\"\n\nexport interface SearchStateOptions {\n /**\n * Replace the current history entry instead of pushing a new one.\n * Defaults to `true` so dragging a slider doesn't fill the back button.\n */\n replace?: boolean\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nfunction useRawSearch(): Record<string, unknown> {\n return useSearch({ strict: false }) as any\n}\n\n/**\n * Writer for one key. Passing `undefined` removes the key so defaults never\n * show up in the URL.\n */\nfunction useSearchWriter(key: string, options: SearchStateOptions): (next: unknown) => void {\n const navigate = useNavigate() as any\n const replace = options.replace ?? true\n return useCallback(\n (next: unknown) => {\n // No `to`: TanStack keeps the current route and its path params, so this\n // works unchanged on a dynamic route like `/sales/$customerId`.\n navigate({\n search: (prev: Record<string, unknown>) => {\n const merged = { ...prev }\n if (next === undefined) delete merged[key]\n else merged[key] = next\n return merged\n },\n replace,\n })\n },\n [navigate, key, replace],\n )\n}\n\n/**\n * A string search param, as `useState`. The value is always a string —\n * TanStack parses `?year=2024` into a number, and this hook normalizes it back.\n *\n * ```tsx\n * const [region, setRegion] = useSearchParam(\"region\", \"all\")\n * <SegmentedControl value={region} onChange={setRegion} options={REGIONS} />\n * ```\n *\n * Writing the default value (or `null`) drops the key from the URL.\n */\nexport function useSearchParam(\n key: string,\n defaultValue = \"\",\n options: SearchStateOptions = {},\n): [string, (value: string | null) => void] {\n const search = useRawSearch()\n const write = useSearchWriter(key, options)\n\n const raw = search[key]\n const value = raw === undefined || raw === null ? defaultValue : String(raw)\n\n const set = useCallback(\n (next: string | null) => {\n write(next === null || next === defaultValue ? undefined : next)\n },\n [write, defaultValue],\n )\n\n return [value, set]\n}\n\n/**\n * Any JSON-serializable search param, as `useState` — arrays for a multi-select,\n * an object for a whole filter bar. Supports a functional update.\n *\n * ```tsx\n * const [segments, setSegments] = useSearchState<string[]>(\"segments\", [])\n * setSegments((prev) => [...prev, \"enterprise\"])\n * ```\n *\n * Writing a value deep-equal to the default drops the key from the URL.\n */\nexport function useSearchState<T>(\n key: string,\n defaultValue: T,\n options: SearchStateOptions = {},\n): [T, (value: T | ((prev: T) => T)) => void] {\n const search = useRawSearch()\n const write = useSearchWriter(key, options)\n\n const raw = search[key]\n const value = raw === undefined || raw === null ? defaultValue : (raw as T)\n\n const set = useCallback(\n (next: T | ((prev: T) => T)) => {\n const resolved = typeof next === \"function\" ? (next as (prev: T) => T)(value) : next\n // Structural comparison: the parsed value is plain JSON, so stringify is\n // both sufficient and cheap at filter sizes.\n const isDefault = JSON.stringify(resolved) === JSON.stringify(defaultValue)\n write(isDefault ? undefined : resolved)\n },\n [write, value, defaultValue],\n )\n\n return [value, set]\n}\n","/**\n * Vantage's thin router API. Users depend on `@squadbase/vantage/router`\n * instead of `@tanstack/react-router`, so the underlying router can evolve\n * without touching user code. Only the ergonomic, SPA-relevant surface is\n * exposed; typed-route generics are intentionally relaxed to plain strings.\n */\nimport {\n Link as TanstackLink,\n Outlet,\n useNavigate as useNavigateBase,\n useParams as useParamsBase,\n useSearch as useSearchBase,\n useRouterState,\n redirect,\n notFound,\n} from \"@tanstack/react-router\"\nimport { createElement, type ReactNode } from \"react\"\n\nexport { Outlet, useRouterState, redirect, notFound }\n\nexport { useRoutes, useCurrentRoute } from \"./routes\"\nexport type { RouteInfo } from \"./registry\"\n\nexport { useSearchParam, useSearchState } from \"./search\"\nexport type { SearchStateOptions } from \"./search\"\n\nexport interface LinkProps {\n to: string\n params?: Record<string, string>\n search?: Record<string, unknown>\n hash?: string\n replace?: boolean\n className?: string\n activeClassName?: string\n children?: ReactNode\n [key: string]: unknown\n}\n\n/** Client-side navigation link. */\nexport function Link({ activeClassName, className, ...props }: LinkProps): ReactNode {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const anyLink = TanstackLink as any\n return createElement(anyLink, {\n ...props,\n className,\n activeProps: activeClassName ? { className: `${className ?? \"\"} ${activeClassName}`.trim() } : undefined,\n })\n}\n\n/** Route params for the current route (e.g. `{ customerId: \"42\" }`). */\nexport function useParams(): Record<string, string> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return useParamsBase({ strict: false }) as any\n}\n\n/** Validated/raw search params for the current route. */\nexport function useSearch(): Record<string, unknown> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return useSearchBase({ strict: false }) as any\n}\n\n/** Imperative navigation. `navigate({ to: \"/sales/42\" })`. */\nexport function useNavigate(): (opts: { to: string; params?: Record<string, string>; search?: Record<string, unknown>; replace?: boolean }) => void {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return useNavigateBase() as any\n}\n"]}
@@ -181,7 +181,7 @@ declare function ProgressValue({ className, ...props }: Progress$1.Value.Props):
181
181
  declare function ScrollArea({ className, children, ...props }: ScrollArea$1.Root.Props): React$1.JSX.Element;
182
182
  declare function ScrollBar({ className, orientation, ...props }: ScrollArea$1.Scrollbar.Props): React$1.JSX.Element;
183
183
 
184
- declare const Select: typeof Select$1.Root;
184
+ declare function Select<Value, Multiple extends boolean | undefined = false>({ items, children, ...props }: Select$1.Root.Props<Value, Multiple>): React$1.JSX.Element;
185
185
  declare function SelectGroup({ className, ...props }: Select$1.Group.Props): React$1.JSX.Element;
186
186
  declare function SelectValue({ className, ...props }: Select$1.Value.Props): React$1.JSX.Element;
187
187
  declare function SelectTrigger({ className, size, children, ...props }: Select$1.Trigger.Props & {
package/dist/ui/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { Badge, Button, Calendar, CalendarDayButton, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Textarea, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buttonVariants, toggleVariants, useIsMobile, useSidebar } from '../chunk-DIABD3KZ.js';
1
+ export { Badge, Button, Calendar, CalendarDayButton, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Textarea, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buttonVariants, toggleVariants, useIsMobile, useSidebar } from '../chunk-JC6MT5UU.js';
2
2
  import { cn } from '../chunk-A2UUGASH.js';
3
3
  export { cn } from '../chunk-A2UUGASH.js';
4
4
  import { Accordion as Accordion$1 } from '@base-ui/react/accordion';
@@ -22,12 +22,29 @@ The skills ship inside the `@squadbase/vantage` package. `vantage add skill` cop
22
22
  your app. They land **directly under the project root** by default; `--dir` puts them elsewhere.
23
23
 
24
24
  ```bash
25
- vantage add skill --all # every bundled skill, under the root
25
+ vantage add skill # list the skills and where they sit (writes nothing)
26
+ vantage add skill --all # every skill that isn't placed yet, under the root
26
27
  vantage add skill vantage-app # a single skill by name
27
28
  vantage add skill --all --dir .claude/skills # where Claude Code looks for them
28
- vantage add skill vantage-app --force # overwrite an existing copy
29
+ vantage add skill --all --force # refresh the placed copies in place
29
30
  ```
30
31
 
32
+ **The destination is scanned for, not assumed.** `add skill` walks the project first (skipping
33
+ `node_modules` and friends) and **skips any skill that is already placed, reporting where it is** —
34
+ under the root, under `.claude/skills/`, under `.squadbase/skills/`, it doesn't matter, so
35
+ **re-running never creates a second copy**. Drop the name to get just the list:
36
+
37
+ ```text
38
+ $ vantage add skill
39
+ Bundled skills:
40
+ vantage-add-feature .claude/skills/vantage-add-feature/
41
+ vantage-app .claude/skills/vantage-app/
42
+ vantage-pitfalls not placed
43
+ ```
44
+
45
+ With `--force`, the copies that were found are rewritten **where they are** (an explicit `--dir`
46
+ still wins as the destination).
47
+
31
48
  A `--dir` path resolves from the project root (absolute paths work too). To have Claude Code pick
32
49
  them up automatically, place them in `.claude/skills/`:
33
50
 
@@ -54,8 +71,8 @@ skill based on the task at hand. For other agents, pass whatever directory that
54
71
 
55
72
  - **The source of truth lives in the package.** Skills are distributed and updated together with
56
73
  the framework version, so bumping `@squadbase/vantage` gets you the latest procedures. To refresh
57
- copies you've already placed, run `vantage add skill --all --force` with the same `--dir` you
58
- used originally.
74
+ copies you've already placed, run `vantage add skill --all --force` the destination is scanned
75
+ for, so you don't have to remember the `--dir` you used originally.
59
76
  - **The Squadbase template ships them pre-placed.** Apps created from the template already contain
60
77
  `.claude/skills/`. For a hand-made app, add them with `vantage add skill`.
61
78
 
@@ -9,6 +9,72 @@ What changed in each Vantage release. Newest is on top.
9
9
  > minor release may include backwards-incompatible changes. A patch is limited to
10
10
  > backwards-compatible additions and bug fixes.
11
11
 
12
+ ## v0.2.2
13
+
14
+ `vantage add skill` now scans for where the skills already are, and charts follow
15
+ stylesheet swaps too. Both are backwards-compatible.
16
+
17
+ ### Changed
18
+
19
+ - **`vantage add skill` looks for placed skills before copying** — it walks the
20
+ project for `<skill>/SKILL.md` and **skips anything already placed, reporting
21
+ where it is**. Under the root, under `.claude/skills/`, under
22
+ `.squadbase/skills/` — it makes no difference, so **re-running never creates a
23
+ second copy**, and an agent no longer has to know the candidate locations.
24
+ - `--force` rewrites the copies **where they were found** (an explicit `--dir`
25
+ still wins as the destination), so refreshing placed copies doesn't require
26
+ remembering the `--dir` you used originally.
27
+ - `vantage add skill` with no name is now a listing command: it prints the
28
+ bundled skills and where each one sits, and writes nothing.
29
+ - The bundled `AGENTS.md` drops its `ls`-the-usual-places routine in favour of
30
+ letting the CLI find them (a placed `AGENTS.md` is synced by `vantage upgrade`).
31
+
32
+ ### Fixed
33
+
34
+ - **[`EChart`](parts/echart) could miss a CSS update** — token
35
+ changes were only watched through attributes on `<html>` / `<body>`, so a
36
+ change that **only rewrites the injected `<style>`** (editing `styles.css` with
37
+ the dev server running, applying a theme preset, …) left the chart painted in
38
+ the previous colours while the DOM tokens were already new. A reload fixed it,
39
+ which made it read as "the preset didn't apply". Stylesheet swaps are now
40
+ watched as well, and the chart repaints **only when the token values actually
41
+ change**.
42
+
43
+ ## v0.2.1
44
+
45
+ Charts now follow the theme tokens, and `Select` works out the value-to-label
46
+ map for you. Both are backwards-compatible.
47
+
48
+ > [!NOTE]
49
+ > **Chart colours change.** Charts without an explicit `option.color` used to paint
50
+ > in ECharts' own defaults; they now use the theme's `--chart-1` through
51
+ > `--chart-5`. Charts that set `option.color` are unaffected.
52
+
53
+ ### Changed
54
+
55
+ - **[`EChart`](parts/echart) follows the theme tokens** — series
56
+ come from `--chart-1` through `--chart-5`, while axes, legend and tooltip are
57
+ built from the text and border tokens, and a light/dark switch is picked up.
58
+ ECharts can't read CSS variables *while painting*, so the tokens are resolved
59
+ once at init. Set `option.color` to override just the series — axes keep
60
+ following the tokens. To paint the whole chart yourself, pass the `theme` prop
61
+ (that turns token-following off entirely).
62
+ - **[`Select`](ui/select) no longer needs `items`** — Base UI's
63
+ `SelectValue` renders the raw selected *value*, so a trigger would read
64
+ `kanto` where the list said "Kanto". Vantage now derives the map from your
65
+ `SelectItem`s. **An explicit `items` still wins**, so existing code is
66
+ unchanged (keep passing it when a separate component returns the options).
67
+
68
+ ### Fixed
69
+
70
+ - The `SegmentedControl` example in `useSearchParam`'s JSDoc passed a
71
+ non-existent `onValueChange` prop (it's `onChange`).
72
+ - Refreshed the bundled `AGENTS.md` and Skills: `vantage add ui|block` can copy
73
+ only `data-table` and `sales-overview`; the mode follows from whether `server/`
74
+ exists rather than being a "promote the SPA to fullstack" procedure; and look
75
+ for already-placed Skills before adding them. A placed `AGENTS.md` is synced by
76
+ `vantage upgrade`.
77
+
12
78
  ## v0.2.0
13
79
 
14
80
  Makes the destination of `vantage add skill` selectable, and drops the `.js`
@@ -13,7 +13,7 @@
13
13
  | `vantage preview` | Run the production build locally (fullstack default `:4173`; spa via Vite preview) |
14
14
  | `vantage check` | Static checks (routes, boundaries, forbidden files). Exit 1 on error |
15
15
  | `vantage routes` | Print the page + API URL map |
16
- | `vantage add <kind> <name>` | Scaffold or place a `page` / `api` / `ui` / `block` / `skill` / `agents` (`--force` to overwrite; `skill` takes `--dir` for the destination) |
16
+ | `vantage add <kind> <name>` | Scaffold or place a `page` / `api` / `ui` / `block` / `skill` / `agents` (`--force` to overwrite; `skill` scans for copies already placed and skips them, `--dir` picks the destination) |
17
17
  | `vantage docs [name]` | Show the bundled guide and component reference |
18
18
  | `vantage search <query>` | Search that same reference in full text (natural language or `--regex`) |
19
19
  | `vantage doctor` | Diagnose environment / installation health |
@@ -42,8 +42,8 @@ from the Radix build, four differences matter.
42
42
  - **[`ToggleGroup`](ui/toggle-group)'s `value` is an array.** For single choice, use
43
43
  [`SegmentedControl`](parts/segmented-control).
44
44
  - **[`Select`](ui/select)'s `onValueChange` gives `string | null`.** You need a
45
- `null` guard. And `SelectValue` shows the raw value by default pass the value-to-label map as
46
- `items` to get labels.
45
+ `null` guard. The value-to-label `items` map is derived from your `SelectItem`s automatically, so
46
+ you rarely write it (only when a separate component returns the options).
47
47
 
48
48
  State is expressed as `data-open` / `data-checked` rather than `data-state="open"`. Vantage's
49
49
  `theme.css` declares custom variants that accept both spellings, so classes like
@@ -1,10 +1,11 @@
1
1
  # EChart
2
2
 
3
- > A thin Apache ECharts wrapper. Set colours in your option.
3
+ > A thin Apache ECharts wrapper whose colours follow the theme tokens.
4
4
 
5
- A thin wrapper around Apache ECharts. It only handles the canvas lifecycle (init, resize, dispose),
6
- the loading overlay, event subscriptions and PNG export. **The palette and axes do not auto-follow
7
- the theme** set colours inside `option`, or pass an ECharts theme to `theme`.
5
+ A thin wrapper around Apache ECharts. It handles the canvas lifecycle (init, resize, dispose), the
6
+ loading overlay, event subscriptions and PNG export. **Colours follow the theme tokens** series
7
+ come from `--chart-1` through `--chart-5`, while axes, legend and tooltip are built from the text
8
+ and border tokens. A light/dark switch is picked up too.
8
9
 
9
10
  ```tsx
10
11
  import { EChart } from "@squadbase/vantage/components";
@@ -16,16 +17,23 @@ import type { EChartsOption } from "@squadbase/vantage/components";
16
17
  | `option` | `EChartsOption` | The ECharts option, passed straight through. |
17
18
  | `height` | `string \| number` | Height; a number means px. `className` works too. |
18
19
  | `loading` | `boolean` | Show ECharts' loading overlay. |
19
- | `theme` | `string \| object` | The theme passed to `echarts.init` (a name or a registered theme). |
20
+ | `theme` | `string \| object` | The theme passed to `echarts.init` (a name or a registered theme). Passing one turns token-following off. |
20
21
  | `onEvents` | `Record<string, (params: unknown) => void>` | Subscribe to ECharts events such as `click`. |
21
22
  | `ariaLabel` | `string` | A description of the chart. |
22
23
  | `actions` | `boolean` | Show actions such as save-as-image. |
23
24
  | `fileName` | `string` | File name used when saving. |
24
25
 
25
- ## Setting the palette
26
+ ## Overriding the palette
26
27
 
27
- Series colours come from `option.color`. To match the theme's `--chart-1` through `--chart-5`, pass
28
- the resolved values ECharts paints to canvas and cannot read CSS variables.
28
+ By default the theme's `--chart-1` through `--chart-5` become the series colours. ECharts can't read
29
+ CSS variables *while painting*, but `EChart` resolves the tokens once at init with
30
+ `getComputedStyle` and passes them in as an ECharts theme. It watches for anything that can change
31
+ those tokens and re-reads them when the values actually move — the `.dark` class going on and off,
32
+ and **stylesheet swaps** too (editing `styles.css` with the dev server running, applying a theme
33
+ preset, …).
34
+
35
+ To give series their own colours, set `option.color` (or `series[].itemStyle.color`). Your option
36
+ wins over the theme, so **axes, legend and tooltip keep following the tokens**.
29
37
 
30
38
  ```ts
31
39
  const option: EChartsOption = {
@@ -34,9 +42,10 @@ const option: EChartsOption = {
34
42
  };
35
43
  ```
36
44
 
37
- To reuse a palette across charts, register a theme with
38
- [`echarts.registerTheme`](https://echarts.apache.org/en/api.html#echarts.registerTheme) and pass its
39
- name to the `theme` prop.
45
+ To paint the whole chart with a theme of your own, register it with
46
+ [`echarts.registerTheme`](https://echarts.apache.org/en/api.html#echarts.registerTheme) and pass the
47
+ name (or the theme object) to `theme`. That turns token-following off entirely — including the
48
+ light/dark switch.
40
49
 
41
50
  ## Keeping the legend off the plot
42
51
 
@@ -39,24 +39,30 @@ import {
39
39
  > <Select onValueChange={(value) => value !== null && setRegion(value)}>
40
40
  > ```
41
41
 
42
- > [!WARNING]
43
- > `SelectValue` shows the **raw selected `value`** by default — it never looks inside `SelectItem`.
44
- > With `value="kanto"` the trigger reads `kanto`, not "Kanto". Give `Select` the value-to-label map
45
- > via `items`.
42
+ > [!NOTE]
43
+ > Base UI's `SelectValue` renders the **raw selected `value`** — it never looks inside `SelectItem`.
44
+ > With `value="kanto"` the trigger would read `kanto`.
46
45
  >
47
- > ```tsx
48
- > const regions = { hokkaido: "Hokkaido", kanto: "Kanto", kansai: "Kansai" };
46
+ > Vantage's `Select` **absorbs that quirk**: it walks its children, collects each `SelectItem`'s
47
+ > `value` label pair and hands them to Base UI as `items`, so the code below puts "Kanto" in the
48
+ > trigger. It's the same treatment `SegmentedControl` gives `ToggleGroup`'s array API.
49
49
  >
50
- > <Select items={regions} value={region} onValueChange={…}>
50
+ > ```tsx
51
+ > <Select value={region} onValueChange={…}>
51
52
  > <SelectTrigger><SelectValue placeholder="Pick a region" /></SelectTrigger>
52
- >
53
+ > <SelectContent>
54
+ > <SelectItem value="kanto">Kanto</SelectItem>
55
+ > </SelectContent>
53
56
  > </Select>;
54
57
  > ```
55
58
  >
56
- > If you'd rather not carry `items`, a function child on `SelectValue` does the same job.
59
+ > Only `SelectItem`s **written in place** are picked up (a `map` over them counts). When the options
60
+ > come back from a separate component, pass `items` yourself — an explicit `items` always wins.
57
61
  >
58
62
  > ```tsx
59
- > <SelectValue>{(value) => regions[value] ?? "Pick a region"}</SelectValue>
63
+ > const regions = { hokkaido: "Hokkaido", kanto: "Kanto", kansai: "Kansai" };
64
+ >
65
+ > <Select items={regions} value={region} onValueChange={…}>
60
66
  > ```
61
67
 
62
68
  `SelectContent` opens over the trigger with the selected item aligned to it. For a plain dropdown
package/docs/index.json CHANGED
@@ -549,7 +549,7 @@
549
549
  "section": "components/parts",
550
550
  "sectionTitle": "@squadbase/vantage/components",
551
551
  "title": "EChart",
552
- "description": "Apache ECharts の薄いラッパー。配色は option で指定する。",
552
+ "description": "Apache ECharts の薄いラッパー。配色はテーマトークンに追従する。",
553
553
  "file": "ja/parts/echart.md",
554
554
  "order": 4
555
555
  },
@@ -1230,7 +1230,7 @@
1230
1230
  "section": "components/parts",
1231
1231
  "sectionTitle": "@squadbase/vantage/components",
1232
1232
  "title": "EChart",
1233
- "description": "A thin Apache ECharts wrapper. Set colours in your option.",
1233
+ "description": "A thin Apache ECharts wrapper whose colours follow the theme tokens.",
1234
1234
  "file": "en/parts/echart.md",
1235
1235
  "order": 4
1236
1236
  },
@@ -22,12 +22,29 @@ Skill のソースは `@squadbase/vantage` パッケージに同梱されてい
22
22
  対象アプリにコピーします。配置先は既定で**プロジェクトルート直下**で、`--dir` で変えられます。
23
23
 
24
24
  ```bash
25
- vantage add skill --all # 同梱スキルをすべて、ルート直下に配置
25
+ vantage add skill # 一覧と配置場所を表示するだけ(何も書かない)
26
+ vantage add skill --all # 未配置のものを、ルート直下に配置
26
27
  vantage add skill vantage-app # 名前を指定して1つだけ配置
27
28
  vantage add skill --all --dir .claude/skills # Claude Code が読む場所に配置
28
- vantage add skill vantage-app --force # 既存を上書き
29
+ vantage add skill --all --force # 配置済みのコピーをその場で最新に更新
29
30
  ```
30
31
 
32
+ **配置先は決め打ちではなく、走査して見つけます。** `add skill` はまずプロジェクト内
33
+ (`node_modules` などを除く)を走査し、**既に配置されている skill はスキップして場所を報告**
34
+ します。ルート直下でも `.claude/skills/` でも `.squadbase/skills/` でも同じで、**再実行しても
35
+ 二重に配置されません**。名前を省いて実行すると、一覧と配置場所だけが出ます:
36
+
37
+ ```text
38
+ $ vantage add skill
39
+ Bundled skills:
40
+ vantage-add-feature .claude/skills/vantage-add-feature/
41
+ vantage-app .claude/skills/vantage-app/
42
+ vantage-pitfalls not placed
43
+ ```
44
+
45
+ `--force` を付けると、**見つかった場所をそのまま**新しい内容で書き換えます(`--dir` を明示した
46
+ ときは、そちらが配置先として優先されます)。
47
+
31
48
  `--dir` のパスはプロジェクトルートからの相対で解決されます(絶対パスも可)。Claude Code に
32
49
  自動認識させるなら `.claude/skills/` に置きます:
33
50
 
@@ -54,8 +71,8 @@ Skill の手順を読み込んで実行します。他のエージェントに
54
71
 
55
72
  - **source-of-truth はパッケージ内**にあります。Skill はフレームワークのバージョンと一緒に
56
73
  配布・更新されるため、`@squadbase/vantage` を上げれば最新の手順が手に入ります。配置済みの
57
- コピーを更新したいときは、配置したときと同じ `--dir` を付けて
58
- `vantage add skill --all --force` を実行します。
74
+ コピーを更新したいときは `vantage add skill --all --force` を実行します(置き場所は走査して
75
+ 見つけるので、`--dir` を覚えておく必要はありません)。
59
76
  - **Squadbase テンプレートには配置済み**です。テンプレートから作ったアプリには、最初から
60
77
  `.claude/skills/` が入っています。手で作ったアプリには `vantage add skill` で足してください。
61
78
 
@@ -8,6 +8,69 @@ Vantage のリリースごとの変更点です。新しいものが上に来ま
8
8
  > まだプロトタイプ(**0.x**)です。0.x の間は **minor が実質のメジャー**扱いで、後方互換性の
9
9
  > ない変更は minor で入ることがあります。patch は後方互換の機能追加とバグ修正のみです。
10
10
 
11
+ ## v0.2.2
12
+
13
+ `vantage add skill` が配置先を走査するようになり、チャートがスタイルシートの差し替えにも
14
+ 追従するようになりました。どちらも後方互換です。
15
+
16
+ ### 変更
17
+
18
+ - **`vantage add skill` が配置済みの Skill を探してからコピーします** ― プロジェクト内を
19
+ 走査して `<skill>/SKILL.md` を探し、**既にあればスキップして場所を報告**します。ルート直下
20
+ でも `.claude/skills/` でも `.squadbase/skills/` でも同じなので、**再実行しても二重に配置
21
+ されません**。エージェント側が置き場所の候補を覚えておく必要がなくなります。
22
+ - `--force` は**見つかった場所をその場で**書き換えます(`--dir` を明示したときは、そちらが
23
+ 配置先として優先されます)。配置済みコピーの更新に、前回と同じ `--dir` を思い出す必要は
24
+ ありません。
25
+ - 名前を省いた `vantage add skill` は、**一覧と配置場所を出すだけ**の表示コマンドに
26
+ なりました(何も書き込まず exit 0)。
27
+ - 同梱の `AGENTS.md` も更新し、置き場所を `ls` で当てにいく手順を「CLI が探す」に
28
+ 置き換えました(配置済みの `AGENTS.md` は `vantage upgrade` で同期されます)。
29
+
30
+ ### 修正
31
+
32
+ - **[`EChart`](parts/echart) が CSS の更新に追従しない場合がありました** ―
33
+ トークンの変化を `html` / `body` の属性でしか見ていなかったため、**注入済みの `<style>` の
34
+ 中身だけが差し替わる経路**(開発中の `styles.css` の書き換え、テーマプリセットの適用など)で
35
+ DOM のトークンは新しいのにチャートだけ前の色のまま残っていました(リロードで直るため
36
+ 「プリセットが効いていない」と誤解しやすい状態でした)。スタイルシートの差し替えも監視
37
+ 対象になり、**トークンの値が実際に変わったときだけ**描き直します。
38
+
39
+ ## v0.2.1
40
+
41
+ チャートをテーマトークンに追従させ、`Select` の値とラベルの対応を Vantage 側で組むように
42
+ しました。API はどちらも後方互換です。
43
+
44
+ > [!NOTE]
45
+ > **チャートの配色が変わります。** これまで `option.color` を書いていないチャートは ECharts の
46
+ > 既定色で描かれていましたが、テーマの `--chart-1` 〜 `--chart-5` で描かれるようになります。
47
+ > 自分で `option.color` を指定しているチャートは今までどおりです。
48
+
49
+ ### 変更
50
+
51
+ - **[`EChart`](parts/echart) がテーマトークンに追従するようになりました** ―
52
+ 系列色は `--chart-1` 〜 `--chart-5`、軸・凡例・ツールチップは文字色と境界色のトークンから
53
+ 組まれ、ライト / ダークの切り替えにも追いかけます。ECharts が CSS 変数を読めないのは
54
+ **キャンバスの描画中**の話なので、init のタイミングでトークンの実値を解決しています。
55
+ 系列の色だけ変えたいときは `option.color` で上書きでき、その場合も軸まわりの追従は
56
+ 残ります。チャート全体を自前のテーマで描きたいときは `theme` プロップを渡してください
57
+ (このときトークン追従は完全に無効になります)。
58
+ - **[`Select`](ui/select) の `items` を書かなくてよくなりました** ―
59
+ Base UI の `SelectValue` は選択中の *value* をそのまま描く部品で、値とラベルが違うと
60
+ トリガーに `kanto` のような生の値が出ていました。`SelectItem` から対応表を組む処理を
61
+ Vantage 側に入れたので、普通に書けばラベルが出ます。**明示した `items` は今までどおり
62
+ 優先される**ので、既存のコードはそのまま動きます(項目を別のコンポーネントが返している
63
+ 場合は、これまでどおり `items` を渡してください)。
64
+
65
+ ### 修正
66
+
67
+ - `useSearchParam` の JSDoc に載せていた `SegmentedControl` の例が、存在しない
68
+ `onValueChange` を渡していました(正しくは `onChange`)。
69
+ - 同梱の `AGENTS.md` と Skill を更新しました ― `vantage add ui|block` でコピーできるのが
70
+ `data-table` と `sales-overview` の 2 つだけであること、モードは `server/` の有無で決まり
71
+ 「SPA から fullstack へ昇格する」手順ではないこと、配置済みの Skill を先に探すこと。
72
+ 配置済みの `AGENTS.md` は `vantage upgrade` で同期されます。
73
+
11
74
  ## v0.2.0
12
75
 
13
76
  `vantage add skill` の配置先を選べるようにし、相対 import の `.js` 指定子の規約を廃止しました。
@@ -13,7 +13,7 @@
13
13
  | `vantage preview` | 本番ビルドをローカルで起動(fullstack は既定 `:4173`、spa は Vite プレビュー) |
14
14
  | `vantage check` | 静的検査(ルート・境界・禁止ファイル)。エラーがあれば exit 1 |
15
15
  | `vantage routes` | ページ + API の URL マップを表示 |
16
- | `vantage add <kind> <name>` | `page` / `api` / `ui` / `block` / `skill` / `agents` を雛形生成・配置(`--force` で上書き、`skill` は `--dir` で配置先を指定) |
16
+ | `vantage add <kind> <name>` | `page` / `api` / `ui` / `block` / `skill` / `agents` を雛形生成・配置(`--force` で上書き、`skill` は配置済みを走査してスキップ・`--dir` で配置先を指定) |
17
17
  | `vantage docs [name]` | 同梱のガイド + コンポーネントリファレンスを表示 |
18
18
  | `vantage search <query>` | 同じリファレンスを全文検索(自然言語 or `--regex`) |
19
19
  | `vantage doctor` | 環境・インストールの健全性を診断 |
@@ -42,8 +42,8 @@ Radix UI から [Base UI](https://base-ui.com) へ切り替えており、Vantag
42
42
  - **[`ToggleGroup`](ui/toggle-group) の `value` は配列。** 単一選択にしたいときは
43
43
  [`SegmentedControl`](parts/segmented-control) を使ってください。
44
44
  - **[`Select`](ui/select) の `onValueChange` は `string | null`。** `null` のガードが
45
- 要ります。また `SelectValue` は既定で選択中の値をそのまま表示するので、ラベルを出すには
46
- `items` に対応表を渡します。
45
+ 要ります。値とラベルが違うときの `items` は Vantage 側が `SelectItem` から自動で組むので、
46
+ 通常は書かなくて済みます(別コンポーネントが項目を返す形のときだけ明示)。
47
47
 
48
48
  状態は `data-state="open"` ではなく `data-open` / `data-checked` のような属性で表現されます。
49
49
  Vantage の `theme.css` は両方の綴りを受ける custom variant を定義しているので、
@@ -1,10 +1,11 @@
1
1
  # EChart
2
2
 
3
- > Apache ECharts の薄いラッパー。配色は option で指定する。
3
+ > Apache ECharts の薄いラッパー。配色はテーマトークンに追従する。
4
4
 
5
5
  Apache ECharts をそのまま使えるようにした薄いラッパーです。キャンバスの初期化・リサイズ・破棄と、
6
- ローディング表示・イベント購読・PNG 保存だけを面倒みます。**配色や軸の見た目はテーマに自動追従
7
- しません** `option` の中で色を決めるか、`theme` に ECharts のテーマを渡してください。
6
+ ローディング表示・イベント購読・PNG 保存を面倒みます。**配色はテーマトークンに追従します** —
7
+ 系列色は `--chart-1` `--chart-5`、軸・凡例・ツールチップは文字色と境界色のトークンから組まれ、
8
+ ライト / ダークの切り替えにも追いかけます。
8
9
 
9
10
  ```tsx
10
11
  import { EChart } from "@squadbase/vantage/components";
@@ -16,16 +17,22 @@ import type { EChartsOption } from "@squadbase/vantage/components";
16
17
  | `option` | `EChartsOption` | ECharts の option。そのまま渡す。 |
17
18
  | `height` | `string \| number` | 高さ。数値は px。`className` で決めても良い。 |
18
19
  | `loading` | `boolean` | ECharts のローディング表示を出す。 |
19
- | `theme` | `string \| object` | `echarts.init` に渡すテーマ(名前または登録済みテーマ)。 |
20
+ | `theme` | `string \| object` | `echarts.init` に渡すテーマ(名前または登録済みテーマ)。渡すとトークン追従は無効になる。 |
20
21
  | `onEvents` | `Record<string, (params: unknown) => void>` | ECharts のイベント購読(`click` など)。 |
21
22
  | `ariaLabel` | `string` | チャートの説明。 |
22
23
  | `actions` | `boolean` | 画像保存などの操作を出す。 |
23
24
  | `fileName` | `string` | 保存時のファイル名。 |
24
25
 
25
- ## 配色を決める
26
+ ## 配色を上書きする
26
27
 
27
- 系列の色は `option.color` で指定します。テーマの `--chart-1` 〜 `--chart-5` に揃えたいときは、
28
- ECharts はキャンバス描画で CSS 変数を読めないため、色の実値を渡してください。
28
+ 既定ではテーマの `--chart-1` 〜 `--chart-5` がそのまま系列色になります。ECharts は**描画中**には
29
+ CSS 変数を読めませんが、`EChart` init のタイミングで `getComputedStyle` からトークンの実値を
30
+ 解決し、ECharts のテーマとして渡しています。**トークンが変わりうる操作は見張っていて**、値が
31
+ 実際に変わったときだけ読み直します ― `.dark` の付け外しだけでなく、**スタイルシートの差し替え**
32
+ (開発中に `styles.css` を書き換えた・テーマプリセットを当てた、など)も拾います。
33
+
34
+ 系列に固有の色を当てたいときは `option.color`(または `series[].itemStyle.color`)で上書きします。
35
+ option はテーマより優先されるので、**軸・凡例・ツールチップのトークン追従は残ります**。
29
36
 
30
37
  ```ts
31
38
  const option: EChartsOption = {
@@ -34,8 +41,9 @@ const option: EChartsOption = {
34
41
  };
35
42
  ```
36
43
 
37
- 複数チャートで配色を使い回すなら、[`echarts.registerTheme`](https://echarts.apache.org/en/api.html#echarts.registerTheme)
38
- でテーマを登録し、その名前を `theme` プロップに渡すのが簡潔です。
44
+ チャート全体を自前のテーマで描きたいときは、[`echarts.registerTheme`](https://echarts.apache.org/en/api.html#echarts.registerTheme)
45
+ で登録したテーマ名(またはテーマオブジェクト)を `theme` プロップに渡します。この場合は
46
+ トークン追従が**完全に無効**になり、明暗の切り替えにも追従しなくなります。
39
47
 
40
48
  ## 凡例と重ならないようにする
41
49