spoko-design-system 1.4.4 → 1.5.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/.claude/settings.json +49 -0
- package/CHANGELOG.md +12 -0
- package/CLAUDE.md +268 -0
- package/package.json +6 -6
- package/src/components/Input.vue +74 -57
- package/src/components/PrCode.vue +77 -95
- package/src/components/ProductCodes.vue +34 -20
- package/src/config.ts +1 -1
- package/src/pages/components/details-list.mdx +14 -3
- package/src/pages/components/jumbotron.mdx +1 -1
- package/src/pages/components/pr-code.mdx +161 -17
|
@@ -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
|
+
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [1.5.0](https://github.com/polo-blue/sds/compare/v1.4.4...v1.5.0) (2025-10-27)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* refactor PR code components with dynamic tooltips and semantic categories ([27a776c](https://github.com/polo-blue/sds/commit/27a776c3e306f12cd69de49c33cb80521dac0090))
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
* remove client-side sorting and simplify PR code components ([73b9760](https://github.com/polo-blue/sds/commit/73b9760f91505cf3f62e7a3549bc090622c10c94))
|
|
10
|
+
* remove computed variantClass and simplify PrCode component ([924aada](https://github.com/polo-blue/sds/commit/924aadae2699369eac850c9b18ba40f8f3193c91))
|
|
11
|
+
* update MDX examples and add defensive checks to ProductCodes ([4d8db14](https://github.com/polo-blue/sds/commit/4d8db14c329131556191c6863b9b9e852599d428))
|
|
12
|
+
|
|
1
13
|
## [1.4.4](https://github.com/polo-blue/sds/compare/v1.4.3...v1.4.4) (2025-10-26)
|
|
2
14
|
|
|
3
15
|
### 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.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "./index.ts",
|
|
6
6
|
"module": "./index.ts",
|
|
@@ -69,24 +69,24 @@
|
|
|
69
69
|
"@iconify-json/et": "^1.2.1",
|
|
70
70
|
"@iconify-json/flowbite": "^1.2.7",
|
|
71
71
|
"@iconify-json/fluent": "^1.2.34",
|
|
72
|
-
"@iconify-json/fluent-emoji": "1.2.
|
|
72
|
+
"@iconify-json/fluent-emoji": "1.2.7",
|
|
73
73
|
"@iconify-json/ic": "^1.2.4",
|
|
74
74
|
"@iconify-json/icon-park-outline": "^1.2.4",
|
|
75
75
|
"@iconify-json/la": "^1.2.1",
|
|
76
76
|
"@iconify-json/lucide": "^1.2.71",
|
|
77
|
-
"@iconify-json/material-symbols-light": "^1.2.
|
|
77
|
+
"@iconify-json/material-symbols-light": "^1.2.43",
|
|
78
78
|
"@iconify-json/mdi": "^1.2.3",
|
|
79
79
|
"@iconify-json/noto-v1": "^1.2.5",
|
|
80
80
|
"@iconify-json/octicon": "^1.2.16",
|
|
81
81
|
"@iconify-json/ph": "^1.2.2",
|
|
82
|
-
"@iconify-json/simple-icons": "^1.2.
|
|
82
|
+
"@iconify-json/simple-icons": "^1.2.56",
|
|
83
83
|
"@iconify-json/streamline": "^1.2.5",
|
|
84
84
|
"@iconify-json/streamline-emojis": "^1.2.4",
|
|
85
85
|
"@iconify-json/streamline-freehand-color": "^1.2.2",
|
|
86
86
|
"@iconify-json/system-uicons": "^1.2.4",
|
|
87
87
|
"@iconify-json/uil": "^1.2.3",
|
|
88
88
|
"@iconify-json/vscode-icons": "^1.2.33",
|
|
89
|
-
"@iconify/json": "^2.2.
|
|
89
|
+
"@iconify/json": "^2.2.401",
|
|
90
90
|
"@iconify/vue": "^5.0.0",
|
|
91
91
|
"@playform/compress": "^0.2.0",
|
|
92
92
|
"@playform/inline": "^0.1.2",
|
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
"astro": "^5.15.1",
|
|
125
125
|
"conventional-changelog-conventionalcommits": "^9.1.0",
|
|
126
126
|
"eslint": "^9.38.0",
|
|
127
|
-
"eslint-plugin-astro": "^1.
|
|
127
|
+
"eslint-plugin-astro": "^1.4.0",
|
|
128
128
|
"eslint-plugin-vue": "^10.5.1",
|
|
129
129
|
"prettier": "^3.6.2",
|
|
130
130
|
"prettier-plugin-astro": "^0.14.1",
|
package/src/components/Input.vue
CHANGED
|
@@ -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
|
|
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
|
|
44
|
+
// Compute input classes going back to direct arbitrary selectors
|
|
45
45
|
const inputClass = computed(() => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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 -
|
|
77
|
+
// Compute label classes - important: add -translate-y for initial state explicitly
|
|
65
78
|
const labelClass = computed(() => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
//
|
|
88
|
-
const handleInput = (event:
|
|
89
|
-
const target = event.target as
|
|
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
|
-
|
|
95
|
-
const
|
|
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
|
|
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
|
|
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
|
-
|
|
140
|
-
>
|
|
155
|
+
>*</span>
|
|
141
156
|
</label>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
151
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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--
|
|
53
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
.
|
|
83
|
-
@apply
|
|
74
|
+
.has-tooltip:hover .tooltip {
|
|
75
|
+
@apply visible;
|
|
84
76
|
}
|
|
85
77
|
|
|
86
|
-
.
|
|
87
|
-
|
|
78
|
+
.tooltip-content {
|
|
79
|
+
@apply relative;
|
|
88
80
|
}
|
|
89
81
|
|
|
90
|
-
.
|
|
91
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
107
|
-
|
|
99
|
+
/* WRC/R - Blue */
|
|
100
|
+
.btn-prcode--variant-wrc {
|
|
101
|
+
@apply text-blue-600 dark:text-blue-500;
|
|
108
102
|
}
|
|
109
103
|
|
|
110
|
-
|
|
111
|
-
.btn-prcode--
|
|
112
|
-
|
|
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
|
-
|
|
119
|
-
.btn-prcode--
|
|
120
|
-
|
|
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
|
-
|
|
127
|
-
.btn-prcode--
|
|
128
|
-
|
|
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
|
-
|
|
135
|
-
.btn-prcode--
|
|
136
|
-
|
|
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<
|
|
8
|
-
default:
|
|
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
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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-
|
|
39
|
-
:
|
|
40
|
-
:
|
|
37
|
+
v-if="!prcode.code.includes('+')"
|
|
38
|
+
:prcode="prcode"
|
|
39
|
+
:isPdp="isPdp"
|
|
41
40
|
/>
|
|
42
|
-
|
|
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
|
@@ -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 = [
|
|
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:
|
|
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:
|
|
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="
|
|
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
|
-
```
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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={[
|
|
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
|
-
```
|
|
63
|
-
|
|
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
|