Add MapPin atom — price-pill map markers with verified/unverified distinction

Airbnb-style markers: pill variant (price label + nub) and dot variant
(no price). Verified = brand palette, unverified = neutral grey.
Active state inverts colours + scale-up. Pure CSS for map overlay use.
Keyboard accessible with role="button" and focus ring.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 19:49:08 +10:00
parent f7efa7165c
commit 4fecb81853
3 changed files with 360 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
import type { Meta, StoryObj } from '@storybook/react';
import Box from '@mui/material/Box';
import { MapPin } from './MapPin';
const meta: Meta<typeof MapPin> = {
title: 'Atoms/MapPin',
component: MapPin,
tags: ['autodocs'],
parameters: {
layout: 'centered',
backgrounds: {
default: 'map',
values: [{ name: 'map', value: '#E5E3DF' }],
},
},
argTypes: {
onClick: { action: 'clicked' },
},
};
export default meta;
type Story = StoryObj<typeof MapPin>;
/** Verified provider with price — warm brand pill */
export const VerifiedWithPrice: Story = {
args: {
price: 900,
verified: true,
},
};
/** Unverified provider with price — neutral grey pill */
export const UnverifiedWithPrice: Story = {
args: {
price: 1200,
verified: false,
},
};
/** Active/selected state — inverted colours, slight scale-up */
export const Active: Story = {
args: {
price: 900,
verified: true,
active: true,
},
};
/** Active unverified */
export const ActiveUnverified: Story = {
args: {
price: 1200,
verified: false,
active: true,
},
};
/** Dot marker — no price, verified */
export const DotVerified: Story = {
args: {
verified: true,
},
};
/** Dot marker — no price, unverified */
export const DotUnverified: Story = {
args: {
verified: false,
},
};
/** Dot marker — active */
export const DotActive: Story = {
args: {
verified: true,
active: true,
},
};
/** Custom price label */
export const CustomLabel: Story = {
args: {
priceLabel: 'POA',
verified: true,
},
};
/** High price — wider pill */
export const HighPrice: Story = {
args: {
price: 12500,
verified: true,
},
};
/** Map simulation — multiple pins on a mock map background */
export const MapSimulation: Story = {
decorators: [
(Story) => (
<Box
sx={{
position: 'relative',
width: 600,
height: 400,
bgcolor: '#E5E3DF',
borderRadius: 2,
overflow: 'hidden',
}}
>
<Story />
</Box>
),
],
render: () => (
<>
{/* Verified providers */}
<Box sx={{ position: 'absolute', top: 80, left: 120 }}>
<MapPin price={900} verified onClick={() => {}} />
</Box>
<Box sx={{ position: 'absolute', top: 160, left: 300 }}>
<MapPin price={1450} verified active onClick={() => {}} />
</Box>
<Box sx={{ position: 'absolute', top: 240, left: 180 }}>
<MapPin price={2200} verified onClick={() => {}} />
</Box>
{/* Unverified providers */}
<Box sx={{ position: 'absolute', top: 120, left: 420 }}>
<MapPin price={1100} onClick={() => {}} />
</Box>
<Box sx={{ position: 'absolute', top: 280, left: 380 }}>
<MapPin onClick={() => {}} />
</Box>
{/* Dot markers */}
<Box sx={{ position: 'absolute', top: 60, left: 480 }}>
<MapPin verified onClick={() => {}} />
</Box>
</>
),
};