UNPKG

@furystack/shades-common-components

Version:

Common UI components for FuryStack Shades

183 lines 7.07 kB
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