From 8ae7d4cccfa4d1d4169693b0eaff6bedbdc327a1 Mon Sep 17 00:00:00 2001 From: Alex Smith Date: Wed, 12 Aug 2026 15:17:39 -0400 Subject: [PATCH 1/8] feat(bulma-ui): accept isFullwidth on every full-width component, deprecate divergent casings --- bestax-mcp/data/catalog.json | 16 ++--- bestax-mcp/data/components/Button.json | 14 +++- bestax-mcp/data/components/Buttons.json | 24 ++++++- bestax-mcp/data/components/File.json | 10 +++ bestax-mcp/data/components/LinkButton.json | 12 +++- bestax-mcp/data/components/Select.json | 10 +++ bestax-mcp/data/components/Sidebar.json | 14 +++- bestax-mcp/data/components/Table.json | 10 +++ bestax-mcp/data/components/Tabs.json | 28 ++++++-- bestax-mcp/data/skills.json | 2 +- bulma-ui/src/components/Sidebar.stories.tsx | 9 ++- bulma-ui/src/components/Sidebar.tsx | 10 ++- bulma-ui/src/components/Tabs.tsx | 8 ++- .../src/components/__tests__/Sidebar.test.tsx | 44 +++++++++++++ .../src/components/__tests__/Tabs.test.tsx | 66 +++++++++++++++++++ bulma-ui/src/elements/Button.stories.tsx | 9 ++- bulma-ui/src/elements/Button.tsx | 5 +- bulma-ui/src/elements/Table.tsx | 5 +- .../src/elements/__tests__/Button.test.tsx | 35 ++++++++++ .../src/elements/__tests__/Table.test.tsx | 43 ++++++++++++ bulma-ui/src/form/File.tsx | 5 +- bulma-ui/src/form/SelectBase.tsx | 5 +- bulma-ui/src/form/__tests__/File.test.tsx | 33 ++++++++++ .../src/form/__tests__/SelectBase.test.tsx | 45 +++++++++++++ docs/docs/api/components/sidebar.md | 35 +++++----- docs/docs/api/components/tabs.md | 14 ++-- docs/docs/api/elements/button.md | 7 +- docs/docs/api/elements/linkbutton.md | 3 +- docs/docs/api/elements/table.md | 23 +++---- docs/docs/api/form/file.md | 1 + docs/docs/api/form/select.md | 1 + .../migration/react-bulma-components.md | 3 +- docs/docs/guides/helpers/visibility.md | 2 +- skills/bestax-form/SKILL.md | 5 +- skills/bestax-form/references/api.md | 2 +- skills/bestax-migrate/references/prop-map.md | 2 +- 36 files changed, 484 insertions(+), 76 deletions(-) diff --git a/bestax-mcp/data/catalog.json b/bestax-mcp/data/catalog.json index db477fe80..6d5a4259b 100644 --- a/bestax-mcp/data/catalog.json +++ b/bestax-mcp/data/catalog.json @@ -202,7 +202,7 @@ "slug": "elements/button", "import": "import { Button } from '@allxsmith/bestax-bulma';", "compound": false, - "propCount": 22, + "propCount": 23, "exampleCount": 24 }, { @@ -213,7 +213,7 @@ "slug": "elements/buttons", "import": "import { Buttons } from '@allxsmith/bestax-bulma';", "compound": true, - "propCount": 50, + "propCount": 52, "exampleCount": 7 }, { @@ -466,7 +466,7 @@ "slug": "form/file", "import": "import { File, Icon } from '@allxsmith/bestax-bulma';", "compound": false, - "propCount": 21, + "propCount": 22, "exampleCount": 16 }, { @@ -576,7 +576,7 @@ "slug": "elements/linkbutton", "import": "import { LinkButton } from '@allxsmith/bestax-bulma';", "compound": false, - "propCount": 20, + "propCount": 21, "exampleCount": 9 }, { @@ -807,7 +807,7 @@ "slug": "form/select", "import": "import { Select, Field, Control } from '@allxsmith/bestax-bulma';", "compound": false, - "propCount": 28, + "propCount": 29, "exampleCount": 16 }, { @@ -818,7 +818,7 @@ "slug": "components/sidebar", "import": "import { Sidebar } from '@allxsmith/bestax-bulma';", "compound": true, - "propCount": 21, + "propCount": 22, "exampleCount": 8 }, { @@ -906,7 +906,7 @@ "slug": "elements/table", "import": "import { Table, Thead, Tbody, Tfoot, Tr, Th, Td } from '@allxsmith/bestax-bulma';", "compound": true, - "propCount": 26, + "propCount": 27, "exampleCount": 7 }, { @@ -917,7 +917,7 @@ "slug": "components/tabs", "import": "import { Tabs } from '@allxsmith/bestax-bulma';", "compound": true, - "propCount": 35, + "propCount": 37, "exampleCount": 13 }, { diff --git a/bestax-mcp/data/components/Button.json b/bestax-mcp/data/components/Button.json index b2c9a4683..1ce275dc0 100644 --- a/bestax-mcp/data/components/Button.json +++ b/bestax-mcp/data/components/Button.json @@ -29,7 +29,7 @@ { "title": "Static", "code": "" }, { "title": "Full Width", - "code": "" + "code": "" }, { "title": "Outlined", @@ -161,7 +161,7 @@ "deprecated": false }, { - "name": "isFullWidth", + "name": "isFullwidth", "type": "boolean", "default": "false", "description": "Makes the button full-width.", @@ -169,6 +169,16 @@ "inherited": false, "deprecated": false }, + { + "name": "isFullWidth", + "type": "boolean", + "default": "false", + "description": "Makes the button full-width.", + "required": false, + "inherited": false, + "deprecated": true, + "deprecationNote": "Use `isFullwidth` instead — `isFullwidth` wins if both are set." + }, { "name": "isOutlined", "type": "boolean", diff --git a/bestax-mcp/data/components/Buttons.json b/bestax-mcp/data/components/Buttons.json index c75e475b4..a199c69c0 100644 --- a/bestax-mcp/data/components/Buttons.json +++ b/bestax-mcp/data/components/Buttons.json @@ -186,7 +186,7 @@ "deprecated": false }, { - "name": "isFullWidth", + "name": "isFullwidth", "type": "boolean", "default": "false", "description": "Makes the button full-width.", @@ -194,6 +194,16 @@ "inherited": false, "deprecated": false }, + { + "name": "isFullWidth", + "type": "boolean", + "default": "false", + "description": "Makes the button full-width.", + "required": false, + "inherited": false, + "deprecated": true, + "deprecationNote": "Use `isFullwidth` instead — `isFullwidth` wins if both are set." + }, { "name": "isOutlined", "type": "boolean", @@ -396,7 +406,7 @@ "deprecated": false }, { - "name": "isFullWidth", + "name": "isFullwidth", "type": "boolean", "default": "false", "description": "Makes the button full-width.", @@ -404,6 +414,16 @@ "inherited": true, "deprecated": false }, + { + "name": "isFullWidth", + "type": "boolean", + "default": "false", + "description": "Makes the button full-width.", + "required": false, + "inherited": true, + "deprecated": true, + "deprecationNote": "Use `isFullwidth` instead — `isFullwidth` wins if both are set." + }, { "name": "isFocused", "type": "boolean", diff --git a/bestax-mcp/data/components/File.json b/bestax-mcp/data/components/File.json index d46988d47..16d953b77 100644 --- a/bestax-mcp/data/components/File.json +++ b/bestax-mcp/data/components/File.json @@ -137,6 +137,16 @@ "inherited": false, "deprecated": false }, + { + "name": "isFullWidth", + "type": "boolean", + "default": "false", + "description": "Whether the file input expands to full width.", + "required": false, + "inherited": false, + "deprecated": true, + "deprecationNote": "Use `isFullwidth` instead — `isFullwidth` wins if both are set." + }, { "name": "isRight", "type": "boolean", diff --git a/bestax-mcp/data/components/LinkButton.json b/bestax-mcp/data/components/LinkButton.json index 221e625e1..744caa104 100644 --- a/bestax-mcp/data/components/LinkButton.json +++ b/bestax-mcp/data/components/LinkButton.json @@ -110,7 +110,7 @@ "deprecated": false }, { - "name": "isFullWidth", + "name": "isFullwidth", "type": "boolean", "default": "false", "description": "Makes the button full-width.", @@ -118,6 +118,16 @@ "inherited": true, "deprecated": false }, + { + "name": "isFullWidth", + "type": "boolean", + "default": "false", + "description": "Makes the button full-width.", + "required": false, + "inherited": true, + "deprecated": true, + "deprecationNote": "Use `isFullwidth` instead — `isFullwidth` wins if both are set." + }, { "name": "isFocused", "type": "boolean", diff --git a/bestax-mcp/data/components/Select.json b/bestax-mcp/data/components/Select.json index a8c2dfd13..3bb4ff007 100644 --- a/bestax-mcp/data/components/Select.json +++ b/bestax-mcp/data/components/Select.json @@ -281,6 +281,16 @@ "inherited": true, "deprecated": false }, + { + "name": "isFullWidth", + "type": "boolean", + "default": "false", + "description": "Makes the select span the full width of parent.", + "required": false, + "inherited": true, + "deprecated": true, + "deprecationNote": "Use `isFullwidth` instead — `isFullwidth` wins if both are set." + }, { "name": "className", "type": "string", diff --git a/bestax-mcp/data/components/Sidebar.json b/bestax-mcp/data/components/Sidebar.json index 1cf82b817..1d45f69b6 100644 --- a/bestax-mcp/data/components/Sidebar.json +++ b/bestax-mcp/data/components/Sidebar.json @@ -19,7 +19,7 @@ }, { "title": "Full Width (Mobile Style)", - "code": "function example() {\n const [isOpen, setIsOpen] = useState(false);\n return (\n <>\n \n setIsOpen(false)}\n fullWidth\n >\n \n Full Width Panel\n setIsOpen(false)} />\n \n \n \n \n Home\n Products\n About\n Contact\n \n \n \n \n \n );\n}" + "code": "function example() {\n const [isOpen, setIsOpen] = useState(false);\n return (\n <>\n \n setIsOpen(false)}\n isFullwidth\n >\n \n Full Width Panel\n setIsOpen(false)} />\n \n \n \n \n Home\n Products\n About\n Contact\n \n \n \n \n \n );\n}" }, { "title": "Without Overlay", @@ -88,7 +88,7 @@ "deprecated": false }, { - "name": "fullWidth", + "name": "isFullwidth", "type": "boolean", "default": "false", "description": "Sidebar takes full width (mobile-style).", @@ -96,6 +96,16 @@ "inherited": false, "deprecated": false }, + { + "name": "fullWidth", + "type": "boolean", + "default": "false", + "description": "Sidebar takes full width (mobile-style).", + "required": false, + "inherited": false, + "deprecated": true, + "deprecationNote": "Use `isFullwidth` instead — `isFullwidth` wins if both are set." + }, { "name": "overlay", "type": "boolean", diff --git a/bestax-mcp/data/components/Table.json b/bestax-mcp/data/components/Table.json index 81aec4bbb..e58aee914 100644 --- a/bestax-mcp/data/components/Table.json +++ b/bestax-mcp/data/components/Table.json @@ -101,6 +101,16 @@ "inherited": false, "deprecated": false }, + { + "name": "isFullWidth", + "type": "boolean", + "default": "false", + "description": "Makes the table span the full width of its parent.", + "required": false, + "inherited": false, + "deprecated": true, + "deprecationNote": "Use `isFullwidth` instead — `isFullwidth` wins if both are set." + }, { "name": "isResponsive", "type": "boolean", diff --git a/bestax-mcp/data/components/Tabs.json b/bestax-mcp/data/components/Tabs.json index 7cfa02fd8..dcd8813d2 100644 --- a/bestax-mcp/data/components/Tabs.json +++ b/bestax-mcp/data/components/Tabs.json @@ -35,7 +35,7 @@ }, { "title": "Fullwidth Tabs", - "code": "\n \n \n One\n \n \n Two\n \n \n Three\n \n \n Four\n \n \n" + "code": "\n \n \n One\n \n \n Two\n \n \n Three\n \n \n Four\n \n \n" }, { "title": "Centered Boxed Tabs with Icons", @@ -43,7 +43,7 @@ }, { "title": "Toggle Fullwidth Tabs with Icons", - "code": "\n \n \n \n \n List\n \n \n \n \n \n Done\n \n \n \n \n \n Removed\n \n \n \n" + "code": "\n \n \n \n \n List\n \n \n \n \n \n Done\n \n \n \n \n \n Removed\n \n \n \n" }, { "title": "Centered Boxed Medium Tabs with Icons", @@ -51,7 +51,7 @@ }, { "title": "Toggle Fullwidth Large Tabs with Icons", - "code": "\n \n \n \n \n Launch\n \n \n \n \n \n Alerts\n \n \n \n \n \n Settings\n \n \n \n" + "code": "\n \n \n \n \n Launch\n \n \n \n \n \n Alerts\n \n \n \n \n \n Settings\n \n \n \n" }, { "title": "Compound (dot-notation) usage", @@ -90,7 +90,7 @@ "deprecated": false }, { - "name": "fullwidth", + "name": "isFullwidth", "type": "boolean", "default": "false", "description": "Tabs expand to fill the horizontal space.", @@ -98,6 +98,26 @@ "inherited": false, "deprecated": false }, + { + "name": "isFullWidth", + "type": "boolean", + "default": "false", + "description": "Tabs expand to fill the horizontal space.", + "required": false, + "inherited": false, + "deprecated": true, + "deprecationNote": "Use `isFullwidth` instead — `isFullwidth` wins if both are set." + }, + { + "name": "fullwidth", + "type": "boolean", + "default": "false", + "description": "Tabs expand to fill the horizontal space.", + "required": false, + "inherited": false, + "deprecated": true, + "deprecationNote": "Use `isFullwidth` instead — `isFullwidth` wins if both are set." + }, { "name": "boxed", "type": "boolean", diff --git a/bestax-mcp/data/skills.json b/bestax-mcp/data/skills.json index 543244c19..4b198d454 100644 --- a/bestax-mcp/data/skills.json +++ b/bestax-mcp/data/skills.json @@ -29,7 +29,7 @@ "promptName": "form", "dir": "bestax-form", "references": [ - { "id": "api", "file": "references/api.md", "bytes": 8378 }, + { "id": "api", "file": "references/api.md", "bytes": 8334 }, { "id": "patterns", "file": "references/patterns.md", "bytes": 5566 } ], "examples": [] diff --git a/bulma-ui/src/components/Sidebar.stories.tsx b/bulma-ui/src/components/Sidebar.stories.tsx index 3f7472b2c..b1e6a88ba 100644 --- a/bulma-ui/src/components/Sidebar.stories.tsx +++ b/bulma-ui/src/components/Sidebar.stories.tsx @@ -52,10 +52,15 @@ const meta: Meta = { control: 'text', description: 'Custom width (e.g., "300px", "80%")', }, - fullWidth: { + isFullwidth: { control: 'boolean', description: 'Sidebar takes full width', }, + fullWidth: { + control: 'boolean', + description: + 'Sidebar takes full width. Deprecated: use `isFullwidth` instead.', + }, overlay: { control: 'boolean', description: 'Show overlay behind sidebar', @@ -204,7 +209,7 @@ export const FullWidth: Story = { - setIsOpen(false)} fullWidth> + setIsOpen(false)} isFullwidth> Full Width Panel setIsOpen(false)} /> diff --git a/bulma-ui/src/components/Sidebar.tsx b/bulma-ui/src/components/Sidebar.tsx index 707db82ca..a85896749 100644 --- a/bulma-ui/src/components/Sidebar.tsx +++ b/bulma-ui/src/components/Sidebar.tsx @@ -25,6 +25,8 @@ export interface SidebarProps /** Custom width of the sidebar. */ width?: string; /** Sidebar takes full width (mobile-style). */ + isFullwidth?: boolean; + /** Sidebar takes full width (mobile-style). @deprecated Use `isFullwidth` instead — `isFullwidth` wins if both are set. */ fullWidth?: boolean; /** Show overlay behind sidebar. Default: true. */ overlay?: boolean; @@ -73,7 +75,8 @@ const SidebarComponent = forwardRef( onClose, position = 'left', width = '260px', - fullWidth = false, + isFullwidth, + fullWidth, overlay = true, overlayClose = true, escapeClose = true, @@ -88,6 +91,7 @@ const SidebarComponent = forwardRef( ) => { const { bulmaHelperClasses, rest } = useBulmaClasses(props); const sidebarRef = useRef(null); + const resolvedFullwidth = isFullwidth ?? fullWidth ?? false; // Close handler const handleClose = useCallback(() => { @@ -165,7 +169,7 @@ const SidebarComponent = forwardRef( const sidebarClasses = usePrefixedClassNames('sidebar', { 'is-active': isOpen, [`is-${position}`]: position, - 'is-fullwidth': fullWidth, + 'is-fullwidth': resolvedFullwidth, }); const backgroundClass = usePrefixedClassNames('sidebar-background', { 'is-active': isOpen, @@ -181,7 +185,7 @@ const SidebarComponent = forwardRef( // Custom style for width const sidebarStyle: React.CSSProperties = { ...style, - '--bulma-sidebar-width': fullWidth ? '100%' : width, + '--bulma-sidebar-width': resolvedFullwidth ? '100%' : width, } as React.CSSProperties; const sidebarContent = ( diff --git a/bulma-ui/src/components/Tabs.tsx b/bulma-ui/src/components/Tabs.tsx index fc3e756c3..74fcf35b4 100644 --- a/bulma-ui/src/components/Tabs.tsx +++ b/bulma-ui/src/components/Tabs.tsx @@ -36,6 +36,10 @@ export interface TabsProps /** Tab size. */ size?: 'small' | 'medium' | 'large'; /** Tabs expand to fill the horizontal space. */ + isFullwidth?: boolean; + /** Tabs expand to fill the horizontal space. @deprecated Use `isFullwidth` instead — `isFullwidth` wins if both are set. */ + isFullWidth?: boolean; + /** Tabs expand to fill the horizontal space. @deprecated Use `isFullwidth` instead — `isFullwidth` wins if both are set. */ fullwidth?: boolean; /** Tabs use the boxed style. */ boxed?: boolean; @@ -104,6 +108,8 @@ export interface TabsProps const TabsComponent: React.FC = ({ align, size, + isFullwidth, + isFullWidth, fullwidth, boxed, toggle, @@ -149,7 +155,7 @@ const TabsComponent: React.FC = ({ [`is-${align}`]: align, [`is-${size}`]: size, [`is-${color}`]: color, - 'is-fullwidth': fullwidth, + 'is-fullwidth': isFullwidth ?? isFullWidth ?? fullwidth, 'is-boxed': boxed, 'is-toggle': toggle, 'is-toggle-rounded': rounded, diff --git a/bulma-ui/src/components/__tests__/Sidebar.test.tsx b/bulma-ui/src/components/__tests__/Sidebar.test.tsx index 7ea81bbf6..1031a22d8 100644 --- a/bulma-ui/src/components/__tests__/Sidebar.test.tsx +++ b/bulma-ui/src/components/__tests__/Sidebar.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Sidebar } from '../Sidebar'; +import { ConfigProvider } from '../../helpers/Config'; describe('Sidebar', () => { describe('Rendering', () => { @@ -114,6 +115,49 @@ describe('Sidebar', () => { ); expect(screen.getByRole('dialog')).toHaveClass('is-fullwidth'); }); + + it('applies is-fullwidth and 100% width via the canonical isFullwidth prop', () => { + render( + {}} isFullwidth> +
Content
+
+ ); + const sidebar = screen.getByRole('dialog'); + expect(sidebar).toHaveClass('is-fullwidth'); + expect(sidebar.style.getPropertyValue('--bulma-sidebar-width')).toBe( + '100%' + ); + }); + + it('isFullwidth wins over fullWidth for both the class and the width', () => { + render( + {}} + isFullwidth={false} + fullWidth + width="320px" + > +
Content
+
+ ); + const sidebar = screen.getByRole('dialog'); + expect(sidebar).not.toHaveClass('is-fullwidth'); + expect(sidebar.style.getPropertyValue('--bulma-sidebar-width')).toBe( + '320px' + ); + }); + + it('applies the class prefix to is-fullwidth', () => { + render( + + {}} isFullwidth> +
Content
+
+
+ ); + expect(screen.getByRole('dialog')).toHaveClass('bestax-is-fullwidth'); + }); }); describe('Closing Behavior', () => { diff --git a/bulma-ui/src/components/__tests__/Tabs.test.tsx b/bulma-ui/src/components/__tests__/Tabs.test.tsx index 8d5e8fa87..605769688 100644 --- a/bulma-ui/src/components/__tests__/Tabs.test.tsx +++ b/bulma-ui/src/components/__tests__/Tabs.test.tsx @@ -892,3 +892,69 @@ describe('Tabs deprecated color prop', () => { expect(warnSpy).not.toHaveBeenCalled(); }); }); + +describe('Tabs fullwidth aliases', () => { + const list = ( + + + Tab + + + ); + + it('applies is-fullwidth via the canonical isFullwidth prop', () => { + render( + + {list} + + ); + expect(screen.getByTestId('tabs')).toHaveClass('is-fullwidth'); + }); + + it('applies is-fullwidth via the deprecated isFullWidth prop', () => { + render( + + {list} + + ); + expect(screen.getByTestId('tabs')).toHaveClass('is-fullwidth'); + }); + + it('applies is-fullwidth via the deprecated fullwidth prop', () => { + render( + + {list} + + ); + expect(screen.getByTestId('tabs')).toHaveClass('is-fullwidth'); + }); + + it('isFullwidth wins over isFullWidth when both are set', () => { + render( + + {list} + + ); + expect(screen.getByTestId('tabs')).not.toHaveClass('is-fullwidth'); + }); + + it('isFullWidth wins over fullwidth when both are set', () => { + render( + + {list} + + ); + expect(screen.getByTestId('tabs')).not.toHaveClass('is-fullwidth'); + }); + + it('applies the class prefix to is-fullwidth', () => { + render( + + + {list} + + + ); + expect(screen.getByTestId('tabs')).toHaveClass('bestax-is-fullwidth'); + }); +}); diff --git a/bulma-ui/src/elements/Button.stories.tsx b/bulma-ui/src/elements/Button.stories.tsx index 19ffa815e..288af26ef 100644 --- a/bulma-ui/src/elements/Button.stories.tsx +++ b/bulma-ui/src/elements/Button.stories.tsx @@ -56,10 +56,15 @@ const meta: Meta = { control: 'boolean', description: 'Makes the button non-interactive.', }, - isFullWidth: { + isFullwidth: { control: 'boolean', description: 'Makes the button full-width.', }, + isFullWidth: { + control: 'boolean', + description: + 'Makes the button full-width. Deprecated: use `isFullwidth` instead.', + }, isOutlined: { control: 'boolean', description: 'Applies outlined styling (requires color).', @@ -264,7 +269,7 @@ export const Static: Story = { export const FullWidth: Story = { args: { color: 'warning', - isFullWidth: true, + isFullwidth: true, children: 'Full Width Button', }, }; diff --git a/bulma-ui/src/elements/Button.tsx b/bulma-ui/src/elements/Button.tsx index 0a376908d..fdc1dd4c9 100644 --- a/bulma-ui/src/elements/Button.tsx +++ b/bulma-ui/src/elements/Button.tsx @@ -38,6 +38,8 @@ export interface ButtonProps /** Makes the button non-interactive. */ isStatic?: boolean; /** Makes the button full-width. */ + isFullwidth?: boolean; + /** Makes the button full-width. @deprecated Use `isFullwidth` instead — `isFullwidth` wins if both are set. */ isFullWidth?: boolean; /** Applies outlined styling (requires color). */ isOutlined?: boolean; @@ -91,6 +93,7 @@ export const Button: React.FC = ({ isRounded, isLoading, isStatic, + isFullwidth, isFullWidth, isOutlined, isInverted, @@ -131,7 +134,7 @@ export const Button: React.FC = ({ 'is-focused': isFocused, 'is-active': isActive, 'is-inverted': isInverted, - 'is-fullwidth': isFullWidth, + 'is-fullwidth': isFullwidth ?? isFullWidth, }); // Combine prefixed Bulma classes with unprefixed user className and prefixed helper classes diff --git a/bulma-ui/src/elements/Table.tsx b/bulma-ui/src/elements/Table.tsx index 0475c5145..fed5da508 100644 --- a/bulma-ui/src/elements/Table.tsx +++ b/bulma-ui/src/elements/Table.tsx @@ -31,6 +31,8 @@ export interface TableProps isHoverable?: boolean; /** Makes the table span the full width of its parent. */ isFullwidth?: boolean; + /** Makes the table span the full width of its parent. @deprecated Use `isFullwidth` instead — `isFullwidth` wins if both are set. */ + isFullWidth?: boolean; /** Makes the table horizontally scrollable on small screens. */ isResponsive?: boolean; /** Table content (should use subcomponents). */ @@ -52,6 +54,7 @@ const TableComponent: React.FC = ({ isNarrow, isHoverable, isFullwidth, + isFullWidth, isResponsive, children, ...props @@ -66,7 +69,7 @@ const TableComponent: React.FC = ({ 'is-striped': isStriped, 'is-narrow': isNarrow, 'is-hoverable': isHoverable, - 'is-fullwidth': isFullwidth, + 'is-fullwidth': isFullwidth ?? isFullWidth, }); const containerClass = usePrefixedClassNames('table-container'); diff --git a/bulma-ui/src/elements/__tests__/Button.test.tsx b/bulma-ui/src/elements/__tests__/Button.test.tsx index bf7e58490..2e4c24861 100644 --- a/bulma-ui/src/elements/__tests__/Button.test.tsx +++ b/bulma-ui/src/elements/__tests__/Button.test.tsx @@ -175,6 +175,41 @@ describe('Button Component', () => { }); }); + describe('Fullwidth', () => { + it('applies is-fullwidth via the canonical isFullwidth prop', () => { + render(); + expect(screen.getByRole('button')).toHaveClass('is-fullwidth'); + }); + + it('applies is-fullwidth via the deprecated isFullWidth prop', () => { + render(); + expect(screen.getByRole('button')).toHaveClass('is-fullwidth'); + }); + + it('isFullwidth wins when both spellings are set', () => { + render( + + ); + expect(screen.getByRole('button')).not.toHaveClass('is-fullwidth'); + }); + + it('does not apply is-fullwidth by default', () => { + render(); + expect(screen.getByRole('button')).not.toHaveClass('is-fullwidth'); + }); + + it('applies the class prefix to is-fullwidth', () => { + render( + + + + ); + expect(screen.getByRole('button')).toHaveClass('bestax-is-fullwidth'); + }); + }); + describe('ClassPrefix', () => { it('applies classPrefix to main class', () => { render( diff --git a/bulma-ui/src/elements/__tests__/Table.test.tsx b/bulma-ui/src/elements/__tests__/Table.test.tsx index 889792ab9..37238119e 100644 --- a/bulma-ui/src/elements/__tests__/Table.test.tsx +++ b/bulma-ui/src/elements/__tests__/Table.test.tsx @@ -213,3 +213,46 @@ describe('Compound components', () => { expect(screen.getByText('Total').closest('tfoot')).not.toBeNull(); }); }); + +describe('Table fullwidth aliases', () => { + const body = ( + + + Cell + + + ); + + it('applies is-fullwidth via the canonical isFullwidth prop', () => { + render({body}
); + expect(screen.getByRole('table')).toHaveClass('is-fullwidth'); + }); + + it('applies is-fullwidth via the deprecated isFullWidth prop', () => { + render({body}
); + expect(screen.getByRole('table')).toHaveClass('is-fullwidth'); + }); + + it('isFullwidth wins when both spellings are set', () => { + render( + + {body} +
+ ); + expect(screen.getByRole('table')).not.toHaveClass('is-fullwidth'); + }); + + it('does not leak the deprecated alias onto the DOM', () => { + render({body}
); + expect(screen.getByRole('table')).not.toHaveAttribute('isFullWidth'); + }); + + it('applies the class prefix to is-fullwidth', () => { + render( + + {body}
+
+ ); + expect(screen.getByRole('table')).toHaveClass('bestax-is-fullwidth'); + }); +}); diff --git a/bulma-ui/src/form/File.tsx b/bulma-ui/src/form/File.tsx index eb96ed589..9b7ce16bc 100644 --- a/bulma-ui/src/form/File.tsx +++ b/bulma-ui/src/form/File.tsx @@ -46,6 +46,8 @@ export interface FileProps isBoxed?: boolean; /** Whether the file input expands to full width. */ isFullwidth?: boolean; + /** Whether the file input expands to full width. @deprecated Use `isFullwidth` instead — `isFullwidth` wins if both are set. */ + isFullWidth?: boolean; /** Position the CTA on the right (with `hasName`). */ isRight?: boolean; /** Center the file input within its container. */ @@ -89,6 +91,7 @@ export const File = forwardRef( size, isBoxed, isFullwidth, + isFullWidth, isRight, isCentered, hasName, @@ -130,7 +133,7 @@ export const File = forwardRef( [`is-${color}`]: !!color, [`is-${size}`]: !!size, 'is-boxed': isBoxed, - 'is-fullwidth': isFullwidth, + 'is-fullwidth': isFullwidth ?? isFullWidth, 'has-name': hasName, }); const fileClass = classNames( diff --git a/bulma-ui/src/form/SelectBase.tsx b/bulma-ui/src/form/SelectBase.tsx index 842b0e807..61938d108 100644 --- a/bulma-ui/src/form/SelectBase.tsx +++ b/bulma-ui/src/form/SelectBase.tsx @@ -36,6 +36,8 @@ export interface SelectBaseProps isFocused?: boolean; /** Makes the select span the full width of parent. */ isFullwidth?: boolean; + /** Makes the select span the full width of parent. @deprecated Use `isFullwidth` instead — `isFullwidth` wins if both are set. */ + isFullWidth?: boolean; /** Additional CSS classes to apply. */ className?: string; /** Disables the select. */ @@ -67,6 +69,7 @@ export const SelectBase = forwardRef( isHovered, isFocused, isFullwidth, + isFullWidth, className, disabled, children, @@ -89,7 +92,7 @@ export const SelectBase = forwardRef( 'is-loading': isLoading, 'is-active': isActive, 'is-multiple': !!multiple, - 'is-fullwidth': isFullwidth, + 'is-fullwidth': isFullwidth ?? isFullWidth, }); const selectClass = classNames(mainClass, bulmaHelperClasses, className); diff --git a/bulma-ui/src/form/__tests__/File.test.tsx b/bulma-ui/src/form/__tests__/File.test.tsx index 8cb2119d4..9546469ca 100644 --- a/bulma-ui/src/form/__tests__/File.test.tsx +++ b/bulma-ui/src/form/__tests__/File.test.tsx @@ -199,3 +199,36 @@ describe('File label association (#368)', () => { expect(container.querySelector('input')).not.toHaveAttribute('id'); }); }); + +describe('File fullwidth aliases', () => { + it('applies is-fullwidth via the canonical isFullwidth prop', () => { + const { container } = render(); + expect(container.querySelector('.file')).toHaveClass('is-fullwidth'); + }); + + it('applies is-fullwidth via the deprecated isFullWidth prop', () => { + const { container } = render(); + expect(container.querySelector('.file')).toHaveClass('is-fullwidth'); + }); + + it('isFullwidth wins when both spellings are set', () => { + const { container } = render(); + expect(container.querySelector('.file')).not.toHaveClass('is-fullwidth'); + }); + + it('does not leak the deprecated alias onto the DOM input', () => { + const { container } = render(); + expect(container.querySelector('input')).not.toHaveAttribute('isFullWidth'); + }); + + it('applies the class prefix to is-fullwidth', () => { + const { container } = render( + + + + ); + expect(container.querySelector('.bestax-file')).toHaveClass( + 'bestax-is-fullwidth' + ); + }); +}); diff --git a/bulma-ui/src/form/__tests__/SelectBase.test.tsx b/bulma-ui/src/form/__tests__/SelectBase.test.tsx index 54ae20343..5d46223b5 100644 --- a/bulma-ui/src/form/__tests__/SelectBase.test.tsx +++ b/bulma-ui/src/form/__tests__/SelectBase.test.tsx @@ -228,3 +228,48 @@ describe('Field label adoption (#495)', () => { expect(screen.getByRole('combobox')).not.toHaveAttribute('id'); }); }); + +describe('SelectBase fullwidth aliases', () => { + const option = ; + + it('applies is-fullwidth via the canonical isFullwidth prop', () => { + render({option}); + expect(screen.getByRole('combobox').parentElement).toHaveClass( + 'is-fullwidth' + ); + }); + + it('applies is-fullwidth via the deprecated isFullWidth prop', () => { + render({option}); + expect(screen.getByRole('combobox').parentElement).toHaveClass( + 'is-fullwidth' + ); + }); + + it('isFullwidth wins when both spellings are set', () => { + render( + + {option} + + ); + expect(screen.getByRole('combobox').parentElement).not.toHaveClass( + 'is-fullwidth' + ); + }); + + it('does not leak the deprecated alias onto the DOM', () => { + render({option}); + expect(screen.getByRole('combobox')).not.toHaveAttribute('isFullWidth'); + }); + + it('applies the class prefix to is-fullwidth', () => { + render( + + {option} + + ); + expect(screen.getByRole('combobox').parentElement).toHaveClass( + 'bestax-is-fullwidth' + ); + }); +}); diff --git a/docs/docs/api/components/sidebar.md b/docs/docs/api/components/sidebar.md index 24536cb6a..d5dcb54fa 100644 --- a/docs/docs/api/components/sidebar.md +++ b/docs/docs/api/components/sidebar.md @@ -160,7 +160,7 @@ function example() { inline isOpen={isOpen} onClose={() => setIsOpen(false)} - fullWidth + isFullwidth > Full Width Panel @@ -391,22 +391,23 @@ Use the Sidebar with a Menu component for building navigation drawers in mobile- -| Prop | Type | Default | Description | -| -------------- | --------------------------------------------------- | --------- | ----------------------------------------------------- | -| `isOpen` | `boolean` | — | Whether the sidebar is open (required). | -| `onClose` | `() => void` | — | Callback when sidebar should close. | -| `position` | `'left'` \| `'right'` | `'left'` | Which side the sidebar appears from. Default: 'left'. | -| `width` | `string` | `'260px'` | Custom width of the sidebar. | -| `fullWidth` | `boolean` | `false` | Sidebar takes full width (mobile-style). | -| `overlay` | `boolean` | `true` | Show overlay behind sidebar. Default: true. | -| `overlayClose` | `boolean` | `true` | Close sidebar when overlay is clicked. Default: true. | -| `escapeClose` | `boolean` | `true` | Close sidebar on Escape key. Default: true. | -| `canCancel` | `boolean` | `true` | Allow closing the sidebar. Default: true. | -| `children` | `React.ReactNode` | — | Content to display in the sidebar. | -| `inline` | `boolean` | `false` | Renders inline instead of using a portal. | -| `className` | `string` | — | Additional CSS classes. | -| `ref` | `React.Ref` | — | Ref forwarded to the sidebar element. | -| `...` | All standard HTML attributes and Bulma helper props | — | See [Helper Props](../helpers/usebulmaclasses.md) | +| Prop | Type | Default | Description | +| -------------- | --------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------ | +| `isOpen` | `boolean` | — | Whether the sidebar is open (required). | +| `onClose` | `() => void` | — | Callback when sidebar should close. | +| `position` | `'left'` \| `'right'` | `'left'` | Which side the sidebar appears from. Default: 'left'. | +| `width` | `string` | `'260px'` | Custom width of the sidebar. | +| `isFullwidth` | `boolean` | `false` | Sidebar takes full width (mobile-style). | +| `fullWidth` | `boolean` | `false` | **Deprecated.** Use `isFullwidth` instead — `isFullwidth` wins if both are set. Sidebar takes full width (mobile-style). | +| `overlay` | `boolean` | `true` | Show overlay behind sidebar. Default: true. | +| `overlayClose` | `boolean` | `true` | Close sidebar when overlay is clicked. Default: true. | +| `escapeClose` | `boolean` | `true` | Close sidebar on Escape key. Default: true. | +| `canCancel` | `boolean` | `true` | Allow closing the sidebar. Default: true. | +| `children` | `React.ReactNode` | — | Content to display in the sidebar. | +| `inline` | `boolean` | `false` | Renders inline instead of using a portal. | +| `className` | `string` | — | Additional CSS classes. | +| `ref` | `React.Ref` | — | Ref forwarded to the sidebar element. | +| `...` | All standard HTML attributes and Bulma helper props | — | See [Helper Props](../helpers/usebulmaclasses.md) | **Subcomponents:** diff --git a/docs/docs/api/components/tabs.md b/docs/docs/api/components/tabs.md index 8d9fda5c5..4b5ae4d65 100644 --- a/docs/docs/api/components/tabs.md +++ b/docs/docs/api/components/tabs.md @@ -237,10 +237,10 @@ Combine the toggle style with rounded corners for a pill-like appearance. This e ### Fullwidth Tabs -Make your tabs span the entire width of their container with the `fullwidth` prop. This example shows fullwidth tabs, which are useful for emphasizing the tab navigation or when you have many tabs to display. +Make your tabs span the entire width of their container with the `isFullwidth` prop. This example shows fullwidth tabs, which are useful for emphasizing the tab navigation or when you have many tabs to display. (The older `fullwidth` spelling still works as a deprecated alias.) ```tsx live - + One @@ -293,10 +293,10 @@ This example combines several features: centered alignment, boxed style, and ico ### Toggle Fullwidth Tabs with Icons -Enhance your toggle tabs with icons for better visual communication. This example also uses the `fullwidth` prop to make the tabs span the entire width, which is useful for mobile views or when you want to emphasize the tab bar. +Enhance your toggle tabs with icons for better visual communication. This example also uses the `isFullwidth` prop to make the tabs span the entire width, which is useful for mobile views or when you want to emphasize the tab bar. ```tsx live - + @@ -358,7 +358,7 @@ This example features centered, boxed tabs in medium size, each with an icon. It The final example showcases toggle tabs with fullwidth and large size, including icons. This combination is powerful for applications with complex navigation needs, ensuring that users can easily understand and access different sections. ```tsx live - + @@ -441,7 +441,9 @@ You can use all [Bulma helper props](../helpers/usebulmaclasses.md) with ` + ``` @@ -332,7 +332,8 @@ You can use all [Bulma helper props](../helpers/usebulmaclasses.md) with ` -| Prop | Type | Default | Description | -| -------------- | -------------------------------------------------------- | ------- | ------------------------------------------------------------- | -| `className` | `string` | — | Additional CSS classes to apply. | -| `isBordered` | `boolean` | `false` | Adds borders to all cells. | -| `isStriped` | `boolean` | `false` | Adds zebra-striping to rows. | -| `isNarrow` | `boolean` | `false` | Makes the table more compact by cutting cell padding in half. | -| `isHoverable` | `boolean` | `false` | Adds a hover effect on rows. | -| `isFullwidth` | `boolean` | `false` | Makes the table span the full width of its parent. | -| `isResponsive` | `boolean` | `false` | Makes the table horizontally scrollable on small screens. | -| `children` | `React.ReactNode` | — | Table content (should use subcomponents). | -| `...` | All standard `` attributes and Bulma helper props | — | See [Helper Props](../helpers/usebulmaclasses.md) | +| Prop | Type | Default | Description | +| -------------- | -------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `className` | `string` | — | Additional CSS classes to apply. | +| `isBordered` | `boolean` | `false` | Adds borders to all cells. | +| `isStriped` | `boolean` | `false` | Adds zebra-striping to rows. | +| `isNarrow` | `boolean` | `false` | Makes the table more compact by cutting cell padding in half. | +| `isHoverable` | `boolean` | `false` | Adds a hover effect on rows. | +| `isFullwidth` | `boolean` | `false` | Makes the table span the full width of its parent. | +| `isFullWidth` | `boolean` | `false` | **Deprecated.** Use `isFullwidth` instead — `isFullwidth` wins if both are set. Makes the table span the full width of its parent. | +| `isResponsive` | `boolean` | `false` | Makes the table horizontally scrollable on small screens. | +| `children` | `React.ReactNode` | — | Table content (should use subcomponents). | +| `...` | All standard `
` attributes and Bulma helper props | — | See [Helper Props](../helpers/usebulmaclasses.md) | **Subcomponents:** diff --git a/docs/docs/api/form/file.md b/docs/docs/api/form/file.md index a8c64055f..3a98007bc 100644 --- a/docs/docs/api/form/file.md +++ b/docs/docs/api/form/file.md @@ -436,6 +436,7 @@ function example() { | `size` | `'small'` \| `'medium'` \| `'large'` | — | Size modifier for the file input. | | `isBoxed` | `boolean` | `false` | Boxed file input. | | `isFullwidth` | `boolean` | `false` | Whether the file input expands to full width. | +| `isFullWidth` | `boolean` | `false` | **Deprecated.** Use `isFullwidth` instead — `isFullwidth` wins if both are set. Whether the file input expands to full width. | | `isRight` | `boolean` | `false` | Position the CTA on the right (with `hasName`). | | `isCentered` | `boolean` | `false` | Center the file input within its container. | | `hasName` | `boolean` | `false` | Show a file name indicator. | diff --git a/docs/docs/api/form/select.md b/docs/docs/api/form/select.md index 09df01443..40ca2d328 100644 --- a/docs/docs/api/form/select.md +++ b/docs/docs/api/form/select.md @@ -455,6 +455,7 @@ function example() { | `isHovered` | `boolean` | `false` | Forces hovered state on the inner select. | | `isFocused` | `boolean` | `false` | Forces focused state on the inner select. | | `isFullwidth` | `boolean` | `false` | Makes the select span the full width of parent. | +| `isFullWidth` | `boolean` | `false` | **Deprecated.** Use `isFullwidth` instead — `isFullwidth` wins if both are set. Makes the select span the full width of parent. | | `className` | `string` | — | Additional CSS classes to apply. | | `disabled` | `boolean` | `false` | Disables the select. | | `multiple` | `boolean` | `false` | Allows multiple selections. | diff --git a/docs/docs/guides/getting-started/migration/react-bulma-components.md b/docs/docs/guides/getting-started/migration/react-bulma-components.md index 159ebe805..ea5e54f11 100644 --- a/docs/docs/guides/getting-started/migration/react-bulma-components.md +++ b/docs/docs/guides/getting-started/migration/react-bulma-components.md @@ -46,7 +46,8 @@ in place: `Hero.Foot`, `Panel.Header` → `Panel.Heading`, `Heading subtitle` → `SubTitle`, `Level.Side align="right"` → `Level.Right`, `Loader` → a plain `
`. - **Props** — `renderAs` → `as` (where supported), boolean modifiers gain their bestax prefixes - (`loading` → `isLoading`, `fullwidth` → `isFullWidth`), numeric spacing and text sizes become + (`loading` → `isLoading`, `fullwidth` → `isFullWidth`, now a deprecated alias of + `isFullwidth`), numeric spacing and text sizes become string unions (`textSize={4}` → `textSize="4"`), and values are remapped (`textAlign="center"` → `"centered"`, `state="hover"` → `isHovered`). - **Responsive objects** — `mobile={{ size: 4 }}` flattens to `sizeMobile={4}`, diff --git a/docs/docs/guides/helpers/visibility.md b/docs/docs/guides/helpers/visibility.md index 228368e08..57e7728a9 100644 --- a/docs/docs/guides/helpers/visibility.md +++ b/docs/docs/guides/helpers/visibility.md @@ -820,7 +820,7 @@ function MobileFirstExample() { displayMobile="block" displayTablet="none" color="primary" - fullWidth + isFullwidth > Mobile Actions diff --git a/skills/bestax-form/SKILL.md b/skills/bestax-form/SKILL.md index 60fa3671d..76d8ffd77 100644 --- a/skills/bestax-form/SKILL.md +++ b/skills/bestax-form/SKILL.md @@ -116,8 +116,9 @@ Across the convenience inputs (`Input`, `Select`, `TextArea`, and similar): Plus the full Bulma **helper props** (`m`, `p`, `textColor`, `display`, …) on every component via `useBulmaClasses`. -⚠️ Full-width casing is inconsistent across the library: `Select`, `File`, and `Table` take -`isFullwidth` (lowercase w); `Button` alone takes `isFullWidth`; `Tabs` takes bare `fullwidth`. +Full-width is `isFullwidth` on every component (`Button`, `Select`, `File`, `Table`, `Tabs`, +`Sidebar`); older spellings (`isFullWidth`, `fullwidth`, `fullWidth`) still work as deprecated +aliases — write `isFullwidth`. The `label` prop on the single-control convenience inputs (`Input`, `Select`, `TextArea`, `File`, `Numberinput`, `Slider`, `DateInput`, `TimeInput`, `DateTimeInput`, `Autocomplete`, diff --git a/skills/bestax-form/references/api.md b/skills/bestax-form/references/api.md index 0f78badbb..464a12e97 100644 --- a/skills/bestax-form/references/api.md +++ b/skills/bestax-form/references/api.md @@ -59,7 +59,7 @@ Wraps a single input; adds icons and loading. ## Select / SelectBase, TextArea / TextAreaBase Same convenience/raw split as Input. `Select` supports `isLoading` (on the control), `color`, -`size`, `isRounded`, `isFullwidth` (lowercase w — Button's is `isFullWidth`), `multiple` + +`size`, `isRounded`, `isFullwidth`, `multiple` + `multipleSize`, plus the Field/Control/message props. `TextArea` adds `rows` and `hasFixedSize`. diff --git a/skills/bestax-migrate/references/prop-map.md b/skills/bestax-migrate/references/prop-map.md index bed2e31f3..3b45560b2 100644 --- a/skills/bestax-migrate/references/prop-map.md +++ b/skills/bestax-migrate/references/prop-map.md @@ -32,7 +32,7 @@ this automatically for literal values; dynamic values get TODOs. ## Boolean modifier convention RBC uses bare booleans (`loading`, `outlined`, `rounded`, `fullwidth`); bestax prefixes -with `is`/`has` (`isLoading`, `isOutlined`, `isRounded`, `isFullWidth`). Per-component +with `is`/`has` (`isLoading`, `isOutlined`, `isRounded`, `isFullwidth`). Per-component tables live in [component-map.md](component-map.md). ## Responsive breakpoint objects → flat per-viewport props From 46e446a6974d79e4afca737b3023d148101b1120 Mon Sep 17 00:00:00 2001 From: Alex Smith Date: Wed, 12 Aug 2026 15:19:54 -0400 Subject: [PATCH 2/8] feat(bulma-ui): add isLight to Tag --- bestax-mcp/data/catalog.json | 6 ++-- bestax-mcp/data/components/Tag.json | 13 ++++++++ bestax-mcp/data/components/Tags.json | 9 ++++++ bestax-mcp/data/skills.json | 2 +- bulma-ui/src/elements/Tag.stories.tsx | 20 ++++++++++++ bulma-ui/src/elements/Tag.tsx | 4 +++ bulma-ui/src/elements/__tests__/Tag.test.tsx | 31 +++++++++++++++++++ docs/docs/api/elements/tag.md | 16 ++++++++++ docs/docs/guides/library/elements/index.md | 2 +- .../references/themeable-components.md | 2 +- 10 files changed, 99 insertions(+), 6 deletions(-) diff --git a/bestax-mcp/data/catalog.json b/bestax-mcp/data/catalog.json index 6d5a4259b..5e1565f7e 100644 --- a/bestax-mcp/data/catalog.json +++ b/bestax-mcp/data/catalog.json @@ -928,8 +928,8 @@ "slug": "elements/tag", "import": "import { Tag } from '@allxsmith/bestax-bulma';", "compound": false, - "propCount": 8, - "exampleCount": 11 + "propCount": 9, + "exampleCount": 12 }, { "name": "Taginput", @@ -950,7 +950,7 @@ "slug": "elements/tags", "import": "import { Tags } from '@allxsmith/bestax-bulma';", "compound": true, - "propCount": 12, + "propCount": 13, "exampleCount": 7 }, { diff --git a/bestax-mcp/data/components/Tag.json b/bestax-mcp/data/components/Tag.json index 5fc928beb..db08f55a6 100644 --- a/bestax-mcp/data/components/Tag.json +++ b/bestax-mcp/data/components/Tag.json @@ -10,6 +10,10 @@ "title": "Colored Tag", "code": "Primary Tag" }, + { + "title": "Light Tag", + "code": "\n \n Light Primary\n \n \n Light Danger\n \n" + }, { "title": "Medium and Large Tag", "code": "\n Medium Tag\n Large Tag\n" @@ -81,6 +85,15 @@ "inherited": false, "deprecated": false }, + { + "name": "isLight", + "type": "boolean", + "default": "false", + "description": "Use the light color variant.", + "required": false, + "inherited": false, + "deprecated": false + }, { "name": "isRounded", "type": "boolean", diff --git a/bestax-mcp/data/components/Tags.json b/bestax-mcp/data/components/Tags.json index 99e8375cc..05e85f9ac 100644 --- a/bestax-mcp/data/components/Tags.json +++ b/bestax-mcp/data/components/Tags.json @@ -120,6 +120,15 @@ "inherited": false, "deprecated": false }, + { + "name": "isLight", + "type": "boolean", + "default": "false", + "description": "Use the light color variant.", + "required": false, + "inherited": false, + "deprecated": false + }, { "name": "isRounded", "type": "boolean", diff --git a/bestax-mcp/data/skills.json b/bestax-mcp/data/skills.json index 4b198d454..732a95a16 100644 --- a/bestax-mcp/data/skills.json +++ b/bestax-mcp/data/skills.json @@ -132,7 +132,7 @@ { "id": "themeable-components", "file": "references/themeable-components.md", - "bytes": 20268 + "bytes": 20266 } ], "examples": [ diff --git a/bulma-ui/src/elements/Tag.stories.tsx b/bulma-ui/src/elements/Tag.stories.tsx index dffaf03ba..d7dd3555e 100644 --- a/bulma-ui/src/elements/Tag.stories.tsx +++ b/bulma-ui/src/elements/Tag.stories.tsx @@ -54,6 +54,10 @@ const meta: Meta = { control: 'select', options: ['normal', 'medium', 'large'], }, + isLight: { + control: 'boolean', + description: 'Use the light color variant.', + }, isRounded: { control: 'boolean' }, isDelete: { control: 'boolean' }, isHoverable: { control: 'boolean' }, @@ -89,6 +93,22 @@ export const Primary: Story = { }, }; +export const Light: Story = { + args: { + children: 'Light Primary Tag', + color: 'primary', + isLight: true, + }, + parameters: { + docs: { + description: { + story: + 'The isLight prop renders the light variant of the chosen color.', + }, + }, + }, +}; + export const Medium: Story = { args: { children: 'Medium Tag', diff --git a/bulma-ui/src/elements/Tag.tsx b/bulma-ui/src/elements/Tag.tsx index 3b1b76655..87a7aecc1 100644 --- a/bulma-ui/src/elements/Tag.tsx +++ b/bulma-ui/src/elements/Tag.tsx @@ -39,6 +39,8 @@ export interface TagProps color?: TagColor; /** Tag size. */ size?: TagSize; + /** Use the light color variant. */ + isLight?: boolean; /** Renders a rounded tag. */ isRounded?: boolean; /** Renders a delete-style tag (delete button). */ @@ -63,6 +65,7 @@ export const Tag: React.FC = ({ className, color, size, + isLight, isRounded, isDelete, isHoverable, @@ -78,6 +81,7 @@ export const Tag: React.FC = ({ const bulmaClasses = usePrefixedClassNames('tag', { [`is-${color}`]: color && validTagColors.includes(color), [`is-${size}`]: size && size !== 'normal' && validTagSizes.includes(size), + 'is-light': isLight, 'is-rounded': isRounded, 'is-delete': isDelete, 'is-hoverable': isHoverable, diff --git a/bulma-ui/src/elements/__tests__/Tag.test.tsx b/bulma-ui/src/elements/__tests__/Tag.test.tsx index d5ed5bb15..dc30c375f 100644 --- a/bulma-ui/src/elements/__tests__/Tag.test.tsx +++ b/bulma-ui/src/elements/__tests__/Tag.test.tsx @@ -47,6 +47,37 @@ describe('Tag Component', () => { expect(tag).toHaveClass('tag is-rounded'); }); + test('applies is-light class via isLight', () => { + render(); + const tag = screen.getByText('Test Tag'); + expect(tag).toHaveClass('tag is-primary is-light'); + }); + + test('does not apply is-light by default', () => { + render(); + const tag = screen.getByText('Test Tag'); + expect(tag).not.toHaveClass('is-light'); + }); + + test('applies is-light on the delete-button render path', () => { + render(); + const button = screen.getByRole('button', { name: 'Delete tag' }); + expect(button).toHaveClass('tag is-delete is-light'); + }); + + test('applies the class prefix to is-light', () => { + render( + + + Prefixed Tag + + + ); + const tag = screen.getByText('Prefixed Tag'); + expect(tag).toHaveClass('bestax-tag'); + expect(tag).toHaveClass('bestax-is-light'); + }); + test('applies hoverable class', () => { render(); const tag = screen.getByText('Test Tag'); diff --git a/docs/docs/api/elements/tag.md b/docs/docs/api/elements/tag.md index 268bf04c2..16d63562c 100644 --- a/docs/docs/api/elements/tag.md +++ b/docs/docs/api/elements/tag.md @@ -52,6 +52,21 @@ Set the `color` prop to apply a Bulma color modifier, such as `color="primary"`, Primary Tag ``` +### Light Tag + +Add the `isLight` prop to render the light variant of the chosen color — a softer, pastel version that works well for secondary status indicators. + +```tsx live + + + Light Primary + + + Light Danger + + +``` + ### Medium and Large Tag Use the `size` prop with values like `size="medium"` or `size="large"` to adjust the tag's size for different UI contexts. @@ -207,6 +222,7 @@ Combine `Tag` with `Tags` for grouped, multi-tag UIs. | `className` | `string` | — | Additional CSS classes to apply. | | `color` | `TagColor` | — | Bulma color modifier for the tag. | | `size` | `'normal'` \| `'medium'` \| `'large'` | — | Tag size. | +| `isLight` | `boolean` | `false` | Use the light color variant. | | `isRounded` | `boolean` | `false` | Renders a rounded tag. | | `isDelete` | `boolean` | `false` | Renders a delete-style tag (delete button). | | `isHoverable` | `boolean` | `false` | Adds hover effect to the tag. | diff --git a/docs/docs/guides/library/elements/index.md b/docs/docs/guides/library/elements/index.md index 12d3b9cfa..3b87c9b33 100644 --- a/docs/docs/guides/library/elements/index.md +++ b/docs/docs/guides/library/elements/index.md @@ -348,7 +348,7 @@ A highly composable, Bulma-styled table system with full support for Bulma's tab ### Tag -A Bulma-styled label or badge, supporting color, size, rounded, hoverable, and delete variants. [View full documentation.](../../../api/elements/tag.md) +A Bulma-styled label or badge, supporting color (with an `isLight` variant), size, rounded, hoverable, and delete variants. [View full documentation.](../../../api/elements/tag.md) ```tsx live Default Tag diff --git a/skills/bestax-theming/references/themeable-components.md b/skills/bestax-theming/references/themeable-components.md index 603da6da1..16f2326bf 100644 --- a/skills/bestax-theming/references/themeable-components.md +++ b/skills/bestax-theming/references/themeable-components.md @@ -50,7 +50,7 @@ Shades (`colorShade` / `backgroundColorShade`): `00, 05, 10, … 95, invert, lig | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `Button` | `primary \| link \| info \| success \| warning \| danger \| white \| light \| dark \| black \| text \| ghost` | `small \| normal \| medium \| large` | adds `text`, `ghost`; also `isLight`, `isOutlined`, `isInverted`, `isRounded` | | `Notification` | the 17 `validColors` (`black-bis`, `black-ter`, and greys deprecated: no CSS, dev-warn, removed next major — see ⚠️) | — | also `isLight` | -| `Tag` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `normal \| medium \| large` | also `isRounded`, `isDelete`, `isHoverable` — **no `isLight`** | +| `Tag` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `normal \| medium \| large` | also `isLight`, `isRounded`, `isDelete`, `isHoverable` | | `Box` | `primary \| link \| info \| success \| warning \| danger` | — | `color` renders `has-text-`, same as `textColor` (which wins when both are set; no `.box.is-*` ships — tint via `bgColor`); also `hasShadow` | | `Message` | `primary \| link \| info \| success \| warning \| danger` | — | the 6 only | | `Input` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded`, `isStatic` | From 39eb6a0745f2d5d6488667593eb310d9d7f64d00 Mon Sep 17 00:00:00 2001 From: Alex Smith Date: Wed, 12 Aug 2026 15:25:45 -0400 Subject: [PATCH 3/8] feat(bulma-ui): accept Bulma helper props on Card.* sub-parts --- bestax-mcp/data/catalog.json | 2 +- bestax-mcp/data/components/Card.json | 224 +++++++++++++- bestax-mcp/data/skills.json | 4 +- bulma-ui/src/components/Card.stories.tsx | 27 ++ bulma-ui/src/components/Card.tsx | 279 ++++++++++++++---- .../src/components/__tests__/Card.test.tsx | 113 +++++++ docs/docs/api/components/card.md | 118 +++++--- scripts/gen-component-catalog.mjs | 4 +- skills/bestax-custom-component/SKILL.md | 7 +- .../bestax-custom-component/references/api.md | 5 +- .../references/component-catalog.md | 4 +- .../references/layout-components.md | 4 +- 12 files changed, 665 insertions(+), 126 deletions(-) diff --git a/bestax-mcp/data/catalog.json b/bestax-mcp/data/catalog.json index 5e1565f7e..1c945ee32 100644 --- a/bestax-mcp/data/catalog.json +++ b/bestax-mcp/data/catalog.json @@ -224,7 +224,7 @@ "slug": "components/card", "import": "import { Card } from '@allxsmith/bestax-bulma';", "compound": true, - "propCount": 28, + "propCount": 49, "exampleCount": 13 }, { diff --git a/bestax-mcp/data/components/Card.json b/bestax-mcp/data/components/Card.json index 2dbccd9f6..c162a7417 100644 --- a/bestax-mcp/data/components/Card.json +++ b/bestax-mcp/data/components/Card.json @@ -201,6 +201,36 @@ "summary": "Card header compound component. Wraps children in a `.card-header` element.", "component": null, "props": [ + { + "name": "color", + "type": "Bulma color | 'inherit' | 'current'", + "default": null, + "description": "Bulma color modifier (text color helper).", + "required": false, + "inherited": false, + "deprecated": false, + "valuesRef": "helpers/valid-values" + }, + { + "name": "bgColor", + "type": "Bulma color | 'inherit' | 'current'", + "default": null, + "description": "Background color helper.", + "required": false, + "inherited": false, + "deprecated": false, + "valuesRef": "helpers/valid-values" + }, + { + "name": "textColor", + "type": "Bulma color | 'inherit' | 'current'", + "default": null, + "description": "Text color helper; wins over `color` when both are set.", + "required": false, + "inherited": false, + "deprecated": false, + "valuesRef": "helpers/valid-values" + }, { "name": "className", "type": "string", @@ -230,7 +260,7 @@ } ], "extraProps": [], - "catchAll": "All standard HTML attributes", + "catchAll": "All standard HTML attributes and Bulma helper props", "types": [] }, { @@ -238,6 +268,36 @@ "summary": "Card header title compound component. Renders a `.card-header-title` element.", "component": null, "props": [ + { + "name": "color", + "type": "Bulma color | 'inherit' | 'current'", + "default": null, + "description": "Bulma color modifier (text color helper).", + "required": false, + "inherited": false, + "deprecated": false, + "valuesRef": "helpers/valid-values" + }, + { + "name": "bgColor", + "type": "Bulma color | 'inherit' | 'current'", + "default": null, + "description": "Background color helper.", + "required": false, + "inherited": false, + "deprecated": false, + "valuesRef": "helpers/valid-values" + }, + { + "name": "textColor", + "type": "Bulma color | 'inherit' | 'current'", + "default": null, + "description": "Text color helper; wins over `color` when both are set.", + "required": false, + "inherited": false, + "deprecated": false, + "valuesRef": "helpers/valid-values" + }, { "name": "className", "type": "string", @@ -267,7 +327,7 @@ } ], "extraProps": [], - "catchAll": "All standard
attributes", + "catchAll": "All standard
attributes and Bulma helper props", "types": [] }, { @@ -275,6 +335,36 @@ "summary": "Card header icon compound component. Renders a `.card-header-icon` button.", "component": null, "props": [ + { + "name": "color", + "type": "Bulma color | 'inherit' | 'current'", + "default": null, + "description": "Bulma color modifier (text color helper).", + "required": false, + "inherited": false, + "deprecated": false, + "valuesRef": "helpers/valid-values" + }, + { + "name": "bgColor", + "type": "Bulma color | 'inherit' | 'current'", + "default": null, + "description": "Background color helper.", + "required": false, + "inherited": false, + "deprecated": false, + "valuesRef": "helpers/valid-values" + }, + { + "name": "textColor", + "type": "Bulma color | 'inherit' | 'current'", + "default": null, + "description": "Text color helper; wins over `color` when both are set.", + "required": false, + "inherited": false, + "deprecated": false, + "valuesRef": "helpers/valid-values" + }, { "name": "className", "type": "string", @@ -295,7 +385,7 @@ } ], "extraProps": [], - "catchAll": "All standard -); +}) => { + const { bulmaHelperClasses, rest } = useBulmaClasses({ + color: textColor ?? color, + backgroundColor: bgColor, + ...props, + }); + return ( + + ); +}; /** * Card image compound component. Wraps children in a `.card-image` element. @@ -393,15 +492,29 @@ const CardHeaderIcon: React.FC = ({ const CardImage: React.FC = ({ className, children, + color, + bgColor, + textColor, ...props -}) => ( -
- {children} -
-); +}) => { + const { bulmaHelperClasses, rest } = useBulmaClasses({ + color: textColor ?? color, + backgroundColor: bgColor, + ...props, + }); + return ( +
+ {children} +
+ ); +}; /** * Card content compound component. Wraps children in a `.card-content` element. @@ -413,15 +526,29 @@ const CardImage: React.FC = ({ const CardContent: React.FC = ({ className, children, + color, + bgColor, + textColor, ...props -}) => ( -
- {children} -
-); +}) => { + const { bulmaHelperClasses, rest } = useBulmaClasses({ + color: textColor ?? color, + backgroundColor: bgColor, + ...props, + }); + return ( +
+ {children} +
+ ); +}; /** * Card footer compound component. Wraps children in a `.card-footer` element. @@ -433,15 +560,29 @@ const CardContent: React.FC = ({ const CardFooter: React.FC = ({ className, children, + color, + bgColor, + textColor, ...props -}) => ( -
- {children} -
-); +}) => { + const { bulmaHelperClasses, rest } = useBulmaClasses({ + color: textColor ?? color, + backgroundColor: bgColor, + ...props, + }); + return ( +
+ {children} +
+ ); +}; /** * Card footer item compound component. Wraps children in a `.card-footer-item` span. @@ -453,15 +594,29 @@ const CardFooter: React.FC = ({ const CardFooterItem: React.FC = ({ className, children, + color, + bgColor, + textColor, ...props -}) => ( - - {children} - -); +}) => { + const { bulmaHelperClasses, rest } = useBulmaClasses({ + color: textColor ?? color, + backgroundColor: bgColor, + ...props, + }); + return ( + + {children} + + ); +}; // Attach nested Title and Icon components to CardHeader const CardHeaderCompound = withSubComponents(CardHeader, { diff --git a/bulma-ui/src/components/__tests__/Card.test.tsx b/bulma-ui/src/components/__tests__/Card.test.tsx index b17f501f9..df139e272 100644 --- a/bulma-ui/src/components/__tests__/Card.test.tsx +++ b/bulma-ui/src/components/__tests__/Card.test.tsx @@ -1184,3 +1184,116 @@ describe('Card color text alias', () => { expect(card).not.toHaveClass('has-text-primary'); }); }); + +describe('Card sub-parts accept Bulma helper props', () => { + type SubPartCase = { + name: string; + selector: string; + render: (props: Record) => React.ReactElement; + }; + + const cases: SubPartCase[] = [ + { + name: 'Card.Header', + selector: '.card-header', + render: props => Header, + }, + { + name: 'Card.Header.Title', + selector: '.card-header-title', + render: props => Title, + }, + { + name: 'Card.Header.Icon', + selector: '.card-header-icon', + render: props => Icon, + }, + { + name: 'Card.Image', + selector: '.card-image', + render: props => Image, + }, + { + name: 'Card.Content', + selector: '.card-content', + render: props => Content, + }, + { + name: 'Card.Footer', + selector: '.card-footer', + render: props => Footer, + }, + { + name: 'Card.FooterItem', + selector: '.card-footer-item', + render: props => Item, + }, + ]; + + cases.forEach(({ name, selector, render: renderSubPart }) => { + describe(name, () => { + it('applies spacing helper props', () => { + const { container } = render(renderSubPart({ p: '4' })); + expect(container.querySelector(selector)).toHaveClass('p-4'); + }); + + it('renders has-text via color alone', () => { + const { container } = render(renderSubPart({ color: 'primary' })); + expect(container.querySelector(selector)).toHaveClass( + 'has-text-primary' + ); + }); + + it('gives textColor precedence over color', () => { + const { container } = render( + renderSubPart({ textColor: 'danger', color: 'primary' }) + ); + const el = container.querySelector(selector); + expect(el).toHaveClass('has-text-danger'); + expect(el).not.toHaveClass('has-text-primary'); + }); + + it('applies bgColor', () => { + const { container } = render(renderSubPart({ bgColor: 'info' })); + expect(container.querySelector(selector)).toHaveClass( + 'has-background-info' + ); + }); + + it('still spreads unknown DOM props', () => { + const { container } = render( + renderSubPart({ 'data-testid': 'sub-part' }) + ); + expect(container.querySelector(selector)).toHaveAttribute( + 'data-testid', + 'sub-part' + ); + }); + + it('prefixes both the part class and helper classes', () => { + const { container } = render( + + {renderSubPart({ p: '4' })} + + ); + const el = container.querySelector(`.bestax-${selector.slice(1)}`); + expect(el).not.toBeNull(); + expect(el).toHaveClass('bestax-p-4'); + }); + }); + }); + + it('keeps the CardHeader quirk: className also lands on the generated title div', () => { + const { container } = render( + + Plain header + + ); + const header = container.querySelector('.card-header'); + expect(header).toHaveClass('custom'); + expect(header).toHaveClass('p-2'); + const title = container.querySelector('.card-header-title'); + expect(title).toHaveClass('custom'); + expect(title).not.toHaveClass('p-2'); + }); +}); diff --git a/docs/docs/api/components/card.md b/docs/docs/api/components/card.md index 203dc9fee..e87fd3832 100644 --- a/docs/docs/api/components/card.md +++ b/docs/docs/api/components/card.md @@ -336,61 +336,82 @@ You can combine the traditional prop-based API with compound components: ### Card.Header -| Prop | Type | Default | Description | -| ----------- | ---------------------------- | ------- | ------------------------------------------------------------ | -| `className` | `string` | — | Additional CSS classes. | -| `children` | `React.ReactNode` | — | Header content. Wrap in Card.Header.Title for Bulma styling. | -| `centered` | `boolean` | `false` | Whether to center the header title text. | -| `...` | All standard HTML attributes | — | | +| Prop | Type | Default | Description | +| ----------- | ----------------------------------------------------------------------- | ------- | ------------------------------------------------------------ | +| `color` | [Bulma color](../helpers/valid-values.md) \| `'inherit'` \| `'current'` | — | Bulma color modifier (text color helper). | +| `bgColor` | [Bulma color](../helpers/valid-values.md) \| `'inherit'` \| `'current'` | — | Background color helper. | +| `textColor` | [Bulma color](../helpers/valid-values.md) \| `'inherit'` \| `'current'` | — | Text color helper; wins over `color` when both are set. | +| `className` | `string` | — | Additional CSS classes. | +| `children` | `React.ReactNode` | — | Header content. Wrap in Card.Header.Title for Bulma styling. | +| `centered` | `boolean` | `false` | Whether to center the header title text. | +| `...` | All standard HTML attributes and Bulma helper props | — | See [Helper Props](../helpers/usebulmaclasses.md) | ### Card.Header.Title -| Prop | Type | Default | Description | -| ----------- | ------------------------------- | ------- | --------------------------------- | -| `className` | `string` | — | Additional CSS classes. | -| `children` | `React.ReactNode` | — | Title text content. | -| `centered` | `boolean` | `false` | Whether to center the title text. | -| `...` | All standard `
` attributes | — | | +| Prop | Type | Default | Description | +| ----------- | ----------------------------------------------------------------------- | ------- | ------------------------------------------------------- | +| `color` | [Bulma color](../helpers/valid-values.md) \| `'inherit'` \| `'current'` | — | Bulma color modifier (text color helper). | +| `bgColor` | [Bulma color](../helpers/valid-values.md) \| `'inherit'` \| `'current'` | — | Background color helper. | +| `textColor` | [Bulma color](../helpers/valid-values.md) \| `'inherit'` \| `'current'` | — | Text color helper; wins over `color` when both are set. | +| `className` | `string` | — | Additional CSS classes. | +| `children` | `React.ReactNode` | — | Title text content. | +| `centered` | `boolean` | `false` | Whether to center the title text. | +| `...` | All standard `
` attributes and Bulma helper props | — | See [Helper Props](../helpers/usebulmaclasses.md) | ### Card.Header.Icon -| Prop | Type | Default | Description | -| ----------- | ---------------------------------- | ------- | ------------------------------------ | -| `className` | `string` | — | Additional CSS classes. | -| `children` | `React.ReactNode` | — | Icon content (e.g. an icon element). | -| `...` | All standard `