Refine ProviderCard v2 — logo, price, badges, footer, unverified treatment
- Logo: circle → 64px rounded rectangle (8px radius), positioned fully inside image area with white border + shadow - Footer removed — redundant since whole card is clickable and price is already in content area - Price: split "Packages from" (body2) + price (h6/500wt) for lighter ecommerce feel, replaces blocky labelLg/700 - Verified badge bumped to medium size for visibility - Capability badge: medium size, trailing info icon + capabilityDescription tooltip prop for plain-language definitions on hover - Unverified cards: 3px top accent bar, list on neutral.50 background - Caption/CaptionSm weight: 400 → 500 system-wide (extends D019) - Meta row: body2 → caption size for clearer tertiary hierarchy Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,7 +37,7 @@ duplicates) and MUST update it after completing one.
|
|||||||
| Component | Status | Composed of | Notes |
|
| Component | Status | Composed of | Notes |
|
||||||
|-----------|--------|-------------|-------|
|
|-----------|--------|-------------|-------|
|
||||||
| FormField | planned | Input + Typography (label) + Typography (helper) | Standard form field with label and validation |
|
| FormField | planned | Input + Typography (label) + Typography (helper) | Standard form field with label and validation |
|
||||||
| ProviderCard | review | Card + Typography + Badge | Provider listing card. Verified: image + logo + "Trusted Partner" badge. Unverified: text-only. Capability badges (arbitrary label + colour). Footer with "Packages from $X >". 7 component tokens. |
|
| ProviderCard | review | Card + Typography + Badge + Tooltip | Provider listing card. Verified: image + logo (64px rounded rect) + "Verified" badge. Unverified: text-only with top accent bar. Capability badges with info icon + tooltip. Price split typography. No footer. 4 component tokens. |
|
||||||
| VenueCard | planned | Card + Typography + Badge | Venue listing card. Always has photo, location, capacity, price. Simpler than ProviderCard. |
|
| VenueCard | planned | Card + Typography + Badge | Venue listing card. Always has photo, location, capacity, price. Simpler than ProviderCard. |
|
||||||
| MapCard | planned | Card + Typography + Badge | Compact horizontal map popup card. Deferred until map integration. |
|
| MapCard | planned | Card + Typography + Badge | Compact horizontal map popup card. Deferred until map integration. |
|
||||||
| ServiceOption | planned | Card + Typography + Chip + Icon | Selectable service item |
|
| ServiceOption | planned | Card + Typography + Chip + Icon | Selectable service item |
|
||||||
|
|||||||
@@ -537,7 +537,46 @@ Each entry follows this structure:
|
|||||||
- **Planned (5 organisms):** ServiceSelector, PricingTable, ArrangementForm, Navigation, Footer
|
- **Planned (5 organisms):** ServiceSelector, PricingTable, ArrangementForm, Navigation, Footer
|
||||||
|
|
||||||
**Next steps:**
|
**Next steps:**
|
||||||
- User to review ProviderCard in Storybook — especially ListLayout story for verified/unverified alignment
|
- ~~User to review ProviderCard in Storybook~~ ✓ Feedback received, iterated in session 25m
|
||||||
|
|
||||||
|
### Session 2026-03-25m — ProviderCard v2 refinements from user feedback
|
||||||
|
|
||||||
|
**Agent(s):** Claude Opus (via conversation)
|
||||||
|
|
||||||
|
**Work completed:**
|
||||||
|
- Iterated on ProviderCard based on 5 feedback items from user review
|
||||||
|
- **Logo**: circle → 64px rounded rectangle (8px radius, `borderRadius.md`). Positioned fully inside image area (absolute, bottom-left) with white border + shadow. Eliminates dead space — name sits directly below image.
|
||||||
|
- **Footer removed**: "View packages >" bar was redundant (whole card is clickable, price already in content). Removed 3 footer tokens (`footer.background`, `.paddingX`, `.paddingY`).
|
||||||
|
- **Price typography**: Split into "Packages from" (body2, 14px, secondary) + price (h6, 16px, weight 500, primary). Lighter, streamlined ecommerce feel vs old blocky labelLg/700.
|
||||||
|
- **Verified badge**: Bumped from `size="small"` to `size="medium"` for visibility.
|
||||||
|
- **Capability badge**: Bumped to `size="medium"`. Added trailing `InfoOutlinedIcon` and new `capabilityDescription` prop — wraps badge in MUI Tooltip on hover/focus for plain-language definitions of industry jargon.
|
||||||
|
- **Unverified card differentiation**: 3px `borderTop` (neutral.200) as visual anchor. ListLayout story now on neutral.50 background.
|
||||||
|
- **Caption weight fix (system-wide)**: Caption and CaptionSm bumped from fontWeight 400 (Regular) → 500 (Medium) in semantic typography tokens. Aligns with D019 — user found Regular too light for Montserrat.
|
||||||
|
- **Meta row**: Location/reviews dropped from body2 (14px) → caption (12px) with 14px icons for clearer tertiary hierarchy.
|
||||||
|
- Updated all stories with `capabilityDescription` tooltips for 3 capability types
|
||||||
|
- Preflight passed all 5 checks
|
||||||
|
|
||||||
|
**Decisions made:**
|
||||||
|
- Logo positioned fully inside image (no overlap into content) — tried flex-row overlap approach first but it "dug into" the photo and pushed the name sideways
|
||||||
|
- Footer removal: card's interactive hover + price in content are sufficient affordance
|
||||||
|
- Caption weight 500: extends D019 (body weight = Medium) to all Montserrat text variants. Only display (Noto Serif SC) stays at 400 per D018.
|
||||||
|
- Info icon for capability badges: trailing position (after label), `cursor: help` when tooltip active
|
||||||
|
|
||||||
|
**Token changes:**
|
||||||
|
- `providerCard.logo.size`: 56px → 64px
|
||||||
|
- `providerCard.logo.borderRadius`: new, → `{borderRadius.md}` (8px)
|
||||||
|
- `providerCard.footer.*`: 3 tokens removed (footer eliminated)
|
||||||
|
- `typography.caption.fontWeight`: `{fontWeight.regular}` → `{fontWeight.medium}`
|
||||||
|
- `typography.captionSm.fontWeight`: `{fontWeight.regular}` → `{fontWeight.medium}`
|
||||||
|
|
||||||
|
**Component status at end of session:**
|
||||||
|
- **Done (8):** Button, Typography, Input, Card, Badge, Chip, Switch, Radio
|
||||||
|
- **Review (1 molecule):** ProviderCard (v2, user approved direction)
|
||||||
|
- **Planned (6 atoms):** IconButton, Icon, Avatar, Divider, ColourToggle, Slider, Link
|
||||||
|
- **Planned (5 molecules):** VenueCard, MapCard, ServiceOption, SearchBar, StepIndicator, FormField
|
||||||
|
- **Planned (5 organisms):** ServiceSelector, PricingTable, ArrangementForm, Navigation, Footer
|
||||||
|
|
||||||
|
**Next steps:**
|
||||||
- Build VenueCard molecule (simpler — always has photo, location, capacity, price)
|
- Build VenueCard molecule (simpler — always has photo, location, capacity, price)
|
||||||
- Consider MapCard as a deferred item until map integration
|
- Consider MapCard as a deferred item until map integration
|
||||||
- Address P2 audit issues in a future cleanup pass
|
- Address P2 audit issues in a future cleanup pass
|
||||||
|
|||||||
@@ -272,9 +272,7 @@ the correct token for any design property.
|
|||||||
| Token path | Value / Reference | Used by | Description |
|
| Token path | Value / Reference | Used by | Description |
|
||||||
|-----------|-----------|---------|-------------|
|
|-----------|-----------|---------|-------------|
|
||||||
| providerCard.image.height | 180px | ProviderCard | Hero image fixed height |
|
| providerCard.image.height | 180px | ProviderCard | Hero image fixed height |
|
||||||
| providerCard.logo.size | 48px | ProviderCard | Logo circle diameter |
|
| providerCard.logo.size | 64px | ProviderCard | Logo width/height — rounded rectangle inside image area |
|
||||||
| providerCard.footer.background | → color.brand.100 (#F7ECDF) | ProviderCard | Warm beige footer bar |
|
| providerCard.logo.borderRadius | → borderRadius.md (8px) | ProviderCard | Rounded rectangle corners |
|
||||||
| providerCard.footer.paddingX | → spacing.4 (16px) | ProviderCard | Footer horizontal padding |
|
| providerCard.content.padding | → spacing.3 (12px) | ProviderCard | Content area padding |
|
||||||
| providerCard.footer.paddingY | → spacing.3 (12px) | ProviderCard | Footer vertical padding |
|
| providerCard.content.gap | → spacing.1 (4px) | ProviderCard | Gap between content rows |
|
||||||
| providerCard.content.padding | → spacing.4 (16px) | ProviderCard | Content area padding |
|
|
||||||
| providerCard.content.gap | → spacing.2 (8px) | ProviderCard | Gap between content rows |
|
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|||||||
import { ProviderCard } from './ProviderCard';
|
import { ProviderCard } from './ProviderCard';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
|
|
||||||
// Placeholder images for stories (grey boxes via data URIs)
|
// Placeholder images for stories
|
||||||
const HERO_PLACEHOLDER =
|
const HERO_PLACEHOLDER =
|
||||||
'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=600&h=300&fit=crop&auto=format';
|
'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=600&h=300&fit=crop&auto=format';
|
||||||
const HERO_PLACEHOLDER_2 =
|
const HERO_PLACEHOLDER_2 =
|
||||||
'https://images.unsplash.com/photo-1497366216548-37526070297c?w=600&h=300&fit=crop&auto=format';
|
'https://images.unsplash.com/photo-1497366216548-37526070297c?w=600&h=300&fit=crop&auto=format';
|
||||||
const HERO_PLACEHOLDER_3 =
|
const HERO_PLACEHOLDER_3 =
|
||||||
'https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?w=600&h=300&fit=crop&auto=format';
|
'https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?w=600&h=300&fit=crop&auto=format';
|
||||||
// Simple grey circle for logo placeholder
|
// Rounded-rect logo placeholder (matches new logo shape)
|
||||||
const LOGO_PLACEHOLDER =
|
const LOGO_PLACEHOLDER =
|
||||||
'data:image/svg+xml,' +
|
'data:image/svg+xml,' +
|
||||||
encodeURIComponent(
|
encodeURIComponent(
|
||||||
'<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96"><circle cx="48" cy="48" r="48" fill="%23E8E8E8"/><text x="48" y="54" text-anchor="middle" font-family="sans-serif" font-size="14" fill="%23737373">Logo</text></svg>',
|
'<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96"><rect width="96" height="96" rx="12" fill="%23E8E8E8"/><text x="48" y="54" text-anchor="middle" font-family="sans-serif" font-size="14" fill="%23737373">Logo</text></svg>',
|
||||||
);
|
);
|
||||||
|
|
||||||
const meta: Meta<typeof ProviderCard> = {
|
const meta: Meta<typeof ProviderCard> = {
|
||||||
@@ -38,6 +38,7 @@ const meta: Meta<typeof ProviderCard> = {
|
|||||||
control: 'select',
|
control: 'select',
|
||||||
options: ['default', 'success', 'warning', 'error', 'info'],
|
options: ['default', 'success', 'warning', 'error', 'info'],
|
||||||
},
|
},
|
||||||
|
capabilityDescription: { control: 'text' },
|
||||||
startingPrice: { control: 'number' },
|
startingPrice: { control: 'number' },
|
||||||
onClick: { action: 'clicked' },
|
onClick: { action: 'clicked' },
|
||||||
},
|
},
|
||||||
@@ -67,6 +68,7 @@ export const Default: Story = {
|
|||||||
reviewCount: 127,
|
reviewCount: 127,
|
||||||
capabilityLabel: 'Online Arrangement',
|
capabilityLabel: 'Online Arrangement',
|
||||||
capabilityColor: 'success',
|
capabilityColor: 'success',
|
||||||
|
capabilityDescription: 'Complete your arrangement entirely online — no in-person visit required.',
|
||||||
startingPrice: 900,
|
startingPrice: 900,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -87,6 +89,7 @@ export const VerifiedProvider: Story = {
|
|||||||
reviewCount={127}
|
reviewCount={127}
|
||||||
capabilityLabel="Online Arrangement"
|
capabilityLabel="Online Arrangement"
|
||||||
capabilityColor="success"
|
capabilityColor="success"
|
||||||
|
capabilityDescription="Complete your arrangement entirely online — no in-person visit required."
|
||||||
startingPrice={900}
|
startingPrice={900}
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
/>
|
/>
|
||||||
@@ -95,7 +98,7 @@ export const VerifiedProvider: Story = {
|
|||||||
|
|
||||||
// ─── Unverified Provider ────────────────────────────────────────────────────
|
// ─── Unverified Provider ────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** Unverified provider — text only, no image/logo/trusted badge */
|
/** Unverified provider — text only with top accent bar, no image/logo/trusted badge */
|
||||||
export const UnverifiedProvider: Story = {
|
export const UnverifiedProvider: Story = {
|
||||||
name: 'Unverified Provider',
|
name: 'Unverified Provider',
|
||||||
render: () => (
|
render: () => (
|
||||||
@@ -106,6 +109,7 @@ export const UnverifiedProvider: Story = {
|
|||||||
reviewCount={43}
|
reviewCount={43}
|
||||||
capabilityLabel="Outsourced"
|
capabilityLabel="Outsourced"
|
||||||
capabilityColor="default"
|
capabilityColor="default"
|
||||||
|
capabilityDescription="This provider uses a third-party service to manage arrangements."
|
||||||
startingPrice={1200}
|
startingPrice={1200}
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
/>
|
/>
|
||||||
@@ -115,15 +119,25 @@ export const UnverifiedProvider: Story = {
|
|||||||
// ─── List Layout ────────────────────────────────────────────────────────────
|
// ─── List Layout ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mixed verified and unverified providers in a scrollable list.
|
* Mixed verified and unverified providers in a scrollable list on
|
||||||
* This is the primary use case — text alignment should be consistent
|
* neutral.50 background. This is the primary use case — text alignment
|
||||||
* across both card types for scan readability.
|
* should be consistent across both card types for scan readability.
|
||||||
|
* Unverified cards have a top accent bar for visibility.
|
||||||
*/
|
*/
|
||||||
export const ListLayout: Story = {
|
export const ListLayout: Story = {
|
||||||
name: 'List Layout — Mixed',
|
name: 'List Layout — Mixed',
|
||||||
decorators: [
|
decorators: [
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<Box sx={{ width: 400, maxHeight: 700, overflow: 'auto' }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 400,
|
||||||
|
maxHeight: 700,
|
||||||
|
overflow: 'auto',
|
||||||
|
backgroundColor: 'var(--fa-color-surface-subtle)',
|
||||||
|
p: 2,
|
||||||
|
borderRadius: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Story />
|
<Story />
|
||||||
</Box>
|
</Box>
|
||||||
),
|
),
|
||||||
@@ -140,6 +154,7 @@ export const ListLayout: Story = {
|
|||||||
reviewCount={127}
|
reviewCount={127}
|
||||||
capabilityLabel="Online Arrangement"
|
capabilityLabel="Online Arrangement"
|
||||||
capabilityColor="success"
|
capabilityColor="success"
|
||||||
|
capabilityDescription="Complete your arrangement entirely online — no in-person visit required."
|
||||||
startingPrice={900}
|
startingPrice={900}
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
/>
|
/>
|
||||||
@@ -153,6 +168,7 @@ export const ListLayout: Story = {
|
|||||||
reviewCount={89}
|
reviewCount={89}
|
||||||
capabilityLabel="Online Arrangement"
|
capabilityLabel="Online Arrangement"
|
||||||
capabilityColor="success"
|
capabilityColor="success"
|
||||||
|
capabilityDescription="Complete your arrangement entirely online — no in-person visit required."
|
||||||
startingPrice={1100}
|
startingPrice={1100}
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
/>
|
/>
|
||||||
@@ -163,6 +179,7 @@ export const ListLayout: Story = {
|
|||||||
reviewCount={43}
|
reviewCount={43}
|
||||||
capabilityLabel="Outsourced"
|
capabilityLabel="Outsourced"
|
||||||
capabilityColor="default"
|
capabilityColor="default"
|
||||||
|
capabilityDescription="This provider uses a third-party service to manage arrangements."
|
||||||
startingPrice={1200}
|
startingPrice={1200}
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
/>
|
/>
|
||||||
@@ -173,6 +190,7 @@ export const ListLayout: Story = {
|
|||||||
reviewCount={18}
|
reviewCount={18}
|
||||||
capabilityLabel="Partial Arrangement"
|
capabilityLabel="Partial Arrangement"
|
||||||
capabilityColor="warning"
|
capabilityColor="warning"
|
||||||
|
capabilityDescription="Some steps can be completed online, but an in-person visit is required to finalise."
|
||||||
startingPrice={950}
|
startingPrice={950}
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
/>
|
/>
|
||||||
@@ -186,6 +204,7 @@ export const ListLayout: Story = {
|
|||||||
reviewCount={203}
|
reviewCount={203}
|
||||||
capabilityLabel="Online Arrangement"
|
capabilityLabel="Online Arrangement"
|
||||||
capabilityColor="success"
|
capabilityColor="success"
|
||||||
|
capabilityDescription="Complete your arrangement entirely online — no in-person visit required."
|
||||||
startingPrice={1500}
|
startingPrice={1500}
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
/>
|
/>
|
||||||
@@ -195,7 +214,7 @@ export const ListLayout: Story = {
|
|||||||
|
|
||||||
// ─── Capability Variants ────────────────────────────────────────────────────
|
// ─── Capability Variants ────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** Three capability badge colours */
|
/** Three capability badge colours with hover tooltips */
|
||||||
export const CapabilityVariants: Story = {
|
export const CapabilityVariants: Story = {
|
||||||
name: 'Capability Variants',
|
name: 'Capability Variants',
|
||||||
decorators: [
|
decorators: [
|
||||||
@@ -212,6 +231,7 @@ export const CapabilityVariants: Story = {
|
|||||||
location="Sydney"
|
location="Sydney"
|
||||||
capabilityLabel="Online Arrangement"
|
capabilityLabel="Online Arrangement"
|
||||||
capabilityColor="success"
|
capabilityColor="success"
|
||||||
|
capabilityDescription="Complete your arrangement entirely online — no in-person visit required."
|
||||||
startingPrice={900}
|
startingPrice={900}
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
/>
|
/>
|
||||||
@@ -220,6 +240,7 @@ export const CapabilityVariants: Story = {
|
|||||||
location="Melbourne"
|
location="Melbourne"
|
||||||
capabilityLabel="Partial Arrangement"
|
capabilityLabel="Partial Arrangement"
|
||||||
capabilityColor="warning"
|
capabilityColor="warning"
|
||||||
|
capabilityDescription="Some steps can be completed online, but an in-person visit is required to finalise."
|
||||||
startingPrice={1100}
|
startingPrice={1100}
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
/>
|
/>
|
||||||
@@ -228,6 +249,7 @@ export const CapabilityVariants: Story = {
|
|||||||
location="Brisbane"
|
location="Brisbane"
|
||||||
capabilityLabel="Outsourced"
|
capabilityLabel="Outsourced"
|
||||||
capabilityColor="default"
|
capabilityColor="default"
|
||||||
|
capabilityDescription="This provider uses a third-party service to manage arrangements."
|
||||||
startingPrice={800}
|
startingPrice={800}
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
/>
|
/>
|
||||||
@@ -278,7 +300,7 @@ export const EdgeCases: Story = {
|
|||||||
startingPrice={600}
|
startingPrice={600}
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
/>
|
/>
|
||||||
{/* No price (footer hidden) */}
|
{/* No price */}
|
||||||
<ProviderCard
|
<ProviderCard
|
||||||
name="Price Unavailable"
|
name="Price Unavailable"
|
||||||
location="Darwin"
|
location="Darwin"
|
||||||
@@ -335,7 +357,7 @@ export const Responsive: Story = {
|
|||||||
|
|
||||||
// ─── On Different Backgrounds ───────────────────────────────────────────────
|
// ─── On Different Backgrounds ───────────────────────────────────────────────
|
||||||
|
|
||||||
/** Cards on white vs grey surfaces */
|
/** Cards on white vs grey surfaces — unverified on grey is the expected usage */
|
||||||
export const OnDifferentBackgrounds: Story = {
|
export const OnDifferentBackgrounds: Story = {
|
||||||
name: 'On Different Backgrounds',
|
name: 'On Different Backgrounds',
|
||||||
decorators: [
|
decorators: [
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
import type { SxProps, Theme } from '@mui/material/styles';
|
import type { SxProps, Theme } from '@mui/material/styles';
|
||||||
import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined';
|
import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined';
|
||||||
import StarRoundedIcon from '@mui/icons-material/StarRounded';
|
import StarRoundedIcon from '@mui/icons-material/StarRounded';
|
||||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
|
||||||
import VerifiedOutlinedIcon from '@mui/icons-material/VerifiedOutlined';
|
import VerifiedOutlinedIcon from '@mui/icons-material/VerifiedOutlined';
|
||||||
|
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||||
import { Card } from '../../atoms/Card';
|
import { Card } from '../../atoms/Card';
|
||||||
import { Badge } from '../../atoms/Badge';
|
import { Badge } from '../../atoms/Badge';
|
||||||
import type { BadgeColor } from '../../atoms/Badge/Badge';
|
import type { BadgeColor } from '../../atoms/Badge/Badge';
|
||||||
@@ -22,7 +23,7 @@ export interface ProviderCardProps {
|
|||||||
verified?: boolean;
|
verified?: boolean;
|
||||||
/** Hero image URL — only rendered when verified */
|
/** Hero image URL — only rendered when verified */
|
||||||
imageUrl?: string;
|
imageUrl?: string;
|
||||||
/** Provider logo URL — circular overlay on image, only rendered when verified */
|
/** Provider logo URL — rounded rectangle overlay on image, only rendered when verified */
|
||||||
logoUrl?: string;
|
logoUrl?: string;
|
||||||
/** Average rating (e.g. 4.8). Omit to hide reviews. */
|
/** Average rating (e.g. 4.8). Omit to hide reviews. */
|
||||||
rating?: number;
|
rating?: number;
|
||||||
@@ -32,7 +33,9 @@ export interface ProviderCardProps {
|
|||||||
capabilityLabel?: string;
|
capabilityLabel?: string;
|
||||||
/** Capability badge colour intent — maps to Badge colour */
|
/** Capability badge colour intent — maps to Badge colour */
|
||||||
capabilityColor?: BadgeColor;
|
capabilityColor?: BadgeColor;
|
||||||
/** Starting price in dollars (shown in footer as "Packages from $X") */
|
/** Tooltip description for the capability badge (shown on hover/focus) */
|
||||||
|
capabilityDescription?: string;
|
||||||
|
/** Starting price in dollars (shown as "From $X") */
|
||||||
startingPrice?: number;
|
startingPrice?: number;
|
||||||
/** Click handler — entire card is clickable */
|
/** Click handler — entire card is clickable */
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
@@ -43,13 +46,10 @@ export interface ProviderCardProps {
|
|||||||
// ─── Constants ───────────────────────────────────────────────────────────────
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const LOGO_SIZE = 'var(--fa-provider-card-logo-size)';
|
const LOGO_SIZE = 'var(--fa-provider-card-logo-size)';
|
||||||
const LOGO_OVERLAP = 28; // half of 56px logo, in px
|
const LOGO_BORDER_RADIUS = 'var(--fa-provider-card-logo-border-radius)';
|
||||||
const IMAGE_HEIGHT = 'var(--fa-provider-card-image-height)';
|
const IMAGE_HEIGHT = 'var(--fa-provider-card-image-height)';
|
||||||
const CONTENT_PADDING = 'var(--fa-provider-card-content-padding)';
|
const CONTENT_PADDING = 'var(--fa-provider-card-content-padding)';
|
||||||
const CONTENT_GAP = 'var(--fa-provider-card-content-gap)';
|
const CONTENT_GAP = 'var(--fa-provider-card-content-gap)';
|
||||||
const FOOTER_BG = 'var(--fa-provider-card-footer-background)';
|
|
||||||
const FOOTER_PX = 'var(--fa-provider-card-footer-padding-x)';
|
|
||||||
const FOOTER_PY = 'var(--fa-provider-card-footer-padding-y)';
|
|
||||||
|
|
||||||
// ─── Component ───────────────────────────────────────────────────────────────
|
// ─── Component ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -60,9 +60,9 @@ const FOOTER_PY = 'var(--fa-provider-card-footer-padding-y)';
|
|||||||
* list. Supports verified (paid partner) and unverified (scraped listing)
|
* list. Supports verified (paid partner) and unverified (scraped listing)
|
||||||
* providers with consistent text alignment for scan readability.
|
* providers with consistent text alignment for scan readability.
|
||||||
*
|
*
|
||||||
* **Verified providers** get a hero image, logo overlay, and "Verified"
|
* **Verified providers** get a hero image, logo (rounded rectangle inside
|
||||||
* badge. **Unverified providers** show text content only — no image, logo,
|
* image area), and "Verified" badge. **Unverified providers** show text
|
||||||
* or verification badge.
|
* content only with a subtle top accent bar for visibility in mixed lists.
|
||||||
*
|
*
|
||||||
* Composes: Card (interactive, padding="none"), Badge, Typography.
|
* Composes: Card (interactive, padding="none"), Badge, Typography.
|
||||||
*
|
*
|
||||||
@@ -95,6 +95,7 @@ export const ProviderCard = React.forwardRef<HTMLDivElement, ProviderCardProps>(
|
|||||||
reviewCount,
|
reviewCount,
|
||||||
capabilityLabel,
|
capabilityLabel,
|
||||||
capabilityColor = 'default',
|
capabilityColor = 'default',
|
||||||
|
capabilityDescription,
|
||||||
startingPrice,
|
startingPrice,
|
||||||
onClick,
|
onClick,
|
||||||
sx,
|
sx,
|
||||||
@@ -118,6 +119,11 @@ export const ProviderCard = React.forwardRef<HTMLDivElement, ProviderCardProps>(
|
|||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: 'background.paper',
|
backgroundColor: 'background.paper',
|
||||||
},
|
},
|
||||||
|
// Unverified cards: subtle top accent so they don't get lost
|
||||||
|
// in a mixed list. Verified cards have the hero image as anchor.
|
||||||
|
...(!showImage && {
|
||||||
|
borderTop: '3px solid var(--fa-color-border-default)',
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
...(Array.isArray(sx) ? sx : [sx]),
|
...(Array.isArray(sx) ? sx : [sx]),
|
||||||
]}
|
]}
|
||||||
@@ -139,14 +145,14 @@ export const ProviderCard = React.forwardRef<HTMLDivElement, ProviderCardProps>(
|
|||||||
<Badge
|
<Badge
|
||||||
variant="filled"
|
variant="filled"
|
||||||
color="brand"
|
color="brand"
|
||||||
size="small"
|
size="medium"
|
||||||
icon={<VerifiedOutlinedIcon />}
|
icon={<VerifiedOutlinedIcon />}
|
||||||
>
|
>
|
||||||
Verified
|
Verified
|
||||||
</Badge>
|
</Badge>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Logo overlay */}
|
{/* Logo — fully inside image area, bottom-left */}
|
||||||
{showLogo && (
|
{showLogo && (
|
||||||
<Box
|
<Box
|
||||||
component="img"
|
component="img"
|
||||||
@@ -154,14 +160,15 @@ export const ProviderCard = React.forwardRef<HTMLDivElement, ProviderCardProps>(
|
|||||||
alt={`${name} logo`}
|
alt={`${name} logo`}
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: -LOGO_OVERLAP,
|
bottom: 12,
|
||||||
left: CONTENT_PADDING,
|
left: CONTENT_PADDING,
|
||||||
width: LOGO_SIZE,
|
width: LOGO_SIZE,
|
||||||
height: LOGO_SIZE,
|
height: LOGO_SIZE,
|
||||||
borderRadius: '50%',
|
borderRadius: LOGO_BORDER_RADIUS,
|
||||||
objectFit: 'cover',
|
objectFit: 'cover',
|
||||||
backgroundColor: 'background.paper',
|
backgroundColor: 'background.paper',
|
||||||
boxShadow: '0 2px 8px rgba(0,0,0,0.12)',
|
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
|
||||||
|
border: '2px solid white',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -175,20 +182,28 @@ export const ProviderCard = React.forwardRef<HTMLDivElement, ProviderCardProps>(
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: CONTENT_GAP,
|
gap: CONTENT_GAP,
|
||||||
p: CONTENT_PADDING,
|
p: CONTENT_PADDING,
|
||||||
// Extra top padding when logo overlaps into content
|
|
||||||
...(showLogo && { pt: `calc(${CONTENT_PADDING} + ${LOGO_OVERLAP}px)` }),
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Provider name */}
|
{/* Provider name — full width, no logo competition */}
|
||||||
<Typography variant="h5" maxLines={2}>
|
<Typography variant="h5" maxLines={2}>
|
||||||
{name}
|
{name}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{/* Price — primary comparison data, prominent position */}
|
{/* Price — "Packages from $X" with subtle size differentiation */}
|
||||||
{startingPrice != null && (
|
{startingPrice != null && (
|
||||||
<Typography variant="labelLg" sx={{ fontWeight: 700 }} color="primary">
|
<Box sx={{ display: 'flex', alignItems: 'baseline', gap: 0.5 }}>
|
||||||
From ${startingPrice.toLocaleString('en-AU')}
|
<Typography variant="body2" color="text.secondary">
|
||||||
</Typography>
|
Packages from
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
component="span"
|
||||||
|
color="primary"
|
||||||
|
sx={{ fontWeight: 500, letterSpacing: '-0.01em' }}
|
||||||
|
>
|
||||||
|
${startingPrice.toLocaleString('en-AU')}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Meta row: location + reviews */}
|
{/* Meta row: location + reviews */}
|
||||||
@@ -203,9 +218,9 @@ export const ProviderCard = React.forwardRef<HTMLDivElement, ProviderCardProps>(
|
|||||||
{/* Location */}
|
{/* Location */}
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||||
<LocationOnOutlinedIcon
|
<LocationOnOutlinedIcon
|
||||||
sx={{ fontSize: 16, color: 'text.secondary' }}
|
sx={{ fontSize: 14, color: 'text.secondary' }}
|
||||||
/>
|
/>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
{location}
|
{location}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -217,10 +232,10 @@ export const ProviderCard = React.forwardRef<HTMLDivElement, ProviderCardProps>(
|
|||||||
aria-label={`Rated ${rating} out of 5${reviewCount != null ? `, ${reviewCount} reviews` : ''}`}
|
aria-label={`Rated ${rating} out of 5${reviewCount != null ? `, ${reviewCount} reviews` : ''}`}
|
||||||
>
|
>
|
||||||
<StarRoundedIcon
|
<StarRoundedIcon
|
||||||
sx={{ fontSize: 16, color: 'warning.main' }}
|
sx={{ fontSize: 14, color: 'warning.main' }}
|
||||||
aria-hidden
|
aria-hidden
|
||||||
/>
|
/>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
{rating}
|
{rating}
|
||||||
{reviewCount != null && ` (${reviewCount.toLocaleString('en-AU')})`}
|
{reviewCount != null && ` (${reviewCount.toLocaleString('en-AU')})`}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -228,35 +243,34 @@ export const ProviderCard = React.forwardRef<HTMLDivElement, ProviderCardProps>(
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Capability badge */}
|
{/* Capability badge — trailing info icon signals hover-for-definition */}
|
||||||
{capabilityLabel && (
|
{capabilityLabel && (
|
||||||
<Box>
|
<Box>
|
||||||
<Badge color={capabilityColor} size="small">
|
{capabilityDescription ? (
|
||||||
{capabilityLabel}
|
<Tooltip
|
||||||
</Badge>
|
title={capabilityDescription}
|
||||||
|
arrow
|
||||||
|
placement="top"
|
||||||
|
enterTouchDelay={0}
|
||||||
|
>
|
||||||
|
<Badge
|
||||||
|
color={capabilityColor}
|
||||||
|
size="medium"
|
||||||
|
sx={{ cursor: 'help' }}
|
||||||
|
>
|
||||||
|
{capabilityLabel}
|
||||||
|
<InfoOutlinedIcon />
|
||||||
|
</Badge>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<Badge color={capabilityColor} size="medium">
|
||||||
|
{capabilityLabel}
|
||||||
|
<InfoOutlinedIcon />
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* ── Footer bar ── */}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
backgroundColor: FOOTER_BG,
|
|
||||||
px: FOOTER_PX,
|
|
||||||
py: FOOTER_PY,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="label" color="text.secondary">
|
|
||||||
View packages
|
|
||||||
</Typography>
|
|
||||||
<ChevronRightIcon
|
|
||||||
sx={{ fontSize: 20, color: 'text.secondary' }}
|
|
||||||
aria-hidden
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
--fa-input-height-md: 48px; /** Medium (default) — standard forms, matches Button large for alignment */
|
--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 */
|
--fa-input-icon-size-default: 20px; /** 20px — icon size inside input field, matches Figma trailing icon */
|
||||||
--fa-provider-card-image-height: 180px; /** Fixed image height for consistent card sizing in list layouts */
|
--fa-provider-card-image-height: 180px; /** Fixed image height for consistent card sizing in list layouts */
|
||||||
--fa-provider-card-logo-size: 56px; /** Logo circle diameter — positioned bottom-left of image, overlapping content area */
|
--fa-provider-card-logo-size: 64px; /** Logo width/height — rounded rectangle, overlapping image bottom into content row */
|
||||||
--fa-radio-size-default: 20px; /** Default radio size — matches Figma 16px + padding for 44px touch target area */
|
--fa-radio-size-default: 20px; /** Default radio size — matches Figma 16px + padding for 44px touch target area */
|
||||||
--fa-radio-dot-size-default: 10px; /** Selected indicator dot — 50% of outer size */
|
--fa-radio-dot-size-default: 10px; /** Selected indicator dot — 50% of outer size */
|
||||||
--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 */
|
||||||
@@ -267,9 +267,7 @@
|
|||||||
--fa-input-font-size-default: var(--fa-font-size-base); /** 16px — prevents iOS auto-zoom on focus, matches Figma */
|
--fa-input-font-size-default: var(--fa-font-size-base); /** 16px — prevents iOS auto-zoom on focus, matches Figma */
|
||||||
--fa-input-border-radius-default: var(--fa-border-radius-sm); /** 4px — subtle rounding, consistent with Figma design */
|
--fa-input-border-radius-default: var(--fa-border-radius-sm); /** 4px — subtle rounding, consistent with Figma design */
|
||||||
--fa-input-gap-default: var(--fa-spacing-2); /** 8px — vertical rhythm between label/input/helper, slightly more generous than Figma's 6px for readability */
|
--fa-input-gap-default: var(--fa-spacing-2); /** 8px — vertical rhythm between label/input/helper, slightly more generous than Figma's 6px for readability */
|
||||||
--fa-provider-card-footer-background: var(--fa-color-brand-100); /** Warm beige footer — brand.100 provides subtle brand warmth */
|
--fa-provider-card-logo-border-radius: var(--fa-border-radius-md); /** 8px rounded rectangle — softer than circle, matches card border radius */
|
||||||
--fa-provider-card-footer-padding-x: var(--fa-spacing-4); /** 16px horizontal padding — matches compact card padding */
|
|
||||||
--fa-provider-card-footer-padding-y: var(--fa-spacing-2); /** 8px vertical padding — compact footer bar */
|
|
||||||
--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 */
|
||||||
@@ -385,10 +383,10 @@
|
|||||||
--fa-typography-label-sm-font-weight: var(--fa-font-weight-medium);
|
--fa-typography-label-sm-font-weight: var(--fa-font-weight-medium);
|
||||||
--fa-typography-caption-font-family: var(--fa-font-family-body);
|
--fa-typography-caption-font-family: var(--fa-font-family-body);
|
||||||
--fa-typography-caption-font-size: var(--fa-font-size-xs); /** 12px */
|
--fa-typography-caption-font-size: var(--fa-font-size-xs); /** 12px */
|
||||||
--fa-typography-caption-font-weight: var(--fa-font-weight-regular);
|
--fa-typography-caption-font-weight: var(--fa-font-weight-medium);
|
||||||
--fa-typography-caption-sm-font-family: var(--fa-font-family-body);
|
--fa-typography-caption-sm-font-family: var(--fa-font-family-body);
|
||||||
--fa-typography-caption-sm-font-size: var(--fa-font-size-2xs); /** 11px — accessibility floor */
|
--fa-typography-caption-sm-font-size: var(--fa-font-size-2xs); /** 11px — accessibility floor */
|
||||||
--fa-typography-caption-sm-font-weight: var(--fa-font-weight-regular);
|
--fa-typography-caption-sm-font-weight: var(--fa-font-weight-medium);
|
||||||
--fa-typography-overline-font-family: var(--fa-font-family-body);
|
--fa-typography-overline-font-family: var(--fa-font-family-body);
|
||||||
--fa-typography-overline-font-size: var(--fa-font-size-xs); /** 12px */
|
--fa-typography-overline-font-size: var(--fa-font-size-xs); /** 12px */
|
||||||
--fa-typography-overline-font-weight: var(--fa-font-weight-semibold);
|
--fa-typography-overline-font-weight: var(--fa-font-weight-semibold);
|
||||||
|
|||||||
@@ -73,10 +73,8 @@ export const InputBorderRadiusDefault = "4px"; // 4px — subtle rounding, consi
|
|||||||
export const InputGapDefault = "8px"; // 8px — vertical rhythm between label/input/helper, slightly more generous than Figma's 6px for readability
|
export const InputGapDefault = "8px"; // 8px — vertical rhythm between label/input/helper, slightly more generous than Figma's 6px for readability
|
||||||
export const InputIconSizeDefault = "20px"; // 20px — icon size inside input field, matches Figma trailing icon
|
export const InputIconSizeDefault = "20px"; // 20px — icon size inside input field, matches Figma trailing icon
|
||||||
export const ProviderCardImageHeight = "180px"; // Fixed image height for consistent card sizing in list layouts
|
export const ProviderCardImageHeight = "180px"; // Fixed image height for consistent card sizing in list layouts
|
||||||
export const ProviderCardLogoSize = "56px"; // Logo circle diameter — positioned bottom-left of image, overlapping content area
|
export const ProviderCardLogoSize = "64px"; // Logo width/height — rounded rectangle, overlapping image bottom into content row
|
||||||
export const ProviderCardFooterBackground = "#f7ecdf"; // Warm beige footer — brand.100 provides subtle brand warmth
|
export const ProviderCardLogoBorderRadius = "8px"; // 8px rounded rectangle — softer than circle, matches card border radius
|
||||||
export const ProviderCardFooterPaddingX = "16px"; // 16px horizontal padding — matches compact card padding
|
|
||||||
export const ProviderCardFooterPaddingY = "8px"; // 8px vertical padding — compact footer bar
|
|
||||||
export const ProviderCardContentPadding = "12px"; // 12px content padding — tight to keep card compact in listing layout
|
export const ProviderCardContentPadding = "12px"; // 12px content padding — tight to keep card compact in listing layout
|
||||||
export const ProviderCardContentGap = "4px"; // 4px vertical gap between content rows — tight for compact listing cards
|
export const ProviderCardContentGap = "4px"; // 4px vertical gap between content rows — tight for compact listing cards
|
||||||
export const RadioSizeDefault = "20px"; // Default radio size — matches Figma 16px + padding for 44px touch target area
|
export const RadioSizeDefault = "20px"; // Default radio size — matches Figma 16px + padding for 44px touch target area
|
||||||
@@ -403,13 +401,13 @@ export const TypographyLabelSmLetterSpacing = "0.2px";
|
|||||||
export const TypographyCaptionFontFamily =
|
export const TypographyCaptionFontFamily =
|
||||||
"'Montserrat', 'Helvetica Neue', Arial, sans-serif";
|
"'Montserrat', 'Helvetica Neue', Arial, sans-serif";
|
||||||
export const TypographyCaptionFontSize = "0.75rem"; // 12px
|
export const TypographyCaptionFontSize = "0.75rem"; // 12px
|
||||||
export const TypographyCaptionFontWeight = 400;
|
export const TypographyCaptionFontWeight = 500;
|
||||||
export const TypographyCaptionLineHeight = 1.417;
|
export const TypographyCaptionLineHeight = 1.417;
|
||||||
export const TypographyCaptionLetterSpacing = "0.2px";
|
export const TypographyCaptionLetterSpacing = "0.2px";
|
||||||
export const TypographyCaptionSmFontFamily =
|
export const TypographyCaptionSmFontFamily =
|
||||||
"'Montserrat', 'Helvetica Neue', Arial, sans-serif";
|
"'Montserrat', 'Helvetica Neue', Arial, sans-serif";
|
||||||
export const TypographyCaptionSmFontSize = "0.6875rem"; // 11px — accessibility floor
|
export const TypographyCaptionSmFontSize = "0.6875rem"; // 11px — accessibility floor
|
||||||
export const TypographyCaptionSmFontWeight = 400;
|
export const TypographyCaptionSmFontWeight = 500;
|
||||||
export const TypographyCaptionSmLineHeight = 1.364;
|
export const TypographyCaptionSmLineHeight = 1.364;
|
||||||
export const TypographyCaptionSmLetterSpacing = "0.2px";
|
export const TypographyCaptionSmLetterSpacing = "0.2px";
|
||||||
export const TypographyOverlineFontFamily =
|
export const TypographyOverlineFontFamily =
|
||||||
|
|||||||
@@ -7,26 +7,16 @@
|
|||||||
"height": { "$value": "180px", "$description": "Fixed image height for consistent card sizing in list layouts" }
|
"height": { "$value": "180px", "$description": "Fixed image height for consistent card sizing in list layouts" }
|
||||||
},
|
},
|
||||||
"logo": {
|
"logo": {
|
||||||
"$type": "dimension",
|
|
||||||
"$description": "Provider logo overlay dimensions.",
|
"$description": "Provider logo overlay dimensions.",
|
||||||
"size": { "$value": "56px", "$description": "Logo circle diameter — positioned bottom-left of image, overlapping content area" }
|
"size": {
|
||||||
},
|
|
||||||
"footer": {
|
|
||||||
"$description": "Footer bar styling — warm beige bar with package pricing.",
|
|
||||||
"background": {
|
|
||||||
"$type": "color",
|
|
||||||
"$value": "{color.brand.100}",
|
|
||||||
"$description": "Warm beige footer — brand.100 provides subtle brand warmth"
|
|
||||||
},
|
|
||||||
"paddingX": {
|
|
||||||
"$type": "dimension",
|
"$type": "dimension",
|
||||||
"$value": "{spacing.4}",
|
"$value": "64px",
|
||||||
"$description": "16px horizontal padding — matches compact card padding"
|
"$description": "Logo width/height — rounded rectangle, overlapping image bottom into content row"
|
||||||
},
|
},
|
||||||
"paddingY": {
|
"borderRadius": {
|
||||||
"$type": "dimension",
|
"$type": "dimension",
|
||||||
"$value": "{spacing.2}",
|
"$value": "{borderRadius.md}",
|
||||||
"$description": "8px vertical padding — compact footer bar"
|
"$description": "8px rounded rectangle — softer than circle, matches card border radius"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
|
|||||||
@@ -162,18 +162,18 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"caption": {
|
"caption": {
|
||||||
"$description": "Caption default — fine print, timestamps, metadata. Montserrat Regular.",
|
"$description": "Caption default — fine print, timestamps, metadata. Montserrat Medium.",
|
||||||
"fontFamily": { "$value": "{fontFamily.body}", "$type": "fontFamily" },
|
"fontFamily": { "$value": "{fontFamily.body}", "$type": "fontFamily" },
|
||||||
"fontSize": { "$value": "{fontSize.xs}", "$type": "dimension", "$description": "12px" },
|
"fontSize": { "$value": "{fontSize.xs}", "$type": "dimension", "$description": "12px" },
|
||||||
"fontWeight": { "$value": "{fontWeight.regular}", "$type": "fontWeight" },
|
"fontWeight": { "$value": "{fontWeight.medium}", "$type": "fontWeight" },
|
||||||
"lineHeight": { "$value": 1.417, "$type": "number" },
|
"lineHeight": { "$value": 1.417, "$type": "number" },
|
||||||
"letterSpacing": { "$value": "0.2px", "$type": "dimension" }
|
"letterSpacing": { "$value": "0.2px", "$type": "dimension" }
|
||||||
},
|
},
|
||||||
"captionSm": {
|
"captionSm": {
|
||||||
"$description": "Caption small — compact metadata, footnotes. Montserrat Regular. Min 11px for accessibility.",
|
"$description": "Caption small — compact metadata, footnotes. Montserrat Medium. Min 11px for accessibility.",
|
||||||
"fontFamily": { "$value": "{fontFamily.body}", "$type": "fontFamily" },
|
"fontFamily": { "$value": "{fontFamily.body}", "$type": "fontFamily" },
|
||||||
"fontSize": { "$value": "{fontSize.2xs}", "$type": "dimension", "$description": "11px — accessibility floor" },
|
"fontSize": { "$value": "{fontSize.2xs}", "$type": "dimension", "$description": "11px — accessibility floor" },
|
||||||
"fontWeight": { "$value": "{fontWeight.regular}", "$type": "fontWeight" },
|
"fontWeight": { "$value": "{fontWeight.medium}", "$type": "fontWeight" },
|
||||||
"lineHeight": { "$value": 1.364, "$type": "number" },
|
"lineHeight": { "$value": 1.364, "$type": "number" },
|
||||||
"letterSpacing": { "$value": "0.2px", "$type": "dimension" }
|
"letterSpacing": { "$value": "0.2px", "$type": "dimension" }
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user