Add VenueCard molecule — venue listing card for service venue select screen
- 3 component tokens (image.height, content.padding, content.gap) - Composes Card (interactive) + Typography, consistent with ProviderCard patterns - Hero image with role="img" aria-label for screen readers - Meta row: location (pin icon) + capacity with "guests" suffix for clarity - Price with "From" qualifier for transparency (split typography like ProviderCard) - 6 Storybook stories: Default, ListLayout, EdgeCases, Responsive, OnDifferentBackgrounds, InteractiveDemo - Critique score: 33/40 (Good) — P2 fixes applied (capacity label, price context, image a11y) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
281
src/components/molecules/VenueCard/VenueCard.stories.tsx
Normal file
281
src/components/molecules/VenueCard/VenueCard.stories.tsx
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { VenueCard } from './VenueCard';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
|
||||||
|
// Venue photo placeholders
|
||||||
|
const VENUE_CHAPEL =
|
||||||
|
'https://images.unsplash.com/photo-1545128485-c400e7702796?w=600&h=300&fit=crop&auto=format';
|
||||||
|
const VENUE_GARDEN =
|
||||||
|
'https://images.unsplash.com/photo-1510076857177-7470076d4098?w=600&h=300&fit=crop&auto=format';
|
||||||
|
const VENUE_HALL =
|
||||||
|
'https://images.unsplash.com/photo-1519167758481-83f550bb49b3?w=600&h=300&fit=crop&auto=format';
|
||||||
|
const VENUE_CHURCH =
|
||||||
|
'https://images.unsplash.com/photo-1438032005730-c779502df39b?w=600&h=300&fit=crop&auto=format';
|
||||||
|
const VENUE_BEACH =
|
||||||
|
'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=600&h=300&fit=crop&auto=format';
|
||||||
|
|
||||||
|
const meta: Meta<typeof VenueCard> = {
|
||||||
|
title: 'Molecules/VenueCard',
|
||||||
|
component: VenueCard,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
design: {
|
||||||
|
type: 'figma',
|
||||||
|
url: 'https://www.figma.com/design/XUDUrw4yMkEexBCCYHXUvT/Parsons?node-id=2997-83018',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
name: { control: 'text' },
|
||||||
|
location: { control: 'text' },
|
||||||
|
imageUrl: { control: 'text' },
|
||||||
|
capacity: { control: 'number' },
|
||||||
|
price: { control: 'number' },
|
||||||
|
onClick: { action: 'clicked' },
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<Box sx={{ width: 380 }}>
|
||||||
|
<Story />
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof VenueCard>;
|
||||||
|
|
||||||
|
// ─── Default ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Default — venue with all fields */
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
name: 'West Chapel',
|
||||||
|
imageUrl: VENUE_CHAPEL,
|
||||||
|
location: 'Strathfield',
|
||||||
|
capacity: 100,
|
||||||
|
price: 900,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── List Layout ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiple venue cards in a scrollable list on neutral.50 background.
|
||||||
|
* This is the primary use case — the venue select screen's left panel.
|
||||||
|
*/
|
||||||
|
export const ListLayout: Story = {
|
||||||
|
name: 'List Layout',
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 400,
|
||||||
|
maxHeight: 700,
|
||||||
|
overflow: 'auto',
|
||||||
|
backgroundColor: 'var(--fa-color-surface-subtle)',
|
||||||
|
p: 2,
|
||||||
|
borderRadius: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Story />
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
render: () => (
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
|
<VenueCard
|
||||||
|
name="West Chapel"
|
||||||
|
imageUrl={VENUE_CHAPEL}
|
||||||
|
location="Strathfield"
|
||||||
|
capacity={100}
|
||||||
|
price={900}
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
<VenueCard
|
||||||
|
name="Macquarie Park Memorial Gardens"
|
||||||
|
imageUrl={VENUE_GARDEN}
|
||||||
|
location="Macquarie Park"
|
||||||
|
capacity={250}
|
||||||
|
price={1200}
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
<VenueCard
|
||||||
|
name="Wollongong Community Hall"
|
||||||
|
imageUrl={VENUE_HALL}
|
||||||
|
location="Wollongong"
|
||||||
|
capacity={300}
|
||||||
|
price={750}
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
<VenueCard
|
||||||
|
name="St. Mary's Church"
|
||||||
|
imageUrl={VENUE_CHURCH}
|
||||||
|
location="Corrimal"
|
||||||
|
capacity={150}
|
||||||
|
price={600}
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
<VenueCard
|
||||||
|
name="Thirroul Beach Pavilion"
|
||||||
|
imageUrl={VENUE_BEACH}
|
||||||
|
location="Thirroul"
|
||||||
|
capacity={80}
|
||||||
|
price={1500}
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── Edge Cases ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Edge cases: long name, missing optional fields, extremes */
|
||||||
|
export const EdgeCases: Story = {
|
||||||
|
name: 'Edge Cases',
|
||||||
|
render: () => (
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
|
{/* Long name — tests maxLines truncation */}
|
||||||
|
<VenueCard
|
||||||
|
name="The Most Honourable Memorial Gardens and Reception Centre of Greater Wollongong"
|
||||||
|
imageUrl={VENUE_GARDEN}
|
||||||
|
location="Wollongong"
|
||||||
|
capacity={500}
|
||||||
|
price={2500}
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
{/* No capacity */}
|
||||||
|
<VenueCard
|
||||||
|
name="Private Residence"
|
||||||
|
imageUrl={VENUE_CHAPEL}
|
||||||
|
location="Austinmer"
|
||||||
|
price={0}
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
{/* No price */}
|
||||||
|
<VenueCard
|
||||||
|
name="Price On Application"
|
||||||
|
imageUrl={VENUE_HALL}
|
||||||
|
location="Sydney CBD"
|
||||||
|
capacity={400}
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
{/* Minimal — just name, image, location */}
|
||||||
|
<VenueCard
|
||||||
|
name="Minimal Venue"
|
||||||
|
imageUrl={VENUE_BEACH}
|
||||||
|
location="Kiama"
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── Responsive ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Cards at different viewport widths */
|
||||||
|
export const Responsive: Story = {
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<Box sx={{ width: 'auto' }}>
|
||||||
|
<Story />
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
render: () => (
|
||||||
|
<Box sx={{ display: 'flex', gap: 3, alignItems: 'start', flexWrap: 'wrap' }}>
|
||||||
|
{[280, 340, 420].map((width) => (
|
||||||
|
<Box key={width} sx={{ width }}>
|
||||||
|
<Box sx={{ mb: 1, fontSize: 12, color: 'text.secondary' }}>
|
||||||
|
{width}px
|
||||||
|
</Box>
|
||||||
|
<VenueCard
|
||||||
|
name="West Chapel"
|
||||||
|
imageUrl={VENUE_CHAPEL}
|
||||||
|
location="Strathfield"
|
||||||
|
capacity={100}
|
||||||
|
price={900}
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── On Different Backgrounds ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Cards on white vs grey surfaces */
|
||||||
|
export const OnDifferentBackgrounds: Story = {
|
||||||
|
name: 'On Different Backgrounds',
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<Box sx={{ width: 'auto' }}>
|
||||||
|
<Story />
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
render: () => (
|
||||||
|
<Box sx={{ display: 'flex', gap: 3 }}>
|
||||||
|
<Box sx={{ width: 360, p: 3, backgroundColor: 'background.default' }}>
|
||||||
|
<Box sx={{ mb: 1, fontSize: 12, color: 'text.secondary' }}>
|
||||||
|
White surface
|
||||||
|
</Box>
|
||||||
|
<VenueCard
|
||||||
|
name="West Chapel"
|
||||||
|
imageUrl={VENUE_CHAPEL}
|
||||||
|
location="Strathfield"
|
||||||
|
capacity={100}
|
||||||
|
price={900}
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 360,
|
||||||
|
p: 3,
|
||||||
|
backgroundColor: 'var(--fa-color-surface-subtle)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ mb: 1, fontSize: 12, color: 'text.secondary' }}>
|
||||||
|
Grey surface (neutral.50)
|
||||||
|
</Box>
|
||||||
|
<VenueCard
|
||||||
|
name="Macquarie Park Gardens"
|
||||||
|
imageUrl={VENUE_GARDEN}
|
||||||
|
location="Macquarie Park"
|
||||||
|
capacity={250}
|
||||||
|
price={1200}
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── Interactive Demo ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Click any card — fires onClick in Storybook actions panel */
|
||||||
|
export const InteractiveDemo: Story = {
|
||||||
|
name: 'Interactive — Click to Select',
|
||||||
|
render: () => (
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
|
<VenueCard
|
||||||
|
name="West Chapel"
|
||||||
|
imageUrl={VENUE_CHAPEL}
|
||||||
|
location="Strathfield"
|
||||||
|
capacity={100}
|
||||||
|
price={900}
|
||||||
|
onClick={() => alert('Selected: West Chapel')}
|
||||||
|
/>
|
||||||
|
<VenueCard
|
||||||
|
name="Macquarie Park Memorial Gardens"
|
||||||
|
imageUrl={VENUE_GARDEN}
|
||||||
|
location="Macquarie Park"
|
||||||
|
capacity={250}
|
||||||
|
price={1200}
|
||||||
|
onClick={() => alert('Selected: Macquarie Park')}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
};
|
||||||
163
src/components/molecules/VenueCard/VenueCard.tsx
Normal file
163
src/components/molecules/VenueCard/VenueCard.tsx
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import type { SxProps, Theme } from '@mui/material/styles';
|
||||||
|
import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined';
|
||||||
|
import PeopleOutlinedIcon from '@mui/icons-material/PeopleOutlined';
|
||||||
|
import { Card } from '../../atoms/Card';
|
||||||
|
import { Typography } from '../../atoms/Typography';
|
||||||
|
|
||||||
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Props for the FA VenueCard molecule */
|
||||||
|
export interface VenueCardProps {
|
||||||
|
/** Venue display name */
|
||||||
|
name: string;
|
||||||
|
/** Venue photo URL — always present for venues */
|
||||||
|
imageUrl: string;
|
||||||
|
/** Location text (suburb, city) */
|
||||||
|
location: string;
|
||||||
|
/** Venue capacity (seating/standing) */
|
||||||
|
capacity?: number;
|
||||||
|
/** Venue hire price in dollars */
|
||||||
|
price?: number;
|
||||||
|
/** Click handler — entire card is clickable */
|
||||||
|
onClick?: () => void;
|
||||||
|
/** MUI sx prop for style overrides */
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const IMAGE_HEIGHT = 'var(--fa-venue-card-image-height)';
|
||||||
|
const CONTENT_PADDING = 'var(--fa-venue-card-content-padding)';
|
||||||
|
const CONTENT_GAP = 'var(--fa-venue-card-content-gap)';
|
||||||
|
|
||||||
|
// ─── Component ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Venue listing card for the FA design system.
|
||||||
|
*
|
||||||
|
* Displays a service venue in the venue select screen's scrollable list.
|
||||||
|
* Venues always have a photo, location, and typically capacity + price.
|
||||||
|
* Simpler than ProviderCard — no verification tiers, no logo, no
|
||||||
|
* capability badges.
|
||||||
|
*
|
||||||
|
* Composes: Card (interactive, padding="none"), Typography.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ```tsx
|
||||||
|
* <VenueCard
|
||||||
|
* name="West Chapel"
|
||||||
|
* imageUrl="/images/west-chapel.jpg"
|
||||||
|
* location="Strathfield"
|
||||||
|
* capacity={100}
|
||||||
|
* price={900}
|
||||||
|
* onClick={() => navigate(`/venues/west-chapel`)}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const VenueCard = React.forwardRef<HTMLDivElement, VenueCardProps>(
|
||||||
|
({ name, imageUrl, location, capacity, price, onClick, sx }, ref) => {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
ref={ref}
|
||||||
|
interactive
|
||||||
|
padding="none"
|
||||||
|
onClick={onClick}
|
||||||
|
sx={[
|
||||||
|
{
|
||||||
|
overflow: 'hidden',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'background.paper',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...(Array.isArray(sx) ? sx : [sx]),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{/* ── Image area ── */}
|
||||||
|
<Box
|
||||||
|
role="img"
|
||||||
|
aria-label={`Photo of ${name}`}
|
||||||
|
sx={{
|
||||||
|
height: IMAGE_HEIGHT,
|
||||||
|
backgroundImage: `url(${imageUrl})`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
backgroundColor: 'var(--fa-color-surface-subtle)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* ── Content area ── */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: CONTENT_GAP,
|
||||||
|
p: CONTENT_PADDING,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Venue name */}
|
||||||
|
<Typography variant="h5" maxLines={2}>
|
||||||
|
{name}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* Meta row: location + capacity */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 2,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Location */}
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||||
|
<LocationOnOutlinedIcon
|
||||||
|
sx={{ fontSize: 14, color: 'text.secondary' }}
|
||||||
|
/>
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
{location}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Capacity */}
|
||||||
|
{capacity != null && (
|
||||||
|
<Box
|
||||||
|
sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}
|
||||||
|
aria-label={`Capacity: ${capacity} guests`}
|
||||||
|
>
|
||||||
|
<PeopleOutlinedIcon
|
||||||
|
sx={{ fontSize: 14, color: 'text.secondary' }}
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
{capacity} guests
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Price */}
|
||||||
|
{price != null && (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'baseline', gap: 0.5, mt: 0.5 }}>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
From
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
component="span"
|
||||||
|
color="primary"
|
||||||
|
sx={{ fontWeight: 600 }}
|
||||||
|
>
|
||||||
|
${price.toLocaleString('en-AU')}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
VenueCard.displayName = 'VenueCard';
|
||||||
|
export default VenueCard;
|
||||||
2
src/components/molecules/VenueCard/index.ts
Normal file
2
src/components/molecules/VenueCard/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { VenueCard, default } from './VenueCard';
|
||||||
|
export type { VenueCardProps } from './VenueCard';
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
--fa-switch-track-width: 44px; /** Track width — slightly narrower than Figma 52px for better proportion with 44px touch target */
|
--fa-switch-track-width: 44px; /** Track width — slightly narrower than Figma 52px for better proportion with 44px touch target */
|
||||||
--fa-switch-track-height: 24px; /** Track height */
|
--fa-switch-track-height: 24px; /** Track height */
|
||||||
--fa-switch-thumb-size: 18px; /** Thumb diameter — sits inside the track with 3px inset */
|
--fa-switch-thumb-size: 18px; /** Thumb diameter — sits inside the track with 3px inset */
|
||||||
|
--fa-venue-card-image-height: 180px; /** Fixed image height — matches ProviderCard for consistent list layout when both card types appear in search results */
|
||||||
--fa-color-brand-50: #fef9f5; /** Lightest warm tint — warm section backgrounds */
|
--fa-color-brand-50: #fef9f5; /** Lightest warm tint — warm section backgrounds */
|
||||||
--fa-color-brand-100: #f7ecdf; /** Light warm — hover backgrounds, subtle fills */
|
--fa-color-brand-100: #f7ecdf; /** Light warm — hover backgrounds, subtle fills */
|
||||||
--fa-color-brand-200: #ebdac8; /** Warm light — secondary backgrounds, divider tones */
|
--fa-color-brand-200: #ebdac8; /** Warm light — secondary backgrounds, divider tones */
|
||||||
@@ -271,6 +272,8 @@
|
|||||||
--fa-provider-card-content-padding: var(--fa-spacing-3); /** 12px content padding — tight to keep card compact in listing layout */
|
--fa-provider-card-content-padding: var(--fa-spacing-3); /** 12px content padding — tight to keep card compact in listing layout */
|
||||||
--fa-provider-card-content-gap: var(--fa-spacing-1); /** 4px vertical gap between content rows — tight for compact listing cards */
|
--fa-provider-card-content-gap: var(--fa-spacing-1); /** 4px vertical gap between content rows — tight for compact listing cards */
|
||||||
--fa-switch-track-border-radius: var(--fa-border-radius-full); /** Pill shape */
|
--fa-switch-track-border-radius: var(--fa-border-radius-full); /** Pill shape */
|
||||||
|
--fa-venue-card-content-padding: var(--fa-spacing-3); /** 12px content padding — matches ProviderCard for visual consistency */
|
||||||
|
--fa-venue-card-content-gap: var(--fa-spacing-1); /** 4px vertical gap between content rows — tight for compact listing cards */
|
||||||
--fa-color-text-primary: var(--fa-color-neutral-800); /** Primary text — body content, headings. Cool charcoal (#2C2E35) for comfortable extended reading */
|
--fa-color-text-primary: var(--fa-color-neutral-800); /** Primary text — body content, headings. Cool charcoal (#2C2E35) for comfortable extended reading */
|
||||||
--fa-color-text-secondary: var(--fa-color-neutral-600); /** Secondary text — helper text, descriptions, metadata, less prominent content */
|
--fa-color-text-secondary: var(--fa-color-neutral-600); /** Secondary text — helper text, descriptions, metadata, less prominent content */
|
||||||
--fa-color-text-tertiary: var(--fa-color-neutral-500); /** Tertiary text — placeholders, timestamps, attribution, meta information */
|
--fa-color-text-tertiary: var(--fa-color-neutral-500); /** Tertiary text — placeholders, timestamps, attribution, meta information */
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ export const SwitchTrackWidth = "44px"; // Track width — slightly narrower tha
|
|||||||
export const SwitchTrackHeight = "24px"; // Track height
|
export const SwitchTrackHeight = "24px"; // Track height
|
||||||
export const SwitchTrackBorderRadius = "9999px"; // Pill shape
|
export const SwitchTrackBorderRadius = "9999px"; // Pill shape
|
||||||
export const SwitchThumbSize = "18px"; // Thumb diameter — sits inside the track with 3px inset
|
export const SwitchThumbSize = "18px"; // Thumb diameter — sits inside the track with 3px inset
|
||||||
|
export const VenueCardImageHeight = "180px"; // Fixed image height — matches ProviderCard for consistent list layout when both card types appear in search results
|
||||||
|
export const VenueCardContentPadding = "12px"; // 12px content padding — matches ProviderCard for visual consistency
|
||||||
|
export const VenueCardContentGap = "4px"; // 4px vertical gap between content rows — tight for compact listing cards
|
||||||
export const ColorBrand50 = "#fef9f5"; // Lightest warm tint — warm section backgrounds
|
export const ColorBrand50 = "#fef9f5"; // Lightest warm tint — warm section backgrounds
|
||||||
export const ColorBrand100 = "#f7ecdf"; // Light warm — hover backgrounds, subtle fills
|
export const ColorBrand100 = "#f7ecdf"; // Light warm — hover backgrounds, subtle fills
|
||||||
export const ColorBrand200 = "#ebdac8"; // Warm light — secondary backgrounds, divider tones
|
export const ColorBrand200 = "#ebdac8"; // Warm light — secondary backgrounds, divider tones
|
||||||
|
|||||||
23
tokens/component/venueCard.json
Normal file
23
tokens/component/venueCard.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"venueCard": {
|
||||||
|
"$description": "VenueCard molecule tokens — listing card for service venues on the venue select screen. Always has a photo, location, capacity, and price. Simpler than ProviderCard — no verification tiers or logo.",
|
||||||
|
"image": {
|
||||||
|
"$type": "dimension",
|
||||||
|
"$description": "Hero image area dimensions.",
|
||||||
|
"height": { "$value": "180px", "$description": "Fixed image height — matches ProviderCard for consistent list layout when both card types appear in search results" }
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"$description": "Content area spacing.",
|
||||||
|
"padding": {
|
||||||
|
"$type": "dimension",
|
||||||
|
"$value": "{spacing.3}",
|
||||||
|
"$description": "12px content padding — matches ProviderCard for visual consistency"
|
||||||
|
},
|
||||||
|
"gap": {
|
||||||
|
"$type": "dimension",
|
||||||
|
"$value": "{spacing.1}",
|
||||||
|
"$description": "4px vertical gap between content rows — tight for compact listing cards"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user