mirror of
https://github.com/teamhanko/hanko.git
synced 2025-11-01 22:28:27 +08:00
chore: moved exampled to frontend
This commit is contained in:
1
frontend/examples/vue/.dockerignore
Normal file
1
frontend/examples/vue/.dockerignore
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
||||
2
frontend/examples/vue/.env
Normal file
2
frontend/examples/vue/.env
Normal file
@ -0,0 +1,2 @@
|
||||
VITE_HANKO_API=http://localhost:8000
|
||||
VITE_TODO_API=http://localhost:8002
|
||||
15
frontend/examples/vue/.eslintrc.cjs
Normal file
15
frontend/examples/vue/.eslintrc.cjs
Normal file
@ -0,0 +1,15 @@
|
||||
/* eslint-env node */
|
||||
require("@rushstack/eslint-patch/modern-module-resolution");
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended",
|
||||
"@vue/eslint-config-typescript",
|
||||
"@vue/eslint-config-prettier",
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
},
|
||||
};
|
||||
28
frontend/examples/vue/.gitignore
vendored
Normal file
28
frontend/examples/vue/.gitignore
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
# 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
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
1
frontend/examples/vue/.prettierrc.json
Normal file
1
frontend/examples/vue/.prettierrc.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
3
frontend/examples/vue/.vscode/extensions.json
vendored
Normal file
3
frontend/examples/vue/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||
}
|
||||
19
frontend/examples/vue/Dockerfile
Normal file
19
frontend/examples/vue/Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
# pull official base image
|
||||
FROM node:16-alpine
|
||||
|
||||
# set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# add `/app/node_modules/.bin` to $PATH
|
||||
ENV PATH /app/node_modules/.bin:$PATH
|
||||
|
||||
# install app dependencies
|
||||
COPY package.json ./
|
||||
COPY package-lock.json ./
|
||||
RUN npm install
|
||||
|
||||
# add app
|
||||
COPY . ./
|
||||
|
||||
# start app
|
||||
CMD ["npm", "start"]
|
||||
21
frontend/examples/vue/README.md
Normal file
21
frontend/examples/vue/README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Hanko React example
|
||||
|
||||
This is a [Vue](https://vuejs.org/) project bootstrapped with Vue version 3.2.39.
|
||||
|
||||
## Starting the app
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- a running Hanko API (see the instructions on how to run the API [in Docker](../../backend/README.md#Docker) or [from Source](../../backend/README.md#from-source))
|
||||
- a running express backend (see the [README](../express) for the express backend)
|
||||
|
||||
### Set up environment variables
|
||||
|
||||
In the `.env` file set up the correct environment variables:
|
||||
|
||||
- `VITE_HANKO_API`: this is the URL of the Hanko API (default: `http://localhost:8000`, can be customized using the `server.public.address` option in the [configuration file](../../backend/docs/Config.md))
|
||||
- `VITE_TODO_API`: this is the URL of the [express](../express) backend (default: `http://localhost:8002`)
|
||||
|
||||
### Run development server
|
||||
|
||||
Run `npm install` to install dependencies, then run `npm run start` for a development server. Navigate to `http://localhost:8888/`. The application will automatically reload if you change any of the source files.
|
||||
1
frontend/examples/vue/env.d.ts
vendored
Normal file
1
frontend/examples/vue/env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
13
frontend/examples/vue/index.html
Normal file
13
frontend/examples/vue/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Hanko Vue Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
27
frontend/examples/vue/package.json
Normal file
27
frontend/examples/vue/package.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "example-vue",
|
||||
"scripts": {
|
||||
"start": "vite --port 8888 --host",
|
||||
"build": "vite build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@teamhanko/hanko-elements": "^0.2.1-alpha",
|
||||
"vue": "^3.2.47",
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.1.4",
|
||||
"@types/node": "^16.11.56",
|
||||
"@vitejs/plugin-vue": "^3.0.3",
|
||||
"@vue/eslint-config-prettier": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.0",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"eslint": "^8.22.0",
|
||||
"eslint-plugin-vue": "^9.3.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "~4.7.4",
|
||||
"vite": "^3.0.9",
|
||||
"vue-tsc": "^0.40.7"
|
||||
}
|
||||
}
|
||||
BIN
frontend/examples/vue/public/favicon.png
Normal file
BIN
frontend/examples/vue/public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 906 B |
55
frontend/examples/vue/src/App.vue
Normal file
55
frontend/examples/vue/src/App.vue
Normal file
@ -0,0 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from "vue-router";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
padding: 24px;
|
||||
border-radius: 17px;
|
||||
color: black;
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
min-width: 330px;
|
||||
margin: 10vh auto;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
padding: 0 0 10px;
|
||||
}
|
||||
|
||||
|
||||
.button {
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
color: grey !important;
|
||||
cursor: default;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.nav {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
|
||||
.nav .button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.nav .button {
|
||||
color: white;
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
19
frontend/examples/vue/src/assets/base.css
Normal file
19
frontend/examples/vue/src/assets/base.css
Normal file
@ -0,0 +1,19 @@
|
||||
body {
|
||||
color: white;
|
||||
margin: 0;
|
||||
background: url("bg.jpg") no-repeat center center fixed;
|
||||
background-size: cover;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
hanko-auth::part(form-item) {
|
||||
min-width: 100%; /* input fields and buttons are on top of each other */
|
||||
}
|
||||
BIN
frontend/examples/vue/src/assets/bg.jpg
Normal file
BIN
frontend/examples/vue/src/assets/bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
21
frontend/examples/vue/src/components/HankoAuth.vue
Normal file
21
frontend/examples/vue/src/components/HankoAuth.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from "vue-router";
|
||||
import { register } from "@teamhanko/hanko-elements";
|
||||
import { onMounted } from "vue";
|
||||
|
||||
const router = useRouter();
|
||||
const api = import.meta.env.VITE_HANKO_API;
|
||||
const emit = defineEmits(["on-error"]);
|
||||
|
||||
const redirectToTodo = () => {
|
||||
router.push({ path: "/todo" });
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
register({ shadow: true }).catch((e) => emit("on-error", e));
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<hanko-auth @hankoAuthSuccess="redirectToTodo" :api="api" />
|
||||
</template>
|
||||
15
frontend/examples/vue/src/components/HankoProfile.vue
Normal file
15
frontend/examples/vue/src/components/HankoProfile.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { register } from "@teamhanko/hanko-elements";
|
||||
import { onMounted } from "vue";
|
||||
|
||||
const api = import.meta.env.VITE_HANKO_API;
|
||||
const emit = defineEmits(["on-error"]);
|
||||
|
||||
onMounted(() => {
|
||||
register({ shadow: true }).catch((e) => emit("on-error", e));
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<hanko-profile :api="api" />
|
||||
</template>
|
||||
11
frontend/examples/vue/src/main.ts
Normal file
11
frontend/examples/vue/src/main.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
|
||||
import "./assets/base.css";
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(router);
|
||||
|
||||
app.mount("#app");
|
||||
27
frontend/examples/vue/src/router/index.ts
Normal file
27
frontend/examples/vue/src/router/index.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import LoginView from "../views/LoginView.vue";
|
||||
import TodoView from "../views/TodoView.vue";
|
||||
import ProfileView from "../views/ProfileView.vue";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: "/",
|
||||
name: "login",
|
||||
component: LoginView,
|
||||
},
|
||||
{
|
||||
path: "/todo",
|
||||
name: "todo",
|
||||
component: TodoView,
|
||||
},
|
||||
{
|
||||
path: "/profile",
|
||||
name: "profile",
|
||||
component: ProfileView,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default router;
|
||||
56
frontend/examples/vue/src/utils/TodoClient.ts
Normal file
56
frontend/examples/vue/src/utils/TodoClient.ts
Normal file
@ -0,0 +1,56 @@
|
||||
export interface Todo {
|
||||
todoID?: string;
|
||||
description: string;
|
||||
checked: boolean;
|
||||
}
|
||||
|
||||
export type Todos = Todo[];
|
||||
|
||||
export class TodoClient {
|
||||
api: string;
|
||||
|
||||
constructor(api: string) {
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
addTodo(todo: Todo) {
|
||||
return fetch(`${this.api}/todo`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(todo),
|
||||
});
|
||||
}
|
||||
|
||||
listTodos() {
|
||||
return fetch(`${this.api}/todo`, {
|
||||
credentials: "include",
|
||||
});
|
||||
}
|
||||
|
||||
patchTodo(id: string, checked: boolean) {
|
||||
return fetch(`${this.api}/todo/${id}`, {
|
||||
method: "PATCH",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ checked }),
|
||||
});
|
||||
}
|
||||
|
||||
deleteTodo(id: string) {
|
||||
return fetch(`${this.api}/todo/${id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
}
|
||||
|
||||
logout() {
|
||||
return fetch(`${this.api}/logout`, {
|
||||
credentials: "include",
|
||||
});
|
||||
}
|
||||
}
|
||||
19
frontend/examples/vue/src/views/LoginView.vue
Normal file
19
frontend/examples/vue/src/views/LoginView.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import HankoAuth from "@/components/HankoAuth.vue";
|
||||
import type { Ref } from "vue";
|
||||
import { ref } from "vue";
|
||||
|
||||
const error: Ref<Error | null> = ref(null);
|
||||
|
||||
function setError(e: Error) {
|
||||
error.value = e;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="content">
|
||||
<div class="error">{{ error?.message }}</div>
|
||||
<HankoAuth @on-error="setError"/>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
46
frontend/examples/vue/src/views/ProfileView.vue
Normal file
46
frontend/examples/vue/src/views/ProfileView.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import HankoProfile from "@/components/HankoProfile.vue";
|
||||
|
||||
import { useRouter } from "vue-router";
|
||||
import { TodoClient } from "@/utils/TodoClient";
|
||||
import type { Ref } from "vue";
|
||||
import { ref } from "vue";
|
||||
|
||||
const router = useRouter();
|
||||
const api = import.meta.env.VITE_TODO_API;
|
||||
const client = new TodoClient(api);
|
||||
const error: Ref<Error | null> = ref(null);
|
||||
|
||||
function setError(e: Error) {
|
||||
error.value = e;
|
||||
}
|
||||
|
||||
function todos() {
|
||||
router.push("/todo");
|
||||
}
|
||||
|
||||
function logout() {
|
||||
client
|
||||
.logout()
|
||||
.then(() => {
|
||||
router.push("/");
|
||||
return;
|
||||
})
|
||||
.catch((e) => {
|
||||
error.value = e;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="nav">
|
||||
<button @click.prevent="logout" class="button">Logout</button>
|
||||
<button disabled class="button">Profile</button>
|
||||
<button @click.prevent="todos" class="button">Todos</button>
|
||||
</nav>
|
||||
<main class="content">
|
||||
<div class="error">{{ error?.message }}</div>
|
||||
<HankoProfile @on-error="setError" />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
280
frontend/examples/vue/src/views/TodoView.vue
Normal file
280
frontend/examples/vue/src/views/TodoView.vue
Normal file
@ -0,0 +1,280 @@
|
||||
<script setup lang="ts">
|
||||
import type { Todos } from "@/utils/TodoClient";
|
||||
import { TodoClient } from "@/utils/TodoClient";
|
||||
import { useRouter } from "vue-router";
|
||||
import type { Ref } from "vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const api = import.meta.env.VITE_TODO_API;
|
||||
const client = new TodoClient(api);
|
||||
|
||||
const error: Ref<Error | null> = ref(null);
|
||||
const todos: Ref<Todos> = ref([]);
|
||||
const description = ref("");
|
||||
|
||||
onMounted(() => {
|
||||
listTodos();
|
||||
});
|
||||
|
||||
function changeDescription(event: any) {
|
||||
description.value = event.currentTarget.value;
|
||||
}
|
||||
|
||||
const changeCheckbox = (event: any) => {
|
||||
const { currentTarget } = event;
|
||||
patchTodo(currentTarget.value, currentTarget.checked);
|
||||
};
|
||||
|
||||
function addTodo() {
|
||||
const todo = { description: description.value, checked: false };
|
||||
|
||||
client
|
||||
.addTodo(todo)
|
||||
.then((res) => {
|
||||
if (res.status === 401) {
|
||||
router.push("/");
|
||||
return;
|
||||
}
|
||||
|
||||
description.value = "";
|
||||
listTodos();
|
||||
|
||||
return;
|
||||
})
|
||||
.catch((e) => {
|
||||
error.value = e;
|
||||
});
|
||||
}
|
||||
|
||||
function listTodos() {
|
||||
client
|
||||
.listTodos()
|
||||
.then((res) => {
|
||||
if (res.status === 401) {
|
||||
router.push("/");
|
||||
return;
|
||||
}
|
||||
|
||||
return res.json();
|
||||
})
|
||||
.then((todo) => {
|
||||
if (todo) {
|
||||
todos.value = todo;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
error.value = e;
|
||||
});
|
||||
}
|
||||
|
||||
const patchTodo = (id: string, checked: boolean) => {
|
||||
client
|
||||
.patchTodo(id, checked)
|
||||
.then((res) => {
|
||||
if (res.status === 401) {
|
||||
router.push("/");
|
||||
return;
|
||||
}
|
||||
|
||||
listTodos();
|
||||
|
||||
return;
|
||||
})
|
||||
.catch((e) => {
|
||||
error.value = e;
|
||||
});
|
||||
};
|
||||
|
||||
const deleteTodo = (id: string) => {
|
||||
client
|
||||
.deleteTodo(id)
|
||||
.then((res) => {
|
||||
if (res.status === 401) {
|
||||
router.push("/");
|
||||
return;
|
||||
}
|
||||
|
||||
listTodos();
|
||||
|
||||
return;
|
||||
})
|
||||
.catch((e) => {
|
||||
error.value = e;
|
||||
});
|
||||
};
|
||||
|
||||
function profile() {
|
||||
router.push("/profile");
|
||||
}
|
||||
|
||||
function logout() {
|
||||
client
|
||||
.logout()
|
||||
.then(() => {
|
||||
router.push("/");
|
||||
return;
|
||||
})
|
||||
.catch((e) => {
|
||||
error.value = e;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="nav">
|
||||
<button @click.prevent="logout" class="button">Logout</button>
|
||||
<button @click.prevent="profile" class="button">Profile</button>
|
||||
<button disabled class="button">Todos</button>
|
||||
</nav>
|
||||
<div class="content">
|
||||
<h1 class="headline">Todos</h1>
|
||||
<div class="error">{{ error?.message }}</div>
|
||||
<form @submit.prevent="addTodo" class="form">
|
||||
<input
|
||||
required
|
||||
class="input"
|
||||
type="text"
|
||||
:value="description"
|
||||
@change="changeDescription"
|
||||
/>
|
||||
<button type="submit" class="button">+</button>
|
||||
</form>
|
||||
<div class="list">
|
||||
<div v-for="(todo, index) in todos" class="item" :key="index">
|
||||
<input
|
||||
class="checkbox"
|
||||
:id="todo.todoID"
|
||||
type="checkbox"
|
||||
:value="todo.todoID"
|
||||
:checked="todo.checked"
|
||||
@change="changeCheckbox"
|
||||
/>
|
||||
<label class="description" :for="todo.todoID">{{
|
||||
todo.description
|
||||
}}</label>
|
||||
<button class="button" @click="() => deleteTodo(todo.todoID)">×</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.nav {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
color: grey !important;
|
||||
cursor: default;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.nav .button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.nav .button {
|
||||
color: white;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24px;
|
||||
border-radius: 17px;
|
||||
color: black;
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
min-width: 330px;
|
||||
margin: 10vh auto;
|
||||
}
|
||||
|
||||
.headline {
|
||||
text-align: center;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
margin-bottom: 17px;
|
||||
}
|
||||
|
||||
.form .input {
|
||||
flex-grow: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.form .button {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 7px;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
column-gap: 7px;
|
||||
}
|
||||
|
||||
.description {
|
||||
flex-grow: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
padding: 0 0 10px;
|
||||
}
|
||||
|
||||
.input {
|
||||
border: 1px solid black;
|
||||
border-radius: 2.4px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin-left: 0;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background-color: #fff;
|
||||
font: inherit;
|
||||
color: currentColor;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border: 1px solid currentColor;
|
||||
border-radius: 0.15em;
|
||||
transform: translateY(-0.075em);
|
||||
display: grid;
|
||||
place-content: center;
|
||||
}
|
||||
|
||||
.checkbox::before {
|
||||
content: "";
|
||||
width: 0.65em;
|
||||
height: 0.65em;
|
||||
transform: scale(0);
|
||||
box-shadow: inset 1em 1em black;
|
||||
|
||||
transform-origin: bottom left;
|
||||
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
|
||||
}
|
||||
|
||||
.checkbox:checked::before {
|
||||
transform: scale(1);
|
||||
}
|
||||
</style>
|
||||
8
frontend/examples/vue/tsconfig.config.json
Normal file
8
frontend/examples/vue/tsconfig.config.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.node.json",
|
||||
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
16
frontend/examples/vue/tsconfig.json
Normal file
16
frontend/examples/vue/tsconfig.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.config.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
20
frontend/examples/vue/vite.config.ts
Normal file
20
frontend/examples/vue/vite.config.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue({
|
||||
template: {
|
||||
compilerOptions: { isCustomElement: (tag) => tag.startsWith("hanko-") },
|
||||
},
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user