Merge pull request #28789 from backstage/canon-imporove-styling
Canon - Improve Button styles
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/canon': minor
|
||||
---
|
||||
|
||||
Updated core CSS tokens and fixing the Button component accordingly.
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ are prefixed with `fg` to make it easier to identify.
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-fg-text-primary</Chip>
|
||||
<Chip head>--canon-fg-primary</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of `--canon-bg-app` or `--canon-bg-elevated`.
|
||||
@@ -194,7 +194,7 @@ are prefixed with `fg` to make it easier to identify.
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-fg-text-secondary</Chip>
|
||||
<Chip head>--canon-fg-secondary</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of `--canon-bg-app` or `--canon-bg-elevated`.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
}
|
||||
|
||||
.icon path {
|
||||
fill: var(--canon-fg-text-primary);
|
||||
fill: var(--canon-fg-primary);
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import React from 'react';
|
||||
import type { Preview, ReactRenderer } from '@storybook/react';
|
||||
import { withThemeByDataAttribute } from '@storybook/addon-themes';
|
||||
import { CanonProvider } from '../src/contexts/canon';
|
||||
|
||||
// Canon specific styles
|
||||
import '../src/css/core.css';
|
||||
import '../src/css/components.css';
|
||||
|
||||
// Custom themes
|
||||
import './themes/backstage.css';
|
||||
import { CanonProvider } from '../src/contexts/canon';
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
controls: {
|
||||
@@ -79,8 +76,6 @@ const preview: Preview = {
|
||||
themes: {
|
||||
Light: 'light',
|
||||
Dark: 'dark',
|
||||
'Backstage Light': 'backstage-light',
|
||||
'Backstage Dark': 'backstage-dark',
|
||||
},
|
||||
defaultTheme: 'Light',
|
||||
}),
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
[data-theme-name='legacy'][data-theme='light'],
|
||||
[data-theme-name='legacy'][data-theme='dark'] {
|
||||
--canon-font-regular: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif;
|
||||
|
||||
.canon-Button {
|
||||
border-radius: 60px;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.875rem;
|
||||
transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,
|
||||
box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,
|
||||
border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
||||
line-height: 1.75;
|
||||
border-radius: 4px;
|
||||
background-color: #1f5493;
|
||||
padding: 6px 16px;
|
||||
box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.canon-Button--variant-primary {
|
||||
color: #fff;
|
||||
background-color: rgb(21, 58, 102);
|
||||
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2),
|
||||
0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2),
|
||||
0px 4px 5px 0px rgba(0, 0, 0, 0.14),
|
||||
0px 1px 10px 0px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
&.canon-Button--variant-secondary {
|
||||
background-color: transparent;
|
||||
border: 1px solid rgba(31, 84, 147, 0.5);
|
||||
color: #1f5493;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid #1f5493;
|
||||
background-color: rgba(31, 84, 147, 0.04);
|
||||
}
|
||||
}
|
||||
|
||||
&.canon-Button--variant-tertiary {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: #1f5493;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme-name='legacy'][data-theme='light'] {
|
||||
/* Colors */
|
||||
--canon-bg-accent: #2e77d0;
|
||||
--canon-bg: #f8f8f8;
|
||||
--canon-bg-elevated: #fff;
|
||||
--canon-bg: #f4f4f4;
|
||||
|
||||
/* Text colors */
|
||||
--canon-fg-text-primary: #000;
|
||||
--canon-fg-accent: #fff;
|
||||
--canon-fg-text-secondary: #646464;
|
||||
}
|
||||
|
||||
[data-theme-name='legacy'][data-theme='dark'] {
|
||||
/* Colors */
|
||||
--canon-bg-accent: #fff;
|
||||
--canon-bg: #000;
|
||||
--canon-bg-elevated: #121212;
|
||||
--canon-bg: #1a1a1a;
|
||||
|
||||
/* Borders */
|
||||
--canon-border: rgba(255, 255, 255, 0.2);
|
||||
--canon-border-warning: #f50000;
|
||||
--canon-border-danger: #f87503;
|
||||
--canon-border-focus: #25d262;
|
||||
|
||||
/* States - Add more states */
|
||||
--canon-fg-danger: #f50000;
|
||||
|
||||
/* Text colors */
|
||||
--canon-fg-text-primary: #fff;
|
||||
--canon-fg-accent: #000;
|
||||
--canon-fg-text-secondary: #b3b3b3;
|
||||
}
|
||||
@@ -121,6 +121,27 @@ export const Button: React_2.ForwardRefExoticComponent<
|
||||
ButtonProps & React_2.RefAttributes<HTMLButtonElement>
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type ButtonOwnProps = GetPropDefTypes<typeof buttonPropDefs>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const buttonPropDefs: {
|
||||
variant: {
|
||||
type: 'enum';
|
||||
values: ('primary' | 'secondary')[];
|
||||
className: string;
|
||||
default: 'primary';
|
||||
responsive: true;
|
||||
};
|
||||
size: {
|
||||
type: 'enum';
|
||||
values: ('small' | 'medium')[];
|
||||
className: string;
|
||||
default: 'medium';
|
||||
responsive: true;
|
||||
};
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface ButtonProps {
|
||||
// (undocumented)
|
||||
@@ -134,15 +155,11 @@ export interface ButtonProps {
|
||||
// (undocumented)
|
||||
iconStart?: IconNames;
|
||||
// (undocumented)
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
|
||||
size?: ButtonOwnProps['size'];
|
||||
// (undocumented)
|
||||
style?: React.CSSProperties;
|
||||
// (undocumented)
|
||||
variant?:
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'tertiary'
|
||||
| Partial<Record<Breakpoint, 'primary' | 'secondary' | 'tertiary'>>;
|
||||
variant?: ButtonOwnProps['variant'];
|
||||
}
|
||||
|
||||
// @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', {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.canon-Box {
|
||||
font-family: var(--canon-font-regular);
|
||||
color: var(--canon-fg-text-primary);
|
||||
color: var(--canon-fg-primary);
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
@@ -137,6 +138,12 @@ export const Playground: Story = {
|
||||
<Text>{variant}</Text>
|
||||
{['small', 'medium'].map(size => (
|
||||
<Flex align="center" key={size}>
|
||||
<Button
|
||||
variant={variant as ButtonProps['variant']}
|
||||
size={size as ButtonProps['size']}
|
||||
>
|
||||
Button
|
||||
</Button>
|
||||
<Button
|
||||
iconStart="cloud"
|
||||
variant={variant as ButtonProps['variant']}
|
||||
@@ -160,6 +167,29 @@ export const Playground: Story = {
|
||||
>
|
||||
Button
|
||||
</Button>
|
||||
<Button
|
||||
variant={variant as ButtonProps['variant']}
|
||||
size={size as ButtonProps['size']}
|
||||
disabled
|
||||
>
|
||||
Button
|
||||
</Button>
|
||||
<Button
|
||||
iconStart="cloud"
|
||||
variant={variant as ButtonProps['variant']}
|
||||
size={size as ButtonProps['size']}
|
||||
disabled
|
||||
>
|
||||
Button
|
||||
</Button>
|
||||
<Button
|
||||
iconEnd="chevronRight"
|
||||
variant={variant as ButtonProps['variant']}
|
||||
size={size as ButtonProps['size']}
|
||||
disabled
|
||||
>
|
||||
Button
|
||||
</Button>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
|
||||
@@ -42,7 +42,6 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
|
||||
return (
|
||||
<button
|
||||
{...rest}
|
||||
ref={ref}
|
||||
disabled={disabled}
|
||||
className={clsx(
|
||||
@@ -52,19 +51,11 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
{...rest}
|
||||
>
|
||||
<span
|
||||
className={[
|
||||
'canon-Button--content',
|
||||
iconStart && iconEnd ? 'canon-Button--icon-start-end' : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{iconStart && <Icon name={iconStart} />}
|
||||
{children}
|
||||
</span>
|
||||
{iconEnd && <Icon name={iconEnd} />}
|
||||
{iconStart && <Icon name={iconStart} className="canon-Button--icon" />}
|
||||
{children}
|
||||
{iconEnd && <Icon name={iconEnd} className="canon-Button--icon" />}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -15,3 +15,5 @@
|
||||
*/
|
||||
export { Button } from './Button';
|
||||
export type { ButtonProps } from './types';
|
||||
export { buttonPropDefs } from './Button.props';
|
||||
export type { ButtonOwnProps } from './Button.props';
|
||||
|
||||
@@ -22,62 +22,73 @@
|
||||
user-select: none;
|
||||
font-family: var(--canon-font-regular);
|
||||
font-weight: var(--canon-font-weight-bold);
|
||||
font-size: var(--canon-font-size-3);
|
||||
padding: 0;
|
||||
transition: all 150ms ease;
|
||||
cursor: pointer;
|
||||
border-radius: var(--canon-radius-2);
|
||||
gap: var(--canon-space-1_5);
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.canon-Button--variant-primary {
|
||||
background-color: var(--canon-bg-accent);
|
||||
color: var(--canon-fg-accent);
|
||||
background-color: var(--canon-bg-solid);
|
||||
color: var(--canon-fg-solid);
|
||||
transition: background-color 150ms ease;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
box-shadow: inset 0 0 0 1px var(--canon-border-focus);
|
||||
color: var(--canon-fg-text-primary);
|
||||
background-color: var(--canon-bg-solid-hover);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--canon-bg-solid-pressed);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--canon-bg-solid-disabled);
|
||||
color: var(--canon-fg-solid-disabled);
|
||||
}
|
||||
}
|
||||
|
||||
.canon-Button--variant-secondary {
|
||||
background-color: transparent;
|
||||
background-color: var(--canon-bg-elevated);
|
||||
box-shadow: inset 0 0 0 1px var(--canon-border);
|
||||
color: var(--canon-fg-text-primary);
|
||||
color: var(--canon-fg-primary);
|
||||
transition: box-shadow 150ms ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 0 0 1px var(--canon-border-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.canon-Button--variant-tertiary {
|
||||
background-color: transparent;
|
||||
color: var(--canon-fg-text-primary);
|
||||
&:active {
|
||||
box-shadow: inset 0 0 0 1px var(--canon-border-focus);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--canon-fg-text-secondary);
|
||||
&:disabled {
|
||||
box-shadow: inset 0 0 0 1px var(--canon-border-disabled);
|
||||
color: var(--canon-fg-disabled);
|
||||
}
|
||||
}
|
||||
|
||||
.canon-Button--size-small {
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.canon-Button--size-medium {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
font-size: var(--canon-font-size-4);
|
||||
padding: 0 var(--canon-space-3);
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.canon-Button--content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--canon-space-3);
|
||||
font-weight: var(--canon-font-weight-bold);
|
||||
.canon-Button--size-small {
|
||||
font-size: var(--canon-font-size-3);
|
||||
padding: 0 var(--canon-space-2);
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.canon-Button--icon-start-end {
|
||||
flex: 1;
|
||||
.canon-Button--size-small .canon-Button--icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.canon-Button--size-medium .canon-Button--icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
@@ -14,19 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { IconNames } from '../Icon';
|
||||
import type { Breakpoint } from '../../types';
|
||||
import type { ButtonOwnProps } from './Button.props';
|
||||
|
||||
/**
|
||||
* Properties for {@link Button}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface ButtonProps {
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
|
||||
variant?:
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'tertiary'
|
||||
| Partial<Record<Breakpoint, 'primary' | 'secondary' | 'tertiary'>>;
|
||||
size?: ButtonOwnProps['size'];
|
||||
variant?: ButtonOwnProps['variant'];
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
import React from 'react';
|
||||
import { Checkbox as CheckboxPrimitive } from '@base-ui-components/react/checkbox';
|
||||
import { Icon } from '@backstage/canon';
|
||||
import type { CheckboxProps } from './types';
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
&[data-checked] {
|
||||
background-color: var(--canon-bg-accent);
|
||||
background-color: var(--canon-bg-solid);
|
||||
color: var(--canon-fg-solid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +24,7 @@
|
||||
gap: var(--canon-space-2);
|
||||
font-size: var(--canon-font-size-xs);
|
||||
font-family: var(--canon-font-regular);
|
||||
color: var(--canon-fg-text-primary);
|
||||
color: var(--canon-fg-primary);
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
.canon-FieldLabel {
|
||||
font-size: var(--canon-font-size-2);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-text-primary);
|
||||
color: var(--canon-fg-primary);
|
||||
margin-bottom: var(--canon-space-1_5);
|
||||
}
|
||||
|
||||
.canon-FieldDescription {
|
||||
font-size: var(--canon-font-size-2);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-text-secondary);
|
||||
color: var(--canon-fg-secondary);
|
||||
margin: 0;
|
||||
padding-top: var(--canon-space-1_5);
|
||||
}
|
||||
@@ -47,7 +47,7 @@
|
||||
.canon-FieldValidity {
|
||||
font-size: var(--canon-font-size-2);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-text-secondary);
|
||||
color: var(--canon-fg-secondary);
|
||||
margin: 0;
|
||||
padding-top: var(--canon-space-1_5);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
.canon-Heading {
|
||||
font-family: var(--canon-font-regular);
|
||||
color: var(--canon-fg-text-primary);
|
||||
color: var(--canon-fg-primary);
|
||||
line-height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
@@ -21,7 +21,7 @@ import clsx from 'clsx';
|
||||
|
||||
/** @public */
|
||||
export const Icon = (props: IconProps) => {
|
||||
const { name, size = 16, className, style, ...restProps } = props;
|
||||
const { name, size, className, style, ...restProps } = props;
|
||||
const { icons } = useCanon();
|
||||
|
||||
const CanonIcon = icons[name] as React.ComponentType<Omit<IconProps, 'name'>>;
|
||||
@@ -34,7 +34,10 @@ export const Icon = (props: IconProps) => {
|
||||
return (
|
||||
<CanonIcon
|
||||
className={clsx('canon-Icon', className)}
|
||||
style={{ width: size, height: size, ...style }}
|
||||
style={{
|
||||
...(size ? { width: size, height: size } : {}),
|
||||
...style,
|
||||
}}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
background-color: var(--canon-bg-elevated);
|
||||
font-size: var(--canon-font-size-3);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-text-primary);
|
||||
color: var(--canon-fg-primary);
|
||||
transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.canon-Input::placeholder {
|
||||
color: var(--canon-fg-text-secondary);
|
||||
color: var(--canon-fg-secondary);
|
||||
}
|
||||
|
||||
.canon-Input:hover {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
.canon-Text {
|
||||
font-family: var(--canon-font-regular);
|
||||
color: var(--canon-fg-text-primary);
|
||||
color: var(--canon-fg-primary);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -62,81 +62,90 @@
|
||||
--canon-space-14: calc(var(--canon-space) * 14); /* 56px */
|
||||
|
||||
/* Radius */
|
||||
--canon-radius-factor: 1.5;
|
||||
--canon-radius-1: calc(3px * var(--canon-radius-factor));
|
||||
--canon-radius-2: calc(4px * var(--canon-radius-factor));
|
||||
--canon-radius-3: calc(6px * var(--canon-radius-factor));
|
||||
--canon-radius-4: calc(8px * var(--canon-radius-factor));
|
||||
--canon-radius-5: calc(12px * var(--canon-radius-factor));
|
||||
--canon-radius-6: calc(16px * var(--canon-radius-factor));
|
||||
--canon-radius-1: calc(0.125rem);
|
||||
--canon-radius-2: calc(0.25rem);
|
||||
--canon-radius-3: calc(0.5rem);
|
||||
--canon-radius-4: calc(0.75rem);
|
||||
--canon-radius-5: calc(1rem);
|
||||
--canon-radius-6: calc(1.25rem);
|
||||
--canon-radius-full: 9999px;
|
||||
|
||||
/* Colors */
|
||||
/* Background Colors */
|
||||
--canon-bg: #f8f8f8;
|
||||
--canon-bg-elevated: #fff;
|
||||
--canon-bg-accent: #000;
|
||||
--canon-bg-accent-focus: #000;
|
||||
--canon-bg-accent-disabled: #000;
|
||||
--canon-bg-accent-hover: #000;
|
||||
--canon-bg-tint: #edf2fe;
|
||||
--canon-bg-tint-active: #c7d7ff;
|
||||
--canon-bg-tint-disabled: #f8f8f8;
|
||||
--canon-bg-tint-hover: #dbe4ff;
|
||||
--canon-bg-danger: #f8f8f8;
|
||||
--canon-bg-elevated: #fff;
|
||||
--canon-bg-success: #f8f8f8;
|
||||
--canon-bg-warning: #f8f8f8;
|
||||
--canon-border: rgba(0, 0, 0, 0.1);
|
||||
--canon-border-accent: #000;
|
||||
--canon-border-accent-focus: #000;
|
||||
--canon-border-accent-hover: #000;
|
||||
--canon-border-danger: #e22b2b;
|
||||
--canon-border-focus: rgba(0, 0, 0, 0.4);
|
||||
--canon-border-hover: rgba(0, 0, 0, 0.2);
|
||||
--canon-border-success: #008000;
|
||||
--canon-border-warning: #e36d05;
|
||||
--canon-fg-text-primary: #000;
|
||||
--canon-fg-text-secondary: #646464;
|
||||
--canon-fg-link: #fff;
|
||||
--canon-fg-link-hover: #fff;
|
||||
--canon-fg-accent: #fff;
|
||||
--canon-fg-tint: #000;
|
||||
--canon-bg-solid: #1f5493;
|
||||
--canon-bg-solid-hover: #163a66;
|
||||
--canon-bg-solid-pressed: #0f2b4e;
|
||||
--canon-bg-solid-disabled: #ebebeb;
|
||||
--canon-bg-tint: transparent;
|
||||
--canon-bg-tint-hover: rgba(31, 84, 147, 0.4);
|
||||
--canon-bg-tint-pressed: rgba(31, 84, 147, 0.6);
|
||||
--canon-bg-tint-disabled: #ebebeb;
|
||||
--canon-bg-danger: #feebe7;
|
||||
--canon-bg-warning: #fff2b2;
|
||||
--canon-bg-success: #e6f6eb;
|
||||
|
||||
/* Foreground Colors */
|
||||
--canon-fg-primary: #000000;
|
||||
--canon-fg-secondary: #757575;
|
||||
--canon-fg-link: #1f5493;
|
||||
--canon-fg-link-hover: #1f2d5c;
|
||||
--canon-fg-disabled: #9e9e9e;
|
||||
--canon-fg-solid: #ffffff;
|
||||
--canon-fg-solid-disabled: #9c9c9c;
|
||||
--canon-fg-tint: #1f5493;
|
||||
--canon-fg-tint-disabled: #9e9e9e;
|
||||
--canon-fg-danger: #e22b2b;
|
||||
--canon-fg-warning: #e36d05;
|
||||
--canon-fg-success: #008000;
|
||||
--canon-fg-success: #1db954;
|
||||
|
||||
/* Border Colors */
|
||||
--canon-border: rgba(0, 0, 0, 0.1);
|
||||
--canon-border-hover: rgba(0, 0, 0, 0.2);
|
||||
--canon-border-focus: rgba(0, 0, 0, 0.4);
|
||||
--canon-border-disabled: rgba(0, 0, 0, 0.1);
|
||||
--canon-border-danger: #f87a7a;
|
||||
--canon-border-warning: #e36d05;
|
||||
--canon-border-success: #53db83;
|
||||
}
|
||||
|
||||
/* Dark theme tokens */
|
||||
[data-theme='dark'] {
|
||||
--canon-bg: #f8f8f8;
|
||||
--canon-bg-elevated: #fff;
|
||||
--canon-bg-accent: #000;
|
||||
--canon-bg-accent-focus: #000;
|
||||
--canon-bg-accent-disabled: #000;
|
||||
--canon-bg-accent-hover: #000;
|
||||
--canon-bg-tint: #edf2fe;
|
||||
--canon-bg-tint-active: #c7d7ff;
|
||||
--canon-bg-tint-disabled: #f8f8f8;
|
||||
--canon-bg-tint-hover: #dbe4ff;
|
||||
--canon-bg-danger: #f8f8f8;
|
||||
--canon-bg-elevated: #fff;
|
||||
--canon-bg-success: #f8f8f8;
|
||||
--canon-bg-warning: #f8f8f8;
|
||||
--canon-border: rgba(0, 0, 0, 0.1);
|
||||
--canon-border-accent: #000;
|
||||
--canon-border-accent-focus: #000;
|
||||
--canon-border-accent-hover: #000;
|
||||
--canon-border-danger: #e22b2b;
|
||||
--canon-border-focus: rgba(0, 0, 0, 0.4);
|
||||
--canon-border-hover: rgba(0, 0, 0, 0.2);
|
||||
--canon-border-success: #008000;
|
||||
--canon-border-warning: #e36d05;
|
||||
--canon-fg-accent: #000;
|
||||
/* Background Colors */
|
||||
--canon-bg: #000000;
|
||||
--canon-bg-elevated: #191919;
|
||||
--canon-bg-solid: #9cc9ff;
|
||||
--canon-bg-solid-hover: #83b9fd;
|
||||
--canon-bg-solid-pressed: #83b9fd;
|
||||
--canon-bg-solid-disabled: #222222;
|
||||
--canon-bg-tint: transparent;
|
||||
--canon-bg-tint-hover: rgba(156, 201, 255, 0.12);
|
||||
--canon-bg-tint-pressed: rgba(156, 201, 255, 0.16);
|
||||
--canon-bg-tint-disabled: transparent;
|
||||
--canon-bg-danger: #3b1219;
|
||||
--canon-bg-warning: #302008;
|
||||
--canon-bg-success: #132d21;
|
||||
|
||||
/* Foreground Colors */
|
||||
--canon-fg-primary: #ffffff;
|
||||
--canon-fg-secondary: #9e9e9e;
|
||||
--canon-fg-link: #9cc9ff;
|
||||
--canon-fg-link-hover: #7eb5f7;
|
||||
--canon-fg-disabled: #9e9e9e;
|
||||
--canon-fg-solid: #101821;
|
||||
--canon-fg-solid-disabled: #575757;
|
||||
--canon-fg-tint: #9cc9ff;
|
||||
--canon-fg-tint-disabled: #575757;
|
||||
--canon-fg-danger: #e22b2b;
|
||||
--canon-fg-success: #008000;
|
||||
--canon-fg-warning: #e36d05;
|
||||
--canon-fg-link: #fff;
|
||||
--canon-fg-link-hover: #fff;
|
||||
--canon-fg-text-primary: #000;
|
||||
--canon-fg-text-secondary: #646464;
|
||||
--canon-fg-success: #1db954;
|
||||
|
||||
/* Border Colors */
|
||||
--canon-border: rgba(255, 255, 255, 0.2);
|
||||
--canon-border-hover: rgba(255, 255, 255, 0.4);
|
||||
--canon-border-focus: rgba(255, 255, 255, 0.5);
|
||||
--canon-border-disabled: rgba(255, 255, 255, 0.2);
|
||||
--canon-border-danger: #f87a7a;
|
||||
--canon-border-warning: #e36d05;
|
||||
--canon-border-success: #53db83;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user