Skip to content

Commit de57e9c

Browse files
committed
feat(v1): add playground
1 parent 46a02e4 commit de57e9c

7 files changed

Lines changed: 498 additions & 2 deletions

File tree

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"version": 1,
33
"lastRunAtMs": 0,
4-
"turnsSinceLastRun": 4,
4+
"turnsSinceLastRun": 5,
55
"lastTranscriptMtimeMs": null,
6-
"lastProcessedGenerationId": "81c51e60-1dca-43e9-84bb-4bd52d966915",
6+
"lastProcessedGenerationId": "d33af693-7bbd-44ea-97e3-193e2af91516",
77
"trialStartedAtMs": null
88
}

β€Žpackage.jsonβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
],
3535
"scripts": {
3636
"dev:playground": "vite --mode development:playground",
37+
"dev:v1-playground": "vite --mode development:v1-playground",
3738
"test": "vitest --coverage",
3839
"build": "run-p type-check \"build-only {@}\" --",
3940
"build-only": "vite build",
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
<script setup lang="ts">
2+
import type { SelectModelValue } from "@v1/types/model";
3+
import type { SelectOption } from "@v1/types/option";
4+
5+
import { Select } from "@v1/index";
6+
import { FlaskConical, Github } from "lucide-vue-next";
7+
import { computed, ref, watch } from "vue";
8+
9+
import DemoHeader from "../playground/components/DemoHeader.vue";
10+
import DemoPanel from "../playground/components/DemoPanel.vue";
11+
import DemoPill from "../playground/components/DemoPill.vue";
12+
import DemoSection from "../playground/components/DemoSection.vue";
13+
import DemoValue from "../playground/components/DemoValue.vue";
14+
15+
type BooleanControl = {
16+
key: "multiple" | "searchable" | "clearable" | "disabled" | "loading";
17+
label: string;
18+
description: string;
19+
};
20+
21+
const booleanControls: BooleanControl[] = [
22+
{ key: "multiple", label: "Multiple", description: "Allow selecting more than one option." },
23+
{ key: "searchable", label: "Searchable", description: "Filter options with a search input." },
24+
{ key: "clearable", label: "Clearable", description: "Show a clear button when a value is set." },
25+
{ key: "disabled", label: "Disabled", description: "Prevent all interaction." },
26+
{ key: "loading", label: "Loading", description: "Show a loading indicator on the trigger." },
27+
];
28+
29+
const options = ref<SelectOption<string>[]>([
30+
{ label: "JavaScript", value: "js" },
31+
{ label: "TypeScript", value: "ts" },
32+
{ label: "Vue", value: "vue" },
33+
{ label: "React", value: "react", disabled: true },
34+
{ label: "Svelte", value: "svelte" },
35+
]);
36+
37+
const model = ref<SelectModelValue<string>>(null);
38+
const multiple = ref(false);
39+
const searchable = ref(false);
40+
const clearable = ref(false);
41+
const disabled = ref(false);
42+
const loading = ref(false);
43+
const placeholder = ref("Pick a framework");
44+
45+
const modelDisplay = computed(() => JSON.stringify(model.value, null, 2));
46+
47+
const controlValues = computed(() => ({
48+
multiple: multiple.value,
49+
searchable: searchable.value,
50+
clearable: clearable.value,
51+
disabled: disabled.value,
52+
loading: loading.value,
53+
}));
54+
55+
const emptyModel = (): SelectModelValue<string> => (multiple.value ? [] : null);
56+
57+
watch(multiple, () => {
58+
model.value = emptyModel();
59+
});
60+
61+
function toggleBooleanControl(key: BooleanControl["key"]) {
62+
const refs = { multiple, searchable, clearable, disabled, loading } as const;
63+
refs[key].value = !refs[key].value;
64+
}
65+
66+
function toggleOptionDisabled(value: string) {
67+
const option = options.value.find((item) => item.value === value);
68+
69+
if (!option) {
70+
return;
71+
}
72+
73+
option.disabled = !option.disabled;
74+
}
75+
</script>
76+
77+
<template>
78+
<div class="min-h-screen">
79+
<header class="border-b border-neutral-800/80 bg-[var(--playground-panel)]/80">
80+
<div class="mx-auto flex w-full max-w-6xl flex-col gap-4 px-6 py-6 lg:flex-row lg:items-center lg:justify-between">
81+
<div class="space-y-2">
82+
<div class="flex items-center gap-2">
83+
<FlaskConical class="h-5 w-5 text-green-400/80" />
84+
<h1 class="text-2xl font-semibold text-neutral-100">
85+
v1 Dev Playground
86+
</h1>
87+
<DemoPill>src-v1</DemoPill>
88+
</div>
89+
90+
<p class="text-xs font-semibold text-neutral-500">
91+
Live preview with HMR β€” edits to <code class="font-mono text-neutral-400">src-v1/</code> reload instantly.
92+
</p>
93+
</div>
94+
95+
<div class="flex flex-wrap items-center gap-3 text-sm text-neutral-400">
96+
<a
97+
class="inline-flex items-center gap-2 rounded-full border border-neutral-800/80 px-3 py-1.5 transition hover:border-neutral-700 hover:text-neutral-200"
98+
href="https://github.com/TotomInc/vue3-select-component"
99+
rel="noreferrer"
100+
target="_blank"
101+
>
102+
<Github class="h-4 w-4" />
103+
GitHub
104+
</a>
105+
</div>
106+
</div>
107+
</header>
108+
109+
<div class="mx-auto flex w-full max-w-6xl flex-col gap-8 px-6 py-8 lg:flex-row">
110+
<aside class="flex w-full flex-col gap-6 lg:w-72 lg:shrink-0">
111+
<DemoSection
112+
title="Props"
113+
description="Toggle assembled Select props in real time."
114+
>
115+
<div class="flex flex-col gap-2">
116+
<label
117+
v-for="control in booleanControls"
118+
:key="control.key"
119+
class="flex cursor-pointer items-start gap-3 rounded-lg border border-neutral-800/80 bg-[var(--playground-panel)] px-3 py-2.5 transition hover:border-neutral-700"
120+
>
121+
<input
122+
type="checkbox"
123+
class="mt-0.5 accent-green-500"
124+
:checked="controlValues[control.key]"
125+
@change="toggleBooleanControl(control.key)"
126+
>
127+
<span class="grid gap-0.5">
128+
<span class="text-sm font-medium text-neutral-200">{{ control.label }}</span>
129+
<span class="text-xs text-neutral-500">{{ control.description }}</span>
130+
</span>
131+
</label>
132+
</div>
133+
</DemoSection>
134+
135+
<DemoSection
136+
title="Placeholder"
137+
description="Customize the empty-state label."
138+
>
139+
<input
140+
v-model="placeholder"
141+
type="text"
142+
class="w-full rounded-lg border border-neutral-800/80 bg-[var(--playground-panel)] px-3 py-2 text-sm text-neutral-200 outline-none transition focus:border-green-500/50"
143+
>
144+
</DemoSection>
145+
146+
<DemoSection
147+
title="Options"
148+
description="Toggle disabled state on individual options."
149+
>
150+
<div class="flex flex-col gap-1.5">
151+
<label
152+
v-for="option in options"
153+
:key="option.value"
154+
class="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm transition hover:bg-neutral-800/50"
155+
>
156+
<input
157+
type="checkbox"
158+
class="accent-green-500"
159+
:checked="option.disabled"
160+
@change="toggleOptionDisabled(option.value)"
161+
>
162+
<span :class="option.disabled ? 'text-neutral-500 line-through' : 'text-neutral-300'">
163+
{{ option.label }}
164+
</span>
165+
</label>
166+
</div>
167+
</DemoSection>
168+
</aside>
169+
170+
<main class="flex min-w-0 flex-1 flex-col gap-6">
171+
<DemoHeader
172+
eyebrow="Assembled Select"
173+
title="Interactive preview"
174+
description="The batteries-included v1 Select composes primitives into a ready-to-use control. Use the sidebar to explore prop combinations."
175+
/>
176+
177+
<DemoPanel accent>
178+
<Select
179+
v-model="model"
180+
:options="options"
181+
:multiple="multiple"
182+
:searchable="searchable"
183+
:clearable="clearable"
184+
:disabled="disabled"
185+
:loading="loading"
186+
:placeholder="placeholder"
187+
/>
188+
189+
<DemoValue label="modelValue">
190+
<code class="font-mono text-xs">{{ modelDisplay }}</code>
191+
</DemoValue>
192+
</DemoPanel>
193+
194+
<DemoSection title="Quick reference">
195+
<div class="grid gap-3 text-sm text-neutral-400">
196+
<p>
197+
Run <code class="rounded bg-neutral-800 px-1.5 py-0.5 font-mono text-xs text-neutral-300">npm run dev:v1-playground</code>
198+
to start this environment.
199+
</p>
200+
<p>
201+
Import path: <code class="rounded bg-neutral-800 px-1.5 py-0.5 font-mono text-xs text-neutral-300">@v1/assembled/Select.vue</code>
202+
or primitives from <code class="rounded bg-neutral-800 px-1.5 py-0.5 font-mono text-xs text-neutral-300">@v1/primitives</code>.
203+
</p>
204+
</div>
205+
</DemoSection>
206+
</main>
207+
</div>
208+
</div>
209+
</template>

β€Žplayground-v1/index.htmlβ€Ž

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<link rel="icon" href="/favicon.ico">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Vue 3 Select β€” v1 Dev Playground</title>
8+
</head>
9+
10+
<body>
11+
<div id="app"></div>
12+
<script type="module" src="/main.ts"></script>
13+
</body>
14+
</html>

β€Žplayground-v1/main.tsβ€Ž

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// <reference types="vite/client" />
2+
3+
import { createApp } from "vue";
4+
5+
import V1Playground from "./V1Playground.vue";
6+
import "./styles.css";
7+
8+
createApp(V1Playground).mount("#app");

0 commit comments

Comments
Β (0)