Improve button component
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -11,9 +11,6 @@ export const ButtonPreview = () => {
|
||||
<Button iconStart="cloud" variant="secondary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart="cloud" variant="tertiary">
|
||||
Button
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user