init commit

This commit is contained in:
2026-05-12 21:33:51 -04:00
commit 204b0760c3
22 changed files with 3589 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
.eslintcache
# Cypress
/cypress/videos/
/cypress/screenshots/
# Vitest
__screenshots__/
# Vite
*.timestamp-*-*.mjs
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"semi": false,
"singleQuote": true
}
+7
View File
@@ -0,0 +1,7 @@
{
"recommendations": [
"Vue.volar",
"vitest.explorer",
"oxc.oxc-vscode"
]
}
+45
View File
@@ -0,0 +1,45 @@
# Mock challenge: BaseButton component
Build a reusable Vue 3 button component suitable for a design system. Focus on API design, accessibility, and Vue 3 best practices.
**Suggested time: 2535 minutes**
---
## Core requirements
- Accept a `variant` prop: `primary`, `secondary`, `danger` — each should have visually distinct styles
- Accept a `size` prop: `sm`, `md`, `lg` — affects padding and font size
- Accept a `disabled` prop — visually and functionally disables the button
- Accept a `loading` prop — shows a loading indicator and prevents interaction
- Use a default `slot` for button label content
- Explicitly declare a `click` emit
---
## Accessibility requirements
- Use a native `<button>` element — not a `<div>`
- When `loading` is true, set `aria-busy="true"` and `aria-disabled="true"`
- When `disabled` is true, use the native `disabled` attribute (not just styling)
- Ensure visible focus styles are not removed — `outline: none` without a replacement is a fail
---
## Bonus points
- Add an optional `icon` slot for a leading icon
- Type all props with TypeScript
- Add a `full-width` prop that makes the button `width: 100%`
- Write at least one Vitest unit test — e.g. that the click event does not fire when disabled
---
## Things they'll be watching for
- Do you use `defineProps` and `defineEmits` correctly with script setup syntax?
- Do you use a `computed` to derive CSS classes from props, or inline ternaries everywhere?
- Do you handle the loading state gracefully — preventing double-clicks, communicating state to screen readers?
- Do you narrate your thinking as you go?
> **Tip:** Before writing any code, spend 12 minutes talking through your approach out loud. Interviewers value reasoning over speed.
+48
View File
@@ -0,0 +1,48 @@
# vue-practice
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Recommended Browser Setup
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
- Firefox:
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
pnpm install
```
### Compile and Hot-Reload for Development
```sh
pnpm dev
```
### Type-Check, Compile and Minify for Production
```sh
pnpm build
```
### Run Unit Tests with [Vitest](https://vitest.dev/)
```sh
pnpm test:unit
```
Vendored
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+39
View File
@@ -0,0 +1,39 @@
{
"name": "vue-practice",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"test:unit": "vitest",
"build-only": "vite build",
"type-check": "vue-tsc --build",
"format": "oxfmt src/"
},
"dependencies": {
"pinia": "^3.0.4",
"vue": "^3.5.32",
"vue-router": "^5.0.4"
},
"devDependencies": {
"@tsconfig/node24": "^24.0.4",
"@types/jsdom": "^28.0.1",
"@types/node": "^24.12.2",
"@vitejs/plugin-vue": "^6.0.6",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.9.1",
"jsdom": "^29.0.2",
"npm-run-all2": "^8.0.4",
"oxfmt": "^0.45.0",
"typescript": "~6.0.0",
"vite": "^8.0.8",
"vite-plugin-vue-devtools": "^8.1.1",
"vitest": "^4.1.4",
"vue-tsc": "^3.2.6"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
}
+3096
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+41
View File
@@ -0,0 +1,41 @@
<template>
<div class="button-group">
<MyButton variant="primary"
size="sm">
Primary Small
</MyButton>
<MyButton variant="secondary"
size="md">
Secondary Medium
</MyButton>
<MyButton variant="danger"
size="lg">
Danger Large
</MyButton>
<MyButton variant="danger"
size="lg"
:loading="true">
Danger Large
</MyButton>
<MyButton variant="danger"
size="lg"
:disabled="true">
Danger Large
</MyButton>
</div>
</template>
<script setup lang="ts">
import MyButton from './components/MyButton.vue';
</script>
<style scoped>
.button-group {
display: flex;
align-items: center;
gap: 1rem;
}
</style>
+11
View File
@@ -0,0 +1,11 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import App from '../App.vue'
describe('App', () => {
it('mounts renders properly', () => {
const wrapper = mount(App)
expect(wrapper.text()).toContain('You did it!')
})
})
+102
View File
@@ -0,0 +1,102 @@
<template>
<button class="btn"
type="button"
:aria-busy="loading"
:class="classes"
:disabled="disabled || loading">
<template v-if="loading">
Loading...
</template>
<template v-else>
<span v-if="icon">
<!-- icon placeholder -->
</span>
<slot></slot>
</template>
</button>
</template>
<script setup lang="ts">
import { computed } from 'vue';
interface ButtonProps {
disabled?: boolean
icon?: string
loading?: boolean
size: 'sm' | 'md' | 'lg'
variant: 'primary' | 'secondary' | 'danger'
}
const {
variant = "primary",
size = "md",
disabled = false,
loading = false
} = defineProps<ButtonProps>()
const emit = defineEmits(['click'])
const classes = computed(() => [
`btn--${variant}`,
`btn--${size}`
])
</script>
<style scoped>
.btn {
--background-color: navy;
--border-radius: 0.25rem;
--cursor-type: pointer;
--text-color: white;
--text-size: 1rem;
--horizontal-padding: 0.75rem;
--vertical-padding: 0.5rem;
background: var(--background-color);
border: 1px solid transparent;
border-radius: var(--border-radius);
color: var(--text-color);
cursor: var(--cursor-type);
font-size: var(--text-size);
padding-block: var(--vertical-padding);
padding-inline: var(--horizontal-padding);
&:focus-visible {
outline: 2px dashed CanvasText;
outline-offset: 2px;
}
&.btn--secondary {
--background-color: green;
}
&.btn--danger {
--background-color: red;
}
&.btn--sm {
--border-radius: 0.15rem;
--text-size: 0.825rem;
--vertical-padding: 0.25rem;
--horizontal-padding: 0.5rem;
}
&.btn--lg {
--text-size: 1.25rem;
--vertical-padding: 0.75rem;
--horizontal-padding: 1rem;
--border-radius: 0.5rem;
}
&[aria-busy="true"] {
opacity: 0.5;
}
&[disabled] {
--background-color: gray;
--cursor-type: not-allowed;
--text-color: black;
}
}
</style>
+12
View File
@@ -0,0 +1,12 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')
+8
View File
@@ -0,0 +1,8 @@
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [],
})
export default router
+12
View File
@@ -0,0 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
+18
View File
@@ -0,0 +1,18 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
// Extra safety for array and object lookups, but may have false positives.
"noUncheckedIndexedAccess": true,
// Path mapping for cleaner imports.
"paths": {
"@/*": ["./src/*"]
},
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
// Specified here to keep it out of the root directory.
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo"
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.vitest.json"
}
]
}
+27
View File
@@ -0,0 +1,27 @@
// TSConfig for modules that run in Node.js environment via either transpilation or type-stripping.
{
"extends": "@tsconfig/node24/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"playwright.config.*",
"eslint.config.*"
],
"compilerOptions": {
// Most tools use transpilation instead of Node.js's native type-stripping.
// Bundler mode provides a smoother developer experience.
"module": "preserve",
"moduleResolution": "bundler",
// Include Node.js types and avoid accidentally including other `@types/*` packages.
"types": ["node"],
// Disable emitting output during `vue-tsc --build`, which is used for type-checking only.
"noEmit": true,
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
// Specified here to keep it out of the root directory.
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo"
}
}
+19
View File
@@ -0,0 +1,19 @@
{
"extends": "./tsconfig.app.json",
// Override to include only test files and clear exclusions.
// Application code imported in tests is automatically included via module resolution.
"include": ["src/**/__tests__/*", "env.d.ts"],
"exclude": [],
"compilerOptions": {
// Vitest runs in a different environment than the application code.
// Adjust lib and types accordingly.
"lib": [],
"types": ["node", "jsdom"],
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
// Specified here to keep it out of the root directory.
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo"
}
}
+18
View File
@@ -0,0 +1,18 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})
+14
View File
@@ -0,0 +1,14 @@
import { fileURLToPath } from 'node:url'
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
import viteConfig from './vite.config'
export default mergeConfig(
viteConfig,
defineConfig({
test: {
environment: 'jsdom',
exclude: [...configDefaults.exclude, 'e2e/**'],
root: fileURLToPath(new URL('./', import.meta.url)),
},
}),
)