@gallop.software/canon 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.
@@ -0,0 +1,103 @@
1
+ # Pattern 010: Spacing System
2
+
3
+ **Canon Version:** 1.0
4
+ **Status:** Stable
5
+ **Category:** Styling
6
+ **Enforcement:** Documentation
7
+
8
+ ## Decision
9
+
10
+ Use consistent spacing values from the Tailwind scale. Follow established defaults for sections, typography, and layout.
11
+
12
+ ## Rationale
13
+
14
+ 1. **Visual rhythm** — Consistent spacing creates harmony
15
+ 2. **Predictable layouts** — Developers know what to expect
16
+ 3. **Easier maintenance** — Change defaults in one place
17
+ 4. **Design system compliance** — Spacing follows the scale
18
+
19
+ ## Default Spacing Values
20
+
21
+ ### Sections
22
+
23
+ | Element | Default | Description |
24
+ |---------|---------|-------------|
25
+ | Section padding (vertical) | `py-20 md:py-30` | Top and bottom padding |
26
+ | Section padding (horizontal) | `px-6 lg:px-8` | Left and right padding |
27
+ | Max width | `max-w-[1600px]` | Content container width |
28
+
29
+ ### Typography
30
+
31
+ | Element | Default | Description |
32
+ |---------|---------|-------------|
33
+ | Heading margin | `mb-8` | All heading levels |
34
+ | Paragraph margin | `mb-8` | Body text blocks |
35
+ | Label margin | `mb-0` | Inline labels |
36
+ | Accent margin | `mb-4` | When above headings |
37
+ | Accent margin (decorative) | `mb-0` | When rotated/decorative |
38
+
39
+ ### Layout
40
+
41
+ | Element | Default | Description |
42
+ |---------|---------|-------------|
43
+ | Column gap | `gap-8 lg:gap-16` | Between columns |
44
+ | Card padding | `p-6` | Internal card padding |
45
+ | Button spacing | `gap-4` | Between buttons |
46
+
47
+ ## Examples
48
+
49
+ ### Section Structure
50
+
51
+ ```tsx
52
+ <Section className="py-20 md:py-30">
53
+ <div className="mx-auto max-w-[1600px] px-6 lg:px-8">
54
+ <Heading margin="mb-8">Section Title</Heading>
55
+ <Paragraph margin="mb-8">Content paragraph.</Paragraph>
56
+ </div>
57
+ </Section>
58
+ ```
59
+
60
+ ### Typography Stack
61
+
62
+ ```tsx
63
+ <Accent margin="mb-4">Featured</Accent>
64
+ <Heading margin="mb-8">Main Heading</Heading>
65
+ <Paragraph margin="mb-8">First paragraph of content.</Paragraph>
66
+ <Paragraph margin="mb-0">Final paragraph, no margin.</Paragraph>
67
+ ```
68
+
69
+ ### Columns Layout
70
+
71
+ ```tsx
72
+ <Columns
73
+ cols="grid-cols-1 lg:grid-cols-2"
74
+ gap="gap-8 lg:gap-16"
75
+ >
76
+ <Column>Left content</Column>
77
+ <Column>Right content</Column>
78
+ </Columns>
79
+ ```
80
+
81
+ ## Spacing Scale Reference
82
+
83
+ | Class | Value |
84
+ |-------|-------|
85
+ | `*-4` | 1rem (16px) |
86
+ | `*-6` | 1.5rem (24px) |
87
+ | `*-8` | 2rem (32px) |
88
+ | `*-10` | 2.5rem (40px) |
89
+ | `*-12` | 3rem (48px) |
90
+ | `*-16` | 4rem (64px) |
91
+ | `*-20` | 5rem (80px) |
92
+ | `*-30` | 7.5rem (120px) |
93
+
94
+ ## Enforcement
95
+
96
+ - **Method:** Code review / Documentation
97
+ - **Component defaults:** Typography components have built-in margins
98
+
99
+ ## References
100
+
101
+ - `src/components/section.tsx` — Section with default padding
102
+ - `src/components/heading.tsx` — Heading with mb-8 default
103
+ - `src/components/paragraph.tsx` — Paragraph with mb-8 default
@@ -0,0 +1,120 @@
1
+ # Pattern 011: Responsive Mobile-First
2
+
3
+ **Canon Version:** 1.0
4
+ **Status:** Stable
5
+ **Category:** Styling
6
+ **Enforcement:** Documentation
7
+
8
+ ## Decision
9
+
10
+ Design mobile-first. Use Tailwind breakpoint prefixes to progressively enhance for larger screens.
11
+
12
+ ## Rationale
13
+
14
+ 1. **Performance** — Mobile styles load first, enhancements are additive
15
+ 2. **Accessibility** — Core experience works on all devices
16
+ 3. **Maintainability** — Base styles are simple, complexity added at breakpoints
17
+ 4. **SEO** — Google uses mobile-first indexing
18
+
19
+ ## Breakpoint Scale
20
+
21
+ | Prefix | Min Width | Target |
22
+ |--------|-----------|--------|
23
+ | (none) | 0px | Mobile phones |
24
+ | `sm:` | 640px | Large phones, small tablets |
25
+ | `md:` | 768px | Tablets |
26
+ | `lg:` | 1024px | Laptops, small desktops |
27
+ | `xl:` | 1280px | Desktops |
28
+ | `2xl:` | 1536px | Large desktops |
29
+
30
+ ## Common Patterns
31
+
32
+ ### Layout Direction
33
+
34
+ ```tsx
35
+ // Stack on mobile, row on desktop
36
+ <div className="flex flex-col lg:flex-row">
37
+ ```
38
+
39
+ ### Grid Columns
40
+
41
+ ```tsx
42
+ // 1 column mobile, 2 columns tablet, 3 columns desktop
43
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
44
+ ```
45
+
46
+ ### Responsive Typography
47
+
48
+ ```tsx
49
+ // Smaller on mobile, larger on desktop
50
+ <Heading fontSize="text-3xl md:text-4xl lg:text-5xl">
51
+ Responsive Heading
52
+ </Heading>
53
+ ```
54
+
55
+ ### Responsive Spacing
56
+
57
+ ```tsx
58
+ // Less padding on mobile, more on desktop
59
+ <Section className="py-12 md:py-20 lg:py-30">
60
+ ```
61
+
62
+ ### Responsive Heights
63
+
64
+ ```tsx
65
+ // Shorter on mobile, taller on desktop
66
+ <div className="h-[300px] sm:h-[450px] lg:h-[600px]">
67
+ ```
68
+
69
+ ### Show/Hide Elements
70
+
71
+ ```tsx
72
+ // Hide on mobile, show on desktop
73
+ <nav className="hidden lg:flex">
74
+
75
+ // Show on mobile, hide on desktop
76
+ <button className="lg:hidden">Menu</button>
77
+ ```
78
+
79
+ ## Examples
80
+
81
+ ### Good: Mobile-First
82
+
83
+ ```tsx
84
+ <div className="
85
+ flex flex-col gap-4
86
+ md:flex-row md:gap-8
87
+ lg:gap-16
88
+ ">
89
+ <div className="w-full md:w-1/2">Content</div>
90
+ <div className="w-full md:w-1/2">Content</div>
91
+ </div>
92
+ ```
93
+
94
+ ### Bad: Desktop-First (Anti-Pattern)
95
+
96
+ ```tsx
97
+ // Don't do this - starts with desktop, removes at mobile
98
+ <div className="
99
+ flex flex-row gap-16
100
+ max-md:flex-col max-md:gap-4
101
+ ">
102
+ ```
103
+
104
+ ## Testing Checklist
105
+
106
+ - [ ] Works on 320px width (small phones)
107
+ - [ ] Works on 375px width (iPhone)
108
+ - [ ] Works on 768px width (tablet)
109
+ - [ ] Works on 1024px width (laptop)
110
+ - [ ] Works on 1440px width (desktop)
111
+
112
+ ## Enforcement
113
+
114
+ - **Method:** Code review / Visual testing
115
+ - **Tools:** Browser DevTools responsive mode
116
+
117
+ ## References
118
+
119
+ - Tailwind CSS responsive design docs
120
+ - All blocks are built mobile-first
@@ -0,0 +1,120 @@
1
+ # Pattern 012: Icon System
2
+
3
+ **Canon Version:** 1.0
4
+ **Status:** Stable
5
+ **Category:** Components
6
+ **Enforcement:** Documentation
7
+
8
+ ## Decision
9
+
10
+ Use Iconify icon packages with the `Icon` component. Do not use inline SVGs or other icon libraries.
11
+
12
+ ## Rationale
13
+
14
+ 1. **Consistent sizing** — Icon component handles dimensions
15
+ 2. **Tree shaking** — Only imported icons are bundled
16
+ 3. **Large library** — Access to thousands of icons
17
+ 4. **Type safety** — Icon imports are typed
18
+
19
+ ## Icon Packages
20
+
21
+ | Package | Usage |
22
+ |---------|-------|
23
+ | `@iconify/icons-heroicons` | UI icons (arrows, actions) |
24
+ | `@iconify/icons-lucide` | General purpose icons |
25
+ | `@iconify/icons-mdi` | Material Design icons |
26
+ | `@iconify/icons-simple-icons` | Brand/logo icons |
27
+
28
+ ## Usage Pattern
29
+
30
+ ### Import Icons
31
+
32
+ ```tsx
33
+ import arrowRightIcon from '@iconify/icons-heroicons/arrow-right-20-solid'
34
+ import playCircleIcon from '@iconify/icons-lucide/play-circle'
35
+ import twitterIcon from '@iconify/icons-simple-icons/twitter'
36
+ ```
37
+
38
+ ### Render with Icon Component
39
+
40
+ ```tsx
41
+ import { Icon } from '@/components'
42
+
43
+ <Icon icon={arrowRightIcon} className="w-5 h-5" />
44
+ <Icon icon={playCircleIcon} className="w-6 h-6 text-accent" />
45
+ ```
46
+
47
+ ### With Buttons
48
+
49
+ ```tsx
50
+ <Button
51
+ href="/contact"
52
+ icon={arrowRightIcon}
53
+ iconPlacement="after"
54
+ >
55
+ Get Started
56
+ </Button>
57
+ ```
58
+
59
+ ## Common Icons
60
+
61
+ | Icon | Import |
62
+ |------|--------|
63
+ | Arrow right | `@iconify/icons-heroicons/arrow-right-20-solid` |
64
+ | Arrow down | `@iconify/icons-heroicons/arrow-down-20-solid` |
65
+ | Play | `@iconify/icons-heroicons/play-solid` |
66
+ | Play circle | `@iconify/icons-lucide/play-circle` |
67
+ | Check | `@iconify/icons-heroicons/check-20-solid` |
68
+ | X/Close | `@iconify/icons-heroicons/x-mark-20-solid` |
69
+ | Menu | `@iconify/icons-heroicons/bars-3-20-solid` |
70
+ | Sparkles | `@iconify/icons-heroicons/sparkles-20-solid` |
71
+
72
+ ## Examples
73
+
74
+ ### Good
75
+
76
+ ```tsx
77
+ import arrowRightIcon from '@iconify/icons-heroicons/arrow-right-20-solid'
78
+ import { Icon } from '@/components'
79
+
80
+ <button className="flex items-center gap-2">
81
+ Next
82
+ <Icon icon={arrowRightIcon} className="w-5 h-5" />
83
+ </button>
84
+ ```
85
+
86
+ ### Bad
87
+
88
+ ```tsx
89
+ // Inline SVG
90
+ <button>
91
+ Next
92
+ <svg className="w-5 h-5" viewBox="0 0 20 20">
93
+ <path d="M..." />
94
+ </svg>
95
+ </button>
96
+
97
+ // Other icon library
98
+ import { ArrowRight } from 'react-icons/hi'
99
+ <ArrowRight />
100
+ ```
101
+
102
+ ## Sizing Guidelines
103
+
104
+ | Context | Size |
105
+ |---------|------|
106
+ | Inline with text | `w-4 h-4` or `w-5 h-5` |
107
+ | Button icons | `w-5 h-5` |
108
+ | Standalone icons | `w-6 h-6` |
109
+ | Large decorative | `w-8 h-8` or larger |
110
+
111
+ ## Enforcement
112
+
113
+ - **Method:** Code review / Documentation
114
+ - **Future:** ESLint rule to detect inline SVGs
115
+
116
+ ## References
117
+
118
+ - `src/components/icon.tsx` — Icon component
119
+ - `src/components/button.tsx` — Button with icon support
120
+ - Iconify icon explorer: https://icon-sets.iconify.design/
@@ -0,0 +1,135 @@
1
+ # Pattern 013: New Component Pattern
2
+
3
+ **Canon Version:** 1.0
4
+ **Status:** Stable
5
+ **Category:** Components
6
+ **Enforcement:** Documentation
7
+
8
+ ## Decision
9
+
10
+ New components expose props for commonly overridden styles instead of relying solely on `className`.
11
+
12
+ ## Rationale
13
+
14
+ 1. **Discoverable API** — Props appear in autocomplete
15
+ 2. **Consistent defaults** — Sensible defaults built-in
16
+ 3. **Type safety** — Props can be typed and documented
17
+ 4. **Easier refactoring** — Change defaults in one place
18
+ 5. **Lint compatibility** — Works with `prefer-component-props` rule
19
+
20
+ ## Component Template
21
+
22
+ ```tsx
23
+ import { clsx } from 'clsx'
24
+
25
+ interface MyComponentProps {
26
+ margin?: string // e.g., "mb-8", "mb-4"
27
+ color?: string // e.g., "text-accent", "text-contrast"
28
+ fontSize?: string // e.g., "text-lg", "text-sm"
29
+ textAlign?: string // e.g., "text-center", "text-left"
30
+ fontWeight?: string // e.g., "font-bold", "font-medium"
31
+ className?: string // Additional styling
32
+ children: React.ReactNode
33
+ }
34
+
35
+ export function MyComponent({
36
+ margin = 'mb-4', // Sensible default
37
+ color = 'text-contrast', // Sensible default
38
+ fontSize = 'text-base',
39
+ textAlign = '',
40
+ fontWeight = '',
41
+ className = '',
42
+ children,
43
+ }: MyComponentProps) {
44
+ return (
45
+ <div
46
+ className={clsx(
47
+ margin,
48
+ color,
49
+ fontSize,
50
+ textAlign,
51
+ fontWeight,
52
+ className
53
+ )}
54
+ >
55
+ {children}
56
+ </div>
57
+ )
58
+ }
59
+ ```
60
+
61
+ ## Standard Props
62
+
63
+ | Prop | Type | Common Values |
64
+ |------|------|---------------|
65
+ | `margin` | string | `mb-0`, `mb-4`, `mb-8` |
66
+ | `color` | string | `text-body`, `text-contrast`, `text-accent` |
67
+ | `fontSize` | string | `text-sm`, `text-base`, `text-lg`, `text-xl` |
68
+ | `textAlign` | string | `text-left`, `text-center`, `text-right` |
69
+ | `fontWeight` | string | `font-normal`, `font-medium`, `font-bold` |
70
+ | `lineHeight` | string | `leading-tight`, `leading-relaxed` |
71
+ | `className` | string | Any additional Tailwind classes |
72
+
73
+ ## File Structure
74
+
75
+ 1. Create file in `src/components/` with lowercase hyphenated name
76
+ 2. Export from `src/components/index.ts`
77
+ 3. Use `clsx` for combining classes
78
+
79
+ ```
80
+ src/components/
81
+ ├── my-component.tsx ← Component file
82
+ ├── index.ts ← Add export here
83
+ ```
84
+
85
+ ## Examples
86
+
87
+ ### Good: Props for Common Overrides
88
+
89
+ ```tsx
90
+ // Definition
91
+ export function Badge({
92
+ margin = 'mb-0',
93
+ color = 'text-white',
94
+ bgColor = 'bg-accent',
95
+ fontSize = 'text-xs',
96
+ className = '',
97
+ children,
98
+ }: BadgeProps) {
99
+ return (
100
+ <span className={clsx(margin, color, bgColor, fontSize, 'px-2 py-1 rounded', className)}>
101
+ {children}
102
+ </span>
103
+ )
104
+ }
105
+
106
+ // Usage
107
+ <Badge color="text-black" bgColor="bg-yellow-400">New</Badge>
108
+ ```
109
+
110
+ ### Bad: className-Only Styling
111
+
112
+ ```tsx
113
+ // Definition - no props for common overrides
114
+ export function Badge({ className, children }: { className?: string; children: React.ReactNode }) {
115
+ return (
116
+ <span className={clsx('text-xs px-2 py-1 rounded', className)}>
117
+ {children}
118
+ </span>
119
+ )
120
+ }
121
+
122
+ // Usage - must override via className
123
+ <Badge className="text-black bg-yellow-400 mb-4">New</Badge>
124
+ ```
125
+
126
+ ## Enforcement
127
+
128
+ - **Method:** Code review / Documentation
129
+ - **Lint:** `gallop/prefer-component-props` encourages prop usage
130
+
131
+ ## References
132
+
133
+ - `src/components/paragraph.tsx` — Example with full prop support
134
+ - `src/components/heading.tsx` — Example with full prop support
135
+ - `src/components/label.tsx` — Example with full prop support
@@ -0,0 +1,128 @@
1
+ # Pattern 014: clsx Not classnames
2
+
3
+ **Canon Version:** 1.0
4
+ **Status:** Stable
5
+ **Category:** Styling
6
+ **Enforcement:** Documentation
7
+
8
+ ## Decision
9
+
10
+ Use `clsx` for conditional class names. Do not use the `classnames` package.
11
+
12
+ ## Rationale
13
+
14
+ 1. **Smaller bundle** — clsx is ~234B vs classnames ~454B
15
+ 2. **Faster runtime** — clsx is optimized for performance
16
+ 3. **Same API** — Drop-in replacement, familiar syntax
17
+ 4. **Project standard** — Consistency across codebase
18
+
19
+ ## Usage
20
+
21
+ ### Import
22
+
23
+ ```tsx
24
+ import { clsx } from 'clsx'
25
+ ```
26
+
27
+ ### Basic Conditionals
28
+
29
+ ```tsx
30
+ <div className={clsx(
31
+ 'base-class',
32
+ isActive && 'active-class',
33
+ isDisabled && 'disabled-class'
34
+ )}>
35
+ ```
36
+
37
+ ### Object Syntax
38
+
39
+ ```tsx
40
+ <div className={clsx({
41
+ 'base-class': true,
42
+ 'active-class': isActive,
43
+ 'disabled-class': isDisabled,
44
+ })}>
45
+ ```
46
+
47
+ ### Array Syntax
48
+
49
+ ```tsx
50
+ <div className={clsx([
51
+ 'base-class',
52
+ isActive ? 'active-class' : 'inactive-class',
53
+ ])}>
54
+ ```
55
+
56
+ ### Combining Props and Conditions
57
+
58
+ ```tsx
59
+ function Button({ className, isLoading }: Props) {
60
+ return (
61
+ <button
62
+ className={clsx(
63
+ 'px-4 py-2 rounded font-medium',
64
+ 'bg-accent text-accent-contrast',
65
+ 'hover:bg-accent2 transition-colors',
66
+ isLoading && 'opacity-50 cursor-not-allowed',
67
+ className // Allow additional classes from parent
68
+ )}
69
+ >
70
+ {isLoading ? 'Loading...' : children}
71
+ </button>
72
+ )
73
+ }
74
+ ```
75
+
76
+ ## Examples
77
+
78
+ ### Good
79
+
80
+ ```tsx
81
+ import { clsx } from 'clsx'
82
+
83
+ <div className={clsx(
84
+ 'flex items-center gap-2',
85
+ variant === 'primary' && 'bg-accent text-white',
86
+ variant === 'secondary' && 'bg-gray-100 text-gray-900',
87
+ disabled && 'opacity-50 pointer-events-none'
88
+ )}>
89
+ ```
90
+
91
+ ### Bad
92
+
93
+ ```tsx
94
+ // Using classnames package
95
+ import classNames from 'classnames'
96
+
97
+ <div className={classNames('flex', { 'bg-accent': isPrimary })}>
98
+
99
+ // Template literals (harder to read)
100
+ <div className={`flex ${isPrimary ? 'bg-accent' : ''}`}>
101
+
102
+ // Array join (non-standard)
103
+ <div className={['flex', isPrimary && 'bg-accent'].filter(Boolean).join(' ')}>
104
+ ```
105
+
106
+ ## Migration from classnames
107
+
108
+ ```tsx
109
+ // Before
110
+ import classNames from 'classnames'
111
+ classNames('foo', { bar: true })
112
+
113
+ // After
114
+ import { clsx } from 'clsx'
115
+ clsx('foo', { bar: true })
116
+ ```
117
+
118
+ The API is identical — just change the import.
119
+
120
+ ## Enforcement
121
+
122
+ - **Method:** Code review / package.json audit
123
+ - **Check:** `classnames` should not appear in dependencies
124
+
125
+ ## References
126
+
127
+ - clsx GitHub: https://github.com/lukeed/clsx
128
+ - All components use clsx for conditional classes