spoko-design-system 1.4.4 → 1.5.1

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,49 @@
1
+ {
2
+ "includeCoAuthoredBy": false,
3
+ "permissions": {
4
+ "allow": [
5
+ "Bash(pnpm dev:*)",
6
+ "Bash(pnpm start:*)",
7
+ "Bash(pnpm build:*)",
8
+ "Bash(pnpm preview:*)",
9
+ "Bash(pnpm check:*)",
10
+ "Bash(pnpm lint:*)",
11
+ "Bash(pnpm format:*)",
12
+ "Bash(git status:*)",
13
+ "Bash(git diff:*)",
14
+ "Bash(git log:*)",
15
+ "Bash(git add:*)",
16
+ "Bash(git commit:*)"
17
+ ],
18
+ "deny": [
19
+ "Bash(pnpm semantic-release:*)",
20
+ "Bash(pnpm publish:*)",
21
+ "Bash(npm publish:*)",
22
+ "Bash(git push:*)"
23
+ ],
24
+ "ask": [
25
+ "Bash(rm:*)",
26
+ "Bash(pnpm install:*)",
27
+ "Bash(pnpm add:*)",
28
+ "Bash(pnpm remove:*)",
29
+ "Bash(git branch:*)",
30
+ "Bash(git checkout:*)",
31
+ "Bash(git merge:*)",
32
+ "Bash(git rebase:*)"
33
+ ]
34
+ },
35
+ "additionalInstructions": "## Project-Specific Instructions\n\n### Component Development\n- When creating new components, always export them from root `index.ts` (not from `src/`)\n- Use the path pattern: `export { default as Name } from './src/components/Name.vue'`\n- Add documentation page in `src/pages/components/` with `.mdx` extension\n- Update `src/config.ts` SIDEBAR array with navigation entry\n\n### UnoCSS Modifications\n- Never edit root `uno.config.ts` directly (it's just a wrapper)\n- Make theme changes in `uno-config/theme/` files\n- Add shortcuts to appropriate file in `uno-config/theme/shortcuts/`\n- When adding icons, update `icon.config.ts` include array\n\n### Commit Messages\n- This project uses semantic-release with conventional commits\n- Format: `<type>[scope]: <description>`\n- Types: feat (minor), fix (patch), docs (patch), chore (no release)\n- Breaking changes: Add `BREAKING CHANGE:` footer for major version\n- Examples:\n - `feat(components): add new Modal component`\n - `fix(Input): resolve floating label z-index issue`\n - `docs: update Button examples`\n\n### Code Quality\n- Run `pnpm lint:fix` before committing\n- Run `pnpm format` to auto-format code\n- Run `pnpm check` for Astro type checking\n- Ensure components work in both contexts: as npm package and in docs site\n\n### Path Conventions\n- Use TypeScript path aliases: `@components/`, `@utils/`, `@types/`\n- In root `index.ts`, always use `./src/` prefix\n- Within `src/`, use aliases or relative paths\n\n### Testing Changes\n- Start dev server with `pnpm dev` (runs on port 1234)\n- Preview production build with `pnpm preview`\n- Check build output with `pnpm build`",
36
+ "customInstructions": {
37
+ "autoApprove": {
38
+ "patterns": [
39
+ "*.vue",
40
+ "*.astro",
41
+ "*.ts",
42
+ "*.js",
43
+ "*.mdx",
44
+ "src/**/*",
45
+ "uno-config/**/*"
46
+ ]
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,41 @@
1
+ # Git Hooks with Husky
2
+
3
+ This directory contains Git hooks managed by [Husky](https://typicode.github.io/husky/).
4
+
5
+ ## Pre-commit Hook
6
+
7
+ The pre-commit hook automatically runs before each commit to ensure code quality:
8
+
9
+ 1. **Builds the project** (`pnpm run build`)
10
+ - Catches MDX syntax errors
11
+ - Validates Vue components
12
+ - Ensures all documentation examples work
13
+
14
+ If the build fails, the commit will be blocked until errors are fixed.
15
+
16
+ ## Setup for Team Members
17
+
18
+ Hooks are automatically installed when running:
19
+
20
+ ```bash
21
+ pnpm install
22
+ ```
23
+
24
+ The `prepare` script in `package.json` handles this automatically.
25
+
26
+ ## Benefits
27
+
28
+ ✅ Prevents broken builds from being committed
29
+ ✅ Catches errors early in development
30
+ ✅ Maintains code quality across the team
31
+ ✅ Reduces failed CI/CD builds
32
+
33
+ ## Bypassing Hooks (Emergency Only)
34
+
35
+ If you absolutely need to bypass the pre-commit hook:
36
+
37
+ ```bash
38
+ git commit --no-verify -m "your message"
39
+ ```
40
+
41
+ **Note:** Only use this in emergencies. Your commit may break the build.
@@ -0,0 +1,12 @@
1
+ echo "🔍 Running pre-commit checks..."
2
+
3
+ # Run Astro build to catch MDX and component errors
4
+ echo "🏗️ Building project..."
5
+ pnpm run build
6
+
7
+ if [ $? -eq 0 ]; then
8
+ echo "✅ Build successful! Proceeding with commit..."
9
+ else
10
+ echo "❌ Build failed! Please fix errors before committing."
11
+ exit 1
12
+ fi
package/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## [1.5.1](https://github.com/polo-blue/sds/compare/v1.5.0...v1.5.1) (2025-10-27)
2
+
3
+ ### Bug Fixes
4
+
5
+ * remove deprecated Husky shebang for v10 compatibility ([c7addfb](https://github.com/polo-blue/sds/commit/c7addfb1834eb480ead83b3d275977b47683a94a))
6
+
7
+ ## [1.5.0](https://github.com/polo-blue/sds/compare/v1.4.4...v1.5.0) (2025-10-27)
8
+
9
+ ### Features
10
+
11
+ * refactor PR code components with dynamic tooltips and semantic categories ([27a776c](https://github.com/polo-blue/sds/commit/27a776c3e306f12cd69de49c33cb80521dac0090))
12
+
13
+ ### Bug Fixes
14
+
15
+ * remove client-side sorting and simplify PR code components ([73b9760](https://github.com/polo-blue/sds/commit/73b9760f91505cf3f62e7a3549bc090622c10c94))
16
+ * remove computed variantClass and simplify PrCode component ([924aada](https://github.com/polo-blue/sds/commit/924aadae2699369eac850c9b18ba40f8f3193c91))
17
+ * update MDX examples and add defensive checks to ProductCodes ([4d8db14](https://github.com/polo-blue/sds/commit/4d8db14c329131556191c6863b9b9e852599d428))
18
+
1
19
  ## [1.4.4](https://github.com/polo-blue/sds/compare/v1.4.3...v1.4.4) (2025-10-26)
2
20
 
3
21
  ### Bug Fixes
package/CLAUDE.md ADDED
@@ -0,0 +1,268 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ **Spoko Design System (SDS)** is an Astro-based design system with Vue 3 components and UnoCSS for styling. It's published as `spoko-design-system` on npm and serves as both a documentation site and a component library. The project uses automated semantic-release for version management and publishing.
8
+
9
+ ## Common Commands
10
+
11
+ ### Development
12
+ ```bash
13
+ pnpm dev # Start development server on port 1234
14
+ pnpm start # Alias for dev
15
+ pnpm build # Build production site
16
+ pnpm preview # Preview production build
17
+ pnpm check # Run Astro type checking
18
+ ```
19
+
20
+ ### Code Quality
21
+ ```bash
22
+ pnpm lint # Lint all source files
23
+ pnpm lint:fix # Lint and auto-fix issues
24
+ pnpm format # Format code with Prettier
25
+ pnpm format:check # Check code formatting
26
+ ```
27
+
28
+ ### Publishing
29
+ ```bash
30
+ pnpm semantic-release # Manual release (usually automated via CI)
31
+ pnpm prepublishOnly # Runs build before publish (automatic)
32
+ ```
33
+
34
+ ## Architecture
35
+
36
+ ### Dual-Purpose Structure
37
+ This project serves two purposes simultaneously:
38
+ 1. **Documentation Site**: Astro-based documentation at https://sds.spoko.space/
39
+ 2. **npm Package**: Component library exported via `index.ts` at project root
40
+
41
+ **Key Principle**: Components must work in BOTH contexts:
42
+ - As part of the Astro documentation site (in `src/`)
43
+ - As importable npm package components (exported from root `index.ts`)
44
+
45
+ ### Component Exports Pattern
46
+ Components are exported from root `index.ts`, NOT from `src/`:
47
+ ```typescript
48
+ // ✅ Correct
49
+ export { default as Button } from './src/components/Button.vue';
50
+
51
+ // ❌ Wrong
52
+ export { default as Button } from './components/Button.vue';
53
+ ```
54
+
55
+ ### UnoCSS Configuration System
56
+
57
+ **Critical**: UnoCSS config is split across two locations:
58
+
59
+ 1. **Root `uno.config.ts`**: Simple wrapper that imports the main config
60
+ ```typescript
61
+ import { createSdsConfig } from './uno-config';
62
+ export default createSdsConfig();
63
+ ```
64
+
65
+ 2. **`uno-config/` directory**: Main configuration system
66
+ - `uno-config/index.ts`: Core config with `createSdsConfig()` function
67
+ - `uno-config/theme/`: Theme definitions (colors, typography, breakpoints, etc.)
68
+ - `uno-config/theme/shortcuts/`: Component shortcuts organized by category
69
+
70
+ **When modifying UnoCSS**:
71
+ - Theme changes: Modify files in `uno-config/theme/`
72
+ - Shortcut additions: Add to appropriate file in `uno-config/theme/shortcuts/`
73
+ - Never edit root `uno.config.ts` directly
74
+ - The `createSdsConfig()` function can be imported by consumers to extend the config
75
+
76
+ ### Icon System
77
+
78
+ Icons use two separate but coordinated systems:
79
+
80
+ 1. **UnoCSS Icons** (in `uno-config/index.ts`):
81
+ - Static imports for all icon JSON files
82
+ - Used via `i-{collection}-{name}` classes (e.g., `i-lucide-car`)
83
+ - All collections statically imported to prevent Vite module runner issues
84
+
85
+ 2. **astro-icon** (in `icon.config.ts`):
86
+ - Configured via `iconConfig` export
87
+ - Lists specific icons to include from each collection
88
+ - Used in Astro components via `<Icon name="collection:icon" />`
89
+
90
+ **Adding new icons**:
91
+ 1. Add icon name to `icon.config.ts` in the appropriate collection's array
92
+ 2. Icon is automatically available in both UnoCSS (class) and astro-icon (component)
93
+
94
+ ### Navigation & Sidebar
95
+
96
+ Site navigation is configured in `src/config.ts`:
97
+ - `SITE`: Site metadata (title, description, social links)
98
+ - `SIDEBAR`: Array of navigation items with `text`, `link`, and optional `header` flag
99
+ - Headers create section dividers in the left sidebar
100
+ - Links are relative to site root (e.g., `/core/introduction/`)
101
+
102
+ ### TypeScript Paths
103
+
104
+ Three main path aliases defined in `tsconfig.json`:
105
+ - `@components/*` → `src/components/*`
106
+ - `@utils/*` → `src/utils/*`
107
+ - `@types/*` → `src/types/*`
108
+
109
+ ### Component Organization
110
+
111
+ Components are split by framework and purpose:
112
+
113
+ **Vue Components** (`.vue`):
114
+ - Interactive components with state
115
+ - Examples: `Button.vue`, `Input.vue`, `ProductDetailsList.vue`
116
+
117
+ **Astro Components** (`.astro`):
118
+ - Static or mostly-static components
119
+ - Examples: `Jumbotron.astro`, `Copyright.astro`, `HandDrive.astro`
120
+
121
+ **Component Subdirectories**:
122
+ - `components/Product/`: Product-related components
123
+ - `components/Category/`: Category management components
124
+ - `components/Post/`: Blog/post components
125
+ - `components/Jumbotron/variants/`: Jumbotron variations (Hero, Post, PostSplit, Default)
126
+
127
+ ### Utilities Organization
128
+
129
+ Utilities are organized by domain in `src/utils/`:
130
+ - `text/`: Text formatting (formatDate, getNumberFormatted, etc.)
131
+ - `product/`: Product-related utilities (getPriceFormatted, getProductChecklist)
132
+ - `seo/`: SEO helpers (getShorterDescription)
133
+ - `api/`: API interaction utilities
134
+ - `category/`: Category management utilities
135
+
136
+ Root `src/utils/text.ts` exports common text utilities (text2paragraphs, countWords, etc.)
137
+
138
+ ### Theme System
139
+
140
+ Theme is modular in `uno-config/theme/`:
141
+ - `colors.ts`: Complete color palette (blue shades, accent, neutral, slate, system)
142
+ - `typography.ts`: Font families, sizes, weights
143
+ - `breakpoints.ts`: Responsive breakpoints
144
+ - `dimensions.ts`: Spacing, sizing scales
145
+ - `effects.ts`: Shadows, borders, transitions
146
+ - `grid.ts`: Grid template configurations
147
+ - `container.ts`: Container max-widths and padding
148
+
149
+ Design constants are duplicated in `src/design.config.ts` for documentation pages showing colors, typography, shadows, and fonts.
150
+
151
+ ### Shortcuts System
152
+
153
+ UnoCSS shortcuts are organized by component type in `uno-config/theme/shortcuts/`:
154
+ - `buttons.ts`: Button variants and states
155
+ - `inputs.ts`: Form input styling with floating labels
156
+ - `layout.ts`: Container, grid, and layout shortcuts
157
+ - `components.ts`: Breadcrumbs, features lists, category links
158
+ - `product.ts`: Product-specific component shortcuts
159
+ - `jumbotron.ts`: Jumbotron variants
160
+ - `constants.ts`: Shared constants used across shortcuts
161
+
162
+ ## Semantic Release & Commit Conventions
163
+
164
+ This project uses automated semantic-release. **All commits MUST follow conventional commits format**:
165
+
166
+ ```
167
+ <type>[optional scope]: <description>
168
+ ```
169
+
170
+ **Commit Types**:
171
+ - `feat`: New feature → MINOR version bump (0.1.0)
172
+ - `fix`: Bug fix → PATCH version bump (0.0.1)
173
+ - `perf`, `refactor`, `style`, `docs`: → PATCH version bump
174
+ - `test`, `ci`, `chore`: → No release
175
+
176
+ **Breaking Changes**: Add `BREAKING CHANGE:` footer → MAJOR version bump (1.0.0)
177
+
178
+ **Examples**:
179
+ ```bash
180
+ feat(components): add Modal component with accessibility features
181
+ fix(Input): resolve floating label positioning
182
+ docs: update Button component examples
183
+ chore: update dependencies [skip ci]
184
+ ```
185
+
186
+ On merge to `main`, GitHub Actions automatically:
187
+ 1. Analyzes commits
188
+ 2. Determines version bump
189
+ 3. Updates package.json and CHANGELOG.md
190
+ 4. Creates GitHub release
191
+ 5. Publishes to npm
192
+
193
+ ## Development Guidelines
194
+
195
+ ### Adding New Components
196
+
197
+ 1. Create component in appropriate location:
198
+ - Vue: `src/components/ComponentName.vue`
199
+ - Astro: `src/components/ComponentName.astro`
200
+
201
+ 2. Export from root `index.ts`:
202
+ ```typescript
203
+ export { default as ComponentName } from './src/components/ComponentName.vue';
204
+ ```
205
+
206
+ 3. Add documentation page in `src/pages/components/component-name.mdx`
207
+
208
+ 4. Add to navigation in `src/config.ts`:
209
+ ```typescript
210
+ { text: 'Component Name', link: '/components/component-name/' }
211
+ ```
212
+
213
+ ### Adding UnoCSS Shortcuts
214
+
215
+ 1. Identify appropriate shortcuts file in `uno-config/theme/shortcuts/`
216
+ 2. Add shortcut as `[name, classes]` tuple array entry
217
+ 3. Shortcuts automatically available as classes
218
+ 4. Use `.component-preview` class in docs to wrap component examples in grid
219
+
220
+ ### Working with Astro Integration
221
+
222
+ - Astro config in `astro.config.mjs` loads:
223
+ - Vue integration for `.vue` components
224
+ - UnoCSS with SDS config
225
+ - MDX for markdown pages
226
+ - PWA support via vite-pwa
227
+ - Pagefind for search
228
+ - Sitemap generation
229
+
230
+ - Layouts in `src/layouts/`:
231
+ - `MainLayout.astro`: Standard page layout with sidebar
232
+ - `Layout.astro`: Base HTML layout
233
+
234
+ ### Path Resolution
235
+
236
+ When importing:
237
+ - Use TypeScript paths (`@components/`, `@utils/`, `@types/`)
238
+ - Root imports in `index.ts` use `./src/` prefix
239
+ - Component imports within `src/` use aliases or relative paths
240
+
241
+ ### Testing Icons
242
+
243
+ Before using an icon, verify it's in `icon.config.ts`:
244
+ - Check `include` object for the collection
245
+ - Icon must be listed in array for that collection
246
+ - Use helper functions: `isIconIncluded()`, `getIncludedIcons()`
247
+
248
+ ## Package Configuration
249
+
250
+ - **Entry point**: `index.ts` (root level)
251
+ - **Exports**:
252
+ - `.`: Main component exports
253
+ - `./styles/*`: Direct style imports
254
+ - `./icons`: Icon configuration
255
+ - `./icon-collections`: Icon collection list
256
+ - `./uno-config`: UnoCSS config for consumers
257
+
258
+ - **Engines**: Node >= 22.17.0, pnpm >= 10.16.1
259
+ - **Package manager**: pnpm (v10.17.1)
260
+
261
+ ## Important Notes
262
+
263
+ - Server runs on port 1234 (not default 4321)
264
+ - Build output goes to `dist/`
265
+ - Development artifacts in `.astro/` and `dev-dist/`
266
+ - Image domains whitelisted: placehold.co, polo.blue, img.freepik.com, polo6r.pl
267
+ - PWA manifest configured for "Spoko Design System"
268
+ - Compression and inlining enabled for production builds
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spoko-design-system",
3
- "version": "1.4.4",
3
+ "version": "1.5.1",
4
4
  "private": false,
5
5
  "main": "./index.ts",
6
6
  "module": "./index.ts",
@@ -26,7 +26,8 @@
26
26
  "format:check": "prettier --check \"src/**/*.{js,ts,jsx,tsx,vue,astro,json,css,md}\"",
27
27
  "lint": "eslint \"src/**/*.{js,ts,jsx,tsx,vue,astro}\"",
28
28
  "lint:fix": "eslint \"src/**/*.{js,ts,jsx,tsx,vue,astro}\" --fix",
29
- "check": "astro check"
29
+ "check": "astro check",
30
+ "prepare": "husky"
30
31
  },
31
32
  "repository": {
32
33
  "type": "git",
@@ -69,24 +70,24 @@
69
70
  "@iconify-json/et": "^1.2.1",
70
71
  "@iconify-json/flowbite": "^1.2.7",
71
72
  "@iconify-json/fluent": "^1.2.34",
72
- "@iconify-json/fluent-emoji": "1.2.6",
73
+ "@iconify-json/fluent-emoji": "1.2.7",
73
74
  "@iconify-json/ic": "^1.2.4",
74
75
  "@iconify-json/icon-park-outline": "^1.2.4",
75
76
  "@iconify-json/la": "^1.2.1",
76
77
  "@iconify-json/lucide": "^1.2.71",
77
- "@iconify-json/material-symbols-light": "^1.2.42",
78
+ "@iconify-json/material-symbols-light": "^1.2.43",
78
79
  "@iconify-json/mdi": "^1.2.3",
79
80
  "@iconify-json/noto-v1": "^1.2.5",
80
81
  "@iconify-json/octicon": "^1.2.16",
81
82
  "@iconify-json/ph": "^1.2.2",
82
- "@iconify-json/simple-icons": "^1.2.55",
83
+ "@iconify-json/simple-icons": "^1.2.56",
83
84
  "@iconify-json/streamline": "^1.2.5",
84
85
  "@iconify-json/streamline-emojis": "^1.2.4",
85
86
  "@iconify-json/streamline-freehand-color": "^1.2.2",
86
87
  "@iconify-json/system-uicons": "^1.2.4",
87
88
  "@iconify-json/uil": "^1.2.3",
88
89
  "@iconify-json/vscode-icons": "^1.2.33",
89
- "@iconify/json": "^2.2.400",
90
+ "@iconify/json": "^2.2.401",
90
91
  "@iconify/vue": "^5.0.0",
91
92
  "@playform/compress": "^0.2.0",
92
93
  "@playform/inline": "^0.1.2",
@@ -124,8 +125,9 @@
124
125
  "astro": "^5.15.1",
125
126
  "conventional-changelog-conventionalcommits": "^9.1.0",
126
127
  "eslint": "^9.38.0",
127
- "eslint-plugin-astro": "^1.3.1",
128
+ "eslint-plugin-astro": "^1.4.0",
128
129
  "eslint-plugin-vue": "^10.5.1",
130
+ "husky": "^9.1.7",
129
131
  "prettier": "^3.6.2",
130
132
  "prettier-plugin-astro": "^0.14.1",
131
133
  "semantic-release": "^25.0.1",
@@ -6,16 +6,16 @@ interface InputProps {
6
6
  name?: string;
7
7
  label: string;
8
8
  variant?: 'filled' | 'standard';
9
- type?: string;
10
- modelValue?: string | number;
9
+ type?: string; // HTMLInputElement['type'] | 'textarea'
10
+ modelValue?: string | number; // For v-model compatibility
11
11
  required?: boolean;
12
- rows?: number;
12
+ rows?: number; // rows for textarea
13
13
  placeholder?: string;
14
14
  error?: string | boolean;
15
15
  success?: string | boolean;
16
- size?: 'sm' | 'md' | 'lg';
17
- class?: string;
18
- [key: string]: any;
16
+ size?: 'sm' | 'md' | 'lg'; // Size prop
17
+ class?: string; // additional classes
18
+ [key: string]: any; // To allow additional props
19
19
  }
20
20
 
21
21
  const props = withDefaults(defineProps<InputProps>(), {
@@ -26,11 +26,11 @@ const props = withDefaults(defineProps<InputProps>(), {
26
26
  modelValue: '',
27
27
  required: false,
28
28
  rows: 3,
29
- placeholder: ' ', // space for "floating label"
29
+ placeholder: ' ', // space is important for "floating label"
30
30
  error: false,
31
31
  success: false,
32
32
  size: 'md',
33
- class: '',
33
+ class: ''
34
34
  });
35
35
 
36
36
  const emit = defineEmits(['update:modelValue', 'input', 'focus', 'blur']);
@@ -38,72 +38,86 @@ const emit = defineEmits(['update:modelValue', 'input', 'focus', 'blur']);
38
38
  // Handle external attrs
39
39
  const attrs = useAttrs();
40
40
 
41
- // Compute wrapper class - uses existing shortcut
42
- const wrapperClass = computed(() => `input-wrapper-${props.variant}`);
41
+ // Compute wrapper class
42
+ const wrapperClass = computed(() => `relative input-wrapper-${props.variant}`);
43
43
 
44
- // Compute input classes - uses shortcuts
44
+ // Compute input classes going back to direct arbitrary selectors
45
45
  const inputClass = computed(() => {
46
- const classes = ['input-base', 'input-placeholder', `input-${props.variant}`];
47
-
48
- // Add size class
46
+ // Base classes
47
+ const classes = ['input-base', `input-${props.variant}`];
48
+
49
+ // Focus and placeholder behavior - using direct arbitrary selectors
50
+ classes.push('[&:focus~label]:text-blue-light');
51
+ classes.push('[&:focus~label]:dark:text-blue-lightest');
52
+ classes.push('[&:focus~label]:scale-75');
53
+ classes.push('[&:placeholder-shown~label]:scale-100');
54
+ classes.push('[&:placeholder-shown~label]:translate-y-0');
55
+ classes.push('[&:not(:placeholder-shown)~label]:scale-75');
56
+
57
+ // Variant-specific behaviors
58
+ if (props.variant === 'standard') {
59
+ classes.push('[&:focus~label]:-translate-y-6');
60
+ classes.push('[&:focus~label]:start-0');
61
+ classes.push('[&:not(:placeholder-shown)~label]:-translate-y-6');
62
+ } else if (props.variant === 'filled') {
63
+ classes.push('[&:focus~label]:-translate-y-4');
64
+ classes.push('[&:not(:placeholder-shown)~label]:-translate-y-4');
65
+ }
66
+
67
+ // Additional classes
49
68
  if (props.size) classes.push(`input-${props.size}`);
50
-
51
- // Add textarea class if needed
52
69
  if (props.type === 'textarea') classes.push('input-textarea');
53
-
54
- // Add status classes
55
70
  if (props.error) classes.push('input-error');
56
71
  else if (props.success) classes.push('input-success');
57
-
58
- // Add custom classes
59
72
  if (props.class) classes.push(props.class);
60
-
73
+
61
74
  return classes.join(' ');
62
75
  });
63
76
 
64
- // Compute label classes - using optimized shortcuts
77
+ // Compute label classes - important: add -translate-y for initial state explicitly
65
78
  const labelClass = computed(() => {
66
- const classes = [
67
- // Base label style
68
- 'input-label-base',
69
-
70
- // Position styling
71
- `input-label-${props.variant}`,
72
-
73
- // State styling - contains all transformations for the specific variant
74
- `input-label-${props.variant}-state`,
75
- ];
76
-
77
- // Add size class
79
+ // Base classes
80
+ const classes = ['input-label-base', `input-label-${props.variant}`];
81
+
82
+ // Explicitly add transform for initial state to ensure consistency
83
+ if (props.variant === 'standard') {
84
+ // Start in position and let focus/content move it
85
+ classes.push('translate-y-0');
86
+ } else if (props.variant === 'filled') {
87
+ // Start in position and let focus/content move it
88
+ classes.push('translate-y-0');
89
+ }
90
+
91
+ // Additional classes
78
92
  if (props.size) classes.push(`input-label-${props.size}`);
79
-
80
- // Add status classes
81
93
  if (props.error) classes.push('input-label-error');
82
94
  else if (props.success) classes.push('input-label-success');
83
-
95
+
84
96
  return classes.join(' ');
85
97
  });
86
98
 
87
- // Event handlers
88
- const handleInput = (event: globalThis.Event) => {
89
- const target = event.target as globalThis.HTMLInputElement | globalThis.HTMLTextAreaElement;
99
+ // Emit modelValue on input change
100
+ const handleInput = (event: Event) => {
101
+ const target = event.target as HTMLInputElement | HTMLTextAreaElement;
90
102
  emit('update:modelValue', target.value);
91
103
  emit('input', event);
92
104
  };
93
105
 
94
- const handleFocus = (event: globalThis.FocusEvent) => emit('focus', event);
95
- const handleBlur = (event: globalThis.FocusEvent) => emit('blur', event);
106
+ // Forward focus and blur events
107
+ const handleFocus = (event: FocusEvent) => emit('focus', event);
108
+ const handleBlur = (event: FocusEvent) => emit('blur', event);
96
109
  </script>
97
110
 
98
111
  <template>
99
112
  <div :class="wrapperClass">
113
+ <!-- Textarea field -->
100
114
  <textarea
101
115
  v-if="type === 'textarea'"
102
116
  :id="id"
103
117
  :name="name || id"
104
118
  :rows="rows"
105
119
  :required="required"
106
- :class="inputClass + ' peer'"
120
+ :class="inputClass"
107
121
  :placeholder="placeholder"
108
122
  :value="modelValue"
109
123
  v-bind="attrs"
@@ -111,47 +125,50 @@ const handleBlur = (event: globalThis.FocusEvent) => emit('blur', event);
111
125
  @focus="handleFocus"
112
126
  @blur="handleBlur"
113
127
  />
114
-
128
+
129
+ <!-- Input field -->
115
130
  <input
116
131
  v-else
117
132
  :id="id"
118
133
  :type="type"
119
134
  :name="name || id"
120
135
  :required="required"
121
- :class="inputClass + ' peer'"
136
+ :class="inputClass"
122
137
  :placeholder="placeholder"
123
138
  :value="modelValue"
124
139
  v-bind="attrs"
125
140
  @input="handleInput"
126
141
  @focus="handleFocus"
127
142
  @blur="handleBlur"
128
- />
129
-
143
+ >
144
+
145
+ <!-- Label with guaranteed correct transform origin -->
130
146
  <label
131
147
  :for="id"
132
148
  :class="labelClass"
133
- style="transform-origin: top left"
149
+ style="transform-origin: top left;"
134
150
  >
135
151
  {{ label }}
136
152
  <span
137
153
  v-if="required"
138
154
  class="text-red-500 ml-1"
139
- >*</span
140
- >
155
+ >*</span>
141
156
  </label>
142
-
143
- <div
144
- v-if="error && typeof error === 'string'"
157
+
158
+ <!-- Error message -->
159
+ <div
160
+ v-if="error && typeof error === 'string'"
145
161
  class="input-error-message"
146
162
  >
147
163
  {{ error }}
148
164
  </div>
149
-
150
- <div
151
- v-if="success && typeof success === 'string'"
165
+
166
+ <!-- Success message -->
167
+ <div
168
+ v-if="success && typeof success === 'string'"
152
169
  class="input-success-message"
153
170
  >
154
171
  {{ success }}
155
172
  </div>
156
173
  </div>
157
- </template>
174
+ </template>
@@ -1,12 +1,21 @@
1
1
  <script lang="ts" setup>
2
+ import type { PropType } from 'vue';
3
+
2
4
  /*
3
- VAG group (VW/Audi/Skoda/Seat/Porsche/Bentley/Lamborghini/Ducati/Cupra/Scania/MAN) manufacturer PR-Code
5
+ VAG group (VW/Audi/Skoda/Seat/Porsche/Bentley/Lamborghini/Ducati/Cupra/Scania/MAN) manufacturer PR-Code
4
6
  */
5
7
 
8
+ interface PrCodeObject {
9
+ id?: number;
10
+ code: string;
11
+ group?: string;
12
+ description?: string;
13
+ variant_category?: string;
14
+ }
15
+
6
16
  const props = defineProps({
7
17
  prcode: {
8
- type: String,
9
- default: null,
18
+ type: Object as PropType<PrCodeObject>,
10
19
  required: true,
11
20
  },
12
21
  isPdp: {
@@ -18,124 +27,97 @@ const props = defineProps({
18
27
  </script>
19
28
 
20
29
  <template>
21
- <span
22
- data-pagefind-filter="PR-Code"
23
- class="btn-prcode"
24
- :class="`btn-prcode--${props.prcode} ${props.isPdp ? ' btn-prcode--pdp' : ''}`"
25
- >
26
- {{ props.prcode }}
30
+ <span class="relative has-tooltip inline-block">
31
+ <span
32
+ data-pagefind-filter="PR-Code"
33
+ class="btn-prcode"
34
+ :class="[
35
+ prcode.variant_category ? `btn-prcode--variant-${prcode.variant_category.toLowerCase()}` : '',
36
+ { 'btn-prcode--pdp': isPdp }
37
+ ]"
38
+ >
39
+ {{ prcode.code }}
40
+ </span>
41
+
42
+ <!-- Dynamic Tooltip with description from API -->
43
+ <div v-if="props.prcode.description" class="tooltip">
44
+ <div class="tooltip-content">
45
+ {{ props.prcode.description }}
46
+ <span v-if="props.prcode.group" class="tooltip-group">
47
+ ({{ props.prcode.group }})
48
+ </span>
49
+ </div>
50
+ </div>
27
51
  </span>
28
52
  </template>
29
53
 
30
- <style>
31
- .btn-prcode--pdp {
32
- @apply mb-1;
33
- }
34
-
35
- .btn-prcode::before {
36
- @apply rounded-2 shadow-sm py-0.5 px-2 bg-gray-100 whitespace-nowrap text-xs dark:text-black dark:bg-accent-light text-center z-50;
37
- display: none;
38
- position: absolute;
39
- top: -10px;
40
- transform: translateY(-50%) translateX(-50%);
41
- left: 50%;
42
- }
43
-
44
- .btn-prcode:hover::before {
45
- display: block;
46
- }
47
-
48
- .btn-prcode--2JK {
49
- color: #f3881d;
54
+ <style scoped>
55
+ /* Base PrCode Button Styles */
56
+ .btn-prcode {
57
+ @apply inline-block relative cursor-default;
50
58
  }
51
59
 
52
- .btn-prcode--2JK::before {
53
- content: 'CROSS';
54
- }
55
-
56
- .btn-prcode--1LR::before,
57
- .btn-prcode--1ZG::before,
58
- .btn-prcode--1ZJ::before {
59
- content: '⌀ 256 mm';
60
- }
61
-
62
- .btn-prcode--1KD::before,
63
- .btn-prcode--1ZP::before,
64
- .btn-prcode--1ZR::before {
65
- content: '⌀ 310 mm';
66
- }
67
-
68
- .btn-prcode--1ZD::before,
69
- .btn-prcode--1ZC::before,
70
- .btn-prcode--1LN::before {
71
- content: '⌀ 288 mm; LUCAS';
72
- }
73
-
74
- .btn-prcode--2JZ {
75
- @apply text-accent-light;
60
+ .btn-prcode--pdp {
61
+ @apply mb-1;
76
62
  }
77
63
 
78
- .btn-prcode--2JZ::before {
79
- content: 'Bluemotion';
64
+ /* Tooltip Styles - Similar to ProductEngine */
65
+ .tooltip {
66
+ @apply invisible absolute left-1/2 -translate-x-1/2 bottom-full mb-2 z-50;
67
+ @apply px-3 py-1.5 rounded-lg shadow-lg whitespace-nowrap;
68
+ @apply bg-blue-darker text-white text-xs;
69
+ @apply pointer-events-none;
70
+ max-width: 300px;
71
+ white-space: normal;
80
72
  }
81
73
 
82
- .btn-prcode--7L6 {
83
- @apply text-accent-light;
74
+ .has-tooltip:hover .tooltip {
75
+ @apply visible;
84
76
  }
85
77
 
86
- .btn-prcode--7L6::before {
87
- content: 'Bluemotion (CFWA + start-stop)';
78
+ .tooltip-content {
79
+ @apply relative;
88
80
  }
89
81
 
90
- .btn-prcode--1KK::before,
91
- .btn-prcode--1KT::before,
92
- .btn-prcode--1KV::before,
93
- .btn-prcode--1LV::before,
94
- .btn-prcode--2EJ::before {
95
- content: '⌀ 230 mm';
82
+ .tooltip-group {
83
+ @apply ml-2 opacity-75 text-xs font-light;
96
84
  }
97
85
 
98
- .btn-prcode--2JE {
99
- @apply text-accent-dark;
86
+ /* Tooltip Arrow */
87
+ .tooltip::after {
88
+ content: '';
89
+ @apply absolute left-1/2 -translate-x-1/2 top-full;
90
+ @apply border-4 border-transparent border-t-blue-darker;
100
91
  }
101
92
 
102
- .btn-prcode--2JE::before {
103
- content: 'BlueGT';
93
+ /* Semantic Variant Category Colors */
94
+ /* GTI - Red */
95
+ .btn-prcode--variant-gti {
96
+ @apply text-red-600 dark:text-red-500;
104
97
  }
105
98
 
106
- .btn-prcode--2JP::before {
107
- content: 'R-Line';
99
+ /* WRC/R - Blue */
100
+ .btn-prcode--variant-wrc {
101
+ @apply text-blue-600 dark:text-blue-500;
108
102
  }
109
103
 
110
- .btn-prcode--E5M,
111
- .btn-prcode--1KD,
112
- .btn-prcode--1ZP,
113
- .btn-prcode--2JQ,
114
- .btn-prcode--TA2 {
115
- color: blue;
104
+ /* CROSS - Orange */
105
+ .btn-prcode--variant-cross {
106
+ color: #f3881d;
116
107
  }
117
108
 
118
- .btn-prcode--E5M::before,
119
- .btn-prcode--1KD::before,
120
- .btn-prcode--1ZP::before,
121
- .btn-prcode--2JQ::before,
122
- .btn-prcode--TA2::before {
123
- content: 'R WRC Street';
109
+ /* BlueGT - Accent Dark */
110
+ .btn-prcode--variant-bluegt {
111
+ @apply text-accent-dark dark:text-accent-dark;
124
112
  }
125
113
 
126
- .btn-prcode--1KV,
127
- .btn-prcode--1ZD,
128
- .btn-prcode--1ZR,
129
- .btn-prcode--0NH,
130
- .btn-prcode--2JD {
131
- color: red;
114
+ /* Bluemotion - Accent Light */
115
+ .btn-prcode--variant-bluemotion {
116
+ @apply text-accent-light dark:text-accent-light;
132
117
  }
133
118
 
134
- .btn-prcode--1KV::before,
135
- .btn-prcode--1ZD::before,
136
- .btn-prcode--1ZR::before,
137
- .btn-prcode--0NH::before,
138
- .btn-prcode--2JD::before {
139
- content: 'GTI';
119
+ /* R-Line - Default styling with possible future customization */
120
+ .btn-prcode--variant-r_line {
121
+ @apply text-gray-800 dark:text-gray-300;
140
122
  }
141
123
  </style>
@@ -2,10 +2,18 @@
2
2
  import type { PropType } from 'vue';
3
3
  import PrCode from './PrCode.vue';
4
4
 
5
+ interface PrCodeObject {
6
+ id?: number;
7
+ code: string;
8
+ group?: string;
9
+ description?: string;
10
+ variant_category?: string;
11
+ }
12
+
5
13
  const props = defineProps({
6
14
  prcodes: {
7
- type: Array as PropType<string[] | null>,
8
- default: null,
15
+ type: Array as PropType<PrCodeObject[]>,
16
+ default: () => [],
9
17
  required: true,
10
18
  },
11
19
  isPdp: {
@@ -14,31 +22,37 @@ const props = defineProps({
14
22
  required: false,
15
23
  },
16
24
  });
17
-
18
- const codes = props.prcodes || [];
19
- const decodedCodes = codes ? codes.sort() : [];
20
-
21
- const settings = {
22
- prcodes: decodedCodes,
23
- };
24
25
  </script>
25
26
 
26
27
  <template>
27
28
  <span
28
- v-for="(prcode, index) in settings.prcodes"
29
- :key="index"
29
+ v-for="(prcode, index) in prcodes"
30
+ :key="prcode?.id || index"
30
31
  class="not-last:mr-1"
31
32
  >
32
- <PrCode
33
- v-if="!String(prcode).includes('+')"
34
- :prcode="prcode"
35
- />
36
- <span v-else>
33
+ <!-- Skip invalid entries -->
34
+ <template v-if="prcode?.code">
35
+ <!-- Handle normal PR codes -->
37
36
  <PrCode
38
- v-for="(splittedCode, index2) in String(prcode).split('+')"
39
- :key="index2"
40
- :prcode="splittedCode"
37
+ v-if="!prcode.code.includes('+')"
38
+ :prcode="prcode"
39
+ :isPdp="isPdp"
41
40
  />
42
- </span>
41
+
42
+ <!-- Handle combined PR codes like "1KD+2JP" -->
43
+ <span v-else>
44
+ <PrCode
45
+ v-for="(code, idx) in prcode.code.split('+')"
46
+ :key="idx"
47
+ :prcode="{
48
+ code: code.trim(),
49
+ group: prcode.group,
50
+ description: null,
51
+ variant_category: prcode.variant_category
52
+ }"
53
+ :isPdp="isPdp"
54
+ />
55
+ </span>
56
+ </template>
43
57
  </span>
44
58
  </template>
package/src/config.ts CHANGED
@@ -3,7 +3,7 @@ export const SITE = {
3
3
  description: 'The Astro design system which facilitates the development of websites.',
4
4
  defaultLanguage: 'en_US',
5
5
  twitter: '@spokospace',
6
- github: 'spokospace',
6
+ github: 'polo-blue/sds',
7
7
  linkedin: 'szymonberski',
8
8
  };
9
9
 
@@ -7,7 +7,12 @@ import ProductDetailsList from '../../components/ProductDetailsList.vue'
7
7
  import ProductNumber from "../../components/Product/ProductNumber.astro"
8
8
  import ProductCodes from '../../components/ProductCodes.vue'
9
9
 
10
- export const prcodesArray = ["PJ4", "CA2", "C4E", "2JZ"]
10
+ export const prcodesArray = [
11
+ { code: "PJ4", group: "BAV", description: "Disk brakes example" },
12
+ { code: "CA2", group: "STF", description: "Standard equipment" },
13
+ { code: "C4E", group: "AAU", description: "Air conditioning" },
14
+ { code: "2JZ", group: "STF", description: "Standard bumpers (e.g. Polo 6R Bluemotion)", variant_category: "BLUEMOTION" }
15
+ ]
11
16
 
12
17
  export const tableItems = [
13
18
  {
@@ -18,7 +23,7 @@ export const tableItems = [
18
23
  {
19
24
  id: "prcodes",
20
25
  label: "PR-Codes",
21
- value: ["PJ4", "CA2", "C4E", "2JZ"],
26
+ value: prcodesArray,
22
27
  },
23
28
  {
24
29
  id: "color",
@@ -58,6 +63,12 @@ export const tableItems = [
58
63
  </div>
59
64
 
60
65
  ```ts
66
+ const prcodesArray = [
67
+ { code: "PJ4", group: "BAV", description: "Disk brakes example" },
68
+ { code: "CA2", group: "STF", description: "Standard equipment" },
69
+ { code: "C4E", group: "AAU", description: "Air conditioning" }
70
+ ]
71
+
61
72
  const tableItems = [
62
73
  {
63
74
  id: "number",
@@ -67,7 +78,7 @@ const tableItems = [
67
78
  {
68
79
  id: "prcodes",
69
80
  label: "PR-Codes",
70
- value: ["PJ4", "CA2", "C4E"]
81
+ value: prcodesArray
71
82
  },
72
83
  {
73
84
  id: "color",
@@ -269,7 +269,7 @@ A two-column layout variant for posts where image and content need equal emphasi
269
269
  <Jumbotron
270
270
  variant="post-split"
271
271
  title="<b>Formatted</b> Blog Post Title <small>Lorem Ipsum</small>"
272
- image="http://localhost:1234/_image?href=https%3A%2F%2Fimg.freepik.com%2Ffree-photo%2Fnature-beauty-tropical-rainforest-adventure-tranquility-freshness-generated-by-artificial-intellingence_25030-62539.jpg%3Fsize%3D960%26ext%3Djpg&w=1200&h=675&f=webp"
272
+ image="https://img.freepik.com/premium-photo/tranquil-scene-nature-beauty-tropical-rainforest-adventure-generated-by-artificial-intelligence_188544-83820.jpg?w=1200&h=675&f=webp"
273
273
  categories={[
274
274
  { name: 'Technology', link: '#' },
275
275
  { name: 'Design', link: '#' }
@@ -7,17 +7,16 @@ import ProductCodes from '../../components/ProductCodes.vue'
7
7
 
8
8
  # PR-Code
9
9
 
10
- PR Code are Production Codes for all of the installed equipment in the vehicle and is used by manufacturers including VW, Audi, Seat, Skoda, Porsche, Lamborghini etc.
11
- PR Codes contain 3 characters comprising of letters and numbers.
10
+ PR Code are Production Codes for all of the installed equipment in the vehicle and is used by manufacturers including VW, Audi, Seat, Skoda, Porsche, Lamborghini etc.
11
+ PR Codes contain 3 characters comprising of letters and numbers.
12
12
 
13
13
  PR codes are located on vehicle´s build sticker that is located on first pages of cars warranty booklet or there should be one in a trunk as well usually under the carpet or in spare tire area.
14
14
 
15
15
  They are important when purchasing spare parts as they provide information about the manufacturer, make, model, year of manufacture, and other vehicle details.
16
16
 
17
+ ## Single PR-Code
17
18
 
18
-
19
- ## PR-Code
20
- Single PR-Code.
19
+ Display a single PR code with tooltip showing description from API.
21
20
 
22
21
  ### import:
23
22
 
@@ -25,25 +24,92 @@ Single PR-Code.
25
24
  import PrCode from 'spoko-design-system/src/components/PrCode.vue'
26
25
  ```
27
26
 
28
- #
27
+ ### Basic Usage:
29
28
 
30
29
  <div class="component-preview">
31
- <div class="bg-white p-6 w-full">
32
- <PrCode prcode="2JP" />
33
- <PrCode prcode="1ZJ" />
30
+ <div class="bg-white p-6 w-full flex gap-2">
31
+ <PrCode prcode={{ code: "2JP", group: "STF", description: "R-Line bumper", variant_category: "R_LINE" }} />
32
+ <PrCode prcode={{ code: "1ZJ", group: "BAV", description: "Disk brakes in front (Geomet D); 256x22mm (vented)" }} />
34
33
  </div>
35
34
  </div>
36
35
 
37
- ```js
38
- <PrCode prcode="2JP" />
39
- <PrCode prcode="1ZJ" />
36
+ ```vue
37
+ <PrCode :prcode="{
38
+ code: '2JP',
39
+ group: 'STF',
40
+ description: 'R-Line bumper',
41
+ variant_category: 'R_LINE'
42
+ }" />
43
+
44
+ <PrCode :prcode="{
45
+ code: '1ZJ',
46
+ group: 'BAV',
47
+ description: 'Disk brakes in front (Geomet D); 256x22mm (vented)'
48
+ }" />
49
+ ```
50
+
51
+ ### Special Edition Variants (Colored):
52
+
53
+ PR codes for special editions are automatically color-coded based on their `variant_category`:
54
+
55
+ <div class="component-preview">
56
+ <div class="bg-white p-6 w-full flex flex-wrap gap-3">
57
+ <div class="flex flex-col gap-1">
58
+ <small class="text-xs text-gray-500">GTI (Red)</small>
59
+ <PrCode prcode={{ code: "2JD", group: "STF", description: "Sports bumpers (e.g. Polo 6R/6C GTI)", variant_category: "GTI" }} />
60
+ </div>
61
+ <div class="flex flex-col gap-1">
62
+ <small class="text-xs text-gray-500">WRC (Blue)</small>
63
+ <PrCode prcode={{ code: "E5M", group: "AAU", description: "Polo WRC", variant_category: "WRC" }} />
64
+ </div>
65
+ <div class="flex flex-col gap-1">
66
+ <small class="text-xs text-gray-500">CROSS (Orange)</small>
67
+ <PrCode prcode={{ code: "2JK", group: "STF", description: "Sports bumpers (e.g. Polo 6R/6C Cross)", variant_category: "CROSS" }} />
68
+ </div>
69
+ <div class="flex flex-col gap-1">
70
+ <small class="text-xs text-gray-500">BlueGT</small>
71
+ <PrCode prcode={{ code: "2JE", group: "STF", description: "Partially painted bumpers (e.g. Polo 6R/6C BlueGT)", variant_category: "BLUEGT" }} />
72
+ </div>
73
+ <div class="flex flex-col gap-1">
74
+ <small class="text-xs text-gray-500">Bluemotion</small>
75
+ <PrCode prcode={{ code: "2JZ", group: "STF", description: "Standard bumpers (e.g. Polo 6R Bluemotion)", variant_category: "BLUEMOTION" }} />
76
+ </div>
77
+ </div>
78
+ </div>
79
+
80
+ ```vue
81
+ <!-- GTI - Red -->
82
+ <PrCode :prcode="{ code: '2JD', group: 'STF', description: 'Sports bumpers (e.g. Polo 6R/6C GTI)', variant_category: 'GTI' }" />
83
+
84
+ <!-- WRC - Blue -->
85
+ <PrCode :prcode="{ code: 'E5M', group: 'AAU', description: 'Polo WRC', variant_category: 'WRC' }" />
86
+
87
+ <!-- CROSS - Orange -->
88
+ <PrCode :prcode="{ code: '2JK', group: 'STF', description: 'Sports bumpers (e.g. Polo 6R/6C Cross)', variant_category: 'CROSS' }" />
89
+
90
+ <!-- BlueGT -->
91
+ <PrCode :prcode="{ code: '2JE', group: 'STF', description: 'Partially painted bumpers (e.g. Polo 6R/6C BlueGT)', variant_category: 'BLUEGT' }" />
92
+
93
+ <!-- Bluemotion -->
94
+ <PrCode :prcode="{ code: '2JZ', group: 'STF', description: 'Standard bumpers (e.g. Polo 6R Bluemotion)', variant_category: 'BLUEMOTION' }" />
40
95
  ```
41
96
 
97
+ ### Variant Categories:
42
98
 
99
+ The following variant categories are supported with automatic color coding:
43
100
 
101
+ | Category | Color | Description | Example Codes |
102
+ |----------|-------|-------------|---------------|
103
+ | `GTI` | Red | GTI Performance variants | 2JD, 1ZD, 1KV, 0NH, 1ZR |
104
+ | `WRC` | Blue | WRC/R Street variants | E5M, 1KD, 1ZP, 2JQ, TA2 |
105
+ | `CROSS` | Orange | Cross/Offroad variants | 2JK |
106
+ | `BLUEGT` | Accent Dark | BlueGT variants | 2JE |
107
+ | `BLUEMOTION` | Accent Light | Bluemotion eco variants | 2JZ, 7L6 |
108
+ | `R_LINE` | Gray | R-Line styling | 2JP |
44
109
 
45
110
  ## PR-Codes List
46
- Component to display list of pr codes from array.
111
+
112
+ Display multiple PR codes from an array of objects (from API response).
47
113
 
48
114
  ### import:
49
115
 
@@ -51,15 +117,93 @@ Component to display list of pr codes from array.
51
117
  import ProductCodes from 'spoko-design-system/src/components/ProductCodes.vue'
52
118
  ```
53
119
 
54
- #
120
+ ### Usage:
55
121
 
56
122
  <div class="component-preview">
57
123
  <div class="bg-white p-6 w-full">
58
- <ProductCodes prcodes={["2JP", "1ZJ"]} />
124
+ <ProductCodes prcodes={[
125
+ { code: "2JP", group: "STF", description: "R-Line bumper", variant_category: "R_LINE" },
126
+ { code: "1ZJ", group: "BAV", description: "Disk brakes in front (Geomet D); 256x22mm (vented)" },
127
+ { code: "2JD", group: "STF", description: "Sports bumpers (e.g. Polo 6R/6C GTI)", variant_category: "GTI" },
128
+ ]} />
59
129
  </div>
60
130
  </div>
61
131
 
62
- ```js
63
- <ProductCodes prcodes={["2JP", "1ZJ"]} />
132
+ ```vue
133
+ <ProductCodes :prcodes="[
134
+ { code: '2JP', group: 'STF', description: 'R-Line bumper', variant_category: 'R_LINE' },
135
+ { code: '1ZJ', group: 'BAV', description: 'Disk brakes in front (Geomet D); 256x22mm (vented)' },
136
+ { code: '2JD', group: 'STF', description: 'Sports bumpers (e.g. Polo 6R/6C GTI)', variant_category: 'GTI' }
137
+ ]" />
138
+ ```
139
+
140
+ ### With API Data:
141
+
142
+ When using data from the API, pass the `pr_codes` array directly:
143
+
144
+ ```vue
145
+ <ProductCodes
146
+ :prcodes="product.pr_codes"
147
+ :isPdp="true"
148
+ />
149
+ ```
150
+
151
+ ### API Response Structure:
152
+
153
+ ```json
154
+ {
155
+ "pr_codes": [
156
+ {
157
+ "id": 1522,
158
+ "code": "1ZP",
159
+ "group": "BAV",
160
+ "description": "Disk brakes in front (Geomet D); 310x25mm (vented)",
161
+ "variant_category": "WRC"
162
+ },
163
+ {
164
+ "id": 2145,
165
+ "code": "2JD",
166
+ "group": "STF",
167
+ "description": "Sports bumpers (e.g. Polo 6R/6C GTI)",
168
+ "variant_category": "GTI"
169
+ }
170
+ ]
171
+ }
64
172
  ```
65
173
 
174
+ ## Props
175
+
176
+ ### PrCode Props:
177
+
178
+ | Prop | Type | Required | Default | Description |
179
+ |------|------|----------|---------|-------------|
180
+ | `prcode` | `Object` | Yes | - | PR code object with code, group, description, variant_category |
181
+ | `prcode.code` | `String` | Yes | - | The 3-character PR code |
182
+ | `prcode.group` | `String` | No | - | PR code group (e.g., "BAV", "STF") |
183
+ | `prcode.description` | `String` | No | - | Description shown in tooltip |
184
+ | `prcode.variant_category` | `String` | No | - | Category for semantic coloring (GTI, WRC, etc.) |
185
+ | `isPdp` | `Boolean` | No | `false` | Product detail page styling |
186
+
187
+ ### ProductCodes Props:
188
+
189
+ | Prop | Type | Required | Default | Description |
190
+ |------|------|----------|---------|-------------|
191
+ | `prcodes` | `Array<Object>` | Yes | `[]` | Array of PR code objects |
192
+ | `isPdp` | `Boolean` | No | `false` | Product detail page styling |
193
+
194
+ ## Features
195
+
196
+ - ✅ **Dynamic Tooltips**: Shows description from API on hover
197
+ - ✅ **Semantic Colors**: Automatic color coding for special editions (GTI, WRC, etc.)
198
+ - ✅ **Dark Mode**: Supports dark mode with appropriate color adjustments
199
+ - ✅ **Accessible**: Uses semantic HTML and proper ARIA attributes
200
+ - ✅ **Search Friendly**: Includes `data-pagefind-filter` for search indexing
201
+ - ✅ **Combination Support**: Handles combined PR codes like "1KD+2JP"
202
+ - ✅ **Defensive**: Gracefully handles invalid or malformed data
203
+
204
+ ## Notes
205
+
206
+ - Hover over any PR code to see its full description
207
+ - Colors are based on semantic variant categories, not hardcoded per code
208
+ - The tooltip design matches the ProductEngine component style
209
+ - All 14,000+ PR codes in the database have English descriptions