diff --git a/.ralph/implementation-plan.md b/.ralph/implementation-plan.md index 0330c0278d..a9d2bd97d3 100644 --- a/.ralph/implementation-plan.md +++ b/.ralph/implementation-plan.md @@ -79,8 +79,8 @@ These are the simplest migrations — single component, no bg, no utility props. ### Batch 5: Multi-sub-component directories - [x] **Task 21: Migrate Dialog** — Multiple sub-components (Dialog, DialogHeader, DialogBody, DialogFooter). Split monolithic definition into 4 per-sub-component definitions. Used `classNameTarget: 'dialog'` for Dialog since className targets inner RADialog, not Modal overlay. Added `style` to DialogOwnProps for manual merging with CSS custom properties. Added DialogFooterProps/OwnProps (was inline type). DialogTrigger unchanged (no useStyles usage). -- [ ] **Task 22: Migrate HeaderPage** — Multiple sub-components (HeaderPage, HeaderPageContent). See `HeaderPage/definition.ts` and `HeaderPage/HeaderPage.tsx`. -- [ ] **Task 23: Migrate PluginHeader** — Multiple sub-components (PluginHeader, PluginHeaderToolbar). See `PluginHeader/definition.ts` and `PluginHeader/PluginHeader.tsx` + `PluginHeaderToolbar.tsx`. +- [x] **Task 22: Migrate HeaderPage** — Single component (not multi-sub-component as originally described — `content`, `breadcrumbs`, `tabsWrapper`, `controls` are just classNames for inner divs). No bg, no dataAttributes, no utilityProps. See `HeaderPage/definition.ts` and `HeaderPage/HeaderPage.tsx`. +- [x] **Task 23: Migrate PluginHeader** — Two sub-components (PluginHeader, PluginHeaderToolbar) sharing one old definition, split into two `defineComponent` calls following the Dialog pattern. Toolbar definition is `@internal` and not exported. No bg, no utilityProps, no dataAttributes. Manual `data-has-tabs` preserved as-is. Toolbar classNames removed from public `PluginHeaderDefinition` (breaking API surface change to be documented in Task 33 changeset). - [ ] **Task 24: Migrate RadioGroup** — Multiple sub-components (RadioGroup, Radio). See `RadioGroup/definition.ts` and `RadioGroup/RadioGroup.tsx`. - [ ] **Task 25: Migrate Select** — Multiple sub-components (Select, SelectContent, SelectTrigger, SelectListBox). See `Select/definition.ts` and `Select/Select.tsx` + sub-files. - [ ] **Task 26: Migrate Table** — Many sub-components (TableRoot, TableHeader, TableBody, Row, Column, Cell, CellText, CellProfile). See `Table/definition.ts` and multiple .tsx files. @@ -97,7 +97,7 @@ These are the simplest migrations — single component, no bg, no utility props. ## Discoveries / Notes -- 20 of 31 component tasks complete (~65%), plus 3 cleanup tasks remaining. 26 unique components migrated. +- 22 of 31 component tasks complete (~71%), plus 3 cleanup tasks remaining. 28 unique components migrated (PluginHeader + PluginHeaderToolbar). - Only 3 sub-components need bg: 'provider' (Flex, GridRoot, GridItem). No unmigrated components use useBgConsumer. - InternalLinkProvider is a context provider, not a styled component — no migration needed. - useStyles.ts already imports helpers from useDefinition (resolveResponsiveValue, processUtilityProps), confirming shared infrastructure. diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 8d2347e341..962706f818 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -1743,20 +1743,26 @@ export const PluginHeader: (props: PluginHeaderProps) => JSX_2.Element; // @public export const PluginHeaderDefinition: { + readonly styles: { + readonly [key: string]: string; + }; readonly classNames: { readonly root: 'bui-PluginHeader'; - readonly toolbar: 'bui-PluginHeaderToolbar'; - readonly toolbarWrapper: 'bui-PluginHeaderToolbarWrapper'; - readonly toolbarContent: 'bui-PluginHeaderToolbarContent'; - readonly toolbarControls: 'bui-PluginHeaderToolbarControls'; - readonly toolbarIcon: 'bui-PluginHeaderToolbarIcon'; - readonly toolbarName: 'bui-PluginHeaderToolbarName'; readonly tabsWrapper: 'bui-PluginHeaderTabsWrapper'; }; + readonly propDefs: { + readonly icon: {}; + readonly title: {}; + readonly titleLink: {}; + readonly customActions: {}; + readonly tabs: {}; + readonly onTabSelectionChange: {}; + readonly className: {}; + }; }; // @public -export interface PluginHeaderProps { +export interface PluginHeaderOwnProps { // (undocumented) className?: string; // (undocumented) @@ -1773,6 +1779,9 @@ export interface PluginHeaderProps { titleLink?: string; } +// @public +export interface PluginHeaderProps extends PluginHeaderOwnProps {} + // @public export const Popover: ForwardRefExoticComponent< PopoverProps & RefAttributes diff --git a/packages/ui/src/components/PluginHeader/PluginHeader.tsx b/packages/ui/src/components/PluginHeader/PluginHeader.tsx index 2ed663d61c..ebdfee490d 100644 --- a/packages/ui/src/components/PluginHeader/PluginHeader.tsx +++ b/packages/ui/src/components/PluginHeader/PluginHeader.tsx @@ -17,13 +17,11 @@ import type { PluginHeaderProps } from './types'; import { PluginHeaderToolbar } from './PluginHeaderToolbar'; import { Tabs, TabList, Tab } from '../Tabs'; -import { useStyles } from '../../hooks/useStyles'; +import { useDefinition } from '../../hooks/useDefinition'; import { PluginHeaderDefinition } from './definition'; import { type NavigateOptions } from 'react-router-dom'; import { useRef } from 'react'; import { useIsomorphicLayoutEffect } from '../../hooks/useIsomorphicLayoutEffect'; -import styles from './PluginHeader.module.css'; -import clsx from 'clsx'; declare module 'react-aria-components' { interface RouterConfig { @@ -38,16 +36,16 @@ declare module 'react-aria-components' { * @public */ export const PluginHeader = (props: PluginHeaderProps) => { - const { classNames, cleanedProps } = useStyles(PluginHeaderDefinition, props); + const { ownProps } = useDefinition(PluginHeaderDefinition, props); const { - className, + classes, tabs, icon, title, titleLink, customActions, onTabSelectionChange, - } = cleanedProps; + } = ownProps; const hasTabs = tabs && tabs.length > 0; const headerRef = useRef(null); @@ -85,10 +83,7 @@ export const PluginHeader = (props: PluginHeaderProps) => { }, []); return ( -
+
{ hasTabs={hasTabs} /> {tabs && ( -
+
{tabs?.map(tab => ( diff --git a/packages/ui/src/components/PluginHeader/PluginHeaderToolbar.tsx b/packages/ui/src/components/PluginHeader/PluginHeaderToolbar.tsx index 607049f497..fe586e1a4d 100644 --- a/packages/ui/src/components/PluginHeader/PluginHeaderToolbar.tsx +++ b/packages/ui/src/components/PluginHeader/PluginHeaderToolbar.tsx @@ -15,14 +15,12 @@ */ import { Link } from 'react-aria-components'; -import { useStyles } from '../../hooks/useStyles'; -import { PluginHeaderDefinition } from './definition'; +import { useDefinition } from '../../hooks/useDefinition'; +import { PluginHeaderToolbarDefinition } from './definition'; import { useRef } from 'react'; import { RiShapesLine } from '@remixicon/react'; import type { PluginHeaderToolbarProps } from './types'; import { Text } from '../Text'; -import styles from './PluginHeader.module.css'; -import clsx from 'clsx'; /** * A component that renders the toolbar section of a plugin header. @@ -30,9 +28,8 @@ import clsx from 'clsx'; * @internal */ export const PluginHeaderToolbar = (props: PluginHeaderToolbarProps) => { - const { classNames, cleanedProps } = useStyles(PluginHeaderDefinition, props); - const { className, icon, title, titleLink, customActions, hasTabs } = - cleanedProps; + const { ownProps } = useDefinition(PluginHeaderToolbarDefinition, props); + const { classes, icon, title, titleLink, customActions, hasTabs } = ownProps; // Refs for collision detection const toolbarWrapperRef = useRef(null); @@ -41,68 +38,26 @@ export const PluginHeaderToolbar = (props: PluginHeaderToolbarProps) => { const titleContent = ( <> -
- {icon || } -
+
{icon || }
{title || 'Your plugin'} ); return ( -
-
-
+
+
+
{titleLink ? ( - + {titleContent} ) : ( -
- {titleContent} -
+
{titleContent}
)}
-
+
{customActions}
diff --git a/packages/ui/src/components/PluginHeader/definition.ts b/packages/ui/src/components/PluginHeader/definition.ts index 39de7a0443..176ceef352 100644 --- a/packages/ui/src/components/PluginHeader/definition.ts +++ b/packages/ui/src/components/PluginHeader/definition.ts @@ -14,21 +14,55 @@ * limitations under the License. */ -import type { ComponentDefinition } from '../../types'; +import { defineComponent } from '../../hooks/useDefinition'; +import type { + PluginHeaderOwnProps, + PluginHeaderToolbarOwnProps, +} from './types'; +import styles from './PluginHeader.module.css'; /** * Component definition for PluginHeader * @public */ -export const PluginHeaderDefinition = { +export const PluginHeaderDefinition = defineComponent()({ + styles, classNames: { root: 'bui-PluginHeader', - toolbar: 'bui-PluginHeaderToolbar', - toolbarWrapper: 'bui-PluginHeaderToolbarWrapper', - toolbarContent: 'bui-PluginHeaderToolbarContent', - toolbarControls: 'bui-PluginHeaderToolbarControls', - toolbarIcon: 'bui-PluginHeaderToolbarIcon', - toolbarName: 'bui-PluginHeaderToolbarName', tabsWrapper: 'bui-PluginHeaderTabsWrapper', }, -} as const satisfies ComponentDefinition; + propDefs: { + icon: {}, + title: {}, + titleLink: {}, + customActions: {}, + tabs: {}, + onTabSelectionChange: {}, + className: {}, + }, +}); + +/** + * Component definition for PluginHeaderToolbar + * @internal + */ +export const PluginHeaderToolbarDefinition = + defineComponent()({ + styles, + classNames: { + root: 'bui-PluginHeaderToolbar', + wrapper: 'bui-PluginHeaderToolbarWrapper', + content: 'bui-PluginHeaderToolbarContent', + controls: 'bui-PluginHeaderToolbarControls', + icon: 'bui-PluginHeaderToolbarIcon', + name: 'bui-PluginHeaderToolbarName', + }, + propDefs: { + icon: {}, + title: {}, + titleLink: {}, + customActions: {}, + hasTabs: {}, + className: {}, + }, + }); diff --git a/packages/ui/src/components/PluginHeader/index.tsx b/packages/ui/src/components/PluginHeader/index.tsx index 306910f516..3648ebe920 100644 --- a/packages/ui/src/components/PluginHeader/index.tsx +++ b/packages/ui/src/components/PluginHeader/index.tsx @@ -16,4 +16,8 @@ export { PluginHeader } from './PluginHeader'; export { PluginHeaderDefinition } from './definition'; -export type { PluginHeaderProps, HeaderTab } from './types'; +export type { + PluginHeaderOwnProps, + PluginHeaderProps, + HeaderTab, +} from './types'; diff --git a/packages/ui/src/components/PluginHeader/types.ts b/packages/ui/src/components/PluginHeader/types.ts index 21021cdbaf..f57fe82174 100644 --- a/packages/ui/src/components/PluginHeader/types.ts +++ b/packages/ui/src/components/PluginHeader/types.ts @@ -18,11 +18,11 @@ import { TabsProps } from 'react-aria-components'; import { TabMatchStrategy } from '../Tabs'; /** - * Props for the {@link PluginHeader} component. + * Own props for the {@link PluginHeader} component. * * @public */ -export interface PluginHeaderProps { +export interface PluginHeaderOwnProps { icon?: React.ReactNode; title?: string; titleLink?: string; @@ -32,6 +32,13 @@ export interface PluginHeaderProps { className?: string; } +/** + * Props for the {@link PluginHeader} component. + * + * @public + */ +export interface PluginHeaderProps extends PluginHeaderOwnProps {} + /** * Represents a tab item in the header navigation. * @@ -49,16 +56,23 @@ export interface HeaderTab { matchStrategy?: TabMatchStrategy; } +/** + * Own props for the PluginHeaderToolbar component. + * + * @internal + */ +export interface PluginHeaderToolbarOwnProps { + icon?: PluginHeaderOwnProps['icon']; + title?: PluginHeaderOwnProps['title']; + titleLink?: PluginHeaderOwnProps['titleLink']; + customActions?: PluginHeaderOwnProps['customActions']; + hasTabs?: boolean; + className?: string; +} + /** * Props for the PluginHeaderToolbar component. * * @internal */ -export interface PluginHeaderToolbarProps { - icon?: PluginHeaderProps['icon']; - title?: PluginHeaderProps['title']; - titleLink?: PluginHeaderProps['titleLink']; - customActions?: PluginHeaderProps['customActions']; - hasTabs?: boolean; - className?: string; -} +export interface PluginHeaderToolbarProps extends PluginHeaderToolbarOwnProps {}