Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .sync/log/6576fb81dd34c25a8e6b2209b9df9b2bf935f99b.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Port: perf(types): decouple `useComponentProps` from the component-types barrel (#6648)

**Upstream:** `6576fb81dd34c25a8e6b2209b9df9b2bf935f99b` (nuxt/ui)
**Decision:** port

## Upstream change
Follow-up to #6646 (b24ui PR #227). `useComponentProps.ts` was the last file
still importing the `../types` barrel (`import type * as ComponentTypes`), kept
alive only because it hosts the `ThemeUI` / `ThemeDefaults` /
`ThemeContextDefaults` / `ThemeSlotOverrides` type definitions which reference
every component's `*Props`. This moves those definitions into a **new**
`src/runtime/types/theme.ts` (which may legitimately pull the barrel via
`import type * as ComponentTypes from './index'`), so the composable no longer
touches the barrel and its eslint `no-restricted-imports` exemption is removed.
`ThemeContext.defaults` is re-typed to the loose
`ComputedRef<Record<string, Record<string, any> | undefined>>` (the strict
shape now lives in `theme.ts`).

## b24ui port — adapted, not 1:1
Same structure, but the moved `ThemeDefaults` interface had to be b24ui's
**own** — b24ui's `useComponentProps.ts` already carries a 114-key registry
that diverges from upstream (commented-out absent components like
`// authForm`/`// blogPost`/`// listbox`/`// tree`, the `range // this is slider`
remap, a `// custom ////` block for `advice`/`countdown`/`descriptionList`/
`tableWrapper`, and the deprecated `navbar*`/`sidebar*` families). So I **moved
b24ui's existing block verbatim** rather than copying upstream's.

### Changes
- **New `src/runtime/types/theme.ts`** — the `ThemeSlotOverrides` / `ThemeUI` /
`ThemeDefaults` / `ThemeContextDefaults` block moved out of
`useComponentProps.ts` verbatim, with imports adjusted for the new location:
`SlotClass` from `./tv`, `* as b24ui` from `#build/b24ui` (b24ui's registry
alias, vs upstream's `* as ui`/`#build/ui`), `* as ComponentTypes` from
`./index`.
- **`useComponentProps.ts`** — dropped the moved block and its three imports
(`* as ComponentTypes from '../types'`, `SlotClass from '../types/tv'`,
`* as b24ui from '#build/b24ui'`; none used elsewhere below the block) and
re-typed `ThemeContext.defaults` to
`ComputedRef<Record<string, Record<string, any> | undefined>>`. `ComputedRef`
import retained (still used). −179 lines.
- **`types/index.ts`** — `export * from './theme'` (after `./prose`).
- **`components/Theme.vue`** — `ThemeContextDefaults`/`ThemeDefaults`/`ThemeUI`
now imported from `../types/theme` (value imports
`injectThemeContext`/`provideThemeContext` still from the composable).
- **`test/composables/useComponentProps.spec.ts`** — `ThemeDefaults` import
retargeted to `../../src/runtime/types/theme`.
- **`eslint.config.mjs`** — removed the `useComponentProps.ts`
`no-restricted-imports: off` exemption added in #227 (no longer needed; the
composable now passes the rule).

The index↔theme type-only cycle (`index` `export *`s `theme`, `theme`
`import type`s `index`) is fine — exactly as upstream.

## Tests
Types-only. The `ThemeDefaults registry` type-test (`useComponentProps.spec.ts`)
— which asserts every themable `#build/b24ui` component has a `ThemeDefaults`
entry and no extras — still passes against the moved definition. No snapshot
churn.

## Verify (CI=true)
`dev:prepare` · `lint` (exemption removed, composable clean) · `typecheck`
(circular type import + registry type-test resolve) · `test` (225 files, 5143
passed / 6 skipped) · `build` — all green.
10 changes: 8 additions & 2 deletions .sync/nuxt-ui.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"upstream": "nuxt/ui",
"branch": "v4",
"sync_enabled": false,
"cursor": "82cd5b526cf87517d931439b8ce85ed70c88cc87",
"cursor": "6576fb81dd34c25a8e6b2209b9df9b2bf935f99b",
"_cursor_note": "cursor = last upstream commit ported into b24ui (oldest-first, manual cadence). sync_enabled stays false until Phase 2 (porter workflow #67 + CLAUDE_CODE_OAUTH_TOKEN) is wired and trusted. `processed` is maintained per port from now on (backfilled #68-#72 on 2026-06-09).",
"stats": {
"queue_depth": 0,
Expand Down Expand Up @@ -791,10 +791,16 @@
"summary": "perf(components): drop the redundant inner in component extend (#6647) — replace `extend: tv(theme)` -> `extend: theme` across all 162 occurrences in src/runtime/{components,components/prose,components/content,vue/overrides/*} (tv's extend takes a raw theme object; inner tv() wrapper redundant). Outer tv({...})(...) unchanged so tv stays imported. Direct 1:1 mechanical (1 line/file, 162 files, +162/-162; upstream 163, delta = diverging component set). appConfig.b24ui?.<name> spread pre-existing/untouched. Behaviour-preserving, no snapshot churn"
},
"82cd5b526cf87517d931439b8ce85ed70c88cc87": {
"pr": 229,
"b24ui_sha": "75348073",
"decision": "port",
"summary": "chore(deps): update dependency reka-ui to v2.10.1 (#6652) — exact-pin bump reka-ui 2.10.0->2.10.1 in root package.json (only manifest pinning it; playgrounds/docs get it transitively) + lockfile regen under CI gate (reka-ui entry + vaul-vue transitive, 8/+8-; integrity sha512-drcOQ4r... matches upstream byte-for-byte). Direct 1:1, no snapshot churn"
},
"6576fb81dd34c25a8e6b2209b9df9b2bf935f99b": {
"pr": null,
"b24ui_sha": "pending-merge",
"decision": "port",
"summary": "chore(deps): update dependency reka-ui to v2.10.1 (#6652) — exact-pin bump reka-ui 2.10.0->2.10.1 in root package.json (only manifest pinning it; playgrounds/docs get it transitively) + lockfile regen under CI gate (reka-ui entry + vaul-vue transitive, 8/+8-; integrity sha512-drcOQ4r... matches upstream byte-for-byte). Direct 1:1, no snapshot churn"
"summary": "perf(types): decouple useComponentProps from the component-types barrel (#6648) — move ThemeSlotOverrides/ThemeUI/ThemeDefaults/ThemeContextDefaults out of composables/useComponentProps.ts into NEW src/runtime/types/theme.ts (imports SlotClass from ./tv, * as b24ui from #build/b24ui, * as ComponentTypes from ./index); drop the 3 now-unused imports from useComponentProps.ts + re-type ThemeContext.defaults -> ComputedRef<Record<string,Record<string,any>|undefined>>; +export * from './theme' in types/index.ts; Theme.vue + useComponentProps.spec.ts retarget type import to ../types/theme; REMOVE the useComponentProps.ts eslint no-restricted-imports exemption added in #227 (composable no longer touches barrel). Adapted: moved b24ui's OWN 114-key ThemeDefaults verbatim (diverges from upstream: commented-out absent comps, range=slider remap, custom advice/countdown/descriptionList/tableWrapper + deprecated navbar*/sidebar*). Types-only, no snapshot churn. 5 files +new theme.ts, +4/-185"
}
}
}
5 changes: 0 additions & 5 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,4 @@ export default createConfigForNuxt({
]
}]
}
}, {
files: ['src/runtime/composables/useComponentProps.ts'],
rules: {
'no-restricted-imports': 'off'
}
})
2 changes: 1 addition & 1 deletion src/runtime/components/Theme.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { VNode } from 'vue'
import { computed } from 'vue'
import defu from 'defu'
import { injectThemeContext, provideThemeContext } from '../composables/useComponentProps'
import type { ThemeContextDefaults, ThemeDefaults, ThemeUI } from '../composables/useComponentProps'
import type { ThemeContextDefaults, ThemeDefaults, ThemeUI } from '../types/theme'

