@stoovles/gap-kit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/index.js +2417 -0
  2. package/dist/styles.css +4309 -0
  3. package/guidelines/components/accordion.md +99 -0
  4. package/guidelines/components/breadcrumb.md +74 -0
  5. package/guidelines/components/buttons.md +90 -0
  6. package/guidelines/components/checkbox.md +86 -0
  7. package/guidelines/components/chip.md +77 -0
  8. package/guidelines/components/divider.md +52 -0
  9. package/guidelines/components/dropdown.md +125 -0
  10. package/guidelines/components/filter-sort.md +108 -0
  11. package/guidelines/components/fulfillment.md +100 -0
  12. package/guidelines/components/global-footer.md +71 -0
  13. package/guidelines/components/global-header.md +67 -0
  14. package/guidelines/components/hamburger-nav.md +79 -0
  15. package/guidelines/components/handle.md +49 -0
  16. package/guidelines/components/icons.md +147 -0
  17. package/guidelines/components/link.md +70 -0
  18. package/guidelines/components/logo.md +63 -0
  19. package/guidelines/components/mega-nav.md +103 -0
  20. package/guidelines/components/notification.md +97 -0
  21. package/guidelines/components/pagination.md +88 -0
  22. package/guidelines/components/popover.md +77 -0
  23. package/guidelines/components/price.md +86 -0
  24. package/guidelines/components/product-card.md +82 -0
  25. package/guidelines/components/radio.md +92 -0
  26. package/guidelines/components/search-input.md +63 -0
  27. package/guidelines/components/selector-swatch.md +78 -0
  28. package/guidelines/components/selector.md +95 -0
  29. package/guidelines/components/slider.md +116 -0
  30. package/guidelines/components/switch.md +108 -0
  31. package/guidelines/components/tabs.md +83 -0
  32. package/guidelines/components/text-input.md +80 -0
  33. package/guidelines/composition/buy-box.md +132 -0
  34. package/guidelines/composition/recommendations.md +100 -0
  35. package/package.json +38 -0
