From 40c630d4c805076203f0e38929b2df7aa29db1d6 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Fri, 21 Aug 2026 10:45:48 +0200 Subject: [PATCH 1/3] feat(ui)!: rebuild Button on the design system axes The component takes variant, size and shape as explicit props and composes its content from prefixIcon, children and suffixIcon, so the three structural subtypes and the children-shape inference behind them are gone. Ten variants cover the solid and ghost treatments, sizes are letter-scaled, and square and round render the icon-only box. Metrics and colours come from the design component set: heights 48/42/36/32/28 with matching padding, gap, radius, icon box and label role per size, and every fill, border and label colour bound to its component token. Call sites move with it: the outlined treatment is now ghost-secondary while secondary is the solid grey fill, error becomes critical, and word sizes become letters. --- .changeset/button-redesign.md | 6 + apps/docs/scripts/ui-components.mjs | 3 +- .../components/ui-examples/button.module.css | 11 + .../src/components/ui-examples/button.tsx | 34 +- apps/docs/src/components/ui-examples/menu.tsx | 2 +- .../src/components/ui-examples/tooltip.tsx | 2 +- .../docs/ui-library/ui-components/button.mdx | 7 +- .../docs/ui-library/ui-components/menu.mdx | 2 +- .../docs/ui-library/ui-components/tooltip.mdx | 2 +- .../button-submit/button-submit.module.css | 4 +- .../button-submit/button-submit.tsx | 1 - .../placeholder-button/placeholder-button.tsx | 11 +- .../export-modal/export-modal.tsx | 3 +- .../import-modal/import-modal.tsx | 14 +- .../ai-tools-control/ai-tools-control.tsx | 8 +- .../add-ai-tool-footer/add-ai-tool-footer.tsx | 2 +- .../condition-modal-footer.tsx | 2 +- .../conditions-form.tsx | 12 +- .../delete-confirmation.tsx | 4 +- .../components/footer/palette-footer.tsx | 2 +- .../properties-bar/properties-bar.tsx | 2 +- .../pane-list/pane-list.tsx | 8 +- .../pane-remove-variable.tsx | 4 +- .../variable-form/variable-form.tsx | 2 +- .../button/regular-button/button.tsx | 155 +++--- .../icon-button/icon-button.tsx | 27 -- .../icon-label-button/icon-label-button.tsx | 27 -- .../label-button/label-button.tsx | 29 -- .../{label-button => }/loader.module.css | 3 +- .../components/button/regular-button/types.ts | 45 +- .../button/styles/border-radius.module.css | 30 ++ .../button/styles/font-size.module.css | 14 + .../components/button/styles/gap.module.css | 25 + .../button/styles/height.module.css | 29 ++ .../button/styles/icon-padding.module.css | 25 + .../button/styles/icon-size.module.css | 42 ++ .../button/styles/padding.module.css | 29 ++ .../button/styles/variant.module.css | 441 ++++++++++++------ .../snackbar/components/action-buttons.tsx | 6 +- 39 files changed, 680 insertions(+), 395 deletions(-) create mode 100644 .changeset/button-redesign.md create mode 100644 apps/docs/src/components/ui-examples/button.module.css delete mode 100644 packages/ui/src/components/button/regular-button/icon-button/icon-button.tsx delete mode 100644 packages/ui/src/components/button/regular-button/icon-label-button/icon-label-button.tsx delete mode 100644 packages/ui/src/components/button/regular-button/label-button/label-button.tsx rename packages/ui/src/components/button/regular-button/{label-button => }/loader.module.css (98%) create mode 100644 packages/ui/src/components/button/styles/height.module.css create mode 100644 packages/ui/src/components/button/styles/padding.module.css diff --git a/.changeset/button-redesign.md b/.changeset/button-redesign.md new file mode 100644 index 000000000..62307a491 --- /dev/null +++ b/.changeset/button-redesign.md @@ -0,0 +1,6 @@ +--- +'@workflowbuilder/ui': major +'@workflowbuilder/sdk': minor +--- + +The Button API now uses variant, letter-based size, shape, prefixIcon, suffixIcon, and children props; children-structure inference has been removed. Variants rename gray to secondary, error to critical, and ghost-destructive to ghost-critical, while the former outlined secondary becomes ghost-secondary and secondary now identifies the solid gray treatment. Sizes now use xl, l, m, s, and xs. diff --git a/apps/docs/scripts/ui-components.mjs b/apps/docs/scripts/ui-components.mjs index 2ca13c574..8ae78379f 100644 --- a/apps/docs/scripts/ui-components.mjs +++ b/apps/docs/scripts/ui-components.mjs @@ -12,11 +12,10 @@ export const COMPONENTS = [ { slug: 'accordion', name: 'Accordion', propsType: 'AccordionProps', dir: 'accordion' }, { slug: 'avatar', name: 'Avatar', propsType: 'AvatarProps', dir: 'avatar' }, - // No single props type - one of three variants depending on `children`. { slug: 'button', name: 'Button', - propsType: ['LabelButtonProps', 'IconButtonProps', 'IconLabelButtonProps'], + propsType: 'ButtonProps', dir: 'button', }, { slug: 'checkbox', name: 'Checkbox', propsType: 'CheckboxProps', dir: 'checkbox' }, diff --git a/apps/docs/src/components/ui-examples/button.module.css b/apps/docs/src/components/ui-examples/button.module.css new file mode 100644 index 000000000..d97af383e --- /dev/null +++ b/apps/docs/src/components/ui-examples/button.module.css @@ -0,0 +1,11 @@ +.example { + display: flex; + flex-direction: column; + gap: var(--wb-space-150); +} + +.row { + display: flex; + flex-wrap: wrap; + gap: var(--wb-space-100); +} diff --git a/apps/docs/src/components/ui-examples/button.tsx b/apps/docs/src/components/ui-examples/button.tsx index c2e5a32e9..f869f3e63 100644 --- a/apps/docs/src/components/ui-examples/button.tsx +++ b/apps/docs/src/components/ui-examples/button.tsx @@ -1,11 +1,43 @@ +import { Plus, X } from '@phosphor-icons/react'; import { Button } from '@workflowbuilder/ui'; +import styles from './button.module.css'; + import { ComponentPreview } from './component-preview'; +const SOLID_VARIANTS = ['primary', 'secondary', 'critical', 'success', 'warning'] as const; +const GHOST_VARIANTS = [ + 'ghost-primary', + 'ghost-secondary', + 'ghost-critical', + 'ghost-success', + 'ghost-warning', +] as const; + export function ButtonExample() { return ( - +
+
+ {SOLID_VARIANTS.map((variant) => ( + + ))} +
+
+ {GHOST_VARIANTS.map((variant) => ( + + ))} +
+
+ +
+
); } diff --git a/apps/docs/src/components/ui-examples/menu.tsx b/apps/docs/src/components/ui-examples/menu.tsx index 8583d4787..e5f9d13c0 100644 --- a/apps/docs/src/components/ui-examples/menu.tsx +++ b/apps/docs/src/components/ui-examples/menu.tsx @@ -13,7 +13,7 @@ export function MenuExample() { { label: 'Delete', destructive: true, onClick: () => {} }, ]} > - + ); diff --git a/apps/docs/src/components/ui-examples/tooltip.tsx b/apps/docs/src/components/ui-examples/tooltip.tsx index 55f37c5f0..2c2771740 100644 --- a/apps/docs/src/components/ui-examples/tooltip.tsx +++ b/apps/docs/src/components/ui-examples/tooltip.tsx @@ -7,7 +7,7 @@ export function TooltipExample() { - + Tooltip diff --git a/apps/docs/src/content/docs/ui-library/ui-components/button.mdx b/apps/docs/src/content/docs/ui-library/ui-components/button.mdx index 7fa976bfb..37b03ef89 100644 --- a/apps/docs/src/content/docs/ui-library/ui-components/button.mdx +++ b/apps/docs/src/content/docs/ui-library/ui-components/button.mdx @@ -7,11 +7,8 @@ import CssVariablesTable from '../../../../components/api/css-variables-table.as import PropsTable from '../../../../components/api/props-table.astro'; import { ButtonExample } from '../../../../components/ui-examples/button'; -`Button` is a flexible, type-safe component that automatically selects the -correct type (label, icon, or icon + label) based on the structure of its -`children`. A single string renders a label button, a single React element -renders an icon button, and a string combined with icons renders an icon-label -button. +`Button` renders its label from `children` and accepts optional `prefixIcon` and +`suffixIcon` props. Use the `square` or `round` shape for icon-only buttons. diff --git a/apps/docs/src/content/docs/ui-library/ui-components/menu.mdx b/apps/docs/src/content/docs/ui-library/ui-components/menu.mdx index 2cdcfc75a..539d8a092 100644 --- a/apps/docs/src/content/docs/ui-library/ui-components/menu.mdx +++ b/apps/docs/src/content/docs/ui-library/ui-components/menu.mdx @@ -28,7 +28,7 @@ function Example() { { label: 'Delete', destructive: true, onClick: () => {} }, ]} > - + ); } diff --git a/apps/docs/src/content/docs/ui-library/ui-components/tooltip.mdx b/apps/docs/src/content/docs/ui-library/ui-components/tooltip.mdx index b8f5f8bb9..1bc2cdc0a 100644 --- a/apps/docs/src/content/docs/ui-library/ui-components/tooltip.mdx +++ b/apps/docs/src/content/docs/ui-library/ui-components/tooltip.mdx @@ -23,7 +23,7 @@ function Example() { return ( - + Default tooltip diff --git a/packages/sdk/src/components/button-submit/button-submit.module.css b/packages/sdk/src/components/button-submit/button-submit.module.css index adf5e92d2..5ae36f913 100644 --- a/packages/sdk/src/components/button-submit/button-submit.module.css +++ b/packages/sdk/src/components/button-submit/button-submit.module.css @@ -10,8 +10,8 @@ --spinner-color: var(--wb-ui-text-onaccent-default); } -.container--ghost-destructive { - --spinner-color: var(--ax-public-button-ghost-destructive-color); +.container--ghost-critical { + --spinner-color: var(--ax-public-button-ghost-critical-color); } .container--pending button { diff --git a/packages/sdk/src/components/button-submit/button-submit.tsx b/packages/sdk/src/components/button-submit/button-submit.tsx index 57810ed7d..35a347f44 100644 --- a/packages/sdk/src/components/button-submit/button-submit.tsx +++ b/packages/sdk/src/components/button-submit/button-submit.tsx @@ -22,7 +22,6 @@ export function ButtonSubmit({ classNameWrapper = '', isPending = false, childre classNameWrapper, )} > - {/* @ts-expect-error The rules for children are complex, and there is no easy way to pass them to a wrapper. */} {isPending && } diff --git a/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx b/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx index 02463a587..1871f637a 100644 --- a/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx +++ b/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx @@ -7,10 +7,15 @@ type Props = { label: string; } & Omit, 'children'>; -export function PlaceholderButton({ label, size = 'extra-small', ...props }: Props) { +export function PlaceholderButton({ label, size = 'xs', ...props }: Props) { return ( - ); diff --git a/packages/sdk/src/features/integration/components/import-export/export-modal/export-modal.tsx b/packages/sdk/src/features/integration/components/import-export/export-modal/export-modal.tsx index db77b0809..0ac0af000 100644 --- a/packages/sdk/src/features/integration/components/import-export/export-modal/export-modal.tsx +++ b/packages/sdk/src/features/integration/components/import-export/export-modal/export-modal.tsx @@ -32,8 +32,7 @@ export function ExportModal() {
-
diff --git a/packages/sdk/src/features/integration/components/import-export/import-modal/import-modal.tsx b/packages/sdk/src/features/integration/components/import-export/import-modal/import-modal.tsx index 6f2710374..f92cf6261 100644 --- a/packages/sdk/src/features/integration/components/import-export/import-modal/import-modal.tsx +++ b/packages/sdk/src/features/integration/components/import-export/import-modal/import-modal.tsx @@ -69,13 +69,19 @@ export function ImportModal() { )}
{warnings.length > 0 && errors.length === 0 && ( - )} -
diff --git a/packages/sdk/src/features/json-form/controls/ai-tools-control/ai-tools-control.tsx b/packages/sdk/src/features/json-form/controls/ai-tools-control/ai-tools-control.tsx index cc11d8182..a1f176b61 100644 --- a/packages/sdk/src/features/json-form/controls/ai-tools-control/ai-tools-control.tsx +++ b/packages/sdk/src/features/json-form/controls/ai-tools-control/ai-tools-control.tsx @@ -58,7 +58,7 @@ function AiToolsControl({ path, handleChange, data, enabled, uischema }: AiTools const label = toolOption?.label; const sharedButtonProps: Partial> = { - variant: 'secondary', + variant: 'ghost-secondary', className: styles['selected-tool-button'], onClick: () => openEditorModal(toolData), disabled: isDisabled, @@ -68,8 +68,7 @@ function AiToolsControl({ path, handleChange, data, enabled, uischema }: AiTools
{icon ? ( - ) : ( @@ -82,8 +81,7 @@ function AiToolsControl({ path, handleChange, data, enabled, uischema }: AiTools ); })} - diff --git a/packages/sdk/src/features/json-form/controls/ai-tools-control/components/add-ai-tool-footer/add-ai-tool-footer.tsx b/packages/sdk/src/features/json-form/controls/ai-tools-control/components/add-ai-tool-footer/add-ai-tool-footer.tsx index a226f6b66..2baeec8f5 100644 --- a/packages/sdk/src/features/json-form/controls/ai-tools-control/components/add-ai-tool-footer/add-ai-tool-footer.tsx +++ b/packages/sdk/src/features/json-form/controls/ai-tools-control/components/add-ai-tool-footer/add-ai-tool-footer.tsx @@ -11,7 +11,7 @@ type Props = { export function AddAiToolFooter({ onCancelClick }: Props) { return (
- diff --git a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form/conditions-form.tsx b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form/conditions-form.tsx index ccce534ff..a91d3799b 100644 --- a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form/conditions-form.tsx +++ b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form/conditions-form.tsx @@ -99,9 +99,15 @@ export const ConditionsForm = forwardRef ))}
- + -
diff --git a/packages/sdk/src/features/palette/components/footer/palette-footer.tsx b/packages/sdk/src/features/palette/components/footer/palette-footer.tsx index 872236283..813d78db5 100644 --- a/packages/sdk/src/features/palette/components/footer/palette-footer.tsx +++ b/packages/sdk/src/features/palette/components/footer/palette-footer.tsx @@ -17,7 +17,7 @@ export function PaletteFooter({ onTemplateClick }: Props) { return (
- diff --git a/packages/sdk/src/features/properties-bar/components/properties-bar/properties-bar.tsx b/packages/sdk/src/features/properties-bar/components/properties-bar/properties-bar.tsx index 3099440c0..52c6c13ed 100644 --- a/packages/sdk/src/features/properties-bar/components/properties-bar/properties-bar.tsx +++ b/packages/sdk/src/features/properties-bar/components/properties-bar/properties-bar.tsx @@ -88,7 +88,7 @@ function PropertiesBarComponent({ } footer={ isExpanded && ( - ) diff --git a/packages/sdk/src/features/variables/modals/tab-global-variables/pane-list/pane-list.tsx b/packages/sdk/src/features/variables/modals/tab-global-variables/pane-list/pane-list.tsx index ab691e0c6..7fa427f1b 100644 --- a/packages/sdk/src/features/variables/modals/tab-global-variables/pane-list/pane-list.tsx +++ b/packages/sdk/src/features/variables/modals/tab-global-variables/pane-list/pane-list.tsx @@ -31,8 +31,12 @@ export function PaneList({ className, setActivePane }: Props) { title="workflowsSettings.tab.globalVariables" description="workflowsSettings.tab.globalVariablesDescription" > - diff --git a/packages/sdk/src/features/variables/modals/tab-global-variables/pane-remove-variable/pane-remove-variable.tsx b/packages/sdk/src/features/variables/modals/tab-global-variables/pane-remove-variable/pane-remove-variable.tsx index 6e354f13c..2ed0fdc41 100644 --- a/packages/sdk/src/features/variables/modals/tab-global-variables/pane-remove-variable/pane-remove-variable.tsx +++ b/packages/sdk/src/features/variables/modals/tab-global-variables/pane-remove-variable/pane-remove-variable.tsx @@ -58,9 +58,9 @@ export function PaneRemoveVariable({ className, setActivePane, id }: Props) { {variable && (
0} > diff --git a/packages/sdk/src/features/variables/modals/tab-global-variables/variable-form/variable-form.tsx b/packages/sdk/src/features/variables/modals/tab-global-variables/variable-form/variable-form.tsx index 98a75d12d..dc5703e3d 100644 --- a/packages/sdk/src/features/variables/modals/tab-global-variables/variable-form/variable-form.tsx +++ b/packages/sdk/src/features/variables/modals/tab-global-variables/variable-form/variable-form.tsx @@ -123,7 +123,7 @@ export function VariableForm(props: Props) { />
- + {t(props.variant === 'add' ? 'workflowsSettings.tab.addVariable' : 'common.save')}
diff --git a/packages/ui/src/components/button/regular-button/button.tsx b/packages/ui/src/components/button/regular-button/button.tsx index 1667083d8..9b5df1e52 100644 --- a/packages/ui/src/components/button/regular-button/button.tsx +++ b/packages/ui/src/components/button/regular-button/button.tsx @@ -1,108 +1,67 @@ import clsx from 'clsx'; -import { ReactElement, forwardRef } from 'react'; +import { forwardRef, isValidElement } from 'react'; import borderRadiusStyles from '../styles/border-radius.module.css'; +import fontSizeStyles from '../styles/font-size.module.css'; +import gapStyles from '../styles/gap.module.css'; +import heightStyles from '../styles/height.module.css'; +import iconPaddingStyles from '../styles/icon-padding.module.css'; +import iconSizeStyles from '../styles/icon-size.module.css'; +import paddingStyles from '../styles/padding.module.css'; import variantStyles from '../styles/variant.module.css'; +import loaderStyles from './loader.module.css'; -import { hasChildrenWithStringAndIcons, hasIconChildrenOnly, hasStringChildrenOnly } from '../guards'; -import { IconButton, IconButtonProps } from './icon-button/icon-button'; -import { IconLabelButton, IconLabelButtonProps } from './icon-label-button/icon-label-button'; -import { LabelButton, LabelButtonProps } from './label-button/label-button'; +import { BaseButton } from '../base-button/base-button'; +import type { ButtonProps } from './types'; -type WithRef = T & { - ref?: React.Ref; -}; +export const Button = forwardRef( + ( + { + children, + className, + isLoading = false, + prefixIcon, + shape = 'default', + size = 'm', + suffixIcon, + variant = 'primary', + ...props + }, + ref, + ) => { + const isIconOnly = shape !== 'default'; + const icon = prefixIcon ?? (isValidElement(children) ? children : null); + const hiddenContentClassName = clsx({ [loaderStyles['hide-content']]: isLoading }); -/** - * ButtonProps defines **discriminated overloads** for the Button component using - * **structural discrimination** rather than a `type` field. - * - * The component dynamically determines which button variant to render based on the - * **structure of the `children` prop**: - * - * - If `children` is a single `string`, it's treated as a **Label Button**. - * - If `children` is a single icon (ReactElement), it's treated as an **Icon Button**. - * - If `children` includes both a string and one or two icons (before/after), - * it's treated as an **Icon Label Button**. - * - * Based on the inferred variant, **only props specific to that variant are allowed**. - * This ensures that incorrect prop combinations (e.g., passing label-specific props - * to an Icon Button) are caught at compile time. - * - * This is intentionally implemented with **overloads** instead of a union type, - * which would incorrectly allow mixing props between types and compromise type safety. - */ -type ButtonProps = { - (props: WithRef): ReactElement; - (props: WithRef): ReactElement; - (props: WithRef): ReactElement; -}; - -const ButtonComponent = forwardRef( - ({ className, variant = 'primary', size = 'medium', ...props }, ref) => { - const buttonProps = { - ref, - ...props, - className: clsx(variantStyles[variant], borderRadiusStyles[size], className), - variant, - size, - }; - - if (hasStringChildrenOnly(props)) { - return {props.children}; - } - - if (hasIconChildrenOnly(props)) { - return {props.children}; - } - - if (hasChildrenWithStringAndIcons(props)) { - return {props.children}; - } - - // Unreachable for type-checked callers; guards dynamic children TS cannot see. - console.error( - 'Button: `children` did not match a supported variant (label, icon, or icon + label). Rendering nothing.', + return ( + + {isIconOnly ? ( + {icon} + ) : ( + <> + {prefixIcon != null && ( + {prefixIcon} + )} + {children} + {suffixIcon != null && ( + {suffixIcon} + )} + + )} + {isLoading && } + ); - return null; }, ); - -/** - * Button is a flexible, and type-safe component that automatically selects - * the correct type (Label Button, Icon Button, or Icon Label Button) based on the - * structure of its `children` prop. - * - * **Automatic Type Selection (Structural Discrimination)** - * The component uses the shape of `children` to infer which button variant to render: - * - **Label Button**: If `children` is a single `string` - * - **Icon Button**: If `children` is a single React element (e.g., an icon) - * - **Icon Label Button**: If `children` is a combination of string + icon(s) - * - * **Type Safety via Overloads** - * Each variant supports its own unique set of props. Thanks to TypeScript overloads, - * only the correct props for a given structure are allowed—invalid combinations - * are caught at compile time. - * - * **How to Use** - * - * ```tsx - * // Label Button - * - * // Icon Button - * - * // Icon Label Button - * ``` - * - * This approach ensures: - * - Simplified usage with fewer props - * - No accidental mixing of incompatible props - * - Autocomplete and type-checking experience - */ -export const Button = ButtonComponent as ButtonProps; diff --git a/packages/ui/src/components/button/regular-button/icon-button/icon-button.tsx b/packages/ui/src/components/button/regular-button/icon-button/icon-button.tsx deleted file mode 100644 index 55402c84e..000000000 --- a/packages/ui/src/components/button/regular-button/icon-button/icon-button.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import clsx from 'clsx'; -import { forwardRef } from 'react'; - -import borderRadiusStyles from '../../styles/border-radius.module.css'; -import iconPaddingStyles from '../../styles/icon-padding.module.css'; -import iconSizeStyles from '../../styles/icon-size.module.css'; - -import { BaseButton } from '../../base-button/base-button'; -import { IconNode, Shape } from '../../types'; -import { BaseRegularButtonProps } from '../types'; - -export type IconButtonProps = { - shape?: Shape; - children: IconNode; -} & BaseRegularButtonProps; - -export const IconButton = forwardRef( - ({ size = 'medium', shape = 'default', children, ...props }, ref) => ( - - {children} - - ), -); diff --git a/packages/ui/src/components/button/regular-button/icon-label-button/icon-label-button.tsx b/packages/ui/src/components/button/regular-button/icon-label-button/icon-label-button.tsx deleted file mode 100644 index ec53f6a4e..000000000 --- a/packages/ui/src/components/button/regular-button/icon-label-button/icon-label-button.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import clsx from 'clsx'; -import { forwardRef } from 'react'; - -import fontSizeStyles from '../../styles/font-size.module.css'; -import gapStyles from '../../styles/gap.module.css'; -import paddingStyles from '../../styles/icon-label-button-padding.module.css'; -import iconSizeStyles from '../../styles/icon-size.module.css'; - -import { BaseButton } from '../../base-button/base-button'; -import { IconNode } from '../../types'; -import { BaseRegularButtonProps } from '../types'; - -export type IconLabelButtonProps = { - children: [IconNode, string] | [string, IconNode] | [IconNode, string, IconNode]; -} & BaseRegularButtonProps; - -export const IconLabelButton = forwardRef( - ({ size = 'medium', children, ...props }, ref) => ( - - {children} - - ), -); diff --git a/packages/ui/src/components/button/regular-button/label-button/label-button.tsx b/packages/ui/src/components/button/regular-button/label-button/label-button.tsx deleted file mode 100644 index ab0041b7b..000000000 --- a/packages/ui/src/components/button/regular-button/label-button/label-button.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import clsx from 'clsx'; -import { forwardRef } from 'react'; - -import fontSizeStyles from '../../styles/font-size.module.css'; -import paddingStyles from '../../styles/label-button-padding.module.css'; -import loaderStyles from './loader.module.css'; - -import { BaseButton } from '../../base-button/base-button'; -import { BaseRegularButtonProps } from '../types'; - -export type LabelButtonProps = { - isLoading?: boolean; - children: string; -} & BaseRegularButtonProps; - -export const LabelButton = forwardRef( - ({ size = 'medium', isLoading, children, ...props }, ref) => ( - - {{children}} - {isLoading && } - - ), -); diff --git a/packages/ui/src/components/button/regular-button/label-button/loader.module.css b/packages/ui/src/components/button/regular-button/loader.module.css similarity index 98% rename from packages/ui/src/components/button/regular-button/label-button/loader.module.css rename to packages/ui/src/components/button/regular-button/loader.module.css index f4370f8a3..5daccd9dd 100644 --- a/packages/ui/src/components/button/regular-button/label-button/loader.module.css +++ b/packages/ui/src/components/button/regular-button/loader.module.css @@ -10,7 +10,7 @@ pointer-events: none; } - .hide-label { + .hide-content { opacity: 0; } @@ -51,6 +51,7 @@ 0% { opacity: 1; } + 100% { opacity: 0.2; } diff --git a/packages/ui/src/components/button/regular-button/types.ts b/packages/ui/src/components/button/regular-button/types.ts index c18dc4bdf..84f4c66ad 100644 --- a/packages/ui/src/components/button/regular-button/types.ts +++ b/packages/ui/src/components/button/regular-button/types.ts @@ -1,24 +1,43 @@ -import { SIZES, Size } from '../../../shared/types/size'; -import { rangeBetween } from '../../../shared/utils/arrays'; -import { BaseButtonProps } from '../types'; +import type { ButtonHTMLAttributes, ReactNode } from 'react'; + +import type { TooltipVariant } from '../../tooltip/types'; export const BUTTON_VARIANTS = [ 'primary', 'secondary', - 'gray', - 'error', - 'warning', + 'critical', 'success', - 'ghost-destructive', + 'warning', + 'ghost-primary', + 'ghost-secondary', + 'ghost-critical', + 'ghost-success', + 'ghost-warning', ] as const; -export const BUTTON_SIZES = rangeBetween(SIZES, 'extra-small', 'extra-large'); +export type ButtonVariant = (typeof BUTTON_VARIANTS)[number]; + +export const BUTTON_SIZES = ['xl', 'l', 'm', 's', 'xs'] as const; + +export type ButtonSize = (typeof BUTTON_SIZES)[number]; -export type ButtonSize = Extract; +export const BUTTON_SHAPES = ['default', 'square', 'round'] as const; -export type Variant = (typeof BUTTON_VARIANTS)[number]; +export type ButtonShape = (typeof BUTTON_SHAPES)[number]; -export type BaseRegularButtonProps = BaseButtonProps & { - variant?: Variant; +export type ButtonProps = { + /** @default 'primary' */ + variant?: ButtonVariant; + /** @default 'm' */ size?: ButtonSize; -}; + /** + * Square and round buttons render only `prefixIcon`, or a single element child as a fallback. + * @default 'default' + */ + shape?: ButtonShape; + prefixIcon?: ReactNode; + suffixIcon?: ReactNode; + isLoading?: boolean; + tooltip?: string; + tooltipType?: TooltipVariant; +} & ButtonHTMLAttributes; diff --git a/packages/ui/src/components/button/styles/border-radius.module.css b/packages/ui/src/components/button/styles/border-radius.module.css index acec76951..379f7258d 100644 --- a/packages/ui/src/components/button/styles/border-radius.module.css +++ b/packages/ui/src/components/button/styles/border-radius.module.css @@ -6,6 +6,12 @@ --ax-public-button-border-radius-extra-small: var(--wb-radius-50); --ax-public-button-border-radius-circle: var(--wb-radius-full); + --ax-public-button-border-radius-xl: var(--wb-radius-100); + --ax-public-button-border-radius-l: var(--wb-radius-100); + --ax-public-button-border-radius-m: var(--wb-radius-75); + --ax-public-button-border-radius-s: var(--wb-radius-50); + --ax-public-button-border-radius-xs: var(--wb-radius-50); + --ax-public-button-border-radius-round: var(--wb-radius-full); } @layer ui.component { @@ -34,4 +40,28 @@ .circle { border-radius: var(--ax-public-button-border-radius-circle); } + + .xl { + border-radius: var(--ax-public-button-border-radius-xl); + } + + .l { + border-radius: var(--ax-public-button-border-radius-l); + } + + .m { + border-radius: var(--ax-public-button-border-radius-m); + } + + .s { + border-radius: var(--ax-public-button-border-radius-s); + } + + .xs { + border-radius: var(--ax-public-button-border-radius-xs); + } + + .round { + border-radius: var(--ax-public-button-border-radius-round); + } } diff --git a/packages/ui/src/components/button/styles/font-size.module.css b/packages/ui/src/components/button/styles/font-size.module.css index e65ebb472..54e58bad6 100644 --- a/packages/ui/src/components/button/styles/font-size.module.css +++ b/packages/ui/src/components/button/styles/font-size.module.css @@ -12,4 +12,18 @@ .extra-small { composes: ax-public-button-small from global; } + + .xl { + composes: wb-text-label-xl-emphasized from global; + } + + .l { + composes: wb-text-label-l-emphasized from global; + } + + .m, + .s, + .xs { + composes: wb-text-label-m-emphasized from global; + } } diff --git a/packages/ui/src/components/button/styles/gap.module.css b/packages/ui/src/components/button/styles/gap.module.css index 05b4f5a54..3de98bdf6 100644 --- a/packages/ui/src/components/button/styles/gap.module.css +++ b/packages/ui/src/components/button/styles/gap.module.css @@ -4,6 +4,11 @@ --ax-public-button-gap-medium: var(--wb-space-100); --ax-public-button-gap-small: var(--wb-space-100); --ax-public-button-gap-extra-small: var(--wb-space-100); + --ax-public-button-gap-xl: var(--wb-space-100); + --ax-public-button-gap-l: var(--wb-space-100); + --ax-public-button-gap-m: var(--wb-space-100); + --ax-public-button-gap-s: var(--wb-space-100); + --ax-public-button-gap-xs: var(--wb-space-100); } @layer ui.component { @@ -26,4 +31,24 @@ .extra-small { gap: var(--ax-public-button-gap-extra-small); } + + .xl { + gap: var(--ax-public-button-gap-xl); + } + + .l { + gap: var(--ax-public-button-gap-l); + } + + .m { + gap: var(--ax-public-button-gap-m); + } + + .s { + gap: var(--ax-public-button-gap-s); + } + + .xs { + gap: var(--ax-public-button-gap-xs); + } } diff --git a/packages/ui/src/components/button/styles/height.module.css b/packages/ui/src/components/button/styles/height.module.css new file mode 100644 index 000000000..abbb43307 --- /dev/null +++ b/packages/ui/src/components/button/styles/height.module.css @@ -0,0 +1,29 @@ +:root { + --ax-public-button-height-xl: var(--wb-size-600); + --ax-public-button-height-l: 2.625rem; + --ax-public-button-height-m: 2.25rem; + --ax-public-button-height-s: var(--wb-size-400); + --ax-public-button-height-xs: 1.75rem; +} + +@layer ui.component { + .xl { + height: var(--ax-public-button-height-xl); + } + + .l { + height: var(--ax-public-button-height-l); + } + + .m { + height: var(--ax-public-button-height-m); + } + + .s { + height: var(--ax-public-button-height-s); + } + + .xs { + height: var(--ax-public-button-height-xs); + } +} diff --git a/packages/ui/src/components/button/styles/icon-padding.module.css b/packages/ui/src/components/button/styles/icon-padding.module.css index fd139eafe..8d41c9cbe 100644 --- a/packages/ui/src/components/button/styles/icon-padding.module.css +++ b/packages/ui/src/components/button/styles/icon-padding.module.css @@ -4,6 +4,11 @@ --ax-public-button-icon-padding-medium: var(--wb-space-112); --ax-public-button-icon-padding-small: var(--wb-space-87); --ax-public-button-icon-padding-extra-small: var(--wb-space-75); + --ax-public-button-icon-padding-xl: var(--wb-space-150); + --ax-public-button-icon-padding-l: var(--wb-space-137); + --ax-public-button-icon-padding-m: var(--wb-space-112); + --ax-public-button-icon-padding-s: var(--wb-space-87); + --ax-public-button-icon-padding-xs: var(--wb-space-75); } @layer ui.component { @@ -26,4 +31,24 @@ .extra-small { padding: var(--ax-public-button-icon-padding-extra-small); } + + .xl { + padding: var(--ax-public-button-icon-padding-xl); + } + + .l { + padding: var(--ax-public-button-icon-padding-l); + } + + .m { + padding: var(--ax-public-button-icon-padding-m); + } + + .s { + padding: var(--ax-public-button-icon-padding-s); + } + + .xs { + padding: var(--ax-public-button-icon-padding-xs); + } } diff --git a/packages/ui/src/components/button/styles/icon-size.module.css b/packages/ui/src/components/button/styles/icon-size.module.css index e965778ff..c62e35052 100644 --- a/packages/ui/src/components/button/styles/icon-size.module.css +++ b/packages/ui/src/components/button/styles/icon-size.module.css @@ -4,6 +4,11 @@ --ax-public-icon-size-medium: 1.125rem /* missing token */; --ax-public-icon-size-small: 1.125rem /* missing token */; --ax-public-icon-size-extra-small: 1rem /* missing token */; + --ax-public-icon-size-xl: var(--wb-size-300); + --ax-public-icon-size-l: calc(var(--wb-size-300) - var(--wb-space-50)); + --ax-public-icon-size-m: calc(var(--wb-size-300) - var(--wb-space-75)); + --ax-public-icon-size-s: calc(var(--wb-size-300) - var(--wb-space-75)); + --ax-public-icon-size-xs: var(--wb-size-200); } @layer ui.component { @@ -41,4 +46,41 @@ height: var(--ax-public-icon-size-extra-small); } } + + .icon { + display: flex; + flex: none; + align-items: center; + justify-content: center; + + svg { + width: 100%; + height: 100%; + } + } + + .xl > .icon { + width: var(--ax-public-icon-size-xl); + height: var(--ax-public-icon-size-xl); + } + + .l > .icon { + width: var(--ax-public-icon-size-l); + height: var(--ax-public-icon-size-l); + } + + .m > .icon { + width: var(--ax-public-icon-size-m); + height: var(--ax-public-icon-size-m); + } + + .s > .icon { + width: var(--ax-public-icon-size-s); + height: var(--ax-public-icon-size-s); + } + + .xs > .icon { + width: var(--ax-public-icon-size-xs); + height: var(--ax-public-icon-size-xs); + } } diff --git a/packages/ui/src/components/button/styles/padding.module.css b/packages/ui/src/components/button/styles/padding.module.css new file mode 100644 index 000000000..d9d478802 --- /dev/null +++ b/packages/ui/src/components/button/styles/padding.module.css @@ -0,0 +1,29 @@ +:root { + --ax-public-button-padding-xl: var(--wb-space-200) var(--wb-space-200); + --ax-public-button-padding-l: var(--wb-space-175) var(--wb-space-200); + --ax-public-button-padding-m: var(--wb-space-150) var(--wb-space-150); + --ax-public-button-padding-s: var(--wb-space-125) var(--wb-space-125); + --ax-public-button-padding-xs: var(--wb-space-100) var(--wb-space-100); +} + +@layer ui.component { + .xl { + padding: var(--ax-public-button-padding-xl); + } + + .l { + padding: var(--ax-public-button-padding-l); + } + + .m { + padding: var(--ax-public-button-padding-m); + } + + .s { + padding: var(--ax-public-button-padding-s); + } + + .xs { + padding: var(--ax-public-button-padding-xs); + } +} diff --git a/packages/ui/src/components/button/styles/variant.module.css b/packages/ui/src/components/button/styles/variant.module.css index 067629d4e..f03ffa79f 100644 --- a/packages/ui/src/components/button/styles/variant.module.css +++ b/packages/ui/src/components/button/styles/variant.module.css @@ -1,180 +1,313 @@ :root { - --ax-public-button-border-size: 0.0625rem; + --ax-public-button-border-size: var(--wb-size-12); --ax-public-button-primary-background: var(--wb-components-button-solid-primary-default); --ax-public-button-primary-background-hover: var(--wb-components-button-solid-primary-hover); --ax-public-button-primary-background-active: var(--wb-components-button-solid-primary-active); --ax-public-button-primary-background-focus: var(--wb-components-button-solid-primary-focus); - + --ax-public-button-primary-background-disabled: var(--wb-components-button-solid-disabled); + --ax-public-button-primary-color: var(--wb-ui-text-onaccent-default); + --ax-public-button-primary-color-disabled: var(--wb-ui-text-ghost-default); + + --ax-public-button-secondary-background: var(--wb-components-button-solid-gray-default); + --ax-public-button-secondary-background-hover: var(--wb-components-button-solid-gray-hover); + --ax-public-button-secondary-background-active: var(--wb-components-button-solid-gray-active); + --ax-public-button-secondary-background-focus: var(--wb-components-button-solid-gray-focus); + --ax-public-button-secondary-background-disabled: var(--wb-components-button-solid-disabled); + --ax-public-button-secondary-color: var(--wb-ui-text-onaccent-default); + --ax-public-button-secondary-color-disabled: var(--wb-ui-text-ghost-default); --ax-public-button-secondary-border-color: var(--wb-ui-stroke-subtle); - --ax-public-button-secondary-color: var(--wb-ui-text-default); - --ax-public-button-secondary-border-color-hover: var(--wb-ui-stroke-focus); + --ax-public-button-secondary-border-color-hover: var(--wb-colors-gray-500); --ax-public-button-secondary-color-active: var(--wb-ui-text-subtle-default); - --ax-public-button-secondary-border-color-active: var(--wb-ui-stroke-focus); - --ax-public-button-secondary-border-color-focus: var(--wb-ui-stroke-focus); + --ax-public-button-secondary-border-color-active: var(--wb-colors-gray-500); + --ax-public-button-secondary-border-color-focus: var(--wb-colors-gray-500); --ax-public-button-secondary-border-color-disabled: var(--wb-ui-stroke-subtle); - --ax-public-button-secondary-color-disabled: var(--wb-ui-text-ghost-default); - - --ax-public-button-ghost-destructive-background-color: var(--wb-components-button-ghost-critical-default); - --ax-public-button-ghost-destructive-border-color: var(--wb-components-button-ghost-critical-stroke-default); - --ax-public-button-ghost-destructive-color: var(--wb-ui-text-critical-default); - --ax-public-button-ghost-destructive-background-color-hover: var(--wb-components-button-ghost-critical-hover); - --ax-public-button-ghost-destructive-background-color-active: var(--wb-components-button-ghost-critical-active); - --ax-public-button-ghost-destructive-background-color-focus: var(--wb-components-button-ghost-critical-focus); - --ax-public-button-ghost-destructive-background-color-disabled: var(--wb-components-button-ghost-critical-disabled); - --ax-public-button-ghost-destructive-color-disabled: var(--wb-ui-text-critical-disabled); - - --ax-public-button-gray-background: var(--wb-components-button-solid-gray-default); - --ax-public-button-gray-background-hover: var(--wb-components-button-solid-gray-hover); - --ax-public-button-gray-background-active: var(--wb-components-button-solid-gray-active); - --ax-public-button-gray-background-focus: var(--wb-components-button-solid-gray-focus); - --ax-public-button-error-background: var(--wb-components-button-solid-critical-default); - --ax-public-button-error-background-hover: var(--wb-components-button-solid-critical-hover); - --ax-public-button-error-background-active: var(--wb-components-button-solid-critical-active); - --ax-public-button-error-background-focus: var(--wb-components-button-solid-critical-focus); + --ax-public-button-critical-background: var(--wb-components-button-solid-critical-default); + --ax-public-button-critical-background-hover: var(--wb-components-button-solid-critical-hover); + --ax-public-button-critical-background-active: var(--wb-components-button-solid-critical-active); + --ax-public-button-critical-background-focus: var(--wb-components-button-solid-critical-focus); + --ax-public-button-critical-background-disabled: var(--wb-components-button-solid-disabled); + --ax-public-button-critical-color: var(--wb-ui-text-onaccent-default); + --ax-public-button-critical-color-disabled: var(--wb-ui-text-ghost-default); --ax-public-button-success-background: var(--wb-components-button-solid-success-default); --ax-public-button-success-background-hover: var(--wb-components-button-solid-success-hover); --ax-public-button-success-background-active: var(--wb-components-button-solid-success-active); --ax-public-button-success-background-focus: var(--wb-components-button-solid-success-focus); + --ax-public-button-success-background-disabled: var(--wb-components-button-solid-disabled); + --ax-public-button-success-color: var(--wb-ui-text-onaccent-default); + --ax-public-button-success-color-disabled: var(--wb-ui-text-ghost-default); --ax-public-button-warning-background: var(--wb-components-button-solid-warning-default); --ax-public-button-warning-background-hover: var(--wb-components-button-solid-warning-hover); --ax-public-button-warning-background-active: var(--wb-components-button-solid-warning-active); --ax-public-button-warning-background-focus: var(--wb-components-button-solid-warning-focus); + --ax-public-button-warning-background-disabled: var(--wb-components-button-solid-disabled); + --ax-public-button-warning-color: var(--wb-ui-text-onaccent-default); + --ax-public-button-warning-color-disabled: var(--wb-ui-text-ghost-default); + + --ax-public-button-ghost-primary-background-color: var(--wb-components-button-ghost-primary-default); + --ax-public-button-ghost-primary-background-color-hover: var(--wb-components-button-ghost-primary-hover); + --ax-public-button-ghost-primary-background-color-active: var(--wb-components-button-ghost-primary-active); + --ax-public-button-ghost-primary-background-color-focus: var(--wb-components-button-ghost-primary-focus); + --ax-public-button-ghost-primary-background-color-disabled: var(--wb-components-button-ghost-primary-disabled); + --ax-public-button-ghost-primary-border-color: var(--wb-components-button-ghost-primary-stroke-default); + --ax-public-button-ghost-primary-color: var(--wb-ui-text-action-default); + --ax-public-button-ghost-primary-color-disabled: var(--wb-ui-text-action-disabled); + + --ax-public-button-ghost-secondary-background-color: var(--wb-colors-transparent); + --ax-public-button-ghost-secondary-background-color-hover: var(--wb-colors-transparent); + --ax-public-button-ghost-secondary-background-color-active: var(--wb-colors-transparent); + --ax-public-button-ghost-secondary-background-color-focus: var(--wb-colors-transparent); + --ax-public-button-ghost-secondary-background-color-disabled: var(--wb-colors-transparent); + --ax-public-button-ghost-secondary-border-color: var(--wb-ui-stroke-subtle); + --ax-public-button-ghost-secondary-border-color-hover: var(--wb-colors-gray-500); + --ax-public-button-ghost-secondary-border-color-active: var(--wb-colors-gray-500); + --ax-public-button-ghost-secondary-border-color-focus: var(--wb-colors-gray-500); + --ax-public-button-ghost-secondary-border-color-disabled: var(--wb-ui-stroke-subtle); + --ax-public-button-ghost-secondary-color: var(--wb-ui-text-default); + --ax-public-button-ghost-secondary-color-disabled: var(--wb-ui-text-ghost-default); + + --ax-public-button-ghost-critical-background-color: var(--wb-components-button-ghost-critical-default); + --ax-public-button-ghost-critical-background-color-hover: var(--wb-components-button-ghost-critical-hover); + --ax-public-button-ghost-critical-background-color-active: var(--wb-components-button-ghost-critical-active); + --ax-public-button-ghost-critical-background-color-focus: var(--wb-components-button-ghost-critical-focus); + --ax-public-button-ghost-critical-background-color-disabled: var(--wb-components-button-ghost-critical-disabled); + --ax-public-button-ghost-critical-border-color: var(--wb-components-button-ghost-critical-stroke-default); + --ax-public-button-ghost-critical-color: var(--wb-ui-text-critical-default); + --ax-public-button-ghost-critical-color-disabled: var(--wb-ui-text-critical-disabled); + + --ax-public-button-ghost-success-background-color: var(--wb-components-button-ghost-success-default); + --ax-public-button-ghost-success-background-color-hover: var(--wb-components-button-ghost-success-hover); + --ax-public-button-ghost-success-background-color-active: var(--wb-components-button-ghost-success-active); + --ax-public-button-ghost-success-background-color-focus: var(--wb-components-button-ghost-success-focus); + --ax-public-button-ghost-success-background-color-disabled: var(--wb-components-button-ghost-success-disabled); + --ax-public-button-ghost-success-border-color: var(--wb-components-button-ghost-success-stroke-default); + --ax-public-button-ghost-success-color: var(--wb-ui-text-success-default); + --ax-public-button-ghost-success-color-disabled: var(--wb-ui-text-success-disabled); + + --ax-public-button-ghost-warning-background-color: var(--wb-components-button-ghost-warning-default); + --ax-public-button-ghost-warning-background-color-hover: var(--wb-components-button-ghost-warning-hover); + --ax-public-button-ghost-warning-background-color-active: var(--wb-components-button-ghost-warning-active); + --ax-public-button-ghost-warning-background-color-focus: var(--wb-components-button-ghost-warning-focus); + --ax-public-button-ghost-warning-background-color-disabled: var(--wb-components-button-ghost-warning-disabled); + --ax-public-button-ghost-warning-border-color: var(--wb-components-button-ghost-warning-stroke-default); + --ax-public-button-ghost-warning-color: var(--wb-ui-text-warning-default); + --ax-public-button-ghost-warning-color-disabled: var(--wb-ui-text-warning-disabled); + + --ax-public-button-gray-background: var(--wb-components-button-solid-gray-default); + --ax-public-button-gray-background-hover: var(--wb-components-button-solid-gray-hover); + --ax-public-button-gray-background-active: var(--wb-components-button-solid-gray-active); + --ax-public-button-gray-background-focus: var(--wb-components-button-solid-gray-focus); + --ax-public-button-error-background: var(--wb-components-button-solid-critical-default); + --ax-public-button-error-background-hover: var(--wb-components-button-solid-critical-hover); + --ax-public-button-error-background-active: var(--wb-components-button-solid-critical-active); + --ax-public-button-error-background-focus: var(--wb-components-button-solid-critical-focus); + --ax-public-button-ghost-destructive-background-color: var(--wb-components-button-ghost-critical-default); + --ax-public-button-ghost-destructive-background-color-hover: var(--wb-components-button-ghost-critical-hover); + --ax-public-button-ghost-destructive-background-color-active: var(--wb-components-button-ghost-critical-active); + --ax-public-button-ghost-destructive-background-color-focus: var(--wb-components-button-ghost-critical-focus); + --ax-public-button-ghost-destructive-background-color-disabled: var(--wb-components-button-ghost-critical-disabled); + --ax-public-button-ghost-destructive-border-color: var(--wb-components-button-ghost-critical-stroke-default); + --ax-public-button-ghost-destructive-color: var(--wb-ui-text-critical-default); + --ax-public-button-ghost-destructive-color-disabled: var(--wb-ui-text-critical-disabled); } @layer ui.component { - button { - &:not(:disabled) { - &.primary { - background-color: var(--ax-public-button-primary-background); - - &:hover { - background-color: var(--ax-public-button-primary-background-hover); - } - - &:active { - background-color: var(--ax-public-button-primary-background-active); - } - - &:focus-visible { - background-color: var(--ax-public-button-primary-background-focus); - } - } - - &.secondary { - border: var(--ax-public-button-border-size) solid var(--ax-public-button-secondary-border-color); - background-color: transparent; - color: var(--ax-public-button-secondary-color); - - &:hover { - border-color: var(--ax-public-button-secondary-border-color-hover); - } - - &:active { - border-color: var(--ax-public-button-secondary-border-color-active); - color: var(--ax-public-button-secondary-color-active); - } - - &:focus-visible { - border-color: var(--ax-public-button-secondary-border-color-focus); - } - - &:disabled { - border-color: var(--ax-public-button-secondary-border-color-disabled); - color: var(--ax-public-button-secondary-color-disabled); - } - } - - &.ghost-destructive { - border: var(--ax-public-button-border-size) solid var(--ax-public-button-ghost-destructive-border-color); - background-color: var(--ax-public-button-ghost-destructive-background-color); - color: var(--ax-public-button-ghost-destructive-color); - - &:hover { - background-color: var(--ax-public-button-ghost-destructive-background-color-hover); - } - - &:active { - background-color: var(--ax-public-button-ghost-destructive-background-color-active); - } - - &:focus-visible { - background-color: var(--ax-public-button-ghost-destructive-background-color-focus); - border-width: 2px; - } - - &:disabled { - background-color: var(--ax-public-button-ghost-destructive-background-color-disabled); - color: var(--ax-public-button-ghost-destructive-color-disabled); - } - } - - &.gray { - background-color: var(--ax-public-button-gray-background); - - &:hover { - background-color: var(--ax-public-button-gray-background-hover); - } - - &:active { - background-color: var(--ax-public-button-gray-background-active); - } - - &:focus-visible { - background-color: var(--ax-public-button-gray-background-focus); - } - } - - &.error { - background-color: var(--ax-public-button-error-background); - - &:hover { - background-color: var(--ax-public-button-error-background-hover); - } - - &:active { - background-color: var(--ax-public-button-error-background-active); - } - - &:focus-visible { - background-color: var(--ax-public-button-error-background-focus); - } - } - - &.success { - background-color: var(--ax-public-button-success-background); - - &:hover { - background-color: var(--ax-public-button-success-background-hover); - } - - &:active { - background-color: var(--ax-public-button-success-background-active); - } - - &:focus-visible { - background-color: var(--ax-public-button-success-background-focus); - } - } - - &.warning { - background-color: var(--ax-public-button-warning-background); - - &:hover { - background-color: var(--ax-public-button-warning-background-hover); - } - - &:active { - background-color: var(--ax-public-button-warning-background-active); - } - - &:focus-visible { - background-color: var(--ax-public-button-warning-background-focus); - } - } + .primary { + --button-background: var(--ax-public-button-primary-background); + --button-background-hover: var(--ax-public-button-primary-background-hover); + --button-background-active: var(--ax-public-button-primary-background-active); + --button-background-focus: var(--ax-public-button-primary-background-focus); + --button-background-disabled: var(--ax-public-button-primary-background-disabled); + --button-color: var(--ax-public-button-primary-color); + --button-color-disabled: var(--ax-public-button-primary-color-disabled); + } + + .secondary { + --button-background: var(--ax-public-button-secondary-background); + --button-background-hover: var(--ax-public-button-secondary-background-hover); + --button-background-active: var(--ax-public-button-secondary-background-active); + --button-background-focus: var(--ax-public-button-secondary-background-focus); + --button-background-disabled: var(--ax-public-button-secondary-background-disabled); + --button-color: var(--ax-public-button-secondary-color); + --button-color-disabled: var(--ax-public-button-secondary-color-disabled); + } + + .critical { + --button-background: var(--ax-public-button-critical-background); + --button-background-hover: var(--ax-public-button-critical-background-hover); + --button-background-active: var(--ax-public-button-critical-background-active); + --button-background-focus: var(--ax-public-button-critical-background-focus); + --button-background-disabled: var(--ax-public-button-critical-background-disabled); + --button-color: var(--ax-public-button-critical-color); + --button-color-disabled: var(--ax-public-button-critical-color-disabled); + } + + .success { + --button-background: var(--ax-public-button-success-background); + --button-background-hover: var(--ax-public-button-success-background-hover); + --button-background-active: var(--ax-public-button-success-background-active); + --button-background-focus: var(--ax-public-button-success-background-focus); + --button-background-disabled: var(--ax-public-button-success-background-disabled); + --button-color: var(--ax-public-button-success-color); + --button-color-disabled: var(--ax-public-button-success-color-disabled); + } + + .warning { + --button-background: var(--ax-public-button-warning-background); + --button-background-hover: var(--ax-public-button-warning-background-hover); + --button-background-active: var(--ax-public-button-warning-background-active); + --button-background-focus: var(--ax-public-button-warning-background-focus); + --button-background-disabled: var(--ax-public-button-warning-background-disabled); + --button-color: var(--ax-public-button-warning-color); + --button-color-disabled: var(--ax-public-button-warning-color-disabled); + } + + .primary, + .secondary, + .critical, + .success, + .warning { + background-color: var(--button-background); + color: var(--button-color); + + &:not(:disabled):hover { + background-color: var(--button-background-hover); + } + + &:not(:disabled):active { + background-color: var(--button-background-active); + } + + &:not(:disabled):focus-visible { + background-color: var(--button-background-focus); + } + + &:disabled { + background-color: var(--button-background-disabled); + color: var(--button-color-disabled); + } + } + + .ghost-primary { + --button-background: var(--ax-public-button-ghost-primary-background-color); + --button-background-hover: var(--ax-public-button-ghost-primary-background-color-hover); + --button-background-active: var(--ax-public-button-ghost-primary-background-color-active); + --button-background-focus: var(--ax-public-button-ghost-primary-background-color-focus); + --button-background-disabled: var(--ax-public-button-ghost-primary-background-color-disabled); + --button-border-color: var(--ax-public-button-ghost-primary-border-color); + --button-border-color-hover: var(--ax-public-button-ghost-primary-border-color); + --button-border-color-active: var(--ax-public-button-ghost-primary-border-color); + --button-border-color-focus: var(--ax-public-button-ghost-primary-border-color); + --button-border-color-disabled: var(--ax-public-button-ghost-primary-border-color); + --button-color: var(--ax-public-button-ghost-primary-color); + --button-color-disabled: var(--ax-public-button-ghost-primary-color-disabled); + } + + .ghost-secondary { + --button-background: var(--ax-public-button-ghost-secondary-background-color); + --button-background-hover: var(--ax-public-button-ghost-secondary-background-color-hover); + --button-background-active: var(--ax-public-button-ghost-secondary-background-color-active); + --button-background-focus: var(--ax-public-button-ghost-secondary-background-color-focus); + --button-background-disabled: var(--ax-public-button-ghost-secondary-background-color-disabled); + --button-border-color: var(--ax-public-button-ghost-secondary-border-color); + --button-border-color-hover: var(--ax-public-button-ghost-secondary-border-color-hover); + --button-border-color-active: var(--ax-public-button-ghost-secondary-border-color-active); + --button-border-color-focus: var(--ax-public-button-ghost-secondary-border-color-focus); + --button-border-color-disabled: var(--ax-public-button-ghost-secondary-border-color-disabled); + --button-color: var(--ax-public-button-ghost-secondary-color); + --button-color-disabled: var(--ax-public-button-ghost-secondary-color-disabled); + } + + .ghost-critical { + --button-background: var(--ax-public-button-ghost-critical-background-color); + --button-background-hover: var(--ax-public-button-ghost-critical-background-color-hover); + --button-background-active: var(--ax-public-button-ghost-critical-background-color-active); + --button-background-focus: var(--ax-public-button-ghost-critical-background-color-focus); + --button-background-disabled: var(--ax-public-button-ghost-critical-background-color-disabled); + --button-border-color: var(--ax-public-button-ghost-critical-border-color); + --button-border-color-hover: var(--ax-public-button-ghost-critical-border-color); + --button-border-color-active: var(--ax-public-button-ghost-critical-border-color); + --button-border-color-focus: var(--ax-public-button-ghost-critical-border-color); + --button-border-color-disabled: var(--ax-public-button-ghost-critical-border-color); + --button-color: var(--ax-public-button-ghost-critical-color); + --button-color-disabled: var(--ax-public-button-ghost-critical-color-disabled); + } + + .ghost-success { + --button-background: var(--ax-public-button-ghost-success-background-color); + --button-background-hover: var(--ax-public-button-ghost-success-background-color-hover); + --button-background-active: var(--ax-public-button-ghost-success-background-color-active); + --button-background-focus: var(--ax-public-button-ghost-success-background-color-focus); + --button-background-disabled: var(--ax-public-button-ghost-success-background-color-disabled); + --button-border-color: var(--ax-public-button-ghost-success-border-color); + --button-border-color-hover: var(--ax-public-button-ghost-success-border-color); + --button-border-color-active: var(--ax-public-button-ghost-success-border-color); + --button-border-color-focus: var(--ax-public-button-ghost-success-border-color); + --button-border-color-disabled: var(--ax-public-button-ghost-success-border-color); + --button-color: var(--ax-public-button-ghost-success-color); + --button-color-disabled: var(--ax-public-button-ghost-success-color-disabled); + } + + .ghost-warning { + --button-background: var(--ax-public-button-ghost-warning-background-color); + --button-background-hover: var(--ax-public-button-ghost-warning-background-color-hover); + --button-background-active: var(--ax-public-button-ghost-warning-background-color-active); + --button-background-focus: var(--ax-public-button-ghost-warning-background-color-focus); + --button-background-disabled: var(--ax-public-button-ghost-warning-background-color-disabled); + --button-border-color: var(--ax-public-button-ghost-warning-border-color); + --button-border-color-hover: var(--ax-public-button-ghost-warning-border-color); + --button-border-color-active: var(--ax-public-button-ghost-warning-border-color); + --button-border-color-focus: var(--ax-public-button-ghost-warning-border-color); + --button-border-color-disabled: var(--ax-public-button-ghost-warning-border-color); + --button-color: var(--ax-public-button-ghost-warning-color); + --button-color-disabled: var(--ax-public-button-ghost-warning-color-disabled); + } + + .ghost-primary, + .ghost-secondary, + .ghost-critical, + .ghost-success, + .ghost-warning { + --button-border-color-current: var(--button-border-color); + + background-color: var(--button-background); + color: var(--button-color); + + &::after { + content: ''; + position: absolute; + inset: 0; + border: var(--ax-public-button-border-size) solid var(--button-border-color-current); + border-radius: inherit; + pointer-events: none; + } + + &:not(:disabled):hover { + --button-border-color-current: var(--button-border-color-hover); + + background-color: var(--button-background-hover); + } + + &:not(:disabled):active { + --button-border-color-current: var(--button-border-color-active); + + background-color: var(--button-background-active); + } + + &:not(:disabled):focus-visible { + --button-border-color-current: var(--button-border-color-focus); + + background-color: var(--button-background-focus); + } + + &:disabled { + --button-border-color-current: var(--button-border-color-disabled); + + background-color: var(--button-background-disabled); + color: var(--button-color-disabled); } } } diff --git a/packages/ui/src/components/snackbar/components/action-buttons.tsx b/packages/ui/src/components/snackbar/components/action-buttons.tsx index ef4d02067..32dc2f8fd 100644 --- a/packages/ui/src/components/snackbar/components/action-buttons.tsx +++ b/packages/ui/src/components/snackbar/components/action-buttons.tsx @@ -1,6 +1,6 @@ import { X } from '@phosphor-icons/react'; import { NavButton } from '@ui/components/button/nav-button/nav-button'; -import { Variant } from '@ui/components/button/regular-button/types'; +import { ButtonVariant } from '@ui/components/button/regular-button/types'; import styles from './action-buttons.module.css'; @@ -16,9 +16,9 @@ type ActionButtonsProps = { }; export function ActionButtons({ variant, buttonLabel, onButtonClick, close, onClose }: ActionButtonsProps) { - const buttonTypeMap: Record = { + const buttonTypeMap: Record = { [SnackbarType.DEFAULT]: 'primary', - [SnackbarType.ERROR]: 'error', + [SnackbarType.ERROR]: 'critical', [SnackbarType.INFO]: 'primary', [SnackbarType.WARNING]: 'warning', [SnackbarType.SUCCESS]: 'success', From 16adecdc9d955d4e22408cb03fe7e2eb51e74260 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Fri, 21 Aug 2026 13:47:52 +0200 Subject: [PATCH 2/3] fix(ui)!: make the disabled and loading states legible, tighten Button props Disabled solid buttons paired the disabled surface with the ghost text colour, which resolves to the same value in the dark theme - the label and icons disappeared. They take the disabled text token the design specifies, and each ghost variant takes its own accent counterpart. The loading dots inherit the current colour instead of the on-accent one, so they stay visible on light ghost buttons. Props are a union: a label button with optional icons, or a square or round button that requires the icon it renders. Combinations that rendered an empty box no longer compile, and the element-child fallback is gone, so nothing inspects children's shape any more. Two SDK wrappers and one call site that erased the discrimination now name the label form explicitly. Loading also reaches assistive technology and blocks activation, not just pointers. The placeholder draws one dashed outline again, the retired variant variables are deleted, and the docs table lists every prop the component takes. --- .changeset/button-redesign.md | 4 +- apps/docs/scripts/ui-components.mjs | 7 +- .../button-submit/button-submit.tsx | 4 +- .../placeholder-button.module.css | 2 +- .../placeholder-button/placeholder-button.tsx | 4 +- .../ai-tools-control/ai-tools-control.tsx | 6 +- .../button/regular-button/button.tsx | 111 ++++++++++-------- .../button/regular-button/loader.module.css | 2 +- .../components/button/regular-button/types.ts | 28 +++-- .../button/styles/variant.module.css | 33 +----- 10 files changed, 98 insertions(+), 103 deletions(-) diff --git a/.changeset/button-redesign.md b/.changeset/button-redesign.md index 62307a491..bf97ce19e 100644 --- a/.changeset/button-redesign.md +++ b/.changeset/button-redesign.md @@ -1,6 +1,6 @@ --- '@workflowbuilder/ui': major -'@workflowbuilder/sdk': minor +'@workflowbuilder/sdk': major --- -The Button API now uses variant, letter-based size, shape, prefixIcon, suffixIcon, and children props; children-structure inference has been removed. Variants rename gray to secondary, error to critical, and ghost-destructive to ghost-critical, while the former outlined secondary becomes ghost-secondary and secondary now identifies the solid gray treatment. Sizes now use xl, l, m, s, and xs. +`Button` takes `variant`, `size` and `shape` and composes its content from `prefixIcon`, `children` and `suffixIcon`; the label/icon/icon+label subtypes and their prop types are gone. Migrate variants (the outlined treatment is `ghost-secondary`, `secondary` is the solid grey former `gray`, `error` is `critical`, `ghost-destructive` is `ghost-critical`), sizes (`extra-large`…`extra-small` become `xl`…`xs`), and `shape="circle"` (now `"round"`). diff --git a/apps/docs/scripts/ui-components.mjs b/apps/docs/scripts/ui-components.mjs index 8ae78379f..8abe83075 100644 --- a/apps/docs/scripts/ui-components.mjs +++ b/apps/docs/scripts/ui-components.mjs @@ -12,12 +12,7 @@ export const COMPONENTS = [ { slug: 'accordion', name: 'Accordion', propsType: 'AccordionProps', dir: 'accordion' }, { slug: 'avatar', name: 'Avatar', propsType: 'AvatarProps', dir: 'avatar' }, - { - slug: 'button', - name: 'Button', - propsType: 'ButtonProps', - dir: 'button', - }, + { slug: 'button', name: 'Button', propsType: ['LabelButtonProps', 'IconButtonProps'], dir: 'button' }, { slug: 'checkbox', name: 'Checkbox', propsType: 'CheckboxProps', dir: 'checkbox' }, { slug: 'chips', name: 'Chip', propsType: 'ChipProps', dir: 'chips' }, { slug: 'collapsible', name: 'Collapsible', propsType: 'CollapsibleProps', dir: 'collapsible' }, diff --git a/packages/sdk/src/components/button-submit/button-submit.tsx b/packages/sdk/src/components/button-submit/button-submit.tsx index 35a347f44..74d5d46ba 100644 --- a/packages/sdk/src/components/button-submit/button-submit.tsx +++ b/packages/sdk/src/components/button-submit/button-submit.tsx @@ -1,10 +1,10 @@ import { Spinner } from '@phosphor-icons/react'; -import { Button } from '@workflowbuilder/ui'; +import { Button, type LabelButtonProps } from '@workflowbuilder/ui'; import clsx from 'clsx'; import styles from './button-submit.module.css'; -type Props = Omit, 'children'> & { +type Props = Omit & { classNameWrapper?: string; isPending: boolean; children: React.ReactNode; diff --git a/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.module.css b/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.module.css index 44e0e4e7e..f8929ccf6 100644 --- a/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.module.css +++ b/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.module.css @@ -1,3 +1,3 @@ -.placeholder-button { +.placeholder-button::after { border-style: dashed; } diff --git a/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx b/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx index 1871f637a..e1fba0edb 100644 --- a/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx +++ b/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx @@ -1,11 +1,11 @@ import { PlusCircle } from '@phosphor-icons/react'; -import { Button } from '@workflowbuilder/ui'; +import { Button, type LabelButtonProps } from '@workflowbuilder/ui'; import styles from './placeholder-button.module.css'; type Props = { label: string; -} & Omit, 'children'>; +} & Omit; export function PlaceholderButton({ label, size = 'xs', ...props }: Props) { return ( diff --git a/packages/sdk/src/features/json-form/controls/ai-tools-control/ai-tools-control.tsx b/packages/sdk/src/features/json-form/controls/ai-tools-control/ai-tools-control.tsx index a1f176b61..d2c4ef34c 100644 --- a/packages/sdk/src/features/json-form/controls/ai-tools-control/ai-tools-control.tsx +++ b/packages/sdk/src/features/json-form/controls/ai-tools-control/ai-tools-control.tsx @@ -1,6 +1,6 @@ import { PlusCircle, Trash } from '@phosphor-icons/react'; import { Button, NavButton } from '@workflowbuilder/ui'; -import { type ComponentProps, useCallback } from 'react'; +import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { Icon } from '@workflow-builder/icons'; @@ -57,8 +57,8 @@ function AiToolsControl({ path, handleChange, data, enabled, uischema }: AiTools const icon = toolOption?.icon; const label = toolOption?.label; - const sharedButtonProps: Partial> = { - variant: 'ghost-secondary', + const sharedButtonProps = { + variant: 'ghost-secondary' as const, className: styles['selected-tool-button'], onClick: () => openEditorModal(toolData), disabled: isDisabled, diff --git a/packages/ui/src/components/button/regular-button/button.tsx b/packages/ui/src/components/button/regular-button/button.tsx index 9b5df1e52..402685037 100644 --- a/packages/ui/src/components/button/regular-button/button.tsx +++ b/packages/ui/src/components/button/regular-button/button.tsx @@ -1,5 +1,6 @@ import clsx from 'clsx'; -import { forwardRef, isValidElement } from 'react'; +import { forwardRef } from 'react'; +import type { MouseEvent } from 'react'; import borderRadiusStyles from '../styles/border-radius.module.css'; import fontSizeStyles from '../styles/font-size.module.css'; @@ -14,54 +15,62 @@ import loaderStyles from './loader.module.css'; import { BaseButton } from '../base-button/base-button'; import type { ButtonProps } from './types'; -export const Button = forwardRef( - ( - { - children, - className, - isLoading = false, - prefixIcon, - shape = 'default', - size = 'm', - suffixIcon, - variant = 'primary', - ...props - }, - ref, - ) => { - const isIconOnly = shape !== 'default'; - const icon = prefixIcon ?? (isValidElement(children) ? children : null); - const hiddenContentClassName = clsx({ [loaderStyles['hide-content']]: isLoading }); +export const Button = forwardRef((props, ref) => { + const { + children, + className, + isLoading = false, + onClick, + prefixIcon, + shape = 'default', + size = 'm', + suffixIcon, + variant = 'primary', + ...rest + } = props; - return ( - - {isIconOnly ? ( - {icon} - ) : ( - <> - {prefixIcon != null && ( - {prefixIcon} - )} - {children} - {suffixIcon != null && ( - {suffixIcon} - )} - - )} - {isLoading && } - - ); - }, -); + const isIconOnly = shape !== 'default'; + const hiddenContentClassName = clsx({ [loaderStyles['hide-content']]: isLoading }); + + function handleClick(event: MouseEvent) { + if (isLoading) { + event.preventDefault(); + return; + } + onClick?.(event); + } + + return ( + + {isIconOnly ? ( + {prefixIcon} + ) : ( + <> + {prefixIcon != null && ( + {prefixIcon} + )} + {children} + {suffixIcon != null && ( + {suffixIcon} + )} + + )} + {isLoading && } + + ); +}); diff --git a/packages/ui/src/components/button/regular-button/loader.module.css b/packages/ui/src/components/button/regular-button/loader.module.css index 5daccd9dd..f2aa8ae00 100644 --- a/packages/ui/src/components/button/regular-button/loader.module.css +++ b/packages/ui/src/components/button/regular-button/loader.module.css @@ -1,7 +1,7 @@ :root { --ax-public-button-loader-dot-size: 0.3125rem; --ax-public-button-loader-dot-border-radius: 0.3125rem; - --ax-public-button-loader-dot-color: var(--wb-ui-text-onaccent-default); + --ax-public-button-loader-dot-color: currentColor; --ax-public-button-loader-animation-duration: 0.75s; } diff --git a/packages/ui/src/components/button/regular-button/types.ts b/packages/ui/src/components/button/regular-button/types.ts index 84f4c66ad..31bb89162 100644 --- a/packages/ui/src/components/button/regular-button/types.ts +++ b/packages/ui/src/components/button/regular-button/types.ts @@ -25,19 +25,33 @@ export const BUTTON_SHAPES = ['default', 'square', 'round'] as const; export type ButtonShape = (typeof BUTTON_SHAPES)[number]; -export type ButtonProps = { +export type LabelButtonProps = { /** @default 'primary' */ variant?: ButtonVariant; /** @default 'm' */ size?: ButtonSize; - /** - * Square and round buttons render only `prefixIcon`, or a single element child as a fallback. - * @default 'default' - */ - shape?: ButtonShape; + /** @default 'default' */ + shape?: 'default'; + children: ReactNode; prefixIcon?: ReactNode; suffixIcon?: ReactNode; isLoading?: boolean; tooltip?: string; tooltipType?: TooltipVariant; -} & ButtonHTMLAttributes; +} & Omit, 'children'>; + +export type IconButtonProps = { + /** @default 'primary' */ + variant?: ButtonVariant; + /** @default 'm' */ + size?: ButtonSize; + shape: 'square' | 'round'; + prefixIcon: ReactNode; + children?: never; + suffixIcon?: never; + isLoading?: boolean; + tooltip?: string; + tooltipType?: TooltipVariant; +} & Omit, 'children'>; + +export type ButtonProps = LabelButtonProps | IconButtonProps; diff --git a/packages/ui/src/components/button/styles/variant.module.css b/packages/ui/src/components/button/styles/variant.module.css index f03ffa79f..3b3c6ac50 100644 --- a/packages/ui/src/components/button/styles/variant.module.css +++ b/packages/ui/src/components/button/styles/variant.module.css @@ -7,7 +7,7 @@ --ax-public-button-primary-background-focus: var(--wb-components-button-solid-primary-focus); --ax-public-button-primary-background-disabled: var(--wb-components-button-solid-disabled); --ax-public-button-primary-color: var(--wb-ui-text-onaccent-default); - --ax-public-button-primary-color-disabled: var(--wb-ui-text-ghost-default); + --ax-public-button-primary-color-disabled: var(--wb-ui-text-disabled); --ax-public-button-secondary-background: var(--wb-components-button-solid-gray-default); --ax-public-button-secondary-background-hover: var(--wb-components-button-solid-gray-hover); @@ -15,13 +15,7 @@ --ax-public-button-secondary-background-focus: var(--wb-components-button-solid-gray-focus); --ax-public-button-secondary-background-disabled: var(--wb-components-button-solid-disabled); --ax-public-button-secondary-color: var(--wb-ui-text-onaccent-default); - --ax-public-button-secondary-color-disabled: var(--wb-ui-text-ghost-default); - --ax-public-button-secondary-border-color: var(--wb-ui-stroke-subtle); - --ax-public-button-secondary-border-color-hover: var(--wb-colors-gray-500); - --ax-public-button-secondary-color-active: var(--wb-ui-text-subtle-default); - --ax-public-button-secondary-border-color-active: var(--wb-colors-gray-500); - --ax-public-button-secondary-border-color-focus: var(--wb-colors-gray-500); - --ax-public-button-secondary-border-color-disabled: var(--wb-ui-stroke-subtle); + --ax-public-button-secondary-color-disabled: var(--wb-ui-text-disabled); --ax-public-button-critical-background: var(--wb-components-button-solid-critical-default); --ax-public-button-critical-background-hover: var(--wb-components-button-solid-critical-hover); @@ -29,7 +23,7 @@ --ax-public-button-critical-background-focus: var(--wb-components-button-solid-critical-focus); --ax-public-button-critical-background-disabled: var(--wb-components-button-solid-disabled); --ax-public-button-critical-color: var(--wb-ui-text-onaccent-default); - --ax-public-button-critical-color-disabled: var(--wb-ui-text-ghost-default); + --ax-public-button-critical-color-disabled: var(--wb-ui-text-disabled); --ax-public-button-success-background: var(--wb-components-button-solid-success-default); --ax-public-button-success-background-hover: var(--wb-components-button-solid-success-hover); @@ -37,7 +31,7 @@ --ax-public-button-success-background-focus: var(--wb-components-button-solid-success-focus); --ax-public-button-success-background-disabled: var(--wb-components-button-solid-disabled); --ax-public-button-success-color: var(--wb-ui-text-onaccent-default); - --ax-public-button-success-color-disabled: var(--wb-ui-text-ghost-default); + --ax-public-button-success-color-disabled: var(--wb-ui-text-disabled); --ax-public-button-warning-background: var(--wb-components-button-solid-warning-default); --ax-public-button-warning-background-hover: var(--wb-components-button-solid-warning-hover); @@ -45,7 +39,7 @@ --ax-public-button-warning-background-focus: var(--wb-components-button-solid-warning-focus); --ax-public-button-warning-background-disabled: var(--wb-components-button-solid-disabled); --ax-public-button-warning-color: var(--wb-ui-text-onaccent-default); - --ax-public-button-warning-color-disabled: var(--wb-ui-text-ghost-default); + --ax-public-button-warning-color-disabled: var(--wb-ui-text-disabled); --ax-public-button-ghost-primary-background-color: var(--wb-components-button-ghost-primary-default); --ax-public-button-ghost-primary-background-color-hover: var(--wb-components-button-ghost-primary-hover); @@ -95,23 +89,6 @@ --ax-public-button-ghost-warning-border-color: var(--wb-components-button-ghost-warning-stroke-default); --ax-public-button-ghost-warning-color: var(--wb-ui-text-warning-default); --ax-public-button-ghost-warning-color-disabled: var(--wb-ui-text-warning-disabled); - - --ax-public-button-gray-background: var(--wb-components-button-solid-gray-default); - --ax-public-button-gray-background-hover: var(--wb-components-button-solid-gray-hover); - --ax-public-button-gray-background-active: var(--wb-components-button-solid-gray-active); - --ax-public-button-gray-background-focus: var(--wb-components-button-solid-gray-focus); - --ax-public-button-error-background: var(--wb-components-button-solid-critical-default); - --ax-public-button-error-background-hover: var(--wb-components-button-solid-critical-hover); - --ax-public-button-error-background-active: var(--wb-components-button-solid-critical-active); - --ax-public-button-error-background-focus: var(--wb-components-button-solid-critical-focus); - --ax-public-button-ghost-destructive-background-color: var(--wb-components-button-ghost-critical-default); - --ax-public-button-ghost-destructive-background-color-hover: var(--wb-components-button-ghost-critical-hover); - --ax-public-button-ghost-destructive-background-color-active: var(--wb-components-button-ghost-critical-active); - --ax-public-button-ghost-destructive-background-color-focus: var(--wb-components-button-ghost-critical-focus); - --ax-public-button-ghost-destructive-background-color-disabled: var(--wb-components-button-ghost-critical-disabled); - --ax-public-button-ghost-destructive-border-color: var(--wb-components-button-ghost-critical-stroke-default); - --ax-public-button-ghost-destructive-color: var(--wb-ui-text-critical-default); - --ax-public-button-ghost-destructive-color-disabled: var(--wb-ui-text-critical-disabled); } @layer ui.component { From 0830f3797ab0c71894f52c2579f28cb93a2742fa Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Mon, 24 Aug 2026 08:45:54 +0200 Subject: [PATCH 3/3] refactor(sdk)!: adopt the redesigned secondary treatment at call sites The design system's secondary is the solid grey fill. Call sites that carried the old outlined secondary move to it instead of keeping their former look through ghost-secondary - cancel and auxiliary actions now render solid, as the redesign intends. The outlined treatment stays available as ghost-secondary for deliberate use. --- .changeset/button-redesign.md | 4 ++-- apps/docs/src/components/ui-examples/menu.tsx | 2 +- apps/docs/src/components/ui-examples/tooltip.tsx | 2 +- apps/docs/src/content/docs/ui-library/ui-components/menu.mdx | 2 +- .../src/content/docs/ui-library/ui-components/tooltip.mdx | 2 +- .../components/placeholder-button/placeholder-button.tsx | 2 +- .../components/add-ai-tool-footer/add-ai-tool-footer.tsx | 2 +- .../dynamic-condition-modal-footer/condition-modal-footer.tsx | 2 +- .../dynamic-conditions-form/conditions-form.tsx | 2 +- .../modals/delete-confirmation/delete-confirmation.tsx | 2 +- .../src/features/palette/components/footer/palette-footer.tsx | 2 +- .../modals/tab-global-variables/pane-list/pane-list.tsx | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.changeset/button-redesign.md b/.changeset/button-redesign.md index bf97ce19e..13df30f89 100644 --- a/.changeset/button-redesign.md +++ b/.changeset/button-redesign.md @@ -1,6 +1,6 @@ --- '@workflowbuilder/ui': major -'@workflowbuilder/sdk': major +'@workflowbuilder/sdk': minor --- -`Button` takes `variant`, `size` and `shape` and composes its content from `prefixIcon`, `children` and `suffixIcon`; the label/icon/icon+label subtypes and their prop types are gone. Migrate variants (the outlined treatment is `ghost-secondary`, `secondary` is the solid grey former `gray`, `error` is `critical`, `ghost-destructive` is `ghost-critical`), sizes (`extra-large`…`extra-small` become `xl`…`xs`), and `shape="circle"` (now `"round"`). +`Button` takes `variant`, `size` and `shape` and composes its content from `prefixIcon`, `children` and `suffixIcon`; the label/icon/icon+label subtypes and their prop types are gone. Migrate variants (`gray` and the former outlined `secondary` both become the solid grey `secondary` — reach for `ghost-secondary` where an outlined treatment should stay — `error` is `critical`, `ghost-destructive` is `ghost-critical`), sizes (`extra-large`…`extra-small` become `xl`…`xs`), and `shape="circle"` (now `"round"`). diff --git a/apps/docs/src/components/ui-examples/menu.tsx b/apps/docs/src/components/ui-examples/menu.tsx index e5f9d13c0..8583d4787 100644 --- a/apps/docs/src/components/ui-examples/menu.tsx +++ b/apps/docs/src/components/ui-examples/menu.tsx @@ -13,7 +13,7 @@ export function MenuExample() { { label: 'Delete', destructive: true, onClick: () => {} }, ]} > - + ); diff --git a/apps/docs/src/components/ui-examples/tooltip.tsx b/apps/docs/src/components/ui-examples/tooltip.tsx index 2c2771740..55f37c5f0 100644 --- a/apps/docs/src/components/ui-examples/tooltip.tsx +++ b/apps/docs/src/components/ui-examples/tooltip.tsx @@ -7,7 +7,7 @@ export function TooltipExample() { - + Tooltip diff --git a/apps/docs/src/content/docs/ui-library/ui-components/menu.mdx b/apps/docs/src/content/docs/ui-library/ui-components/menu.mdx index 539d8a092..2cdcfc75a 100644 --- a/apps/docs/src/content/docs/ui-library/ui-components/menu.mdx +++ b/apps/docs/src/content/docs/ui-library/ui-components/menu.mdx @@ -28,7 +28,7 @@ function Example() { { label: 'Delete', destructive: true, onClick: () => {} }, ]} > - + ); } diff --git a/apps/docs/src/content/docs/ui-library/ui-components/tooltip.mdx b/apps/docs/src/content/docs/ui-library/ui-components/tooltip.mdx index 1bc2cdc0a..b8f5f8bb9 100644 --- a/apps/docs/src/content/docs/ui-library/ui-components/tooltip.mdx +++ b/apps/docs/src/content/docs/ui-library/ui-components/tooltip.mdx @@ -23,7 +23,7 @@ function Example() { return ( - + Default tooltip diff --git a/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx b/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx index e1fba0edb..5be8e259f 100644 --- a/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx +++ b/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx @@ -12,7 +12,7 @@ export function PlaceholderButton({ label, size = 'xs', ...props }: Props) { diff --git a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form/conditions-form.tsx b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form/conditions-form.tsx index a91d3799b..fdd4dce6d 100644 --- a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form/conditions-form.tsx +++ b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form/conditions-form.tsx @@ -102,7 +102,7 @@ export const ConditionsForm = forwardRef} aria-label="Add condition" diff --git a/packages/sdk/src/features/modals/delete-confirmation/delete-confirmation.tsx b/packages/sdk/src/features/modals/delete-confirmation/delete-confirmation.tsx index 60f01e5af..0b84ac3aa 100644 --- a/packages/sdk/src/features/modals/delete-confirmation/delete-confirmation.tsx +++ b/packages/sdk/src/features/modals/delete-confirmation/delete-confirmation.tsx @@ -56,7 +56,7 @@ export function DeleteConfirmationButtons({ onDeleteClick, onCancelClick }: Dele return (
- diff --git a/packages/sdk/src/features/variables/modals/tab-global-variables/pane-list/pane-list.tsx b/packages/sdk/src/features/variables/modals/tab-global-variables/pane-list/pane-list.tsx index 7fa427f1b..de6ffe2ab 100644 --- a/packages/sdk/src/features/variables/modals/tab-global-variables/pane-list/pane-list.tsx +++ b/packages/sdk/src/features/variables/modals/tab-global-variables/pane-list/pane-list.tsx @@ -32,7 +32,7 @@ export function PaneList({ className, setActivePane }: Props) { description="workflowsSettings.tab.globalVariablesDescription" >