export interface ThemeProps {
/**
Expand Down
179 changes: 1 addition & 178 deletions src/runtime/composables/useComponentProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,187 +3,10 @@ import { computed, getCurrentInstance } from 'vue'
import defu from 'defu'
import { createContext } from 'reka-ui'
import { useAppConfig } from '#imports'
import type * as ComponentTypes from '../types'
import type { SlotClass } from '../types/tv'
import type * as b24ui from '#build/b24ui'
import { get } from '../utils'

type ThemeSlotOverrides<T> = T extends { slots: infer S extends Record<string, any> }
? { [K in keyof S]?: SlotClass }
: { [K in keyof T]?: T[K] extends any[] ? SlotClass : T[K] extends Record<string, any> ? ThemeSlotOverrides<T[K]> : SlotClass }

/**
* Flat slot-class override shape: `{ button: { base: '...' }, modal: {...} }`.
* Powers the `:b24ui` prop on `<B24Theme>`, which remains the recommended way to
* scope class overrides without touching component prop defaults.
*/
export type ThemeUI = {
[K in keyof typeof b24ui]?: ThemeSlotOverrides<(typeof b24ui)[K]>
}

/**
* Strict per-component defaults shape used by `<B24Theme :props>`. Authored as
* a flat interface with literal keys (rather than a mapped type) so editors —
* Volar in particular — surface key completions inside template inline
* objects (`:props="{ button: { … } }"`). Volar reliably iterates interface
* members but not mapped-type members in this position.
*
* Keys mirror the theme registry exposed by `#build/ui` (see
* `src/templates.ts`): every themable component gets one camelCase entry
* whose value is a `Partial` of that component's `<PascalCase>Props`.
*/
export interface ThemeDefaults {
accordion?: Partial<ComponentTypes.AccordionProps>
alert?: Partial<ComponentTypes.AlertProps>
// authForm?: Partial<ComponentTypes.AuthFormProps>
avatar?: Partial<ComponentTypes.AvatarProps>
avatarGroup?: Partial<ComponentTypes.AvatarGroupProps>
badge?: Partial<ComponentTypes.BadgeProps>
banner?: Partial<ComponentTypes.BannerProps>
// blogPost?: Partial<ComponentTypes.BlogPostProps>
// blogPosts?: Partial<ComponentTypes.BlogPostsProps>
breadcrumb?: Partial<ComponentTypes.BreadcrumbProps>
button?: Partial<ComponentTypes.ButtonProps>
calendar?: Partial<ComponentTypes.CalendarProps>
card?: Partial<ComponentTypes.CardProps>
// carousel?: Partial<ComponentTypes.CarouselProps>
// changelogVersion?: Partial<ComponentTypes.ChangelogVersionProps>
// changelogVersions?: Partial<ComponentTypes.ChangelogVersionsProps>
chatMessage?: Partial<ComponentTypes.ChatMessageProps>
chatMessages?: Partial<ComponentTypes.ChatMessagesProps>
chatPalette?: Partial<ComponentTypes.ChatPaletteProps>
chatPrompt?: Partial<ComponentTypes.ChatPromptProps>
chatPromptSubmit?: Partial<ComponentTypes.ChatPromptSubmitProps>
chatReasoning?: Partial<ComponentTypes.ChatReasoningProps>
chatShimmer?: Partial<ComponentTypes.ChatShimmerProps>
chatTool?: Partial<ComponentTypes.ChatToolProps>
checkbox?: Partial<ComponentTypes.CheckboxProps>
checkboxGroup?: Partial<ComponentTypes.CheckboxGroupProps>
chip?: Partial<ComponentTypes.ChipProps>
collapsible?: Partial<ComponentTypes.CollapsibleProps>
colorPicker?: Partial<ComponentTypes.ColorPickerProps>
commandPalette?: Partial<ComponentTypes.CommandPaletteProps>
container?: Partial<ComponentTypes.ContainerProps>
// contentNavigation?: Partial<ComponentTypes.ContentNavigationProps>
contentSearch?: Partial<ComponentTypes.ContentSearchProps>
contentSearchButton?: Partial<ComponentTypes.ContentSearchButtonProps>
contentSurround?: Partial<ComponentTypes.ContentSurroundProps>
contentToc?: Partial<ComponentTypes.ContentTocProps>
contextMenu?: Partial<ComponentTypes.ContextMenuProps>
dashboardGroup?: Partial<ComponentTypes.DashboardGroupProps>
dashboardNavbar?: Partial<ComponentTypes.DashboardNavbarProps>
dashboardPanel?: Partial<ComponentTypes.DashboardPanelProps>
dashboardResizeHandle?: Partial<ComponentTypes.DashboardResizeHandleProps>
dashboardSearch?: Partial<ComponentTypes.DashboardSearchProps>
dashboardSearchButton?: Partial<ComponentTypes.DashboardSearchButtonProps>
dashboardSidebar?: Partial<ComponentTypes.DashboardSidebarProps>
dashboardSidebarCollapse?: Partial<ComponentTypes.DashboardSidebarCollapseProps>
dashboardSidebarToggle?: Partial<ComponentTypes.DashboardSidebarToggleProps>
dashboardToolbar?: Partial<ComponentTypes.DashboardToolbarProps>
drawer?: Partial<ComponentTypes.DrawerProps>
dropdownMenu?: Partial<ComponentTypes.DropdownMenuProps>
editor?: Partial<ComponentTypes.EditorProps>
editorDragHandle?: Partial<ComponentTypes.EditorDragHandleProps>
editorToolbar?: Partial<ComponentTypes.EditorToolbarProps>
empty?: Partial<ComponentTypes.EmptyProps>
error?: Partial<ComponentTypes.ErrorProps>
fieldGroup?: Partial<ComponentTypes.FieldGroupProps>
fileUpload?: Partial<ComponentTypes.FileUploadProps>
footer?: Partial<ComponentTypes.FooterProps>
footerColumns?: Partial<ComponentTypes.FooterColumnsProps>
// TODO: `FormProps` carries three generics for state, schema, and fields —
// none of which are themable defaults. Loosened to `any` so this entry stays
// assignable from any concrete `Form` instance.
form?: Partial<ComponentTypes.FormProps<any, any, any>>
formField?: Partial<ComponentTypes.FormFieldProps>
header?: Partial<ComponentTypes.HeaderProps>
input?: Partial<ComponentTypes.InputProps>
inputDate?: Partial<ComponentTypes.InputDateProps>
inputMenu?: Partial<ComponentTypes.InputMenuProps>
inputNumber?: Partial<ComponentTypes.InputNumberProps>
inputTags?: Partial<ComponentTypes.InputTagsProps>
inputTime?: Partial<ComponentTypes.InputTimeProps>
kbd?: Partial<ComponentTypes.KbdProps>
// listbox?: Partial<ComponentTypes.ListboxProps>
main?: Partial<ComponentTypes.MainProps>
// marquee?: Partial<ComponentTypes.MarqueeProps>
modal?: Partial<ComponentTypes.ModalProps>
navigationMenu?: Partial<ComponentTypes.NavigationMenuProps>
page?: Partial<ComponentTypes.PageProps>
// pageAnchors?: Partial<ComponentTypes.PageAnchorsProps>
pageAside?: Partial<ComponentTypes.PageAsideProps>
pageBody?: Partial<ComponentTypes.PageBodyProps>
// pageCTA?: Partial<ComponentTypes.PageCTAProps>
pageCard?: Partial<ComponentTypes.PageCardProps>
pageCardGroup?: Partial<ComponentTypes.PageCardGroupProps>
pageColumns?: Partial<ComponentTypes.PageColumnsProps>
pageFeature?: Partial<ComponentTypes.PageFeatureProps>
pageGrid?: Partial<ComponentTypes.PageGridProps>
pageHeader?: Partial<ComponentTypes.PageHeaderProps>
// pageHero?: Partial<ComponentTypes.PageHeroProps>
pageLinks?: Partial<ComponentTypes.PageLinksProps>
pageList?: Partial<ComponentTypes.PageListProps>
// pageLogos?: Partial<ComponentTypes.PageLogosProps>
pageSection?: Partial<ComponentTypes.PageSectionProps>
pagination?: Partial<ComponentTypes.PaginationProps>
pinInput?: Partial<ComponentTypes.PinInputProps>
popover?: Partial<ComponentTypes.PopoverProps>
// pricingPlan?: Partial<ComponentTypes.PricingPlanProps>
// pricingPlans?: Partial<ComponentTypes.PricingPlansProps>
// pricingTable?: Partial<ComponentTypes.PricingTableProps>
progress?: Partial<ComponentTypes.ProgressProps>
radioGroup?: Partial<ComponentTypes.RadioGroupProps>
range?: Partial<ComponentTypes.RangeProps> // this is slider
scrollArea?: Partial<ComponentTypes.ScrollAreaProps>
select?: Partial<ComponentTypes.SelectProps>
selectMenu?: Partial<ComponentTypes.SelectMenuProps>
separator?: Partial<ComponentTypes.SeparatorProps>
sidebar?: Partial<ComponentTypes.SidebarProps>
skeleton?: Partial<ComponentTypes.SkeletonProps>
slideover?: Partial<ComponentTypes.SlideoverProps>
// slider?: Partial<ComponentTypes.SliderProps> - move to range
stepper?: Partial<ComponentTypes.StepperProps>
switch?: Partial<ComponentTypes.SwitchProps>
table?: Partial<ComponentTypes.TableProps>
tabs?: Partial<ComponentTypes.TabsProps>
textarea?: Partial<ComponentTypes.TextareaProps>
timeline?: Partial<ComponentTypes.TimelineProps>
toast?: Partial<ComponentTypes.ToastProps>
toaster?: Partial<ComponentTypes.ToasterProps>
tooltip?: Partial<ComponentTypes.TooltipProps>
// tree?: Partial<ComponentTypes.TreeProps>
user?: Partial<ComponentTypes.UserProps>
// custom ////
advice?: Partial<ComponentTypes.AdviceProps>
countdown?: Partial<ComponentTypes.CountdownProps>
descriptionList?: Partial<ComponentTypes.DescriptionListProps>
// modalDialogClose?: Partial<ComponentTypes.ModalDialogCloseProps>
tableWrapper?: Partial<ComponentTypes.TableWrapperProps>
// @deprecated
navbar?: Partial<ComponentTypes.NavbarProps>
navbarDivider?: Partial<ComponentTypes.NavbarDividerProps>
navbarSection?: Partial<ComponentTypes.NavbarSectionProps>
navbarSpacer?: Partial<ComponentTypes.NavbarSpacerProps>
sidebarBody?: Partial<ComponentTypes.SidebarBodyProps>
sidebarFooter?: Partial<ComponentTypes.SidebarFooterProps>
sidebarHeader?: Partial<ComponentTypes.SidebarHeaderProps>
sidebarHeading?: Partial<ComponentTypes.SidebarHeadingProps>
sidebarLayout?: Partial<ComponentTypes.SidebarLayoutProps>
sidebarSection?: Partial<ComponentTypes.SidebarSectionProps>
sidebarSpacer?: Partial<ComponentTypes.SidebarSpacerProps>
}

/**
* Loose internal shape stored on the injected `ThemeContext`. Allows the
* `prose` namespace (lifted by `normalizeUi`) and any unknown keys to flow
* through without polluting the user-facing `ThemeDefaults` type.
*/
export type ThemeContextDefaults = ThemeDefaults & {
[name: string]: Record<string, any> | undefined
}

export type ThemeContext = {
defaults: ComputedRef<ThemeContextDefaults>
defaults: ComputedRef<Record<string, Record<string, any> | undefined>>
}

const [_injectThemeContext, provideThemeContext] = createContext<ThemeContext>('B24Theme', 'RootContext')
Expand Down
1 change: 1 addition & 0 deletions src/runtime/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,6 @@ export * from './input'
export * from './icons'
export * from './locale'
export * from './prose'
export * from './theme'
export * from './color-mode'
export * from './utils'
Loading