Add Chip atom component
- Interactive tag for filtering, selection, and dismissible labels - Wraps MUI Chip with FA tokens: 12 component tokens (height, padding, font, icon sizes) - Two variants (filled/outlined) × two colours (default/primary) × two sizes (sm/md) - Custom `selected` prop promotes to brand colour with warm bg (outlined) - MUI theme overrides: soft tonal fills, branded outlines, hover/focus states - 10 Storybook stories including interactive filter and removable tag demos - Preflight passed all 5 checks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
361
src/components/atoms/Chip/Chip.stories.tsx
Normal file
361
src/components/atoms/Chip/Chip.stories.tsx
Normal file
@@ -0,0 +1,361 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Chip } from './Chip';
|
||||
import { Card } from '../Card';
|
||||
import { Typography } from '../Typography';
|
||||
import Box from '@mui/material/Box';
|
||||
import LocalOfferIcon from '@mui/icons-material/LocalOffer';
|
||||
import FaceIcon from '@mui/icons-material/Face';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||
import ChurchIcon from '@mui/icons-material/Church';
|
||||
import LocalFloristIcon from '@mui/icons-material/LocalFlorist';
|
||||
import DirectionsCarIcon from '@mui/icons-material/DirectionsCar';
|
||||
import RestaurantIcon from '@mui/icons-material/Restaurant';
|
||||
import MusicNoteIcon from '@mui/icons-material/MusicNote';
|
||||
import PhotoCameraIcon from '@mui/icons-material/PhotoCamera';
|
||||
|
||||
const meta: Meta<typeof Chip> = {
|
||||
title: 'Atoms/Chip',
|
||||
component: Chip,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['filled', 'outlined'],
|
||||
description: 'Visual style variant',
|
||||
table: { defaultValue: { summary: 'filled' } },
|
||||
},
|
||||
color: {
|
||||
control: 'select',
|
||||
options: ['default', 'primary'],
|
||||
description: 'Colour intent',
|
||||
table: { defaultValue: { summary: 'default' } },
|
||||
},
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['small', 'medium'],
|
||||
description: 'Size preset',
|
||||
table: { defaultValue: { summary: 'medium' } },
|
||||
},
|
||||
selected: {
|
||||
control: 'boolean',
|
||||
description: 'Selected/active state',
|
||||
table: { defaultValue: { summary: 'false' } },
|
||||
},
|
||||
clickable: {
|
||||
control: 'boolean',
|
||||
description: 'Whether the chip is clickable',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Chip>;
|
||||
|
||||
// ─── Default ────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Default chip — filled variant, neutral colour */
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
label: 'Chip label',
|
||||
},
|
||||
};
|
||||
|
||||
// ─── Variants ───────────────────────────────────────────────────────────────
|
||||
|
||||
/** Both visual variants with default and primary colour */
|
||||
export const Variants: Story = {
|
||||
render: () => (
|
||||
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<Chip label="Filled default" />
|
||||
<Chip label="Filled primary" color="primary" />
|
||||
<Chip variant="outlined" label="Outlined default" />
|
||||
<Chip variant="outlined" label="Outlined primary" color="primary" />
|
||||
</Box>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Sizes ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Small and medium sizes side by side */
|
||||
export const Sizes: Story = {
|
||||
render: () => (
|
||||
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
|
||||
<Chip size="small" label="Small" icon={<LocalOfferIcon />} />
|
||||
<Chip size="medium" label="Medium" icon={<LocalOfferIcon />} />
|
||||
<Chip size="small" variant="outlined" label="Small outlined" icon={<LocalOfferIcon />} />
|
||||
<Chip size="medium" variant="outlined" label="Medium outlined" icon={<LocalOfferIcon />} />
|
||||
</Box>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── With Icons ─────────────────────────────────────────────────────────────
|
||||
|
||||
/** Chips with leading icons */
|
||||
export const WithIcons: Story = {
|
||||
name: 'With Icons',
|
||||
render: () => (
|
||||
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<Chip icon={<ChurchIcon />} label="Chapel" />
|
||||
<Chip icon={<LocalFloristIcon />} label="Flowers" color="primary" />
|
||||
<Chip icon={<DirectionsCarIcon />} label="Transport" variant="outlined" />
|
||||
<Chip icon={<FaceIcon />} label="Family" variant="outlined" color="primary" />
|
||||
</Box>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Clickable ──────────────────────────────────────────────────────────────
|
||||
|
||||
/** Clickable chips respond to click events (for filtering/toggling) */
|
||||
export const Clickable: Story = {
|
||||
render: () => (
|
||||
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<Chip label="Clickable default" onClick={() => {}} />
|
||||
<Chip label="Clickable primary" color="primary" onClick={() => {}} />
|
||||
<Chip label="Clickable outlined" variant="outlined" onClick={() => {}} />
|
||||
<Chip label="Clickable outlined primary" variant="outlined" color="primary" onClick={() => {}} />
|
||||
</Box>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Deletable ──────────────────────────────────────────────────────────────
|
||||
|
||||
/** Deletable chips show a close icon */
|
||||
export const Deletable: Story = {
|
||||
render: () => (
|
||||
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<Chip label="Remove me" onDelete={() => {}} />
|
||||
<Chip label="Brand deletable" color="primary" onDelete={() => {}} />
|
||||
<Chip label="Outlined deletable" variant="outlined" onDelete={() => {}} />
|
||||
<Chip label="With icon" icon={<LocalOfferIcon />} onDelete={() => {}} />
|
||||
</Box>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Selected State ─────────────────────────────────────────────────────────
|
||||
|
||||
/** Selected state promotes chip to brand colour */
|
||||
export const Selected: Story = {
|
||||
render: () => (
|
||||
<Box sx={{ display: 'flex', gap: 2, flexDirection: 'column' }}>
|
||||
<Box>
|
||||
<Typography variant="label" sx={{ mb: 1 }}>Filled</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1.5 }}>
|
||||
<Chip label="Not selected" onClick={() => {}} />
|
||||
<Chip label="Selected" selected onClick={() => {}} />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="label" sx={{ mb: 1 }}>Outlined</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1.5 }}>
|
||||
<Chip variant="outlined" label="Not selected" onClick={() => {}} />
|
||||
<Chip variant="outlined" label="Selected" selected onClick={() => {}} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Interactive: Filter Chips ──────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Toggle filter pattern — commonly used for service category filtering.
|
||||
* Click chips to toggle selection.
|
||||
*/
|
||||
export const FilterChips: Story = {
|
||||
name: 'Interactive — Filter Chips',
|
||||
render: () => {
|
||||
const categories = [
|
||||
{ label: 'Chapel', icon: <ChurchIcon /> },
|
||||
{ label: 'Flowers', icon: <LocalFloristIcon /> },
|
||||
{ label: 'Transport', icon: <DirectionsCarIcon /> },
|
||||
{ label: 'Catering', icon: <RestaurantIcon /> },
|
||||
{ label: 'Music', icon: <MusicNoteIcon /> },
|
||||
{ label: 'Photography', icon: <PhotoCameraIcon /> },
|
||||
];
|
||||
|
||||
const FilterDemo = () => {
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set(['Chapel', 'Flowers']));
|
||||
|
||||
const toggle = (label: string) => {
|
||||
setSelected((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(label)) next.delete(label);
|
||||
else next.add(label);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ maxWidth: 450 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
|
||||
<FilterListIcon sx={{ color: 'text.secondary', fontSize: 20 }} />
|
||||
<Typography variant="label">Filter services</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||
{categories.map(({ label, icon }) => (
|
||||
<Chip
|
||||
key={label}
|
||||
label={label}
|
||||
icon={selected.has(label) ? <CheckIcon /> : icon}
|
||||
selected={selected.has(label)}
|
||||
onClick={() => toggle(label)}
|
||||
variant="outlined"
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 2 }}>
|
||||
Selected: {selected.size === 0 ? 'None' : Array.from(selected).join(', ')}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
return <FilterDemo />;
|
||||
},
|
||||
};
|
||||
|
||||
// ─── Interactive: Removable Tags ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Removable tag pattern — for selected items that can be dismissed.
|
||||
* Click the × icon to remove a tag.
|
||||
*/
|
||||
export const RemovableTags: Story = {
|
||||
name: 'Interactive — Removable Tags',
|
||||
render: () => {
|
||||
const TagDemo = () => {
|
||||
const [tags, setTags] = useState([
|
||||
'White roses',
|
||||
'Organ music',
|
||||
'Prayer cards',
|
||||
'Memorial video',
|
||||
'Guest book',
|
||||
]);
|
||||
|
||||
const remove = (tag: string) => {
|
||||
setTags((prev) => prev.filter((t) => t !== tag));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ maxWidth: 450 }}>
|
||||
<Typography variant="label" sx={{ mb: 1 }}>
|
||||
Selected additions ({tags.length})
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', minHeight: 32 }}>
|
||||
{tags.length === 0 ? (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
No items selected
|
||||
</Typography>
|
||||
) : (
|
||||
tags.map((tag) => (
|
||||
<Chip
|
||||
key={tag}
|
||||
label={tag}
|
||||
color="primary"
|
||||
onDelete={() => remove(tag)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
return <TagDemo />;
|
||||
},
|
||||
};
|
||||
|
||||
// ─── In Context: Service Option ─────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Chips used inside a ServiceOption-style card layout,
|
||||
* showing service tags and category labels.
|
||||
*/
|
||||
export const InServiceOption: Story = {
|
||||
name: 'In Context — Service Option',
|
||||
render: () => (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, maxWidth: 400 }}>
|
||||
<Card interactive>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start', mb: 1 }}>
|
||||
<Typography variant="h5">Chapel Ceremony</Typography>
|
||||
<Typography variant="display3" color="primary">$1,200</Typography>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||
Traditional chapel service with celebrant and music of your choosing.
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||
<Chip size="small" icon={<ChurchIcon />} label="Indoor" />
|
||||
<Chip size="small" icon={<MusicNoteIcon />} label="Music included" />
|
||||
<Chip size="small" label="60 minutes" />
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
<Card interactive>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start', mb: 1 }}>
|
||||
<Typography variant="h5">Graveside Service</Typography>
|
||||
<Typography variant="display3" color="primary">$900</Typography>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||
Intimate outdoor farewell at the burial site.
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||
<Chip size="small" label="Outdoor" />
|
||||
<Chip size="small" label="30 minutes" />
|
||||
<Chip size="small" color="primary" label="Popular" />
|
||||
</Box>
|
||||
</Card>
|
||||
</Box>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Complete Matrix ────────────────────────────────────────────────────────
|
||||
|
||||
/** Full variant × colour × size × state matrix for visual QA */
|
||||
export const CompleteMatrix: Story = {
|
||||
name: 'Complete Matrix',
|
||||
render: () => {
|
||||
const variants = ['filled', 'outlined'] as const;
|
||||
const colors = ['default', 'primary'] as const;
|
||||
const sizes = ['medium', 'small'] as const;
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
{variants.map((variant) => (
|
||||
<Box key={variant}>
|
||||
<Typography variant="label" sx={{ mb: 1, textTransform: 'capitalize' }}>
|
||||
{variant}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
{sizes.map((size) => (
|
||||
<Box key={size} sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
|
||||
<Box sx={{ width: 70, fontSize: 12, color: 'text.secondary' }}>{size}</Box>
|
||||
{colors.map((color) => (
|
||||
<React.Fragment key={color}>
|
||||
<Chip variant={variant} color={color} size={size} label={color} />
|
||||
<Chip variant={variant} color={color} size={size} label={`${color} + icon`} icon={<LocalOfferIcon />} />
|
||||
<Chip variant={variant} color={color} size={size} label={`${color} delete`} onDelete={() => {}} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
<Box>
|
||||
<Typography variant="label" sx={{ mb: 1 }}>Selected state</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
|
||||
<Chip selected label="Filled selected" onClick={() => {}} />
|
||||
<Chip selected variant="outlined" label="Outlined selected" onClick={() => {}} />
|
||||
<Chip selected label="With icon" icon={<CheckIcon />} onClick={() => {}} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
};
|
||||
76
src/components/atoms/Chip/Chip.tsx
Normal file
76
src/components/atoms/Chip/Chip.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import MuiChip from '@mui/material/Chip';
|
||||
import type { ChipProps as MuiChipProps } from '@mui/material/Chip';
|
||||
|
||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Props for the FA Chip component */
|
||||
export interface ChipProps extends MuiChipProps {
|
||||
/** Whether the chip is in a selected/active state */
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
// ─── Component ───────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Interactive tag for the FA design system.
|
||||
*
|
||||
* Pill-shaped chip for filtering, selection, and dismissible labels.
|
||||
* Used in ServiceOption (service tags), search filters, arrangement forms,
|
||||
* and anywhere users interact with categorised content.
|
||||
*
|
||||
* Unlike Badge (display-only), Chip is interactive — it can be clicked
|
||||
* (for toggling/selection) or deleted (for dismissing/removing).
|
||||
*
|
||||
* Variant mapping:
|
||||
* - `filled` (default) — soft tonal background, like Button's soft variant
|
||||
* - `outlined` — border-only, lighter visual weight
|
||||
*
|
||||
* Colour options:
|
||||
* - `default` — neutral grey (general tags, filters)
|
||||
* - `primary` — warm brand (selected states, category tags)
|
||||
*
|
||||
* Interactive modes:
|
||||
* - **Clickable** — pass `onClick` or set `clickable` for toggle/filter chips
|
||||
* - **Deletable** — pass `onDelete` for dismissible tags
|
||||
* - **Both** — clickable + deletable for full interactive chips
|
||||
* - **Static** — no onClick/onDelete for display-only tags (prefer Badge for pure status)
|
||||
*
|
||||
* Selected state:
|
||||
* - `selected` prop applies brand styling (filled primary bg or outlined primary border)
|
||||
*/
|
||||
export const Chip = React.forwardRef<HTMLDivElement, ChipProps>(
|
||||
(
|
||||
{
|
||||
selected = false,
|
||||
variant = 'filled',
|
||||
color,
|
||||
sx,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
// When selected, promote to primary colour unless explicitly set
|
||||
const resolvedColor = color ?? (selected ? 'primary' : 'default');
|
||||
|
||||
return (
|
||||
<MuiChip
|
||||
ref={ref}
|
||||
variant={variant}
|
||||
color={resolvedColor}
|
||||
sx={[
|
||||
selected && variant === 'outlined' && {
|
||||
borderWidth: 2,
|
||||
borderColor: 'var(--fa-color-brand-500)',
|
||||
backgroundColor: 'var(--fa-color-brand-50)',
|
||||
},
|
||||
...(Array.isArray(sx) ? sx : [sx]),
|
||||
]}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Chip.displayName = 'Chip';
|
||||
export default Chip;
|
||||
2
src/components/atoms/Chip/index.ts
Normal file
2
src/components/atoms/Chip/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { Chip, default } from './Chip';
|
||||
export type { ChipProps } from './Chip';
|
||||
@@ -17,6 +17,12 @@
|
||||
--fa-button-icon-size-sm: 16px; /** 16px icons in small buttons */
|
||||
--fa-button-icon-size-md: 18px; /** 18px icons in medium buttons */
|
||||
--fa-button-icon-size-lg: 20px; /** 20px icons in large buttons */
|
||||
--fa-chip-height-sm: 28px; /** Small — compact inline tags, tight layouts */
|
||||
--fa-chip-height-md: 32px; /** Medium — default interactive chips, filter tags */
|
||||
--fa-chip-icon-size-sm: 16px; /** 16px icons in small chips */
|
||||
--fa-chip-icon-size-md: 18px; /** 18px icons in medium chips */
|
||||
--fa-chip-delete-icon-size-sm: 14px; /** 14px delete icon in small chips */
|
||||
--fa-chip-delete-icon-size-md: 16px; /** 16px delete icon in medium chips */
|
||||
--fa-input-height-sm: 40px; /** Small — compact forms, admin layouts, matches Button medium height */
|
||||
--fa-input-height-md: 48px; /** Medium (default) — standard forms, matches Button large for alignment */
|
||||
--fa-input-icon-size-default: 20px; /** 20px — icon size inside input field, matches Figma trailing icon */
|
||||
@@ -242,6 +248,12 @@
|
||||
--fa-card-padding-compact: var(--fa-spacing-4); /** 16px — compact card padding (mobile, tight layouts) */
|
||||
--fa-card-shadow-default: var(--fa-shadow-md); /** Medium shadow — resting elevated card */
|
||||
--fa-card-shadow-hover: var(--fa-shadow-lg); /** High shadow — interactive card on hover */
|
||||
--fa-chip-padding-x-sm: var(--fa-spacing-2); /** 8px — compact horizontal padding */
|
||||
--fa-chip-padding-x-md: var(--fa-spacing-3); /** 12px — default horizontal padding */
|
||||
--fa-chip-font-size-sm: var(--fa-font-size-xs); /** 12px — small chip text */
|
||||
--fa-chip-font-size-md: var(--fa-font-size-sm); /** 14px — default chip text */
|
||||
--fa-chip-icon-gap-default: var(--fa-spacing-1); /** 4px icon-text gap */
|
||||
--fa-chip-border-radius-default: var(--fa-border-radius-full); /** Pill shape — fully rounded */
|
||||
--fa-input-padding-x-default: var(--fa-spacing-3); /** 12px — inner horizontal padding matching Figma design */
|
||||
--fa-input-padding-y-sm: var(--fa-spacing-2); /** 8px — compact vertical padding for small size */
|
||||
--fa-input-padding-y-md: var(--fa-spacing-3); /** 12px — standard vertical padding for medium size */
|
||||
|
||||
@@ -51,6 +51,18 @@ export const CardBorderSelected = "#ba834e"; // Brand border for selected/active
|
||||
export const CardBackgroundDefault = "#ffffff"; // White — standard card background (raised surface)
|
||||
export const CardBackgroundHover = "#fafafa"; // Subtle grey fill on hover — neutral.50 for soft interactive feedback
|
||||
export const CardBackgroundSelected = "#fef9f5"; // Warm tint for selected cards — brand.50 reinforces active state
|
||||
export const ChipHeightSm = "28px"; // Small — compact inline tags, tight layouts
|
||||
export const ChipHeightMd = "32px"; // Medium — default interactive chips, filter tags
|
||||
export const ChipPaddingXSm = "8px"; // 8px — compact horizontal padding
|
||||
export const ChipPaddingXMd = "12px"; // 12px — default horizontal padding
|
||||
export const ChipFontSizeSm = "0.75rem"; // 12px — small chip text
|
||||
export const ChipFontSizeMd = "0.875rem"; // 14px — default chip text
|
||||
export const ChipIconSizeSm = "16px"; // 16px icons in small chips
|
||||
export const ChipIconSizeMd = "18px"; // 18px icons in medium chips
|
||||
export const ChipDeleteIconSizeSm = "14px"; // 14px delete icon in small chips
|
||||
export const ChipDeleteIconSizeMd = "16px"; // 16px delete icon in medium chips
|
||||
export const ChipIconGapDefault = "4px"; // 4px icon-text gap
|
||||
export const ChipBorderRadiusDefault = "9999px"; // Pill shape — fully rounded
|
||||
export const InputHeightSm = "40px"; // Small — compact forms, admin layouts, matches Button medium height
|
||||
export const InputHeightMd = "48px"; // Medium (default) — standard forms, matches Button large for alignment
|
||||
export const InputPaddingXDefault = "12px"; // 12px — inner horizontal padding matching Figma design
|
||||
|
||||
@@ -577,6 +577,63 @@ export const theme = createTheme({
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiChip: {
|
||||
defaultProps: {
|
||||
size: 'medium',
|
||||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderRadius: parseInt(t.ChipBorderRadiusDefault, 10),
|
||||
fontWeight: 500,
|
||||
letterSpacing: '0.01em',
|
||||
transition: 'background-color 150ms ease-in-out, border-color 150ms ease-in-out, box-shadow 150ms ease-in-out',
|
||||
'&:focus-visible': {
|
||||
outline: `2px solid ${t.ColorInteractiveFocus}`,
|
||||
outlineOffset: '2px',
|
||||
},
|
||||
},
|
||||
sizeMedium: {
|
||||
height: parseInt(t.ChipHeightMd, 10),
|
||||
fontSize: t.ChipFontSizeMd,
|
||||
'& .MuiChip-icon': { fontSize: t.ChipIconSizeMd, marginLeft: t.ChipPaddingXMd },
|
||||
'& .MuiChip-deleteIcon': { fontSize: t.ChipDeleteIconSizeMd, marginRight: t.ChipPaddingXMd },
|
||||
},
|
||||
sizeSmall: {
|
||||
height: parseInt(t.ChipHeightSm, 10),
|
||||
fontSize: t.ChipFontSizeSm,
|
||||
'& .MuiChip-icon': { fontSize: t.ChipIconSizeSm, marginLeft: t.ChipPaddingXSm },
|
||||
'& .MuiChip-deleteIcon': { fontSize: t.ChipDeleteIconSizeSm, marginRight: t.ChipPaddingXSm },
|
||||
},
|
||||
filled: {
|
||||
'&.MuiChip-colorDefault': {
|
||||
backgroundColor: t.ColorNeutral200,
|
||||
color: t.ColorNeutral700,
|
||||
'&:hover': { backgroundColor: t.ColorNeutral300 },
|
||||
'& .MuiChip-deleteIcon': { color: t.ColorNeutral500, '&:hover': { color: t.ColorNeutral700 } },
|
||||
},
|
||||
'&.MuiChip-colorPrimary': {
|
||||
backgroundColor: t.ColorBrand200,
|
||||
color: t.ColorBrand700,
|
||||
'&:hover': { backgroundColor: t.ColorBrand300 },
|
||||
'& .MuiChip-deleteIcon': { color: t.ColorBrand400, '&:hover': { color: t.ColorBrand700 } },
|
||||
},
|
||||
},
|
||||
outlined: {
|
||||
'&.MuiChip-colorDefault': {
|
||||
borderColor: t.ColorNeutral300,
|
||||
color: t.ColorNeutral700,
|
||||
'&:hover': { backgroundColor: t.ColorNeutral100, borderColor: t.ColorNeutral400 },
|
||||
'& .MuiChip-deleteIcon': { color: t.ColorNeutral400, '&:hover': { color: t.ColorNeutral700 } },
|
||||
},
|
||||
'&.MuiChip-colorPrimary': {
|
||||
borderColor: t.ColorBrand400,
|
||||
color: t.ColorBrand700,
|
||||
'&:hover': { backgroundColor: t.ColorBrand100, borderColor: t.ColorBrand500 },
|
||||
'& .MuiChip-deleteIcon': { color: t.ColorBrand400, '&:hover': { color: t.ColorBrand700 } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user