Improve button component

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-02-10 15:45:49 +00:00
parent 05a2cc685e
commit a2c2e52fa2
12 changed files with 89 additions and 77 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
"build": "npm run build:css && next build",
"build:css": "node scripts/build-css.js",
"lint": "next lint",
"start": "next dev"
"start": "npm run build:css && next dev"
},
"dependencies": {
"@codemirror/lang-sass": "^6.0.2",
-6
View File
@@ -9,11 +9,6 @@ const source1 = path.join(__dirname, `${source}/core.css`);
const destination1 = path.join(__dirname, `${destination}/core.css`);
const source2 = path.join(__dirname, `${source}/components.css`);
const destination2 = path.join(__dirname, `${destination}/components.css`);
const source3 = path.join(
__dirname,
`../../packages/canon/.storybook/themes/backstage.css`,
);
const destination3 = path.join(__dirname, `${destination}/backstage.css`);
// Function to bundle and copy the CSS file
const bundleAndCopyFile = async (source, destination) => {
@@ -34,7 +29,6 @@ const bundleAndCopyFile = async (source, destination) => {
Promise.all([
bundleAndCopyFile(source1, destination1),
bundleAndCopyFile(source2, destination2),
bundleAndCopyFile(source3, destination3),
])
.then(() => {
// Add an empty line after all operations are complete - It looks better in the terminal :)
@@ -11,6 +11,7 @@ import {
ButtonResponsive,
} from '@/snippets/button';
import { buttonVariants } from '@/snippets/_snippets';
import { buttonPropDefs } from '../../../../../../packages/canon/src/components/Button/Button.props';
# Button
@@ -58,34 +59,7 @@ A button component that can be used to trigger actions.
## API reference
<PropsTable
data={{
size: {
type: ['small', 'medium'],
responsive: true,
},
variant: {
type: ['primary', 'secondary', 'tertiary'],
responsive: true,
},
disabled: {
type: 'boolean',
responsive: false,
},
children: {
type: 'ReactNode',
responsive: false,
},
className: {
type: 'string',
responsive: false,
},
style: {
type: 'CSSProperties',
responsive: false,
},
}}
/>
<PropsTable data={buttonPropDefs} />
## Examples
@@ -6,8 +6,9 @@ import { icons } from '../../../../packages/canon';
// Define a more specific type for the data object
type PropData = {
type: string | string[];
responsive: boolean;
values?: string | string[];
responsive?: boolean;
default?: string;
};
// Modify the PropsTable component to use the new type
@@ -16,17 +17,34 @@ export const PropsTable = <T extends Record<string, PropData>>({
}: {
data: T;
}) => {
const completeData = {
...data,
children: {
values: 'ReactNode',
responsive: false,
},
className: {
values: 'string',
responsive: false,
},
style: {
values: 'CSSProperties',
responsive: false,
},
};
return (
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Type</Table.HeaderCell>
<Table.HeaderCell>Default</Table.HeaderCell>
<Table.HeaderCell>Responsive</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
{Object.keys(data).map(n => (
{Object.keys(completeData).map(n => (
<Table.Row key={n}>
<Table.Cell>
<Chip head>{n}</Chip>
@@ -35,17 +53,22 @@ export const PropsTable = <T extends Record<string, PropData>>({
<div
style={{ display: 'flex', flexWrap: 'wrap', gap: '0.375rem' }}
>
{data[n].type === 'icon' ? (
{completeData[n].values === 'icon' ? (
Object.keys(icons).map(icon => <Chip key={icon}>{icon}</Chip>)
) : Array.isArray(data[n].type) ? (
data[n].type.map(t => <Chip key={t}>{t}</Chip>)
) : Array.isArray(completeData[n].values) ? (
completeData[n].values.map(t => <Chip key={t}>{t}</Chip>)
) : (
<Chip>{data[n].type}</Chip>
<Chip>{completeData[n].values}</Chip>
)}
</div>
</Table.Cell>
<Table.Cell>
<Chip>{data[n].responsive ? 'Yes' : 'No'}</Chip>
<Chip>
{completeData[n].default ? completeData[n].default : '-'}
</Chip>
</Table.Cell>
<Table.Cell>
<Chip>{completeData[n].responsive ? 'Yes' : 'No'}</Chip>
</Table.Cell>
</Table.Row>
))}
-3
View File
@@ -11,9 +11,6 @@ export const ButtonPreview = () => {
<Button iconStart="cloud" variant="secondary">
Button
</Button>
<Button iconStart="cloud" variant="tertiary">
Button
</Button>
</Flex>
);
};
+1 -2
View File
@@ -141,8 +141,7 @@ export interface ButtonProps {
variant?:
| 'primary'
| 'secondary'
| 'tertiary'
| Partial<Record<Breakpoint, 'primary' | 'secondary' | 'tertiary'>>;
| Partial<Record<Breakpoint, 'primary' | 'secondary'>>;
}
// @public (undocumented)
@@ -37,6 +37,7 @@ export const Box = forwardRef<HTMLDivElement, BoxProps>((props, ref) => {
...displayPropDefs,
...boxPropDefs,
};
const { className, style } = extractProps(props, propDefs);
return createElement(props.as || 'div', {
@@ -0,0 +1,41 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { PropDef, GetPropDefTypes } from '../../props/prop-def';
/** @public */
export const buttonPropDefs = {
variant: {
type: 'enum',
values: ['primary', 'secondary'],
className: 'canon-Button--variant',
default: 'primary',
responsive: true,
},
size: {
type: 'enum',
values: ['small', 'medium'],
className: 'canon-Button--size',
default: 'medium',
responsive: true,
},
} satisfies {
variant: PropDef<'primary' | 'secondary'>;
size: PropDef<'small' | 'medium'>;
};
/** @public */
export type ButtonOwnProps = GetPropDefTypes<typeof buttonPropDefs>;
@@ -29,9 +29,14 @@ const meta = {
control: 'select',
options: ['small', 'medium'],
},
variant: {
control: 'select',
options: ['primary', 'secondary'],
},
},
args: {
size: 'medium',
variant: 'primary',
},
} satisfies Meta<typeof Button>;
@@ -57,9 +62,6 @@ export const Variants: Story = {
<Button iconStart="cloud" variant="secondary">
Button
</Button>
<Button iconStart="cloud" variant="tertiary">
Button
</Button>
</Flex>
),
};
@@ -94,7 +96,7 @@ export const FullWidth: Story = {
children: 'Button',
},
render: args => (
<Flex style={{ width: '300px' }}>
<Flex direction="column" gap="4" style={{ width: '300px' }}>
<Button {...args} iconStart="cloud" />
<Button {...args} iconEnd="chevronRight" />
<Button {...args} iconStart="cloud" iconEnd="chevronRight" />
@@ -115,7 +117,6 @@ export const Responsive: Story = {
variant: {
initial: 'primary',
sm: 'secondary',
md: 'tertiary',
},
size: {
xs: 'small',
@@ -124,7 +125,7 @@ export const Responsive: Story = {
},
};
const variants: string[] = ['primary', 'secondary', 'tertiary'];
const variants: string[] = ['primary', 'secondary'];
export const Playground: Story = {
args: {
@@ -17,7 +17,8 @@
import React, { forwardRef } from 'react';
import { Icon } from '../Icon';
import clsx from 'clsx';
import { useResponsiveValue } from '../../hooks/useResponsiveValue';
import { extractProps } from '../../utils/extractProps';
import { buttonPropDefs } from './Button.props';
import type { ButtonProps } from './types';
@@ -31,25 +32,16 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
iconStart,
iconEnd,
children,
className,
style,
...rest
} = props;
// Get the responsive value for the variant
const responsiveSize = useResponsiveValue(size);
const responsiveVariant = useResponsiveValue(variant);
const { className, style } = extractProps(props, buttonPropDefs);
return (
<button
ref={ref}
disabled={disabled}
className={clsx(
'canon-Button',
`canon-Button--size-${responsiveSize}`,
`canon-Button--variant-${responsiveVariant}`,
className,
)}
className={clsx('canon-Button', className)}
style={style}
{...rest}
>
@@ -71,15 +71,6 @@
}
}
.canon-Button--variant-tertiary {
background-color: transparent;
color: var(--canon-fg-primary);
&:hover {
color: var(--canon-fg-text-secondary);
}
}
.canon-Button--size-medium {
font-size: var(--canon-font-size-4);
padding: 0 var(--canon-space-3);
@@ -25,8 +25,7 @@ export interface ButtonProps {
variant?:
| 'primary'
| 'secondary'
| 'tertiary'
| Partial<Record<Breakpoint, 'primary' | 'secondary' | 'tertiary'>>;
| Partial<Record<Breakpoint, 'primary' | 'secondary'>>;
children: React.ReactNode;
className?: string;
disabled?: boolean;