tux-review 0.1.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.
Files changed (60) hide show
  1. package/README.md +67 -0
  2. package/bin/tux.js +17 -0
  3. package/client/tux-review.css +155 -0
  4. package/client/tux-review.js +586 -0
  5. package/package.json +59 -0
  6. package/skills/tux-design-create.md +50 -0
  7. package/skills/tux-design-incorporate.md +61 -0
  8. package/skills/tux-design-install.md +56 -0
  9. package/skills/tux-design-start-review.md +51 -0
  10. package/skills/tux-design-stop-review.md +44 -0
  11. package/skills/tux-feedback-delete.md +45 -0
  12. package/skills/tux-feedback-export.md +47 -0
  13. package/skills/tux-feedback-show.md +55 -0
  14. package/skills/tux-live-create.md +50 -0
  15. package/skills/tux-live-incorporate.md +62 -0
  16. package/skills/tux-live-install.md +63 -0
  17. package/skills/tux-live-start-review.md +47 -0
  18. package/skills/tux-live-stop-review.md +46 -0
  19. package/src/canonical.js +28 -0
  20. package/src/cli.js +229 -0
  21. package/src/config.js +72 -0
  22. package/src/design.js +140 -0
  23. package/src/errors.js +28 -0
  24. package/src/feedback.js +289 -0
  25. package/src/ids.js +57 -0
  26. package/src/index.js +17 -0
  27. package/src/live.js +261 -0
  28. package/src/schema.js +77 -0
  29. package/src/serve-main.js +42 -0
  30. package/src/server.js +347 -0
  31. package/src/store.js +72 -0
  32. package/src/templates.js +44 -0
  33. package/templates/angular/README.md +19 -0
  34. package/templates/angular/angular.json +49 -0
  35. package/templates/angular/package.json +25 -0
  36. package/templates/angular/src/app/app.component.html +40 -0
  37. package/templates/angular/src/app/app.component.ts +30 -0
  38. package/templates/angular/src/index.html +11 -0
  39. package/templates/angular/src/main.ts +4 -0
  40. package/templates/angular/src/styles.css +11 -0
  41. package/templates/angular/tsconfig.app.json +8 -0
  42. package/templates/angular/tsconfig.json +11 -0
  43. package/templates/react/README.md +18 -0
  44. package/templates/react/index.html +12 -0
  45. package/templates/react/package.json +18 -0
  46. package/templates/react/src/App.jsx +80 -0
  47. package/templates/react/src/main.jsx +6 -0
  48. package/templates/react/src/styles.css +11 -0
  49. package/templates/vanilla/README.md +22 -0
  50. package/templates/vanilla/index.html +18 -0
  51. package/templates/vanilla/package.json +6 -0
  52. package/templates/vanilla/src/app.js +79 -0
  53. package/templates/vanilla/src/styles.css +11 -0
  54. package/templates/vue/README.md +18 -0
  55. package/templates/vue/index.html +12 -0
  56. package/templates/vue/package.json +17 -0
  57. package/templates/vue/src/App.vue +61 -0
  58. package/templates/vue/src/main.js +5 -0
  59. package/templates/vue/src/styles.css +11 -0
  60. package/templates/vue/vite.config.js +6 -0