@@ -0,0 +1,63 @@
1
+ # GapLogo
2
+
3
+ ## When to use
4
+
5
+ Use the `GapLogo` component to render the official Gap wordmark. Never recreate the logo with text, fonts, or custom SVGs. Always import from `@stoovles/gap-kit`.
6
+
7
+ ```tsx
8
+ import { GapLogo } from '@stoovles/gap-kit'
9
+ ```
10
+
11
+ ## Props
12
+
13
+ | Prop | Type | Default | Description |
14
+ |---|---|---|---|
15
+ | `color` | `"dark" \| "light"` | `"dark"` | Dark renders in `--color-type-primary` (#000), Light renders in `--color-type-inverse` (#fff) |
16
+ | `height` | `number` | `60` | Height in pixels; width scales proportionally (aspect ratio 81:60) |
17
+ | `className` | `string` | — | Additional CSS class |
18
+ | `aria-label` | `string` | `"Gap"` | Accessible label |
19
+
20
+ ## Sizing
21
+
22
+ The logo has a fixed aspect ratio of **81:60** (1.35:1). Only set `height` — width is calculated automatically.
23
+
24
+ | Context | Recommended height |
25
+ |---|---|
26
+ | Global header (desktop) | `24` – `32` |
27
+ | Global header (mobile) | `20` – `24` |
28
+ | Footer | `20` – `24` |
29
+ | Hero / splash | `48` – `60` |
30
+
31
+ ## Color decision tree
32
+
33
+ ```
34
+ Which logo color should I use?
35
+
36
+ ├─ On a light background (white, gray, subtle)?
37
+ │ └─ color="dark" (default)
38
+
39
+ └─ On a dark background (black, brand bar, dark overlay)?
40
+ └─ color="light"
41
+ ```
42
+
43
+ ## Examples
44
+
45
+ ```tsx
46
+ {/* Default logo in header */}
47
+ <GapLogo height={24} />
48
+
49
+ {/* White logo on dark header bar */}
50
+ <GapLogo height={24} color="light" />
51
+
52
+ {/* Large logo for splash screen */}
53
+ <GapLogo height={60} />
54
+ ```
55
+
56
+ ## Rules
57
+
58
+ - Never distort the logo — only change `height`, width scales automatically
59
+ - Never change the logo color to anything other than "dark" (black) or "light" (white)
60
+ - Do not add borders, shadows, or effects to the logo
61
+ - Always provide sufficient contrast — dark on light backgrounds, light on dark backgrounds
62
+ - Do not place the logo inside colored containers that reduce contrast
63
+ - Minimum recommended height is 20px for legibility
@@ -0,0 +1,103 @@
1
+ # Mega Navigation
2
+
3
+ Desktop horizontal navigation bar with hover-triggered category dropdown panels. Contains utility links (top-right), a logo, department links, and an inline search input.
4
+
5
+ ## Usage
6
+
7
+ ```jsx
8
+ import { MegaNav, SearchInput, GapLogo } from "@stoovles/gap-kit";
9
+
10
+ <MegaNav
11
+ logo={<GapLogo width={60} />}
12
+ onLogoClick={() => navigate("/")}
13
+ utilityLinks={[
14
+ { label: "Find a Store", href: "/stores" },
15
+ { label: "Rewards", href: "/rewards" },
16
+ { label: "Gift Card", href: "/gift-cards" },
17
+ ]}
18
+ departments={[
19
+ {
20
+ label: "Women",
21
+ href: "/women",
22
+ categories: [
23
+ {
24
+ title: "Clothing",
25
+ links: [
26
+ { label: "Tops", href: "/women/tops" },
27
+ { label: "Dresses", href: "/women/dresses" },
28
+ { label: "Jeans", href: "/women/jeans" },
29
+ ],
30
+ },
31
+ {
32
+ title: "Accessories",
33
+ links: [
34
+ { label: "Bags", href: "/women/bags" },
35
+ { label: "Shoes", href: "/women/shoes" },
36
+ ],
37
+ },
38
+ ],
39
+ marketingImages: [
40
+ {
41
+ imageUrl: "/promo-women.jpg",
42
+ label: "Spring collection",
43
+ href: "/women/spring",
44
+ },
45
+ ],
46
+ },
47
+ { label: "Men", href: "/men" },
48
+ { label: "Today's Deals!", href: "/deals", sale: true },
49
+ ]}
50
+ searchSlot={<SearchInput onSubmit={(q) => search(q)} />}
51
+ />
52
+ ```
53
+
54
+ ## Props
55
+
56
+ | Prop | Type | Default | Description |
57
+ |------|------|---------|-------------|
58
+ | `logo` | `ReactNode` | — | Logo element (e.g. `<GapLogo />`) |
59
+ | `onLogoClick` | `() => void` | — | Callback when logo is clicked |
60
+ | `departments` | `MegaNavDepartment[]` | **required** | Department links |
61
+ | `utilityLinks` | `{ label, href }[]` | `[]` | Top-right utility links |
62
+ | `searchSlot` | `ReactNode` | — | Search input slot |
63
+
64
+ ### MegaNavDepartment
65
+
66
+ | Field | Type | Description |
67
+ |-------|------|-------------|
68
+ | `label` | `string` | Display text |
69
+ | `href` | `string` | Link URL |
70
+ | `sale` | `boolean` | Renders in sale red |
71
+ | `categories` | `MegaNavCategory[]` | Dropdown category columns |
72
+ | `marketingImages` | `MegaNavMarketingImage[]` | Promo images in dropdown |
73
+
74
+ ### MegaNavCategory
75
+
76
+ | Field | Type | Description |
77
+ |-------|------|-------------|
78
+ | `title` | `string` | Category header |
79
+ | `links` | `{ label, href }[]` | Sub-category links |
80
+
81
+ ### MegaNavMarketingImage
82
+
83
+ | Field | Type | Description |
84
+ |-------|------|-------------|
85
+ | `imageUrl` | `string` | Promotional image |
86
+ | `label` | `string` | Link text below image |
87
+ | `href` | `string` | Link destination |
88
+
89
+ ## Visual reference
90
+
91
+ - **Utility links:** 14px accent color, slash-separated, aligned right
92
+ - **Nav bar:** Logo left, department links centered (32px gap), search right
93
+ - **Department links:** 16px, accent color; sale items in red
94
+ - **Dropdown:** Full-width white panel with shadow; up to 5 category columns (208px max each) + 2 marketing image columns (208px)
95
+ - **Category headers:** 16px primary; links: 12px secondary gray, 4px vertical padding
96
+ - **Marketing images:** 87:122 aspect ratio with underlined link below
97
+
98
+ ## Rules
99
+
100
+ - The dropdown appears on hover over a department link and dismisses on mouse-leave.
101
+ - Departments without `categories` do not open a dropdown.
102
+ - Use the `searchSlot` to pass an existing `SearchInput` component.
103
+ - The `sale` flag should be reserved for one or two department links maximum.
@@ -0,0 +1,97 @@
1
+ # Notification
2
+
3
+ ## When to use
4
+
5
+ Use the `Notification` component to display contextual messages — informational updates, warnings, success confirmations, or error alerts. Available in three layout variants depending on prominence.
6
+
7
+ ```tsx
8
+ import { Notification } from '@stoovles/gap-kit'
9
+ ```
10
+
11
+ ## Props
12
+
13
+ | Prop | Type | Default | Description |
14
+ |---|---|---|---|
15
+ | `type` | `"info" \| "warning" \| "success" \| "error"` | `"info"` | Controls background color, icon, and semantics |
16
+ | `variant` | `"banner" \| "inline" \| "bare"` | `"banner"` | Layout size — banner is full, inline is compact, bare is text-only |
17
+ | `children` | `ReactNode` | — | Message content |
18
+ | `showIcon` | `boolean` | `true` | Whether to render the type icon (not shown in `bare` variant) |
19
+ | `onDismiss` | `() => void` | — | If provided, shows a close (X) button — only in `banner` variant |
20
+ | `className` | `string` | — | Additional CSS class |
21
+
22
+ ## Visual reference
23
+
24
+ ### Type backgrounds
25
+
26
+ | Type | Background | Icon color | Text color |
27
+ |---|---|---|---|
28
+ | `info` | Transparent | Primary (#000) | Primary |
29
+ | `warning` | Light yellow (`--color-background-warning`) | Amber (`--color-type-warning`) | Primary |
30
+ | `success` | Light green (`--color-background-success`) | Green (`--color-type-success`) | Primary |
31
+ | `error` | Light red (`--color-background-error`) | Red (`--color-type-error`) | Primary |
32
+
33
+ ### Variant layouts
34
+
35
+ | Variant | Padding | Gap | Close button | Icon |
36
+ |---|---|---|---|---|
37
+ | `banner` | 16px | 16px | Yes (if `onDismiss`) | Yes |
38
+ | `inline` | 8px | 8px | No | Yes |
39
+ | `bare` | 8px | 0 | No | No |
40
+
41
+ ### Bare text color overrides
42
+
43
+ | Type | Text color |
44
+ |---|---|
45
+ | `error` | Red (`--color-type-error`) |
46
+ | `success` | Green (`--color-type-success`) |
47
+ | `info` / `warning` | Primary (#000) |
48
+
49
+ ## Sizing
50
+
51
+ - Icon: 16 × 16px, inline SVG
52
+ - Font: Gap Sans, 10px (`--text-caption-font-size`), weight 400, letter-spacing 0.2px
53
+ - Close button: 16 × 16px
54
+
55
+ ## Examples
56
+
57
+ ```tsx
58
+ {/* Info banner with dismiss */}
59
+ <Notification type="info" onDismiss={() => setShow(false)}>
60
+ Your order has been updated. Check your email for details.
61
+ </Notification>
62
+
63
+ {/* Warning banner */}
64
+ <Notification type="warning" onDismiss={() => {}}>
65
+ Please re-enter your credit card security code (CVV) to continue.
66
+ </Notification>
67
+
68
+ {/* Success banner */}
69
+ <Notification type="success" onDismiss={() => {}}>
70
+ Your payment was processed successfully.
71
+ </Notification>
72
+
73
+ {/* Error banner */}
74
+ <Notification type="error" onDismiss={() => {}}>
75
+ We couldn't process your request. Please try again.
76
+ </Notification>
77
+
78
+ {/* Inline compact notification */}
79
+ <Notification type="success" variant="inline">
80
+ Item added to bag.
81
+ </Notification>
82
+
83
+ {/* Bare text-only (e.g. under a form field) */}
84
+ <Notification type="error" variant="bare">
85
+ Please enter a valid email address.
86
+ </Notification>
87
+ ```
88
+
89
+ ## Rules
90
+
91
+ - Use `banner` for page-level or section-level messages that need attention
92
+ - Use `inline` for compact contextual feedback within a flow (e.g. below a form section)
93
+ - Use `bare` for minimal text-only messages (e.g. field-level validation summaries)
94
+ - The `error` type renders with `role="alert"` for screen reader announcement; all others use `role="status"`
95
+ - Provide `onDismiss` only when the user should be able to close the message
96
+ - Do not use notifications for permanent content — they are transient messages
97
+ - Keep message text concise and actionable
@@ -0,0 +1,88 @@
1
+ # Pagination
2
+
3
+ ## When to use
4
+
5
+ Use the `Pagination` component for progressive loading on product listing pages. It shows the user how many items they've seen, offers a button to load more, and optionally provides a "Back to Top" shortcut.
6
+
7
+ ```tsx
8
+ import { Pagination } from '@stoovles/gap-kit'
9
+ ```
10
+
11
+ ## Props
12
+
13
+ | Prop | Type | Default | Description |
14
+ |---|---|---|---|
15
+ | `shown` | `number` | — | Number of items currently displayed |
16
+ | `total` | `number` | — | Total number of items available |
17
+ | `moreCount` | `number` | — | Items remaining to load — renders the "View N more" button |
18
+ | `loading` | `boolean` | `false` | Shows a loading indicator inside the button |
19
+ | `onLoadMore` | `() => void` | — | Callback when "View more" is clicked |
20
+ | `showBackToTop` | `boolean` | `false` | Replaces the "View more" button with "Back to Top" |
21
+ | `onBackToTop` | `() => void` | — | Callback when "Back to Top" is clicked |
22
+ | `className` | `string` | — | Additional CSS class |
23
+
24
+ ## Visual reference
25
+
26
+ | State | Count text | Button |
27
+ |---|---|---|
28
+ | View More | "48 of 120 items" | Outlined — "View 24 more" |
29
+ | Loading | "48 of 120 items" | Solid black with dot loader |
30
+ | Back to Top | "120 of 120 items" | Outlined — "Back to Top" |
31
+
32
+ ### Button states
33
+
34
+ | State | Background | Border | Text |
35
+ |---|---|---|---|
36
+ | Default | White | 1px accent (#000) | Accent (#000) |
37
+ | Hover | Brand blue (`--button-secondary-hover-fill`) | Brand blue | White, underlined |
38
+ | Press | White | Accent (#000) | Accent (#000) |
39
+ | Loading | Black (`--button-secondary-fill`) | Black | White dots |
40
+
41
+ ## Sizing
42
+
43
+ - Vertical padding: 24px (`--spacing-utk-xl`)
44
+ - Gap (count to button): 16px (`--spacing-utk-l`)
45
+ - Button: min-height 44px, min-width 175px, horizontal padding 24px
46
+ - Count font: Gap Sans, 16px, weight 400, 0.32px letter-spacing
47
+ - Button font: Gap Sans, 16px (`--text-button-font-size`), weight 400
48
+ - Loader dots: 3px / 4.5px / 6px white circles
49
+
50
+ ## Examples
51
+
52
+ ```tsx
53
+ {/* Standard "View more" */}
54
+ <Pagination
55
+ shown={48}
56
+ total={120}
57
+ moreCount={24}
58
+ onLoadMore={handleLoadMore}
59
+ />
60
+
61
+ {/* Loading state */}
62
+ <Pagination
63
+ shown={48}
64
+ total={120}
65
+ loading
66
+ />
67
+
68
+ {/* All items loaded — show "Back to Top" */}
69
+ <Pagination
70
+ shown={120}
71
+ total={120}
72
+ showBackToTop
73
+ onBackToTop={() => window.scrollTo({ top: 0, behavior: "smooth" })}
74
+ />
75
+
76
+ {/* Count only (no button) */}
77
+ <Pagination shown={120} total={120} />
78
+ ```
79
+
80
+ ## Rules
81
+
82
+ - Always provide `shown` and `total` — the count text is always visible
83
+ - Pass `moreCount` to show the "View more" button; omit it when all items are loaded
84
+ - Set `loading` to `true` while fetching to swap the button content to the loader
85
+ - Use `showBackToTop` after all items are loaded to offer scroll-to-top navigation
86
+ - `showBackToTop` takes priority over `moreCount` — they should not both be active
87
+ - Do not show both loading and "View more" buttons simultaneously
88
+ - The component is full-width and centers its content
@@ -0,0 +1,77 @@
1
+ # Popover
2
+
3
+ ## When to use
4
+
5
+ Use the `Popover` component to overlay supplementary content near a trigger element — tooltips, contextual help, short descriptions, or inline guidance. It renders as a white card with a directional arrow pointing toward the trigger.
6
+
7
+ ```tsx
8
+ import { Popover } from '@stoovles/gap-kit'
9
+ ```
10
+
11
+ ## Props
12
+
13
+ | Prop | Type | Default | Description |
14
+ |---|---|---|---|
15
+ | `position` | `"top" \| "bottom" \| "left" \| "right"` | `"top"` | Which side of the trigger the popover appears on |
16
+ | `align` | `"start" \| "center" \| "end"` | `"center"` | Where the arrow sits along the popover edge |
17
+ | `children` | `ReactNode` | — | Content inside the popover body |
18
+ | `className` | `string` | — | Additional CSS class |
19
+
20
+ ## Visual reference
21
+
22
+ | Element | Style |
23
+ |---|---|
24
+ | Background | White (`--color-background-default-white`) |
25
+ | Shadow | 2px 4px 8px rgba(0,0,0,0.08) (`--drop-shadow-high-*`) |
26
+ | Arrow | 18 × 9px CSS triangle matching the white background |
27
+ | Text | Gap Sans, 12px, weight 400, 0.24px letter-spacing, secondary color (`--color-type-secondary`) |
28
+
29
+ ### Position + Align combinations
30
+
31
+ | Position | Arrow direction | Align start | Align center | Align end |
32
+ |---|---|---|---|---|
33
+ | `top` | Points down | Left-aligned arrow | Centered arrow | Right-aligned arrow |
34
+ | `bottom` | Points up | Left-aligned arrow | Centered arrow | Right-aligned arrow |
35
+ | `left` | Points right | Arrow near top | Arrow centered | Arrow near bottom |
36
+ | `right` | Points left | Arrow near top | Arrow centered | Arrow near bottom |
37
+
38
+ ## Sizing
39
+
40
+ - Width: 280px
41
+ - Padding: 12px vertical (`--spacing-utk-m`), 16px horizontal (`--spacing-utk-l`)
42
+ - Arrow: 18px wide × 9px tall (CSS border triangle)
43
+ - Font: Gap Sans, 12px (`--font-size--1`), weight 400, letter-spacing 0.24px
44
+ - Text color: `--color-type-secondary` (#595959)
45
+
46
+ ## Examples
47
+
48
+ ```tsx
49
+ {/* Tooltip above, centered arrow */}
50
+ <Popover position="top" align="center">
51
+ Free shipping on orders over $50
52
+ </Popover>
53
+
54
+ {/* Below trigger, arrow aligned to start */}
55
+ <Popover position="bottom" align="start">
56
+ Select your preferred size for accurate fit recommendations.
57
+ </Popover>
58
+
59
+ {/* Right of trigger, arrow centered */}
60
+ <Popover position="right" align="center">
61
+ <p>This item is final sale and cannot be returned.</p>
62
+ </Popover>
63
+
64
+ {/* Left of trigger, arrow at end */}
65
+ <Popover position="left" align="end">
66
+ Price shown includes member discount.
67
+ </Popover>
68
+ ```
69
+
70
+ ## Rules
71
+
72
+ - The `Popover` is a visual-only element — positioning relative to a trigger must be handled by layout or JavaScript
73
+ - Use `position` to match where the popover sits relative to its trigger; the arrow automatically points toward the trigger
74
+ - Keep content concise — the popover is 280px wide; long text will wrap
75
+ - Do not nest interactive elements (buttons, links) inside unless absolutely necessary
76
+ - For dismissible or complex overlays, consider a modal or dialog instead
77
+ - The arrow is a pure CSS triangle — no images required
@@ -0,0 +1,86 @@
1
+ # Price
2
+
3
+ ## When to use
4
+
5
+ Use the `Price` component to display product pricing consistently across the site — in product cards, product detail pages, cart, and checkout. It handles regular prices, sale prices with strikethrough, discount percentages, and price ranges.
6
+
7
+ ```tsx
8
+ import { Price } from '@stoovles/gap-kit'
9
+ ```
10
+
11
+ ## Props
12
+
13
+ | Prop | Type | Default | Description |
14
+ |---|---|---|---|
15
+ | `amount` | `string` | — | Current / sale price (e.g. `"$29.99"`) |
16
+ | `originalAmount` | `string` | — | Original price before sale, shown with strikethrough. Omit for non-sale items. |
17
+ | `discount` | `string` | — | Discount label (e.g. `"30% off"`). Only shown during sale. |
18
+ | `rangeEnd` | `string` | — | Upper bound of a price range (e.g. `"$49.99"`) |
19
+ | `layout` | `"inline" \| "stacked"` | `"inline"` | Inline places all elements in a row; stacked arranges vertically |
20
+ | `className` | `string` | — | Additional CSS class |
21
+
22
+ ## Layout decision tree
23
+
24
+ ```
25
+ Where is this price displayed?
26
+
27
+ ├─ Product card or compact listing?
28
+ │ └─ layout="inline"
29
+
30
+ └─ Product detail page or area with more vertical space?
31
+ └─ layout="stacked"
32
+ ```
33
+
34
+ ## Visual reference
35
+
36
+ | Scenario | Display |
37
+ |---|---|
38
+ | Regular price | `$29.99` in black |
39
+ | Sale (inline) | ~~`$49.99`~~ gray strikethrough, `30% off` dark gray, `$29.99` red |
40
+ | Sale (stacked) | ~~`$49.99`~~ on line 1, `30% off` on line 2, `$29.99` red on line 3 |
41
+ | Sale + range | `$29.99` red ` - $39.99` black |
42
+
43
+ ## Color mapping
44
+
45
+ | Element | Token | Gap value |
46
+ |---|---|---|
47
+ | Regular price | `--color-type-primary` | #000000 |
48
+ | Original (strikethrough) | `--product-price-strikethrough` | #757575 |
49
+ | Discount text | `--product-price-percent-off-font` | #595959 |
50
+ | Sale price | `--color-type-sale` | #e10000 |
51
+
52
+ ## Examples
53
+
54
+ ```tsx
55
+ {/* Regular price */}
56
+ <Price amount="$39.95" />
57
+
58
+ {/* Sale price */}
59
+ <Price amount="$29.99" originalAmount="$49.99" discount="40% off" />
60
+
61
+ {/* Stacked sale */}
62
+ <Price
63
+ amount="$29.99"
64
+ originalAmount="$49.99"
65
+ discount="40% off"
66
+ layout="stacked"
67
+ />
68
+
69
+ {/* Sale with range */}
70
+ <Price
71
+ amount="$19.99"
72
+ originalAmount="$39.99"
73
+ discount="50% off"
74
+ rangeEnd="$29.99"
75
+ layout="stacked"
76
+ />
77
+ ```
78
+
79
+ ## Rules
80
+
81
+ - Always format prices with a dollar sign and two decimal places (`$0.00`)
82
+ - `originalAmount` must be higher than `amount` — never show a "sale" where the price went up
83
+ - Only show `discount` when `originalAmount` is also provided
84
+ - Use `rangeEnd` only with `layout="stacked"` for clarity
85
+ - Do not add custom colors — sale red and strikethrough gray are controlled by tokens
86
+ - Keep the same `layout` for all prices within a product card for consistency
@@ -0,0 +1,82 @@
1
+ # Product Card
2
+
3
+ A product listing card used on PLP grids, search results, and recommendation carousels. Available in two variants: `default` (vertical) and `mini` (horizontal).
4
+
5
+ ## Usage
6
+
7
+ ```jsx
8
+ import { ProductCard } from "@stoovles/gap-kit";
9
+
10
+ <ProductCard
11
+ imageUrl="/images/denim-jacket.jpg"
12
+ name="Classic Denim Jacket"
13
+ originalPrice="$89.95"
14
+ salePrice="$59.99"
15
+ discount="33% off"
16
+ badge="New"
17
+ marketingFlag="Selling fast"
18
+ rating={4}
19
+ reviewCount={128}
20
+ swatches={[
21
+ { color: "#8BA3C7", label: "Light Wash" },
22
+ { color: "#2C3E50", label: "Dark Wash" },
23
+ ]}
24
+ moreSwatches={6}
25
+ />
26
+ ```
27
+
28
+ ## Props
29
+
30
+ | Prop | Type | Default | Description |
31
+ |------|------|---------|-------------|
32
+ | `variant` | `"default" \| "mini"` | `"default"` | Card layout |
33
+ | `imageUrl` | `string` | — | Product image URL |
34
+ | `imageAlt` | `string` | `""` | Image alt text |
35
+ | `name` | `string` | **required** | Product name |
36
+ | `badge` | `string` | — | Overlay badge text (default only) |
37
+ | `originalPrice` | `string` | — | Strikethrough price |
38
+ | `salePrice` | `string` | — | Sale price in red |
39
+ | `discount` | `string` | — | Discount text (e.g. "30% off") |
40
+ | `marketingFlag` | `string` | — | Marketing callout (default only) |
41
+ | `rating` | `number` | — | Star rating 0–5 |
42
+ | `reviewCount` | `number` | — | Number of reviews |
43
+ | `swatches` | `ProductCardSwatch[]` | — | Color swatch array |
44
+ | `moreSwatches` | `number` | — | Additional swatch count |
45
+ | `onClick` | `() => void` | — | Card click handler |
46
+
47
+ ## Visual reference
48
+
49
+ ### Default variant (220px wide)
50
+ - Image area: full width, 136:181 aspect ratio
51
+ - Badge: bottom-left overlay on image
52
+ - Swatches: 20px circles with 4px ring padding, first has active outline
53
+ - Product name: 16px neutral gray
54
+ - Price: stacked (original strikethrough → discount → sale)
55
+ - Marketing flag: 16px gray
56
+ - Star rating: 14px filled/outline stars + review count link
57
+
58
+ ### Mini variant (326px wide)
59
+ - 60×80px rounded image thumbnail on the left
60
+ - Product name + inline price on the right
61
+
62
+ ## Examples
63
+
64
+ Mini card for recently viewed:
65
+
66
+ ```jsx
67
+ <ProductCard
68
+ variant="mini"
69
+ imageUrl="/thumb.jpg"
70
+ name="Relaxed Fit Jeans"
71
+ originalPrice="$59.95"
72
+ salePrice="$39.99"
73
+ discount="33% off"
74
+ />
75
+ ```
76
+
77
+ ## Rules
78
+
79
+ - Always provide `name` — it's the only required field.
80
+ - Swatches only render in the default variant.
81
+ - If no `imageUrl` is given, a gray placeholder is rendered.
82
+ - The card width can be overridden via `className` for responsive grids.
@@ -0,0 +1,92 @@
1
+ # Radio
2
+
3
+ ## When to use
4
+
5
+ Use `Radio` when the user must select exactly one option from a list of mutually-exclusive choices. For multi-select, use `Checkbox` instead.
6
+
7
+ ```tsx
8
+ import { Radio, RadioGroup } from '@stoovles/gap-kit'
9
+ ```
10
+
11
+ ## Radio Props
12
+
13
+ | Prop | Type | Default | Description |
14
+ |---|---|---|---|
15
+ | `label` | `ReactNode` | — | Text label displayed next to the radio |
16
+ | `error` | `boolean` | `false` | Renders the radio in the error state (red) |
17
+ | `disabled` | `boolean` | `false` | Grays out the radio and prevents interaction |
18
+ | `checked` | `boolean` | — | Controlled checked state |
19
+ | `value` | `string` | — | Value submitted with the form |
20
+ | `onChange` | `(e) => void` | — | Change handler |
21
+ | `className` | `string` | — | Additional CSS class |
22
+
23
+ All standard `<input>` HTML attributes are also supported.
24
+
25
+ ## RadioGroup Props
26
+
27
+ | Prop | Type | Default | Description |
28
+ |---|---|---|---|
29
+ | `children` | `ReactNode` | — | `Radio` components |
30
+ | `name` | `string` | — | Shared `name` attribute automatically applied to all child radios |
31
+ | `label` | `string` | — | Group legend (optional) |
32
+ | `error` | `boolean` | `false` | Applies error styling to the group |
33
+ | `className` | `string` | — | Additional CSS class |
34
+
35
+ ## Visual reference
36
+
37
+ | State | Unselected | Selected |
38
+ |---|---|---|
39
+ | Default | White circle, black 1px border | White circle, black border, dark gray inner dot (#595959) |
40
+ | Error | White circle, red border | White circle, red border, red inner dot (#d00000) |
41
+ | Disabled | White circle, gray border (#ededed) | White circle, gray border, gray inner dot (#ededed) |
42
+
43
+ ## Sizing
44
+
45
+ - Outer circle: 20px inside a 24px touch target
46
+ - Inner dot (selected): 10px
47
+ - Gap between radio and label: 4px
48
+ - Group spacing between items: 24px
49
+ - Group vertical padding: 16px
50
+ - Font: Gap Sans, 16px, weight 400
51
+
52
+ ## Examples
53
+
54
+ ```tsx
55
+ {/* RadioGroup with controlled value */}
56
+ <RadioGroup name="shipping" label="Shipping method">
57
+ <Radio
58
+ label="Standard (5-7 days)"
59
+ value="standard"
60
+ checked={shipping === 'standard'}
61
+ onChange={() => setShipping('standard')}
62
+ />
63
+ <Radio
64
+ label="Express (2-3 days)"
65
+ value="express"
66
+ checked={shipping === 'express'}
67
+ onChange={() => setShipping('express')}
68
+ />
69
+ <Radio
70
+ label="Next day"
71
+ value="nextday"
72
+ checked={shipping === 'nextday'}
73
+ onChange={() => setShipping('nextday')}
74
+ />
75
+ </RadioGroup>
76
+
77
+ {/* Error state */}
78
+ <RadioGroup name="size" label="Select a size" error>
79
+ <Radio label="S" value="s" error />
80
+ <Radio label="M" value="m" error />
81
+ <Radio label="L" value="l" error />
82
+ </RadioGroup>
83
+ ```
84
+
85
+ ## Rules
86
+
87
+ - Always wrap radios in a `RadioGroup` with a shared `name`
88
+ - Provide a `label` on the group via `<legend>` for accessibility
89
+ - Always provide a `label` on each radio — if no visible text, use `aria-label`
90
+ - Pre-select a default option whenever possible
91
+ - Never use a single radio — use a checkbox for boolean toggles
92
+ - Do not mix error and disabled states in the same group