@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.
- package/dist/index.js +2417 -0
- package/dist/styles.css +4309 -0
- package/guidelines/components/accordion.md +99 -0
- package/guidelines/components/breadcrumb.md +74 -0
- package/guidelines/components/buttons.md +90 -0
- package/guidelines/components/checkbox.md +86 -0
- package/guidelines/components/chip.md +77 -0
- package/guidelines/components/divider.md +52 -0
- package/guidelines/components/dropdown.md +125 -0
- package/guidelines/components/filter-sort.md +108 -0
- package/guidelines/components/fulfillment.md +100 -0
- package/guidelines/components/global-footer.md +71 -0
- package/guidelines/components/global-header.md +67 -0
- package/guidelines/components/hamburger-nav.md +79 -0
- package/guidelines/components/handle.md +49 -0
- package/guidelines/components/icons.md +147 -0
- package/guidelines/components/link.md +70 -0
- package/guidelines/components/logo.md +63 -0
- package/guidelines/components/mega-nav.md +103 -0
- package/guidelines/components/notification.md +97 -0
- package/guidelines/components/pagination.md +88 -0
- package/guidelines/components/popover.md +77 -0
- package/guidelines/components/price.md +86 -0
- package/guidelines/components/product-card.md +82 -0
- package/guidelines/components/radio.md +92 -0
- package/guidelines/components/search-input.md +63 -0
- package/guidelines/components/selector-swatch.md +78 -0
- package/guidelines/components/selector.md +95 -0
- package/guidelines/components/slider.md +116 -0
- package/guidelines/components/switch.md +108 -0
- package/guidelines/components/tabs.md +83 -0
- package/guidelines/components/text-input.md +80 -0
- package/guidelines/composition/buy-box.md +132 -0
- package/guidelines/composition/recommendations.md +100 -0
- package/package.json +38 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Filter & Sort
|
|
2
|
+
|
|
3
|
+
A two-part filtering system for PLPs: **FilterControls** (horizontal chip bar with active filters) and **FilterDrawer** (a slide-out drawer with accordion sections).
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
### FilterControls
|
|
8
|
+
|
|
9
|
+
```jsx
|
|
10
|
+
import { FilterControls } from "@stoovles/gap-kit";
|
|
11
|
+
|
|
12
|
+
<FilterControls
|
|
13
|
+
chips={[
|
|
14
|
+
{ label: "Filter & Sort", value: "filter-sort" },
|
|
15
|
+
{ label: "Category", value: "category" },
|
|
16
|
+
{ label: "Size", value: "size" },
|
|
17
|
+
{ label: "Color", value: "color" },
|
|
18
|
+
{ label: "Model Size", value: "model-size", hasDropdown: true },
|
|
19
|
+
]}
|
|
20
|
+
activeChip="filter-sort"
|
|
21
|
+
onChipClick={(val) => openFilter(val)}
|
|
22
|
+
activeFilters={[
|
|
23
|
+
{ label: "Blue", value: "color-blue" },
|
|
24
|
+
{ label: "Medium", value: "size-m" },
|
|
25
|
+
]}
|
|
26
|
+
onRemoveFilter={(val) => removeFilter(val)}
|
|
27
|
+
onClearAll={() => clearFilters()}
|
|
28
|
+
resultCount="128 Results"
|
|
29
|
+
/>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### FilterDrawer
|
|
33
|
+
|
|
34
|
+
```jsx
|
|
35
|
+
import { FilterDrawer, Checkbox, CheckboxGroup } from "@stoovles/gap-kit";
|
|
36
|
+
|
|
37
|
+
<FilterDrawer
|
|
38
|
+
open={drawerOpen}
|
|
39
|
+
title="Filter & Sort"
|
|
40
|
+
onClose={() => setDrawerOpen(false)}
|
|
41
|
+
sections={[
|
|
42
|
+
{
|
|
43
|
+
title: "Category",
|
|
44
|
+
count: 12,
|
|
45
|
+
subtitle: "Tops, Dresses",
|
|
46
|
+
children: (
|
|
47
|
+
<CheckboxGroup>
|
|
48
|
+
<Checkbox label="Tops" />
|
|
49
|
+
<Checkbox label="Dresses" />
|
|
50
|
+
<Checkbox label="Jeans" />
|
|
51
|
+
</CheckboxGroup>
|
|
52
|
+
),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
title: "Size",
|
|
56
|
+
count: 8,
|
|
57
|
+
subtitle: "S, M, L",
|
|
58
|
+
children: <Selector options={sizeOptions} />,
|
|
59
|
+
},
|
|
60
|
+
]}
|
|
61
|
+
onApply={() => applyFilters()}
|
|
62
|
+
/>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Props — FilterControls
|
|
66
|
+
|
|
67
|
+
| Prop | Type | Default | Description |
|
|
68
|
+
|------|------|---------|-------------|
|
|
69
|
+
| `chips` | `FilterChip[]` | `[]` | Available filter chips |
|
|
70
|
+
| `activeChip` | `string` | — | Currently active chip value |
|
|
71
|
+
| `onChipClick` | `(value: string) => void` | — | Chip click handler |
|
|
72
|
+
| `activeFilters` | `ActiveFilter[]` | `[]` | Active filter tags |
|
|
73
|
+
| `onRemoveFilter` | `(value: string) => void` | — | Remove a filter |
|
|
74
|
+
| `onClearAll` | `() => void` | — | Clear all filters |
|
|
75
|
+
| `resultCount` | `string` | — | Result count text |
|
|
76
|
+
|
|
77
|
+
## Props — FilterDrawer
|
|
78
|
+
|
|
79
|
+
| Prop | Type | Default | Description |
|
|
80
|
+
|------|------|---------|-------------|
|
|
81
|
+
| `open` | `boolean` | **required** | Whether the drawer is visible |
|
|
82
|
+
| `title` | `string` | `"Filter & Sort"` | Drawer header title |
|
|
83
|
+
| `onClose` | `() => void` | **required** | Close handler |
|
|
84
|
+
| `sections` | `FilterSection[]` | `[]` | Accordion filter sections |
|
|
85
|
+
| `applyLabel` | `string` | `"Apply"` | Apply button label |
|
|
86
|
+
| `onApply` | `() => void` | — | Apply callback (renders button) |
|
|
87
|
+
|
|
88
|
+
## Visual reference
|
|
89
|
+
|
|
90
|
+
### FilterControls
|
|
91
|
+
- **Chips:** Bordered pills, 16px text; active chip gets 2px accent border
|
|
92
|
+
- **"Filter & Sort" chip:** Includes a small filter icon to the left
|
|
93
|
+
- **Active tags:** Compact pills with accent border + X close icon
|
|
94
|
+
- **"Clear all":** Underlined text link
|
|
95
|
+
- **Result count:** Small 10px secondary text
|
|
96
|
+
|
|
97
|
+
### FilterDrawer
|
|
98
|
+
- **Width:** 320px, slides in from the right
|
|
99
|
+
- **Header:** 20px bold title + close X, 24px padding
|
|
100
|
+
- **Sections:** Each has a subtle divider, bold title with optional (count) and subtitle, chevron toggle
|
|
101
|
+
- **Apply button:** Full-width primary button at the bottom
|
|
102
|
+
|
|
103
|
+
## Rules
|
|
104
|
+
|
|
105
|
+
- FilterControls and FilterDrawer are separate components; use them together for the full filtering experience.
|
|
106
|
+
- The drawer sections accept any `children` — compose with Checkbox, Selector, SelectorSwatch, RangeSlider, etc.
|
|
107
|
+
- Always show `resultCount` so the user knows how many items match the current filters.
|
|
108
|
+
- The "Filter & Sort" chip value should be `"filter-sort"` to trigger the filter icon rendering.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Fulfillment
|
|
2
|
+
|
|
3
|
+
## When to use
|
|
4
|
+
|
|
5
|
+
Use `Fulfillment` to let customers choose between shipping and in-store pickup. Each option is a `FulfillmentTile` — a card with a title, description, and availability status. Wrap them in a `Fulfillment` container for side-by-side layout.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Fulfillment, FulfillmentTile } from '@stoovles/gap-kit'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Props
|
|
12
|
+
|
|
13
|
+
### FulfillmentTile
|
|
14
|
+
|
|
15
|
+
| Prop | Type | Default | Description |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| `title` | `string` | — | Tile heading (e.g. "Free shipping", "In-store pickup") |
|
|
18
|
+
| `selected` | `boolean` | `false` | Whether this tile is the active fulfillment method |
|
|
19
|
+
| `availability` | `string` | — | Status text (e.g. "In Stock") |
|
|
20
|
+
| `children` | `ReactNode` | — | Subtext, links, store name, etc. |
|
|
21
|
+
| `onClick` | `() => void` | — | Selection handler |
|
|
22
|
+
| `className` | `string` | — | Additional CSS class |
|
|
23
|
+
|
|
24
|
+
### Fulfillment
|
|
25
|
+
|
|
26
|
+
| Prop | Type | Default | Description |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| `children` | `ReactNode` | — | `FulfillmentTile` elements |
|
|
29
|
+
| `className` | `string` | — | Additional CSS class |
|
|
30
|
+
|
|
31
|
+
## Visual reference
|
|
32
|
+
|
|
33
|
+
| State | Border | Availability color |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| Unselected | 1px subtle gray (`--color-border-subtle`) | Primary (#000) |
|
|
36
|
+
| Selected | 2px accent blue (`--color-border-accent`) | Success green (`--color-type-success`) |
|
|
37
|
+
|
|
38
|
+
## Sizing
|
|
39
|
+
|
|
40
|
+
- Tile min-height: 124px
|
|
41
|
+
- Padding: 16px (`--spacing-utk-l`)
|
|
42
|
+
- Border radius: 0px (Gap square-corner identity)
|
|
43
|
+
- Title: Gap Sans, 16px, weight 400 (`--text-selector-header-tile-font-weight`)
|
|
44
|
+
- Subtext / availability: Gap Sans, 16px, weight 400
|
|
45
|
+
- Gap between tiles: 8px (`--spacing-utk-s`)
|
|
46
|
+
- Tiles are equal-width (flex: 1 1 0)
|
|
47
|
+
|
|
48
|
+
## Examples
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
{/* Basic shipping vs. pickup */}
|
|
52
|
+
<Fulfillment>
|
|
53
|
+
<FulfillmentTile
|
|
54
|
+
title="Free shipping"
|
|
55
|
+
selected={method === "ship"}
|
|
56
|
+
availability="In Stock"
|
|
57
|
+
onClick={() => setMethod("ship")}
|
|
58
|
+
>
|
|
59
|
+
<span>on $50+ for Rewards Members. </span>
|
|
60
|
+
<a href="/rewards">Sign in</a>
|
|
61
|
+
</FulfillmentTile>
|
|
62
|
+
<FulfillmentTile
|
|
63
|
+
title="In-store pickup"
|
|
64
|
+
selected={method === "pickup"}
|
|
65
|
+
onClick={() => setMethod("pickup")}
|
|
66
|
+
>
|
|
67
|
+
<a href="/store-locator">Find a store</a>
|
|
68
|
+
</FulfillmentTile>
|
|
69
|
+
</Fulfillment>
|
|
70
|
+
|
|
71
|
+
{/* With store selected */}
|
|
72
|
+
<Fulfillment>
|
|
73
|
+
<FulfillmentTile
|
|
74
|
+
title="Free shipping"
|
|
75
|
+
availability="In Stock"
|
|
76
|
+
onClick={() => setMethod("ship")}
|
|
77
|
+
>
|
|
78
|
+
on $50+ for Rewards Members.
|
|
79
|
+
</FulfillmentTile>
|
|
80
|
+
<FulfillmentTile
|
|
81
|
+
title="In-store pickup"
|
|
82
|
+
selected
|
|
83
|
+
availability="In Stock"
|
|
84
|
+
onClick={() => setMethod("pickup")}
|
|
85
|
+
>
|
|
86
|
+
<span>Walnut Creek</span>
|
|
87
|
+
<br />
|
|
88
|
+
<a href="/change-store">Change store</a>
|
|
89
|
+
</FulfillmentTile>
|
|
90
|
+
</Fulfillment>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Rules
|
|
94
|
+
|
|
95
|
+
- Always provide two tiles: shipping and pickup — they sit side by side
|
|
96
|
+
- Use `selected` to highlight the active fulfillment method
|
|
97
|
+
- When selected, the "In Stock" availability text turns green (`--color-type-success`)
|
|
98
|
+
- Place links (e.g. "Sign in", "Find a store") inside `children`
|
|
99
|
+
- The component is a `<button>` for keyboard accessibility
|
|
100
|
+
- Each tile flexes to fill available width equally
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Global Footer
|
|
2
|
+
|
|
3
|
+
The site-wide footer containing promotional blocks (email sign-up, rewards credit card, app download), navigation columns, social links, and legal text.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```jsx
|
|
8
|
+
import { GlobalFooter } from "@stoovles/gap-kit";
|
|
9
|
+
|
|
10
|
+
<GlobalFooter />
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
All sections ship with Gap-branded defaults, so a bare `<GlobalFooter />` renders a fully populated footer.
|
|
14
|
+
|
|
15
|
+
## Props
|
|
16
|
+
|
|
17
|
+
| Prop | Type | Default | Description |
|
|
18
|
+
|------|------|---------|-------------|
|
|
19
|
+
| `blocks` | `FooterBlock[]` | Email, Credit Card, App | Top-row promotional blocks |
|
|
20
|
+
| `navColumns` | `FooterNavColumn[]` | Customer Support, Rewards, About Us, Find Us | Navigation link columns |
|
|
21
|
+
| `socialLinks` | `SocialLink[]` | TikTok, Instagram, YouTube, Spotify | Social icons below the last nav column |
|
|
22
|
+
| `legalLines` | `(string \| ReactNode)[]` | Gap Inc. legal boilerplate | Bottom legal text lines |
|
|
23
|
+
|
|
24
|
+
### FooterBlock
|
|
25
|
+
|
|
26
|
+
| Field | Type | Description |
|
|
27
|
+
|-------|------|-------------|
|
|
28
|
+
| `title` | `string` | Block heading |
|
|
29
|
+
| `content` | `ReactNode` | Free-form block body (email form, links, copy, etc.) |
|
|
30
|
+
|
|
31
|
+
### FooterNavColumn
|
|
32
|
+
|
|
33
|
+
| Field | Type | Description |
|
|
34
|
+
|-------|------|-------------|
|
|
35
|
+
| `title` | `string` | Column heading |
|
|
36
|
+
| `links` | `{ label, href }[]` | List of footer links |
|
|
37
|
+
|
|
38
|
+
### SocialLink
|
|
39
|
+
|
|
40
|
+
| Field | Type | Description |
|
|
41
|
+
|-------|------|-------------|
|
|
42
|
+
| `label` | `string` | Accessible name |
|
|
43
|
+
| `href` | `string` | Link URL |
|
|
44
|
+
| `icon` | `ReactNode` | 32×32 SVG icon |
|
|
45
|
+
|
|
46
|
+
## Visual reference
|
|
47
|
+
|
|
48
|
+
- **Background:** White (`--global-footer-background`)
|
|
49
|
+
- **Top blocks:** 3-column layout with border-right dividers, 18px bold titles
|
|
50
|
+
- **Nav columns:** 4-column row, 16px bold headers, 16px secondary gray links
|
|
51
|
+
- **Social icons:** 32×32px dark circular icons (TikTok, Instagram, YouTube, Spotify)
|
|
52
|
+
- **Legal text:** 12px body, pipe-separated items across two lines
|
|
53
|
+
|
|
54
|
+
## Examples
|
|
55
|
+
|
|
56
|
+
Override just the nav columns:
|
|
57
|
+
|
|
58
|
+
```jsx
|
|
59
|
+
<GlobalFooter
|
|
60
|
+
navColumns={[
|
|
61
|
+
{ title: "Help", links: [{ label: "FAQ", href: "/faq" }] },
|
|
62
|
+
{ title: "About", links: [{ label: "Careers", href: "/careers" }] },
|
|
63
|
+
]}
|
|
64
|
+
/>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Rules
|
|
68
|
+
|
|
69
|
+
- Always render at the bottom of every page layout.
|
|
70
|
+
- The footer is full-width; inner content is constrained to 1264px.
|
|
71
|
+
- Do not remove the legal section in production prototypes.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Global Header
|
|
2
|
+
|
|
3
|
+
The top-level navigation bar present on every page. Contains the sister-brand bar, a promotional banner (EDFS — Everyday Free Shipping), and account/bag action icons.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```jsx
|
|
8
|
+
import { GlobalHeader } from "@stoovles/gap-kit";
|
|
9
|
+
|
|
10
|
+
<GlobalHeader
|
|
11
|
+
promoText="Free shipping on $50+ for Rewards Members"
|
|
12
|
+
promoLinks={[
|
|
13
|
+
{ label: "Sign In or Join", href: "/signin" },
|
|
14
|
+
{ label: "Details", href: "/shipping-details" },
|
|
15
|
+
]}
|
|
16
|
+
bagCount={3}
|
|
17
|
+
onBagClick={() => openBag()}
|
|
18
|
+
onAccountClick={() => openAccount()}
|
|
19
|
+
/>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Props
|
|
23
|
+
|
|
24
|
+
| Prop | Type | Default | Description |
|
|
25
|
+
|------|------|---------|-------------|
|
|
26
|
+
| `brands` | `BrandLink[]` | Gap, Gap Factory, Old Navy, Banana Republic, Athleta, Banana Republic Factory | Sister-brand links in the top-left bar |
|
|
27
|
+
| `promoText` | `string` | `"Free shipping on $50+ for Rewards Members"` | Promotional text in center |
|
|
28
|
+
| `promoLinks` | `{ label, href }[]` | Sign In or Join, Details | Underlined links after promo text |
|
|
29
|
+
| `bagCount` | `number` | — | Items in the shopping bag; shows a badge when > 0 |
|
|
30
|
+
| `onBagClick` | `() => void` | — | Callback for bag icon click |
|
|
31
|
+
| `onAccountClick` | `() => void` | — | Callback for account icon click |
|
|
32
|
+
| `onLogoClick` | `() => void` | — | Callback for logo click |
|
|
33
|
+
|
|
34
|
+
## Visual reference
|
|
35
|
+
|
|
36
|
+
- **Background:** Solid black (`--global-header-brand-bar-bg`)
|
|
37
|
+
- **Brand links:** Gray text at 10px, white on hover
|
|
38
|
+
- **Promo area:** White text centered, underlined links
|
|
39
|
+
- **Icons:** White 24px person + 18×24px bag outlines
|
|
40
|
+
- **Badge:** 16×16px white circle with black count text, positioned inside the bag icon
|
|
41
|
+
- **Height:** 48px fixed
|
|
42
|
+
|
|
43
|
+
## Examples
|
|
44
|
+
|
|
45
|
+
Minimal header with no bag items:
|
|
46
|
+
|
|
47
|
+
```jsx
|
|
48
|
+
<GlobalHeader bagCount={0} />
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Custom brands:
|
|
52
|
+
|
|
53
|
+
```jsx
|
|
54
|
+
<GlobalHeader
|
|
55
|
+
brands={[
|
|
56
|
+
{ label: "Gap", href: "/gap" },
|
|
57
|
+
{ label: "Old Navy", href: "/oldnavy" },
|
|
58
|
+
]}
|
|
59
|
+
/>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Rules
|
|
63
|
+
|
|
64
|
+
- Always render the header at the very top of every page layout.
|
|
65
|
+
- The sister-brand bar scrolls horizontally if the viewport is too narrow.
|
|
66
|
+
- The promo text should reflect the current promotion; override via `promoText`.
|
|
67
|
+
- Do not put the header inside another container that constrains its width.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Hamburger Navigation
|
|
2
|
+
|
|
3
|
+
A mobile-style slide-out navigation drawer that covers the viewport with a dark overlay. Contains the sister-brand bar, a search field, department links with right chevrons, and secondary utility links on a subtle gray background.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```jsx
|
|
8
|
+
import { HamburgerNav } from "@stoovles/gap-kit";
|
|
9
|
+
|
|
10
|
+
const [navOpen, setNavOpen] = useState(false);
|
|
11
|
+
|
|
12
|
+
<HamburgerNav
|
|
13
|
+
open={navOpen}
|
|
14
|
+
onClose={() => setNavOpen(false)}
|
|
15
|
+
sections={[
|
|
16
|
+
{
|
|
17
|
+
items: [
|
|
18
|
+
{ label: "New", hasChildren: true, onClick: () => {} },
|
|
19
|
+
{ label: "Women", hasChildren: true, onClick: () => {} },
|
|
20
|
+
{ label: "Men", hasChildren: true, onClick: () => {} },
|
|
21
|
+
{ label: "Sale", hasChildren: true, onClick: () => {} },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
secondary: true,
|
|
26
|
+
items: [
|
|
27
|
+
{ label: "Customer Service" },
|
|
28
|
+
{ label: "Orders & Returns" },
|
|
29
|
+
{ label: "Gift Cards", hasChildren: true },
|
|
30
|
+
{ label: "Find a Store" },
|
|
31
|
+
{ label: "Sign In" },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
]}
|
|
35
|
+
onSearch={(q) => navigate(`/search?q=${q}`)}
|
|
36
|
+
/>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Props
|
|
40
|
+
|
|
41
|
+
| Prop | Type | Default | Description |
|
|
42
|
+
|------|------|---------|-------------|
|
|
43
|
+
| `open` | `boolean` | **required** | Whether the drawer is visible |
|
|
44
|
+
| `onClose` | `() => void` | **required** | Called to close the drawer |
|
|
45
|
+
| `sections` | `HamburgerNavSection[]` | **required** | Nav sections |
|
|
46
|
+
| `headerSlot` | `ReactNode` | — | Content above the search (e.g. brand bar, promo) |
|
|
47
|
+
| `searchPlaceholder` | `string` | `"Search Gap"` | Search input placeholder |
|
|
48
|
+
| `onSearch` | `(value: string) => void` | — | Fires on Enter in search |
|
|
49
|
+
|
|
50
|
+
### HamburgerNavSection
|
|
51
|
+
|
|
52
|
+
| Field | Type | Description |
|
|
53
|
+
|-------|------|-------------|
|
|
54
|
+
| `items` | `HamburgerNavItem[]` | Nav items in this section |
|
|
55
|
+
| `secondary` | `boolean` | Gray background for utility links |
|
|
56
|
+
|
|
57
|
+
### HamburgerNavItem
|
|
58
|
+
|
|
59
|
+
| Field | Type | Description |
|
|
60
|
+
|-------|------|-------------|
|
|
61
|
+
| `label` | `string` | Display text |
|
|
62
|
+
| `href` | `string` | Optional link URL (renders `<a>` vs `<button>`) |
|
|
63
|
+
| `hasChildren` | `boolean` | Shows a right chevron |
|
|
64
|
+
| `onClick` | `() => void` | Click callback |
|
|
65
|
+
|
|
66
|
+
## Visual reference
|
|
67
|
+
|
|
68
|
+
- **Overlay:** 30% dark transparent background covering the full viewport
|
|
69
|
+
- **Panel:** 350px wide, white, slides in from the left
|
|
70
|
+
- **Search:** 44px bordered input with magnifying glass icon
|
|
71
|
+
- **Nav rows:** 16px text, 24px top padding per row, right chevron for expandable items
|
|
72
|
+
- **Secondary section:** subtle gray background (#f7f7f7)
|
|
73
|
+
- **Close button:** White X icon positioned to the right of the panel
|
|
74
|
+
|
|
75
|
+
## Rules
|
|
76
|
+
|
|
77
|
+
- The hamburger nav is a fixed-position overlay — it blocks all content behind it.
|
|
78
|
+
- Always provide `onClose`; the overlay click and X button both trigger it.
|
|
79
|
+
- Split navigation into primary (departments) and secondary (utility) sections.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Handle
|
|
2
|
+
|
|
3
|
+
## When to use
|
|
4
|
+
|
|
5
|
+
Use the `Handle` component as a draggable thumb for sliders, range selectors, and similar interactive controls. This is a visual primitive — pair it with your own drag logic.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Handle } from '@stoovles/gap-kit'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Props
|
|
12
|
+
|
|
13
|
+
| Prop | Type | Default | Description |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| `size` | `"large" \| "small"` | `"large"` | Large is 24px, small is 20px |
|
|
16
|
+
| `className` | `string` | — | Additional CSS class |
|
|
17
|
+
|
|
18
|
+
## Visual reference
|
|
19
|
+
|
|
20
|
+
| State | Ring | Thumb |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| Default | 32px invisible ring | White circle with drop shadow |
|
|
23
|
+
| Hover | 48px, gray (#757575) at 30% opacity | White circle with drop shadow |
|
|
24
|
+
| Press | 48px, gray (#757575) at 50% opacity | White circle with drop shadow |
|
|
25
|
+
|
|
26
|
+
## Sizing
|
|
27
|
+
|
|
28
|
+
| Size | Thumb diameter | Default ring | Hover/press ring |
|
|
29
|
+
|---|---|---|---|
|
|
30
|
+
| `large` | 24px | 32px | 48px |
|
|
31
|
+
| `small` | 20px | 32px | 48px |
|
|
32
|
+
|
|
33
|
+
## Examples
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
{/* Default large handle */}
|
|
37
|
+
<Handle />
|
|
38
|
+
|
|
39
|
+
{/* Small handle for compact sliders */}
|
|
40
|
+
<Handle size="small" />
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Rules
|
|
44
|
+
|
|
45
|
+
- Always use within a slider or range control context
|
|
46
|
+
- The handle uses `cursor: grab` by default and `cursor: grabbing` when active
|
|
47
|
+
- The hover ring provides a generous touch target — do not override its size
|
|
48
|
+
- Background is always white; the drop shadow provides visual lift
|
|
49
|
+
- Do not add borders or outlines to the handle — it relies on shadow for definition
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Icon
|
|
2
|
+
|
|
3
|
+
## When to use
|
|
4
|
+
|
|
5
|
+
Use the `Icon` component for all iconography. Never use raw SVGs, emoji, or Unicode symbols. Always import from `@stoovles/gap-kit`.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Icon } from '@stoovles/gap-kit'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Props
|
|
12
|
+
|
|
13
|
+
| Prop | Type | Default | Description |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| `name` | `IconName` | required | Icon identifier (see catalog below) |
|
|
16
|
+
| `size` | `12 \| 16 \| 24 \| 32` | `16` | Pixel size of the icon |
|
|
17
|
+
| `color` | `"dark" \| "light"` | `"dark"` | Dark renders in `--color-icon-default` (#000), Light renders in `--color-icon-inverse` (#fff) |
|
|
18
|
+
| `className` | `string` | — | Additional CSS class |
|
|
19
|
+
| `aria-label` | `string` | — | Accessible label; when set, icon has `role="img"` |
|
|
20
|
+
|
|
21
|
+
## Icon catalog
|
|
22
|
+
|
|
23
|
+
### Navigation
|
|
24
|
+
|
|
25
|
+
| Name | Purpose | Common sizes |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `chevron-left` | Navigate back, collapse | 12, 16 |
|
|
28
|
+
| `chevron-right` | Navigate forward, expand | 12, 16 |
|
|
29
|
+
| `chevron-down` | Expand dropdown, accordion | 12, 16 |
|
|
30
|
+
| `chevron-up` | Collapse dropdown, accordion | 12, 16 |
|
|
31
|
+
| `menu` | Hamburger menu toggle | 16, 24 |
|
|
32
|
+
| `x` | Close, dismiss, remove | 12, 16, 24 |
|
|
33
|
+
|
|
34
|
+
### Actions
|
|
35
|
+
|
|
36
|
+
| Name | Purpose | Common sizes |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `search` | Search trigger or input icon | 16, 24 |
|
|
39
|
+
| `filter` | Filter controls toggle | 16 |
|
|
40
|
+
| `bag` | Shopping bag | 16, 24 |
|
|
41
|
+
| `profile` | User account | 16, 24 |
|
|
42
|
+
| `quickadd` | Add to bag (bag + plus) | 16, 24 |
|
|
43
|
+
|
|
44
|
+
### Status & feedback
|
|
45
|
+
|
|
46
|
+
| Name | Purpose | Common sizes |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `checkmark-filled` | Confirmed, completed (solid circle) | 12, 16 |
|
|
49
|
+
| `checkmark-outlined` | Selected, checked (stroke only) | 12, 16 |
|
|
50
|
+
| `alert-circle` | Warning or error (solid circle) | 16 |
|
|
51
|
+
| `info-circle` | Informational (solid circle) | 16 |
|
|
52
|
+
| `info` | Informational (outlined circle) | 12, 16, 24 |
|
|
53
|
+
| `loader` | Loading animation dots | 16, 24 |
|
|
54
|
+
|
|
55
|
+
### Media
|
|
56
|
+
|
|
57
|
+
| Name | Purpose | Common sizes |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `play` | Play video/audio | 12, 16 |
|
|
60
|
+
| `pause` | Pause video/audio | 12, 16 |
|
|
61
|
+
| `volume-on` | Audio on/unmuted | 16 |
|
|
62
|
+
| `volume-off` | Audio off/muted | 16 |
|
|
63
|
+
| `star-filled` | Full rating star | 16 |
|
|
64
|
+
| `star-half` | Half rating star | 16 |
|
|
65
|
+
| `star-empty` | Empty rating star | 16 |
|
|
66
|
+
|
|
67
|
+
### Content
|
|
68
|
+
|
|
69
|
+
| Name | Purpose | Common sizes |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `fire` | Trending, hot | 16 |
|
|
72
|
+
| `show` | Eye/visibility toggle | 16 |
|
|
73
|
+
| `email-outlined` | Email, contact | 16 |
|
|
74
|
+
| `location-outlined` | Store locator (outlined) | 16 |
|
|
75
|
+
| `location-filled` | Current location (solid) | 16 |
|
|
76
|
+
| `phone` | Phone, call | 16 |
|
|
77
|
+
| `swatch` | Color swatch circle | 16 |
|
|
78
|
+
|
|
79
|
+
## Size decision tree
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
Which icon size should I use?
|
|
83
|
+
|
|
84
|
+
├─ Inline with small text (captions, badges)?
|
|
85
|
+
│ └─ 12
|
|
86
|
+
│
|
|
87
|
+
├─ Standard UI element (buttons, inputs, cards)?
|
|
88
|
+
│ └─ 16 (default)
|
|
89
|
+
│
|
|
90
|
+
├─ Prominent action (header icons, primary actions)?
|
|
91
|
+
│ └─ 24
|
|
92
|
+
│
|
|
93
|
+
└─ Social media badge or large display?
|
|
94
|
+
└─ 32
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Color decision tree
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
Which icon color should I use?
|
|
101
|
+
|
|
102
|
+
├─ On a light background (white, gray, brand-tertiary)?
|
|
103
|
+
│ └─ color="dark" (default)
|
|
104
|
+
│
|
|
105
|
+
└─ On a dark background (black, brand fill, dark overlay)?
|
|
106
|
+
└─ color="light"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Examples
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
{/* Standard navigation chevron */}
|
|
113
|
+
<Icon name="chevron-right" size={16} />
|
|
114
|
+
|
|
115
|
+
{/* Search icon in header (on dark background) */}
|
|
116
|
+
<Icon name="search" size={24} color="light" />
|
|
117
|
+
|
|
118
|
+
{/* Rating stars */}
|
|
119
|
+
<div style={{ display: 'flex', gap: '2px' }}>
|
|
120
|
+
<Icon name="star-filled" size={16} />
|
|
121
|
+
<Icon name="star-filled" size={16} />
|
|
122
|
+
<Icon name="star-filled" size={16} />
|
|
123
|
+
<Icon name="star-half" size={16} />
|
|
124
|
+
<Icon name="star-empty" size={16} />
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
{/* Close button with accessible label */}
|
|
128
|
+
<button>
|
|
129
|
+
<Icon name="x" size={24} aria-label="Close dialog" />
|
|
130
|
+
</button>
|
|
131
|
+
|
|
132
|
+
{/* Bag icon with count */}
|
|
133
|
+
<div style={{ position: 'relative' }}>
|
|
134
|
+
<Icon name="bag" size={24} color="light" />
|
|
135
|
+
<span className="bag-count">3</span>
|
|
136
|
+
</div>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Rules
|
|
140
|
+
|
|
141
|
+
- Always use `name` prop with a valid icon name from the catalog — do not guess names
|
|
142
|
+
- Default size is 16 — only change when a larger or smaller icon is specifically needed
|
|
143
|
+
- Use `color="light"` only on dark backgrounds — never on white or light gray surfaces
|
|
144
|
+
- For clickable icons, wrap in a `<button>` element and add `aria-label` to the Icon
|
|
145
|
+
- Icons inherit the container's alignment — use flexbox to center if needed
|
|
146
|
+
- Do not apply custom fill or stroke colors via className — use the `color` prop
|
|
147
|
+
- Star rating always uses exactly 5 icons: a combination of `star-filled`, `star-half`, and `star-empty`
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Link
|
|
2
|
+
|
|
3
|
+
## When to use
|
|
4
|
+
|
|
5
|
+
Use the `Link` component for navigational text that routes to another page or section. Renders a semantic `<a>` element.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Link } from '@stoovles/gap-kit'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Props
|
|
12
|
+
|
|
13
|
+
| Prop | Type | Default | Description |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| `variant` | `"inline" \| "subtle"` | `"inline"` | Inline is underlined and blue; subtle is plain gray |
|
|
16
|
+
| `children` | `ReactNode` | — | Link label text |
|
|
17
|
+
| `href` | `string` | — | Destination URL |
|
|
18
|
+
| `className` | `string` | — | Additional CSS class |
|
|
19
|
+
|
|
20
|
+
All standard `<a>` HTML attributes are also supported (e.g. `target`, `rel`, `aria-label`).
|
|
21
|
+
|
|
22
|
+
## Variant decision tree
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
What context is this link in?
|
|
26
|
+
|
|
27
|
+
├─ Within body copy, or a primary navigational action?
|
|
28
|
+
│ └─ variant="inline" (underlined blue)
|
|
29
|
+
│
|
|
30
|
+
└─ Secondary or de-emphasized link (footer, breadcrumb, supporting text)?
|
|
31
|
+
└─ variant="subtle" (plain gray, underlines on hover)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Visual reference
|
|
35
|
+
|
|
36
|
+
| Variant | Default | Hover / Active | Visited |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| `inline` | Blue text, blue 1px underline | Blue text, blue underline | Gray text (#757575), gray underline |
|
|
39
|
+
| `subtle` | Gray text (#757575), no underline | Blue text, blue 1px underline | Gray text, no underline |
|
|
40
|
+
|
|
41
|
+
## Sizing
|
|
42
|
+
|
|
43
|
+
- Font: Gap Sans, 16px, weight 400, letter-spacing 0.32px
|
|
44
|
+
- Underline: 1px solid bottom border
|
|
45
|
+
- Focus ring: 2px blue (#031ba1) outline, 2px offset
|
|
46
|
+
|
|
47
|
+
## Examples
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
{/* Inline link in body text */}
|
|
51
|
+
<p>
|
|
52
|
+
Check our <Link href="/returns">return policy</Link> for details.
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
{/* Subtle link in footer */}
|
|
56
|
+
<Link variant="subtle" href="/privacy">Privacy Policy</Link>
|
|
57
|
+
|
|
58
|
+
{/* External link */}
|
|
59
|
+
<Link href="https://gap.com" target="_blank" rel="noopener noreferrer">
|
|
60
|
+
Gap.com
|
|
61
|
+
</Link>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Rules
|
|
65
|
+
|
|
66
|
+
- Always use `inline` for links within body copy so they are clearly identifiable
|
|
67
|
+
- Use `subtle` only where context already implies navigation (e.g. footer link lists)
|
|
68
|
+
- Never use a `Link` for actions — use `Button` instead
|
|
69
|
+
- Always provide an `href` — for click-only actions without navigation, use `Button`
|
|
70
|
+
- For external links, include `target="_blank"` and `rel="noopener noreferrer"`
|