@@ -0,0 +1,40 @@
1
+ <nav data-tux-component="MainNav">
2
+ <a href="/" (click)="navigate('/', $event)">Home</a>
3
+ <a href="/products" (click)="navigate('/products', $event)">Products</a>
4
+ <a href="/checkout" (click)="navigate('/checkout', $event)">Checkout</a>
5
+ </nav>
6
+ <main>
7
+ @if (route === '/products') {
8
+ <h1>Products</h1>
9
+ <div data-tux-component="ProductTabs" [attr.data-tux-state-tab]="tab">
10
+ <div role="tablist">
11
+ @for (t of tabs; track t) {
12
+ <button role="tab" [attr.aria-selected]="tab === t" [attr.data-tab]="t" (click)="tab = t">{{ t }}</button>
13
+ }
14
+ </div>
15
+ <ul data-tux-component="ProductList" [style.display]="tab === 'featured' ? 'none' : ''">
16
+ @for (p of products; track p.id) {
17
+ <li data-tux-component="ProductCard" [attr.data-tux-instance]="p.id">
18
+ <span [attr.data-tux-id]="'product-name-' + p.id">{{ p.name }}</span>
19
+ <span [attr.data-tux-id]="'product-price-' + p.id" data-tux-component="PriceLabel">{{ p.price }}</span>
20
+ </li>
21
+ }
22
+ </ul>
23
+ </div>
24
+ } @else if (route === '/checkout') {
25
+ <h1>Checkout</h1>
26
+ <div data-tux-component="CheckoutActions">
27
+ <button data-tux-id="coupon-open" (click)="modalOpen = true">Add coupon</button>
28
+ <button data-tux-id="checkout-submit" data-tux-component="SubmitButton">Complete purchase</button>
29
+ </div>
30
+ @if (modalOpen) {
31
+ <div role="dialog" aria-modal="true" aria-label="Coupon" data-tux-component="CouponModal">
32
+ <p>Add a coupon code.</p>
33
+ <input data-tux-id="coupon-input" placeholder="Code" />
34
+ <button data-tux-id="coupon-apply" (click)="modalOpen = false">Apply</button>
35
+ </div>
36
+ }
37
+ } @else {
38
+ <h1>Home</h1>
39
+ }
40
+ </main>
@@ -0,0 +1,30 @@
1
+ import { Component } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'app-root',
5
+ standalone: true,
6
+ templateUrl: './app.component.html',
7
+ })
8
+ export class AppComponent {
9
+ readonly tabs = ['all', 'featured'];
10
+ readonly products = [
11
+ { id: 'p1', name: 'Trail Shoes', price: '119 €' },
12
+ { id: 'p2', name: 'Rain Jacket', price: '89 €' },
13
+ ];
14
+
15
+ route = window.location.pathname;
16
+ tab = 'all';
17
+ modalOpen = false;
18
+
19
+ constructor() {
20
+ window.onpopstate = () => {
21
+ this.route = window.location.pathname;
22
+ };
23
+ }
24
+
25
+ navigate(href: string, event: Event): void {
26
+ event.preventDefault();
27
+ window.history.pushState(null, '', href);
28
+ this.route = href;
29
+ }
30
+ }
@@ -0,0 +1,11 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Checkout Design</title>
7
+ </head>
8
+ <body>
9
+ <app-root></app-root>
10
+ </body>
11
+ </html>
@@ -0,0 +1,4 @@
1
+ import { bootstrapApplication } from '@angular/platform-browser';
2
+ import { AppComponent } from './app/app.component';
3
+
4
+ bootstrapApplication(AppComponent).catch((err) => console.error(err));
@@ -0,0 +1,11 @@
1
+ /* Clickable design styles (minimal). */
2
+ :root { font-family: system-ui, sans-serif; }
3
+ body { margin: 0; color: #1e1e1e; }
4
+ nav { display: flex; gap: 16px; padding: 12px 20px; border-bottom: 1px solid rgba(0,0,0,.12); }
5
+ main { padding: 20px; }
6
+ [role=tablist] { display: flex; gap: 8px; margin-bottom: 12px; }
7
+ [role=tab] { padding: 6px 14px; border: 1px solid rgba(0,0,0,.12); background: #fff; border-radius: 999px; cursor: pointer; }
8
+ [role=tab][aria-selected=true] { background: #569cd6; color: #fff; border-color: #569cd6; }
9
+ li { margin: 8px 0; display: flex; gap: 12px; }
10
+ [role=dialog] { border: 1px solid rgba(0,0,0,.12); border-radius: 12px; padding: 20px; }
11
+ button { font: inherit; }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./out-tsc/app"
5
+ },
6
+ "files": ["src/main.ts"],
7
+ "include": ["src/**/*.d.ts"]
8
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "./dist/out-tsc",
4
+ "strict": true,
5
+ "esModuleInterop": true,
6
+ "moduleResolution": "bundler",
7
+ "target": "ES2022",
8
+ "module": "ES2022",
9
+ "lib": ["ES2022", "dom"]
10
+ }
11
+ }
@@ -0,0 +1,18 @@
1
+ # checkout (react design)
2
+
3
+ This design itself uses npm. Choose one TUX CLI: Node users run
4
+ `npm install --save-dev tux-review` then `npx tux …`; Python users install
5
+ `tux-review` with `pipx` or `pip` then run `tux …`. The TUX commands below are
6
+ otherwise identical.
7
+
8
+ ```bash
9
+ npm install
10
+
11
+ # Node.js/npm TUX variant — serves the design with TUX review
12
+ npx tux design start-review --foreground --dir .
13
+
14
+ # Python/PyPI TUX variant — same command after Python installation
15
+ tux design start-review --foreground --dir .
16
+
17
+ # Alternative for either variant: npm run dev + <tux command> live start-review --url http://localhost:5173
18
+ ```
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Checkout Design</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.jsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "tux-design",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --port 5173",
8
+ "build": "vite build"
9
+ },
10
+ "dependencies": {
11
+ "react": "^18.3.1",
12
+ "react-dom": "^18.3.1"
13
+ },
14
+ "devDependencies": {
15
+ "@vitejs/plugin-react": "^4.3.4",
16
+ "vite": "^6.0.0"
17
+ }
18
+ }
@@ -0,0 +1,80 @@
1
+ import React, { useState } from 'react';
2
+
3
+ const PRODUCTS = [
4
+ { id: 'p1', name: 'Trail Shoes', price: '119 €' },
5
+ { id: 'p2', name: 'Rain Jacket', price: '89 €' },
6
+ ];
7
+
8
+ function ProductCard({ product }) {
9
+ return (
10
+ <li data-tux-component="ProductCard" data-tux-instance={product.id}>
11
+ <span data-tux-id={`product-name-${product.id}`}>{product.name}</span>
12
+ <span data-tux-id={`product-price-${product.id}`} data-tux-component="PriceLabel">
13
+ {product.price}
14
+ </span>
15
+ </li>
16
+ );
17
+ }
18
+
19
+ function CouponModal({ open, onClose }) {
20
+ if (!open) return null;
21
+ return (
22
+ <div role="dialog" aria-modal="true" aria-label="Coupon" data-tux-component="CouponModal">
23
+ <p>Add a coupon code.</p>
24
+ <input data-tux-id="coupon-input" placeholder="Code" />
25
+ <button data-tux-id="coupon-apply" onClick={onClose}>Apply</button>
26
+ </div>
27
+ );
28
+ }
29
+
30
+ export default function App() {
31
+ const [route, setRoute] = useState(window.location.pathname);
32
+ const [tab, setTab] = useState('all');
33
+ const [modalOpen, setModalOpen] = useState(false);
34
+
35
+ window.onpopstate = () => setRoute(window.location.pathname);
36
+ const navigate = (href) => (e) => {
37
+ e.preventDefault();
38
+ window.history.pushState(null, '', href);
39
+ setRoute(href);
40
+ };
41
+
42
+ return (
43
+ <>
44
+ <nav data-tux-component="MainNav">
45
+ <a href="/" onClick={navigate('/')}>Home</a>
46
+ <a href="/products" onClick={navigate('/products')}>Products</a>
47
+ <a href="/checkout" onClick={navigate('/checkout')}>Checkout</a>
48
+ </nav>
49
+ <main>
50
+ {route === '/products' && (
51
+ <>
52
+ <h1>Products</h1>
53
+ <div data-tux-component="ProductTabs" data-tux-state-tab={tab}>
54
+ <div role="tablist">
55
+ {['all', 'featured'].map((t) => (
56
+ <button key={t} role="tab" aria-selected={tab === t} data-tab={t}
57
+ onClick={() => setTab(t)}>{t}</button>
58
+ ))}
59
+ </div>
60
+ <ul data-tux-component="ProductList" style={tab === 'featured' ? { display: 'none' } : {}}>
61
+ {PRODUCTS.map((p) => <ProductCard key={p.id} product={p} />)}
62
+ </ul>
63
+ </div>
64
+ </>
65
+ )}
66
+ {route === '/checkout' && (
67
+ <>
68
+ <h1>Checkout</h1>
69
+ <div data-tux-component="CheckoutActions">
70
+ <button data-tux-id="coupon-open" onClick={() => setModalOpen(true)}>Add coupon</button>
71
+ <button data-tux-id="checkout-submit" data-tux-component="SubmitButton">Complete purchase</button>
72
+ </div>
73
+ <CouponModal open={modalOpen} onClose={() => setModalOpen(false)} />
74
+ </>
75
+ )}
76
+ {route === '/' && <h1>Home</h1>}
77
+ </main>
78
+ </>
79
+ );
80
+ }
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import App from './App.jsx';
4
+ import './styles.css';
5
+
6
+ createRoot(document.getElementById('root')).render(<App />);
@@ -0,0 +1,11 @@
1
+ /* Clickable design styles (minimal). */
2
+ :root { font-family: system-ui, sans-serif; }
3
+ body { margin: 0; color: #1e1e1e; }
4
+ nav { display: flex; gap: 16px; padding: 12px 20px; border-bottom: 1px solid rgba(0,0,0,.12); }
5
+ main { padding: 20px; }
6
+ [role=tablist] { display: flex; gap: 8px; margin-bottom: 12px; }
7
+ [role=tab] { padding: 6px 14px; border: 1px solid rgba(0,0,0,.12); background: #fff; border-radius: 999px; cursor: pointer; }
8
+ [role=tab][aria-selected=true] { background: #569cd6; color: #fff; border-color: #569cd6; }
9
+ li { margin: 8px 0; display: flex; gap: 12px; }
10
+ [role=dialog] { border: 1px solid rgba(0,0,0,.12); border-radius: 12px; padding: 20px; }
11
+ button { font: inherit; }
@@ -0,0 +1,22 @@
1
+ # checkout (vanilla design)
2
+
3
+ ## Choose the TUX CLI
4
+
5
+ Choose one variant; the commands are identical. Node.js users install TUX in
6
+ this project with `npm install --save-dev tux-review` and run
7
+ `npx tux …`. Python users install `tux-review` with `pipx install tux-review`
8
+ (or `python -m pip install tux-review`) and run `tux …`. You do not need both.
9
+
10
+ ## Start the review
11
+
12
+ From the project root (or this directory):
13
+
14
+ ```bash
15
+ # Node.js/npm variant
16
+ npx tux design start-review --foreground --dir .
17
+
18
+ # Python/PyPI variant
19
+ tux design start-review --foreground --dir .
20
+ ```
21
+
22
+ Routes: `/`, `/products` (tabs), `/checkout` (modal).
@@ -0,0 +1,18 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Checkout Design</title>
7
+ <link rel="stylesheet" href="src/styles.css">
8
+ </head>
9
+ <body>
10
+ <nav data-tux-component="MainNav">
11
+ <a href="/" data-tux-id="nav-home">Home</a>
12
+ <a href="/products" data-tux-id="nav-products">Products</a>
13
+ <a href="/checkout" data-tux-id="nav-checkout">Checkout</a>
14
+ </nav>
15
+ <main id="view"></main>
16
+ <script type="module" src="src/app.js"></script>
17
+ </body>
18
+ </html>
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "tux-design",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "Clickable design (vanilla) — review with: tux design start-review"
6
+ }
@@ -0,0 +1,79 @@
1
+ // Clickable design (vanilla SPA with History-API routing).
2
+ const view = document.getElementById('view');
3
+
4
+ const PRODUCTS = [
5
+ { id: 'p1', name: 'Trail Shoes', price: '119 €' },
6
+ { id: 'p2', name: 'Rain Jacket', price: '89 €' },
7
+ ];
8
+
9
+ function productsPage() {
10
+ return `
11
+ <h1>Products</h1>
12
+ <div data-tux-component="ProductTabs" data-tux-state-tab="all">
13
+ <div role="tablist">
14
+ <button role="tab" aria-selected="true" data-tab="all">All</button>
15
+ <button role="tab" aria-selected="false" data-tab="featured">Featured</button>
16
+ </div>
17
+ <ul id="product-list" data-tux-component="ProductList">
18
+ ${PRODUCTS.map((p) => `
19
+ <li data-tux-component="ProductCard" data-tux-instance="${p.id}">
20
+ <span data-tux-id="product-name-${p.id}">${p.name}</span>
21
+ <span data-tux-id="product-price-${p.id}" data-tux-component="PriceLabel">${p.price}</span>
22
+ </li>`).join('')}
23
+ </ul>
24
+ </div>`;
25
+ }
26
+
27
+ function checkoutPage() {
28
+ return `
29
+ <h1>Checkout</h1>
30
+ <div data-tux-component="CheckoutActions">
31
+ <button data-tux-id="coupon-open">Add coupon</button>
32
+ <button data-tux-id="checkout-submit" data-tux-component="SubmitButton">Complete purchase</button>
33
+ </div>
34
+ <dialog id="coupon-modal" data-tux-component="CouponModal">
35
+ <p>Add a coupon code.</p>
36
+ <input data-tux-id="coupon-input" placeholder="Code">
37
+ <button data-tux-id="coupon-apply">Apply</button>
38
+ </dialog>`;
39
+ }
40
+
41
+ function homePage() {
42
+ return '<h1>Home</h1><p>Clickable TUX design.</p>';
43
+ }
44
+
45
+ function render() {
46
+ const route = location.pathname;
47
+ if (route === '/products') view.innerHTML = productsPage();
48
+ else if (route === '/checkout') view.innerHTML = checkoutPage();
49
+ else view.innerHTML = homePage();
50
+ bind();
51
+ }
52
+
53
+ function bind() {
54
+ view.querySelectorAll('[data-tab]').forEach((btn) => {
55
+ btn.addEventListener('click', () => {
56
+ btn.closest('[role=tablist]').querySelectorAll('[role=tab]').forEach((t) => {
57
+ t.setAttribute('aria-selected', String(t === btn));
58
+ });
59
+ const wrap = btn.closest('[data-tux-component]');
60
+ wrap.dataset.tuxStateTab = btn.dataset.tab;
61
+ const list = document.getElementById('product-list');
62
+ list.style.display = btn.dataset.tab === 'featured' ? 'none' : '';
63
+ });
64
+ });
65
+ const open = document.querySelector('[data-tux-id=coupon-open]');
66
+ const modal = document.getElementById('coupon-modal');
67
+ if (open && modal) open.addEventListener('click', () => modal.showModal());
68
+ }
69
+
70
+ document.addEventListener('click', (e) => {
71
+ const a = e.target.closest('a[href^="/"]');
72
+ if (!a) return;
73
+ e.preventDefault();
74
+ history.pushState(null, '', a.getAttribute('href'));
75
+ render();
76
+ });
77
+
78
+ window.addEventListener('popstate', render);
79
+ render();
@@ -0,0 +1,11 @@
1
+ /* Clickable design styles (minimal). */
2
+ :root { font-family: system-ui, sans-serif; }
3
+ body { margin: 0; color: #1e1e1e; }
4
+ nav { display: flex; gap: 16px; padding: 12px 20px; border-bottom: 1px solid rgba(0,0,0,.12); }
5
+ main { padding: 20px; }
6
+ [role=tablist] { display: flex; gap: 8px; margin-bottom: 12px; }
7
+ [role=tab] { padding: 6px 14px; border: 1px solid rgba(0,0,0,.12); background: #fff; border-radius: 999px; cursor: pointer; }
8
+ [role=tab][aria-selected=true] { background: #569cd6; color: #fff; border-color: #569cd6; }
9
+ li { margin: 8px 0; display: flex; gap: 12px; }
10
+ dialog { border: 1px solid rgba(0,0,0,.12); border-radius: 12px; padding: 20px; }
11
+ button { font: inherit; }
@@ -0,0 +1,18 @@
1
+ # checkout (vue design)
2
+
3
+ This design itself uses npm. Choose one TUX CLI: Node users run
4
+ `npm install --save-dev tux-review` then `npx tux …`; Python users install
5
+ `tux-review` with `pipx` or `pip` then run `tux …`. The TUX commands below are
6
+ otherwise identical.
7
+
8
+ ```bash
9
+ npm install
10
+
11
+ # Node.js/npm TUX variant — serves the design with TUX review
12
+ npx tux design start-review --foreground --dir .
13
+
14
+ # Python/PyPI TUX variant — same command after Python installation
15
+ tux design start-review --foreground --dir .
16
+
17
+ # Alternative for either variant: npm run dev + <tux command> live start-review --url http://localhost:5173
18
+ ```
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Checkout Design</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.js"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "tux-design",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --port 5173",
8
+ "build": "vite build"
9
+ },
10
+ "dependencies": {
11
+ "vue": "^3.5.13"
12
+ },
13
+ "devDependencies": {
14
+ "@vitejs/plugin-vue": "^5.2.1",
15
+ "vite": "^6.0.0"
16
+ }
17
+ }
@@ -0,0 +1,61 @@
1
+ <script setup>
2
+ import { ref } from 'vue';
3
+
4
+ const PRODUCTS = [
5
+ { id: 'p1', name: 'Trail Shoes', price: '119 €' },
6
+ { id: 'p2', name: 'Rain Jacket', price: '89 €' },
7
+ ];
8
+
9
+ const route = ref(window.location.pathname);
10
+ const tab = ref('all');
11
+ const modalOpen = ref(false);
12
+
13
+ window.onpopstate = () => {
14
+ route.value = window.location.pathname;
15
+ };
16
+ const navigate = (href) => (event) => {
17
+ event.preventDefault();
18
+ window.history.pushState(null, '', href);
19
+ route.value = href;
20
+ };
21
+ </script>
22
+
23
+ <template>
24
+ <nav data-tux-component="MainNav">
25
+ <a href="/" @click="navigate('/')">Home</a>
26
+ <a href="/products" @click="navigate('/products')">Products</a>
27
+ <a href="/checkout" @click="navigate('/checkout')">Checkout</a>
28
+ </nav>
29
+ <main>
30
+ <template v-if="route === '/products'">
31
+ <h1>Products</h1>
32
+ <div data-tux-component="ProductTabs" :data-tux-state-tab="tab">
33
+ <div role="tablist">
34
+ <button v-for="t in ['all', 'featured']" :key="t" role="tab"
35
+ :aria-selected="tab === t" :data-tab="t" @click="tab = t">{{ t }}</button>
36
+ </div>
37
+ <ul data-tux-component="ProductList" :style="tab === 'featured' ? { display: 'none' } : {}">
38
+ <li v-for="p in PRODUCTS" :key="p.id" data-tux-component="ProductCard" :data-tux-instance="p.id">
39
+ <span :data-tux-id="`product-name-${p.id}`">{{ p.name }}</span>
40
+ <span :data-tux-id="`product-price-${p.id}`" data-tux-component="PriceLabel">{{ p.price }}</span>
41
+ </li>
42
+ </ul>
43
+ </div>
44
+ </template>
45
+ <template v-else-if="route === '/checkout'">
46
+ <h1>Checkout</h1>
47
+ <div data-tux-component="CheckoutActions">
48
+ <button data-tux-id="coupon-open" @click="modalOpen = true">Add coupon</button>
49
+ <button data-tux-id="checkout-submit" data-tux-component="SubmitButton">Complete purchase</button>
50
+ </div>
51
+ <div v-if="modalOpen" role="dialog" aria-modal="true" aria-label="Coupon" data-tux-component="CouponModal">
52
+ <p>Add a coupon code.</p>
53
+ <input data-tux-id="coupon-input" placeholder="Code" />
54
+ <button data-tux-id="coupon-apply" @click="modalOpen = false">Apply</button>
55
+ </div>
56
+ </template>
57
+ <template v-else>
58
+ <h1>Home</h1>
59
+ </template>
60
+ </main>
61
+ </template>
@@ -0,0 +1,5 @@
1
+ import { createApp } from 'vue';
2
+ import App from './App.vue';
3
+ import './styles.css';
4
+
5
+ createApp(App).mount('#app');
@@ -0,0 +1,11 @@
1
+ /* Clickable design styles (minimal). */
2
+ :root { font-family: system-ui, sans-serif; }
3
+ body { margin: 0; color: #1e1e1e; }
4
+ nav { display: flex; gap: 16px; padding: 12px 20px; border-bottom: 1px solid rgba(0,0,0,.12); }
5
+ main { padding: 20px; }
6
+ [role=tablist] { display: flex; gap: 8px; margin-bottom: 12px; }
7
+ [role=tab] { padding: 6px 14px; border: 1px solid rgba(0,0,0,.12); background: #fff; border-radius: 999px; cursor: pointer; }
8
+ [role=tab][aria-selected=true] { background: #569cd6; color: #fff; border-color: #569cd6; }
9
+ li { margin: 8px 0; display: flex; gap: 12px; }
10
+ [role=dialog] { border: 1px solid rgba(0,0,0,.12); border-radius: 12px; padding: 20px; }
11
+ button { font: inherit; }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from 'vite';
2
+ import vue from '@vitejs/plugin-vue';
3
+
4
+ export default defineConfig({
5
+ plugins: [vue()],
6
+ });