@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,99 @@
|
|
|
1
|
+
# Accordion
|
|
2
|
+
|
|
3
|
+
## When to use
|
|
4
|
+
|
|
5
|
+
Use the `Accordion` to present a vertically stacked list of sections that can be individually expanded or collapsed. Common uses include FAQs, filter panels, product detail sections, and settings groups.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Accordion, AccordionItem } from '@stoovles/gap-kit'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Props
|
|
12
|
+
|
|
13
|
+
### Accordion
|
|
14
|
+
|
|
15
|
+
| Prop | Type | Default | Description |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| `children` | `ReactNode` | — | One or more `AccordionItem` elements |
|
|
18
|
+
| `className` | `string` | — | Additional CSS class |
|
|
19
|
+
|
|
20
|
+
### AccordionItem
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Description |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| `title` | `string` | — | Header text for the section |
|
|
25
|
+
| `count` | `string \| number` | — | Optional count displayed after the title, e.g. `(3)` |
|
|
26
|
+
| `subtitle` | `string` | — | Secondary text shown below the title when collapsed |
|
|
27
|
+
| `attributes` | `string` | — | Summary text shown below the title when expanded (e.g. selected filter values) |
|
|
28
|
+
| `defaultExpanded` | `boolean` | `false` | Whether the item starts expanded |
|
|
29
|
+
| `children` | `ReactNode` | — | Content revealed when expanded |
|
|
30
|
+
| `className` | `string` | — | Additional CSS class |
|
|
31
|
+
|
|
32
|
+
## Visual reference
|
|
33
|
+
|
|
34
|
+
| State | Chevron | Subtitle | Content |
|
|
35
|
+
|---|---|---|---|
|
|
36
|
+
| Collapsed | Points down | Visible (if provided) | Hidden |
|
|
37
|
+
| Expanded | Points up | Hidden | Visible |
|
|
38
|
+
|
|
39
|
+
Each item is separated by a subtle divider. The chevron rotates 180 degrees with a smooth transition on expand/collapse.
|
|
40
|
+
|
|
41
|
+
## Sizing
|
|
42
|
+
|
|
43
|
+
- Item bottom padding: 24px (`--spacing-l`)
|
|
44
|
+
- Header-to-content gap: 24px (`--spacing-l`)
|
|
45
|
+
- Title-to-subtitle gap: 8px (`--spacing-s`)
|
|
46
|
+
- Title font: Gap Sans, 16px (`--text-header-font-size`), weight 700 (`--font-weight-base-heavier`), letter-spacing 0.28px
|
|
47
|
+
- Count font: same as title
|
|
48
|
+
- Subtitle font: Gap Sans, 14px (`--text-subhead-font-size`), weight 400, secondary color (`--color-type-secondary`)
|
|
49
|
+
- Chevron: 10 × 6px, stroked, `currentColor`
|
|
50
|
+
|
|
51
|
+
## Examples
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
{/* Basic accordion with static content */}
|
|
55
|
+
<Accordion>
|
|
56
|
+
<AccordionItem title="Shipping & Returns">
|
|
57
|
+
<p>Free shipping on orders over $50. Returns accepted within 30 days.</p>
|
|
58
|
+
</AccordionItem>
|
|
59
|
+
<AccordionItem title="Size Guide">
|
|
60
|
+
<p>Refer to our size chart for the perfect fit.</p>
|
|
61
|
+
</AccordionItem>
|
|
62
|
+
<AccordionItem title="Product Details">
|
|
63
|
+
<p>100% organic cotton. Machine wash cold.</p>
|
|
64
|
+
</AccordionItem>
|
|
65
|
+
</Accordion>
|
|
66
|
+
|
|
67
|
+
{/* With counts and subtitles (e.g. filter panel) */}
|
|
68
|
+
<Accordion>
|
|
69
|
+
<AccordionItem title="Color" count={3} subtitle="Blue, Red, Green">
|
|
70
|
+
{/* Checkbox filters here */}
|
|
71
|
+
</AccordionItem>
|
|
72
|
+
<AccordionItem title="Size" count={2} subtitle="M, L">
|
|
73
|
+
{/* Checkbox filters here */}
|
|
74
|
+
</AccordionItem>
|
|
75
|
+
</Accordion>
|
|
76
|
+
|
|
77
|
+
{/* With expanded attributes summary */}
|
|
78
|
+
<Accordion>
|
|
79
|
+
<AccordionItem
|
|
80
|
+
title="Color"
|
|
81
|
+
count={3}
|
|
82
|
+
subtitle="3 selected"
|
|
83
|
+
attributes="Blue, Red, Green"
|
|
84
|
+
defaultExpanded
|
|
85
|
+
>
|
|
86
|
+
{/* Filter chips or checkboxes */}
|
|
87
|
+
</AccordionItem>
|
|
88
|
+
</Accordion>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Rules
|
|
92
|
+
|
|
93
|
+
- Always provide a `title` for each `AccordionItem`
|
|
94
|
+
- Use `subtitle` for a brief collapsed-state summary and `attributes` for an expanded-state summary — only one is visible at a time
|
|
95
|
+
- Place any content type inside the expanded area: text, form controls, images, other components
|
|
96
|
+
- Use `count` to communicate the number of items or selections within a section
|
|
97
|
+
- Each item manages its own open/closed state independently — multiple items can be open at once
|
|
98
|
+
- The subtle divider at the top of each item comes from the existing `Divider` component styles
|
|
99
|
+
- Do not nest accordions inside accordions
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Breadcrumb
|
|
2
|
+
|
|
3
|
+
## When to use
|
|
4
|
+
|
|
5
|
+
Use the `Breadcrumb` component to show the user's current location within a site hierarchy and provide quick navigation back to parent pages. Common on product detail pages, category pages, and multi-step flows.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Breadcrumb } from '@stoovles/gap-kit'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Props
|
|
12
|
+
|
|
13
|
+
### Breadcrumb
|
|
14
|
+
|
|
15
|
+
| Prop | Type | Default | Description |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| `items` | `BreadcrumbItem[]` | — | Ordered list of links from root to current page |
|
|
18
|
+
| `className` | `string` | — | Additional CSS class |
|
|
19
|
+
|
|
20
|
+
### BreadcrumbItem
|
|
21
|
+
|
|
22
|
+
| Field | Type | Description |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `label` | `string` | Display text for the crumb |
|
|
25
|
+
| `href` | `string` | URL the crumb links to |
|
|
26
|
+
|
|
27
|
+
## Visual reference
|
|
28
|
+
|
|
29
|
+
| Element | Style |
|
|
30
|
+
|---|---|
|
|
31
|
+
| Link text | Gap Sans, 16px, weight 400, link color (`--color-type-link`), underlined |
|
|
32
|
+
| Last link (multi-level) | Same color, no underline, `aria-current="page"` |
|
|
33
|
+
| Separator `/` | Gap Sans, 16px, weight 400, primary color (`--color-type-primary`) |
|
|
34
|
+
|
|
35
|
+
## Sizing
|
|
36
|
+
|
|
37
|
+
- Vertical padding: 16px (`--spacing-utk-l`)
|
|
38
|
+
- Gap between crumbs and separators: 4px (`--spacing-xs`)
|
|
39
|
+
- Font size: 16px (`--text-link-regular-font-size`)
|
|
40
|
+
- Letter spacing: 0.32px (`--font-letter-spacing-0`)
|
|
41
|
+
|
|
42
|
+
## Examples
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
{/* Single-level breadcrumb */}
|
|
46
|
+
<Breadcrumb items={[{ label: "Women", href: "/women" }]} />
|
|
47
|
+
|
|
48
|
+
{/* Two-level breadcrumb */}
|
|
49
|
+
<Breadcrumb
|
|
50
|
+
items={[
|
|
51
|
+
{ label: "Women", href: "/women" },
|
|
52
|
+
{ label: "Dresses", href: "/women/dresses" },
|
|
53
|
+
]}
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
{/* Deep navigation */}
|
|
57
|
+
<Breadcrumb
|
|
58
|
+
items={[
|
|
59
|
+
{ label: "Home", href: "/" },
|
|
60
|
+
{ label: "Women", href: "/women" },
|
|
61
|
+
{ label: "Clothing", href: "/women/clothing" },
|
|
62
|
+
{ label: "Dresses", href: "/women/clothing/dresses" },
|
|
63
|
+
]}
|
|
64
|
+
/>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Rules
|
|
68
|
+
|
|
69
|
+
- Always provide at least one item — the component renders nothing for an empty array
|
|
70
|
+
- Order items from the broadest category (root) to the most specific (current page)
|
|
71
|
+
- The last item in a multi-level breadcrumb receives `aria-current="page"` and no underline
|
|
72
|
+
- The component renders inside a `<nav aria-label="Breadcrumb">` with a semantic `<ol>` for accessibility
|
|
73
|
+
- Use concise labels — breadcrumb text should match the target page's title or heading
|
|
74
|
+
- Do not use breadcrumbs as the primary navigation; they supplement the main nav
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Button
|
|
2
|
+
|
|
3
|
+
## When to use
|
|
4
|
+
|
|
5
|
+
Use the `Button` component for all interactive actions — form submissions, confirmations, navigation triggers, and destructive operations. Never use a `<div>` or `<a>` when a semantic `<button>` is needed.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Button } from '@stoovles/gap-kit'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Props
|
|
12
|
+
|
|
13
|
+
| Prop | Type | Default | Description |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| `variant` | `"critical" \| "primary" \| "secondary"` | `"primary"` | Visual style of the button |
|
|
16
|
+
| `loading` | `boolean` | `false` | Shows a dot loader and disables interaction |
|
|
17
|
+
| `fullWidth` | `boolean` | `false` | Stretches button to fill its container |
|
|
18
|
+
| `disabled` | `boolean` | `false` | Grays out the button and prevents clicks |
|
|
19
|
+
| `children` | `ReactNode` | — | Button label text |
|
|
20
|
+
| `className` | `string` | — | Additional CSS class |
|
|
21
|
+
|
|
22
|
+
All standard `<button>` HTML attributes are also supported (e.g. `type`, `onClick`, `aria-label`).
|
|
23
|
+
|
|
24
|
+
## Variant decision tree
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
What action does this button perform?
|
|
28
|
+
|
|
29
|
+
├─ A destructive or high-stakes action (delete, cancel order, remove)?
|
|
30
|
+
│ └─ variant="critical"
|
|
31
|
+
│
|
|
32
|
+
├─ The primary call-to-action on the page (add to bag, checkout, submit)?
|
|
33
|
+
│ └─ variant="primary"
|
|
34
|
+
│
|
|
35
|
+
└─ A secondary or supporting action (save for later, continue shopping)?
|
|
36
|
+
└─ variant="secondary"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Visual reference
|
|
40
|
+
|
|
41
|
+
| Variant | Default | Hover | Active | Disabled |
|
|
42
|
+
|---|---|---|---|---|
|
|
43
|
+
| `critical` | Blue fill (#031ba1), white text | Black fill, white text | Blue fill, white text | Gray fill (#ededed), gray text |
|
|
44
|
+
| `primary` | White fill, gray border (#ccc), black text | Blue fill (#031ba1), black border, white text | White fill, black border, black text | White fill, gray border (#ededed), gray text |
|
|
45
|
+
| `secondary` | Black fill, black border, white text | Blue fill (#031ba1), blue border, white text | Blue fill, blue border, white text | Gray fill (#ededed), gray text |
|
|
46
|
+
|
|
47
|
+
## Sizing
|
|
48
|
+
|
|
49
|
+
- Minimum height: 44px (touch target compliance)
|
|
50
|
+
- Horizontal padding: 48px
|
|
51
|
+
- Border radius: 0px (Gap's square-corner identity)
|
|
52
|
+
- Font: Gap Sans, 16px, weight 400, letter-spacing 0.32px
|
|
53
|
+
|
|
54
|
+
## Examples
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
{/* Primary CTA */}
|
|
58
|
+
<Button variant="primary">Add to Bag</Button>
|
|
59
|
+
|
|
60
|
+
{/* Secondary action */}
|
|
61
|
+
<Button variant="secondary">Save for Later</Button>
|
|
62
|
+
|
|
63
|
+
{/* Destructive action */}
|
|
64
|
+
<Button variant="critical">Remove Item</Button>
|
|
65
|
+
|
|
66
|
+
{/* Loading state (e.g. during form submission) */}
|
|
67
|
+
<Button variant="secondary" loading>
|
|
68
|
+
Processing
|
|
69
|
+
</Button>
|
|
70
|
+
|
|
71
|
+
{/* Disabled */}
|
|
72
|
+
<Button variant="primary" disabled>
|
|
73
|
+
Select a Size
|
|
74
|
+
</Button>
|
|
75
|
+
|
|
76
|
+
{/* Full-width in a narrow container */}
|
|
77
|
+
<Button variant="secondary" fullWidth>
|
|
78
|
+
Checkout
|
|
79
|
+
</Button>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Rules
|
|
83
|
+
|
|
84
|
+
- Every page should have at most one `critical` button visible at a time
|
|
85
|
+
- Prefer `primary` for the main CTA; use `secondary` for supporting actions
|
|
86
|
+
- Never place two `critical` buttons side by side
|
|
87
|
+
- Always provide a text label — icon-only actions should use a different component
|
|
88
|
+
- Use `loading` during async operations instead of manually disabling and re-enabling
|
|
89
|
+
- Do not override the 44px minimum height — it ensures touch accessibility
|
|
90
|
+
- When pairing buttons, place `primary` or `critical` on the right, `secondary` on the left
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Checkbox
|
|
2
|
+
|
|
3
|
+
## When to use
|
|
4
|
+
|
|
5
|
+
Use `Checkbox` when the user needs to select zero or more options from a list, or toggle a single boolean setting (e.g. "Remember me"). For mutually-exclusive choices use a radio group instead.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Checkbox, CheckboxGroup } from '@stoovles/gap-kit'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Checkbox Props
|
|
12
|
+
|
|
13
|
+
| Prop | Type | Default | Description |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| `label` | `ReactNode` | — | Text label displayed next to the checkbox |
|
|
16
|
+
| `error` | `boolean` | `false` | Renders the checkbox in the error state (red border / fill) |
|
|
17
|
+
| `disabled` | `boolean` | `false` | Grays out the checkbox and prevents interaction |
|
|
18
|
+
| `checked` | `boolean` | — | Controlled checked state |
|
|
19
|
+
| `defaultChecked` | `boolean` | — | Uncontrolled initial checked state |
|
|
20
|
+
| `onChange` | `(e) => void` | — | Change handler |
|
|
21
|
+
| `className` | `string` | — | Additional CSS class |
|
|
22
|
+
|
|
23
|
+
All standard `<input>` HTML attributes are also supported (e.g. `name`, `value`, `required`).
|
|
24
|
+
|
|
25
|
+
## CheckboxGroup Props
|
|
26
|
+
|
|
27
|
+
| Prop | Type | Default | Description |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| `children` | `ReactNode` | — | `Checkbox` components |
|
|
30
|
+
| `label` | `string` | — | Group legend (optional) |
|
|
31
|
+
| `error` | `boolean` | `false` | Applies error styling to the group wrapper |
|
|
32
|
+
| `className` | `string` | — | Additional CSS class |
|
|
33
|
+
|
|
34
|
+
## Visual reference
|
|
35
|
+
|
|
36
|
+
| State | Unchecked | Checked |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| Default | White fill, black 1px border | Dark gray fill (#595959), black border, white checkmark |
|
|
39
|
+
| Error | White fill, red border | Red fill (#d00000), red border, white checkmark |
|
|
40
|
+
| Disabled | White fill, gray border (#ededed) | Gray fill (#ededed), gray border, white checkmark |
|
|
41
|
+
|
|
42
|
+
## Sizing
|
|
43
|
+
|
|
44
|
+
- Checkbox box: 22×22px inside a 24×24px touch target
|
|
45
|
+
- Border radius: 0px (Gap square-corner identity)
|
|
46
|
+
- Gap between box and label: 8px
|
|
47
|
+
- Group spacing between items: 24px
|
|
48
|
+
- Font: Gap Sans, 16px, weight 400
|
|
49
|
+
|
|
50
|
+
## Examples
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
{/* Single checkbox */}
|
|
54
|
+
<Checkbox label="I agree to the terms" />
|
|
55
|
+
|
|
56
|
+
{/* Controlled checkbox */}
|
|
57
|
+
<Checkbox
|
|
58
|
+
label="Subscribe to newsletter"
|
|
59
|
+
checked={subscribed}
|
|
60
|
+
onChange={(e) => setSubscribed(e.target.checked)}
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
{/* Checkbox group */}
|
|
64
|
+
<CheckboxGroup label="Select sizes">
|
|
65
|
+
<Checkbox label="XS" name="size" value="xs" />
|
|
66
|
+
<Checkbox label="S" name="size" value="s" />
|
|
67
|
+
<Checkbox label="M" name="size" value="m" />
|
|
68
|
+
<Checkbox label="L" name="size" value="l" />
|
|
69
|
+
<Checkbox label="XL" name="size" value="xl" />
|
|
70
|
+
</CheckboxGroup>
|
|
71
|
+
|
|
72
|
+
{/* Error state */}
|
|
73
|
+
<Checkbox label="Required field" error />
|
|
74
|
+
|
|
75
|
+
{/* Disabled */}
|
|
76
|
+
<Checkbox label="Out of stock" disabled checked />
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Rules
|
|
80
|
+
|
|
81
|
+
- Always provide a `label` — a checkbox without visible text needs an `aria-label`
|
|
82
|
+
- Use `CheckboxGroup` with a `label` (rendered as `<legend>`) when presenting related options
|
|
83
|
+
- Never use a checkbox for a single mutually-exclusive choice — use a radio button instead
|
|
84
|
+
- Place the checkbox to the left of its label
|
|
85
|
+
- In error states, validate on form submission or blur, not on every keystroke
|
|
86
|
+
- Do not mix error and disabled states on the same checkbox
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Chip
|
|
2
|
+
|
|
3
|
+
## When to use
|
|
4
|
+
|
|
5
|
+
Use `Chip` to display an active filter or attribute that the user can dismiss. Chips are typically shown in a `ChipGroup` after filter selections on product listing and search results pages.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Chip, ChipGroup } from '@stoovles/gap-kit'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Chip Props
|
|
12
|
+
|
|
13
|
+
| Prop | Type | Default | Description |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| `label` | `string` | — | The filter attribute text (e.g. "Blue", "Size M") |
|
|
16
|
+
| `onDismiss` | `() => void` | — | Callback when the dismiss (×) button is clicked. Omit to hide the dismiss button. |
|
|
17
|
+
| `swatch` | `string` | — | CSS color value for an optional color swatch circle (e.g. `"#031ba1"`) |
|
|
18
|
+
| `className` | `string` | — | Additional CSS class |
|
|
19
|
+
|
|
20
|
+
## ChipGroup Props
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Description |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| `children` | `ReactNode` | — | `Chip` components |
|
|
25
|
+
| `onClearAll` | `() => void` | — | Callback for the "Clear all" link. Omit to hide the link. |
|
|
26
|
+
| `clearAllLabel` | `string` | `"Clear all"` | Label for the clear-all action |
|
|
27
|
+
| `className` | `string` | — | Additional CSS class |
|
|
28
|
+
|
|
29
|
+
## Visual reference
|
|
30
|
+
|
|
31
|
+
| State | Appearance |
|
|
32
|
+
|---|---|
|
|
33
|
+
| Default | Semi-transparent gray background, blue text (#031ba1), no visible border |
|
|
34
|
+
| Hover | 1px border appears |
|
|
35
|
+
| Focus | Gray background (#ccc), 1px border |
|
|
36
|
+
| Active | Darker gray background (#afafaf) |
|
|
37
|
+
|
|
38
|
+
## Sizing
|
|
39
|
+
|
|
40
|
+
- Min height: 32px, min width: 48px
|
|
41
|
+
- Padding: 8px vertical, 12px horizontal
|
|
42
|
+
- Gap between chip elements: 8px
|
|
43
|
+
- Border radius: 0px (Gap square-corner identity)
|
|
44
|
+
- Font: Gap Sans, 16px, letter-spacing 0.24px
|
|
45
|
+
- Dismiss icon: 9×9px × in a 16px touch target
|
|
46
|
+
- Swatch: 16px circle (fully round)
|
|
47
|
+
- Group chip gap: 12px row, 8px column
|
|
48
|
+
|
|
49
|
+
## Examples
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
{/* Single dismissible chip */}
|
|
53
|
+
<Chip label="Blue" onDismiss={() => removeFilter('blue')} />
|
|
54
|
+
|
|
55
|
+
{/* Chip with color swatch */}
|
|
56
|
+
<Chip label="Navy" swatch="#031ba1" onDismiss={() => removeFilter('navy')} />
|
|
57
|
+
|
|
58
|
+
{/* Chip without dismiss (read-only display) */}
|
|
59
|
+
<Chip label="On Sale" />
|
|
60
|
+
|
|
61
|
+
{/* ChipGroup with clear-all */}
|
|
62
|
+
<ChipGroup onClearAll={() => clearAllFilters()}>
|
|
63
|
+
<Chip label="Size M" onDismiss={() => removeFilter('size-m')} />
|
|
64
|
+
<Chip label="Blue" swatch="#031ba1" onDismiss={() => removeFilter('blue')} />
|
|
65
|
+
<Chip label="Under $50" onDismiss={() => removeFilter('under-50')} />
|
|
66
|
+
</ChipGroup>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Rules
|
|
70
|
+
|
|
71
|
+
- Always include a meaningful `label` — do not use chips with only a swatch
|
|
72
|
+
- Provide `onDismiss` for every chip when the user should be able to remove individual filters
|
|
73
|
+
- Use `ChipGroup` with `onClearAll` when displaying 2 or more active filters
|
|
74
|
+
- Place the `ChipGroup` above the product grid, below the filter bar
|
|
75
|
+
- Use the `swatch` prop only for color-based filters
|
|
76
|
+
- Keep labels concise — one or two words maximum
|
|
77
|
+
- Do not use chips for navigation or as toggle buttons; use them only for applied filters
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Divider
|
|
2
|
+
|
|
3
|
+
## When to use
|
|
4
|
+
|
|
5
|
+
Use the `Divider` component to visually separate sections of content. It renders a semantic `<hr>` element.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Divider } from '@stoovles/gap-kit'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Props
|
|
12
|
+
|
|
13
|
+
| Prop | Type | Default | Description |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| `variant` | `"default" \| "subtle"` | `"default"` | Default is dark (#595959), subtle is light (#ccc) |
|
|
16
|
+
| `className` | `string` | — | Additional CSS class |
|
|
17
|
+
|
|
18
|
+
## Variant decision tree
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
How prominent should the separation be?
|
|
22
|
+
|
|
23
|
+
├─ Clear visual break between major sections?
|
|
24
|
+
│ └─ variant="default" (dark line)
|
|
25
|
+
│
|
|
26
|
+
└─ Gentle separation between related items (e.g. list rows)?
|
|
27
|
+
└─ variant="subtle" (light line)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Sizing
|
|
31
|
+
|
|
32
|
+
- Width: 100% of parent container
|
|
33
|
+
- Border width: 2px (`--divider-border-width`)
|
|
34
|
+
- Default color: #595959 (`--divider-color`)
|
|
35
|
+
- Subtle color: #ccc (`--color-border-subtle`)
|
|
36
|
+
|
|
37
|
+
## Examples
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
{/* Default dark divider */}
|
|
41
|
+
<Divider />
|
|
42
|
+
|
|
43
|
+
{/* Subtle divider between list items */}
|
|
44
|
+
<Divider variant="subtle" />
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Rules
|
|
48
|
+
|
|
49
|
+
- Use `default` for major section breaks (e.g. between content blocks)
|
|
50
|
+
- Use `subtle` for lighter separations (e.g. between list items, accordion rows)
|
|
51
|
+
- Do not stack multiple dividers — one is sufficient
|
|
52
|
+
- Spacing above and below the divider should be controlled by the parent layout, not the divider itself
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Dropdown
|
|
2
|
+
|
|
3
|
+
## When to use
|
|
4
|
+
|
|
5
|
+
Use the `Dropdown` component when the user needs to select a single value from a predefined list of options. Appropriate for form fields like country, size, sort order, or any finite set of choices.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Dropdown } from '@stoovles/gap-kit'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Props
|
|
12
|
+
|
|
13
|
+
### Dropdown
|
|
14
|
+
|
|
15
|
+
| Prop | Type | Default | Description |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| `label` | `string` | — | Field label (shown as placeholder when empty, floats when filled) |
|
|
18
|
+
| `options` | `DropdownOption[]` | — | List of selectable options |
|
|
19
|
+
| `value` | `string` | — | Controlled selected value |
|
|
20
|
+
| `defaultValue` | `string` | — | Uncontrolled initial value |
|
|
21
|
+
| `onChange` | `(value: string) => void` | — | Callback when selection changes |
|
|
22
|
+
| `error` | `boolean` | `false` | Renders the field in error state with red border |
|
|
23
|
+
| `disabled` | `boolean` | `false` | Grays out the field and prevents interaction |
|
|
24
|
+
| `className` | `string` | — | Additional CSS class |
|
|
25
|
+
| `id` | `string` | — | HTML id for the trigger button |
|
|
26
|
+
|
|
27
|
+
### DropdownOption
|
|
28
|
+
|
|
29
|
+
| Field | Type | Description |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `label` | `string` | Display text shown in the menu and trigger |
|
|
32
|
+
| `value` | `string` | Unique value passed to `onChange` |
|
|
33
|
+
|
|
34
|
+
## Visual reference
|
|
35
|
+
|
|
36
|
+
| State | Border | Label | Background |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| Empty (closed) | 1px gray (`--dropdown-border-color`) | Inline as placeholder (subtle color) | White |
|
|
39
|
+
| Open | 2px gray (`--dropdown-border-color`) | Inline or floating | White |
|
|
40
|
+
| Filled (closed) | 1px gray | Floating above field | White |
|
|
41
|
+
| Error | 1px or 2px red (`--color-border-error`) | Floating, red text | White |
|
|
42
|
+
| Disabled | No visible border | Floating, subtle color | Gray (`--color-fill-disabled`) |
|
|
43
|
+
|
|
44
|
+
### Menu rows
|
|
45
|
+
|
|
46
|
+
| State | Background | Text color |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| Default | White | Primary (`--color-type-primary`) |
|
|
49
|
+
| Hover / Active | Light gray (`--color-gray-2`) | Primary |
|
|
50
|
+
| Selected | Dark blue (`--dropdown-fill-selected`) | White (`--dropdown-font-color`) |
|
|
51
|
+
|
|
52
|
+
## Sizing
|
|
53
|
+
|
|
54
|
+
- Trigger height: 44px
|
|
55
|
+
- Horizontal padding: 16px (`--spacing-utk-l`)
|
|
56
|
+
- Border radius: 0px (Gap square-corner identity)
|
|
57
|
+
- Font: Gap Sans, 16px, weight 400, 0.32px letter-spacing
|
|
58
|
+
- Floating label: 10px, positioned above the border
|
|
59
|
+
- Menu row height: 42px, padding 12px vertical / 16px horizontal
|
|
60
|
+
- Menu max-height: 252px (scrollable)
|
|
61
|
+
- Menu shadow: 2px 4px 8px rgba(0,0,0,0.08)
|
|
62
|
+
|
|
63
|
+
## Examples
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
{/* Basic dropdown */}
|
|
67
|
+
<Dropdown
|
|
68
|
+
label="Size"
|
|
69
|
+
options={[
|
|
70
|
+
{ label: "Small", value: "s" },
|
|
71
|
+
{ label: "Medium", value: "m" },
|
|
72
|
+
{ label: "Large", value: "l" },
|
|
73
|
+
{ label: "X-Large", value: "xl" },
|
|
74
|
+
]}
|
|
75
|
+
/>
|
|
76
|
+
|
|
77
|
+
{/* Controlled dropdown */}
|
|
78
|
+
const [sort, setSort] = useState("");
|
|
79
|
+
<Dropdown
|
|
80
|
+
label="Sort By"
|
|
81
|
+
value={sort}
|
|
82
|
+
onChange={setSort}
|
|
83
|
+
options={[
|
|
84
|
+
{ label: "Newest", value: "newest" },
|
|
85
|
+
{ label: "Price: Low to High", value: "price-asc" },
|
|
86
|
+
{ label: "Price: High to Low", value: "price-desc" },
|
|
87
|
+
]}
|
|
88
|
+
/>
|
|
89
|
+
|
|
90
|
+
{/* Error state */}
|
|
91
|
+
<Dropdown
|
|
92
|
+
label="Country"
|
|
93
|
+
options={countries}
|
|
94
|
+
error={!selectedCountry}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
{/* Disabled */}
|
|
98
|
+
<Dropdown
|
|
99
|
+
label="Region"
|
|
100
|
+
defaultValue="us"
|
|
101
|
+
options={[{ label: "United States", value: "us" }]}
|
|
102
|
+
disabled
|
|
103
|
+
/>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Keyboard navigation
|
|
107
|
+
|
|
108
|
+
| Key | Action |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `Enter` / `Space` | Open menu or select focused option |
|
|
111
|
+
| `ArrowDown` | Open menu or move focus down |
|
|
112
|
+
| `ArrowUp` | Move focus up |
|
|
113
|
+
| `Escape` | Close menu |
|
|
114
|
+
| `Tab` | Close menu and move focus |
|
|
115
|
+
|
|
116
|
+
## Rules
|
|
117
|
+
|
|
118
|
+
- Always provide a `label` — it acts as both placeholder and floating label
|
|
119
|
+
- Each option must have a unique `value`
|
|
120
|
+
- The menu appears directly below the trigger and matches its width
|
|
121
|
+
- The selected option is highlighted with the brand blue background
|
|
122
|
+
- Use `error` for validation — the border and floating label turn red
|
|
123
|
+
- Do not use a dropdown for fewer than 3 options — use `Radio` instead
|
|
124
|
+
- Do not use a dropdown for more than ~15 options — consider a searchable input
|
|
125
|
+
- The component supports both controlled (`value` + `onChange`) and uncontrolled (`defaultValue`) patterns
|