@furystack/shades-common-components
Version:
Common UI components for FuryStack Shades
183 lines • 7.07 kB
JavaScript
import { Shade, createComponent } from '@furystack/shades';
import { buildTransition, cssVariableTheme } from '../services/css-variable-theme.js';
/**
* A versatile surface component for grouping related content and actions.
* Supports elevation and outlined variants with optional hover interactions.
*
* Compose with CardHeader, CardContent, CardMedia, and CardActions for structured layouts.
*/
export const Card = Shade({
customElementName: 'shade-card',
css: {
display: 'flex',
fontFamily: cssVariableTheme.typography.fontFamily,
flexDirection: 'column',
borderRadius: cssVariableTheme.shape.borderRadius.md,
background: cssVariableTheme.background.paper,
backgroundImage: cssVariableTheme.background.paperImage,
color: cssVariableTheme.text.primary,
overflow: 'hidden',
transition: buildTransition(['box-shadow', cssVariableTheme.transitions.duration.normal, cssVariableTheme.transitions.easing.default], ['transform', cssVariableTheme.transitions.duration.normal, cssVariableTheme.transitions.easing.default]),
// Elevation variant shadows
'&[data-variant="elevation"][data-elevation="0"]': {
boxShadow: cssVariableTheme.shadows.none,
},
'&[data-variant="elevation"][data-elevation="1"]': {
boxShadow: cssVariableTheme.shadows.sm,
},
'&[data-variant="elevation"][data-elevation="2"]': {
boxShadow: cssVariableTheme.shadows.md,
},
'&[data-variant="elevation"][data-elevation="3"]': {
boxShadow: cssVariableTheme.shadows.lg,
},
// Outlined variant
'&[data-variant="outlined"]': {
boxShadow: cssVariableTheme.shadows.none,
border: `1px solid ${cssVariableTheme.action.subtleBorder}`,
},
// Clickable state
'&[data-clickable]': {
cursor: 'pointer',
},
'&[data-clickable]:hover': {
transform: 'translateY(-2px)',
},
'&[data-clickable][data-variant="elevation"]:hover': {
boxShadow: cssVariableTheme.shadows.lg,
},
'&[data-clickable][data-variant="outlined"]:hover': {
borderColor: cssVariableTheme.text.secondary,
},
},
render: ({ props, children, useHostProps }) => {
const { elevation = 1, variant = 'elevation', clickable, style, ...rest } = props;
useHostProps({
'data-variant': variant,
'data-elevation': elevation.toString(),
'data-clickable': clickable || rest.onclick ? '' : undefined,
...(style ? { style: style } : {}),
});
return createComponent(createComponent, null, children);
},
});
/**
* Displays a title, optional subheader, avatar, and action area at the top of a Card.
*/
export const CardHeader = Shade({
customElementName: 'shade-card-header',
css: {
display: 'flex',
alignItems: 'center',
padding: cssVariableTheme.spacing.md,
gap: cssVariableTheme.spacing.md,
'& .card-header-avatar': {
flexShrink: '0',
},
'& .card-header-content': {
flex: '1',
minWidth: '0',
},
'& .card-header-title': {
margin: '0',
fontFamily: cssVariableTheme.typography.fontFamily,
fontSize: cssVariableTheme.typography.fontSize.lg,
fontWeight: cssVariableTheme.typography.fontWeight.semibold,
lineHeight: cssVariableTheme.typography.lineHeight.tight,
color: cssVariableTheme.text.primary,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
},
'& .card-header-subheader': {
margin: '0',
marginTop: cssVariableTheme.spacing.xs,
fontFamily: cssVariableTheme.typography.fontFamily,
fontSize: cssVariableTheme.typography.fontSize.sm,
fontWeight: cssVariableTheme.typography.fontWeight.normal,
lineHeight: cssVariableTheme.typography.lineHeight.normal,
color: cssVariableTheme.text.secondary,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
},
'& .card-header-action': {
flexShrink: '0',
marginLeft: 'auto',
alignSelf: 'flex-start',
},
},
render: ({ props }) => {
const { title, subheader, avatar, action } = props;
return (createComponent(createComponent, null,
avatar ? createComponent("div", { className: "card-header-avatar" }, avatar) : null,
createComponent("div", { className: "card-header-content" },
createComponent("div", { className: "card-header-title" }, title),
subheader ? createComponent("div", { className: "card-header-subheader" }, subheader) : null),
action ? createComponent("div", { className: "card-header-action" }, action) : null));
},
});
/**
* Provides padded content area within a Card.
*/
export const CardContent = Shade({
customElementName: 'shade-card-content',
css: {
display: 'block',
padding: `0 ${cssVariableTheme.spacing.md} ${cssVariableTheme.spacing.md}`,
fontFamily: cssVariableTheme.typography.fontFamily,
fontSize: cssVariableTheme.typography.fontSize.md,
lineHeight: cssVariableTheme.typography.lineHeight.normal,
color: cssVariableTheme.text.secondary,
'&:first-child': {
paddingTop: cssVariableTheme.spacing.md,
},
},
render: ({ children }) => {
return createComponent(createComponent, null, children);
},
});
/**
* Displays an image or media element within a Card.
*/
export const CardMedia = Shade({
customElementName: 'shade-card-media',
css: {
display: 'block',
overflow: 'hidden',
'& img': {
display: 'block',
width: '100%',
height: '100%',
objectFit: 'cover',
objectPosition: 'center',
},
},
render: ({ props, useHostProps }) => {
const { image, alt = '', height = '200px' } = props;
useHostProps({ style: { height } });
return createComponent("img", { src: image, alt: alt });
},
});
/**
* Provides a row of actions (buttons, links) at the bottom of a Card.
*/
export const CardActions = Shade({
customElementName: 'shade-card-actions',
css: {
display: 'flex',
alignItems: 'center',
padding: cssVariableTheme.spacing.sm,
gap: cssVariableTheme.spacing.sm,
'&[data-disable-spacing]': {
justifyContent: 'flex-end',
},
},
render: ({ props, children, useHostProps }) => {
useHostProps({
'data-disable-spacing': props.disableSpacing ? '' : undefined,
});
return createComponent(createComponent, null, children);
},
});
//# sourceMappingURL=card.js.map