create-nexwire 4.2.4 → 4.2.5

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 (35) hide show
  1. package/express/.vscode/extensions.json +6 -0
  2. package/express/.vscode/settings.json +37 -26
  3. package/express/biome.json +99 -66
  4. package/express/package.json +30 -28
  5. package/express/src/database/schema.ts +2 -2
  6. package/express/src/resources/src/components/Checkbox.vue +32 -29
  7. package/express/src/resources/src/components/Input.vue +243 -223
  8. package/express/src/resources/src/components/InputGroup.vue +147 -145
  9. package/express/src/resources/src/components/InputPasswordToggle.vue +229 -213
  10. package/express/src/resources/src/components/Modal.vue +71 -55
  11. package/express/src/resources/src/components/Refresh.vue +19 -0
  12. package/express/src/resources/src/helpers/assets.ts +51 -0
  13. package/express/src/resources/src/main.ts +3 -1
  14. package/express/src/resources/src/plugins/gum.ts +1 -1
  15. package/express/src/resources/src/stores/auth.ts +184 -164
  16. package/express/src/resources/vite.config.ts +9 -2
  17. package/express/src/storage/database/nexwire.sqlite +0 -0
  18. package/hono/.vscode/extensions.json +6 -0
  19. package/hono/.vscode/settings.json +37 -26
  20. package/hono/biome.json +25 -2
  21. package/hono/package.json +24 -22
  22. package/hono/src/database/schema.ts +2 -2
  23. package/hono/src/resources/src/components/Checkbox.vue +32 -29
  24. package/hono/src/resources/src/components/Input.vue +243 -234
  25. package/hono/src/resources/src/components/InputGroup.vue +147 -149
  26. package/hono/src/resources/src/components/InputPasswordToggle.vue +229 -222
  27. package/hono/src/resources/src/components/Modal.vue +71 -55
  28. package/hono/src/resources/src/components/Refresh.vue +19 -0
  29. package/hono/src/resources/src/helpers/assets.ts +51 -0
  30. package/hono/src/resources/src/main.ts +3 -1
  31. package/hono/src/resources/src/plugins/gum.ts +1 -1
  32. package/hono/src/resources/src/stores/auth.ts +184 -164
  33. package/hono/src/resources/vite.config.ts +9 -2
  34. package/hono/src/storage/database/nexwire.sqlite +0 -0
  35. package/package.json +1 -1
@@ -15,7 +15,9 @@ import "bootstrap/dist/js/bootstrap.bundle.min.js";
15
15
 
16
16
  const app = createApp(App);
17
17
  const pinia = createPinia();
18
- const head = createHead({ titleTemplate: (title) => (title ? `${title} | nexwire` : "nexwire") });
18
+ const head = createHead({
19
+ init: [{ titleTemplate: (title) => (title ? `${title} | nexwire` : "nexwire") }],
20
+ });
19
21
 
20
22
  app.use(pinia);
21
23
  app.use(router);
@@ -50,7 +50,7 @@ type ValidationErrorPayload = {
50
50
  };
51
51
 
52
52
  function normalizeValidationErrors(payload?: ValidationErrorPayload): NormalizedErrors {
53
- const normalized: NormalizedErrors = { ...(payload?.errors || {}) };
53
+ const normalized: NormalizedErrors = { ...payload?.errors };
54
54
 
55
55
  const issues = payload?.error?.issues;
56
56
  if (Array.isArray(issues)) {
@@ -1,164 +1,184 @@
1
- import axios from "axios";
2
- import { defineStore } from "pinia";
3
- import { ref } from "vue";
4
- import { type AuthUser, clearUser, setUser } from "@/composables/useAuth";
5
-
6
- type LoginPayload = { email: string; password: string; remember?: boolean };
7
- type RegisterPayload = {
8
- name: string;
9
- email: string;
10
- password: string;
11
- password_confirmation: string;
12
- };
13
- type VerifyEmailPayload = { email: string; token: string };
14
- type ForgotPayload = { email: string };
15
- type ResetPayload = {
16
- email: string;
17
- token: string;
18
- password: string;
19
- password_confirmation: string;
20
- };
21
-
22
- type ApiResponse<T> = { message: string; data?: T };
23
- type AuthData = { user: AuthUser };
24
-
25
- async function request<T>(method: "GET" | "POST", path: string, payload?: unknown): Promise<ApiResponse<T>> {
26
- try {
27
- const response = await axios.request<ApiResponse<T>>({
28
- method,
29
- url: `/api/auth${path}`,
30
- data: payload
31
- });
32
-
33
- return response.data;
34
- } catch (error: unknown) {
35
- if (axios.isAxiosError(error)) {
36
- throw new Error(String(error.response?.data?.message || error.message || "Request failed"));
37
- }
38
- throw new Error("Request failed");
39
- }
40
- }
41
-
42
- const MAX_BOOTSTRAP_ATTEMPTS = 5;
43
- const BOOTSTRAP_BACKOFF_MS = 500;
44
-
45
- const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
46
-
47
- export const useAuthStore = defineStore("auth", () => {
48
- const user = ref<AuthUser | null>(null);
49
- const isAuthenticated = ref(false);
50
- const processing = ref(false);
51
- const initialized = ref(false);
52
-
53
- const syncUser = (value: AuthUser | null) => {
54
- user.value = value;
55
- isAuthenticated.value = !!value;
56
- if (value) setUser(value);
57
- else clearUser();
58
- };
59
-
60
- const bootstrap = async () => {
61
- if (initialized.value) return;
62
-
63
- for (let attempt = 1; attempt <= MAX_BOOTSTRAP_ATTEMPTS; attempt++) {
64
- try {
65
- const response = await axios.get<ApiResponse<AuthUser>>("/api/auth/me");
66
- syncUser((response.data?.data || null) as AuthUser | null);
67
- break;
68
- } catch (error) {
69
- const backendUnreachable = axios.isAxiosError(error) && !error.response;
70
- if (!backendUnreachable || attempt === MAX_BOOTSTRAP_ATTEMPTS) {
71
- syncUser(null);
72
- break;
73
- }
74
- await sleep(BOOTSTRAP_BACKOFF_MS * 2 ** (attempt - 1));
75
- }
76
- }
77
-
78
- initialized.value = true;
79
- };
80
-
81
- const login = async (payload: LoginPayload) => {
82
- processing.value = true;
83
- try {
84
- const data = await request<AuthData>("POST", "/login", payload);
85
- syncUser((data?.data?.user || null) as AuthUser | null);
86
- initialized.value = true;
87
- return data.message || "Login successful";
88
- } finally {
89
- processing.value = false;
90
- }
91
- };
92
-
93
- const register = async (payload: RegisterPayload) => {
94
- processing.value = true;
95
- try {
96
- const data = await request<AuthData>("POST", "/register", payload);
97
- const createdUser = (data?.data?.user || null) as AuthUser | null;
98
- if (createdUser) {
99
- syncUser(createdUser);
100
- initialized.value = true;
101
- } else {
102
- syncUser(null);
103
- }
104
- return data.message || "Registration successful";
105
- } finally {
106
- processing.value = false;
107
- }
108
- };
109
-
110
- const verifyEmail = async (payload: VerifyEmailPayload) => {
111
- processing.value = true;
112
- try {
113
- const data = await request<unknown>("POST", "/verify-email", payload);
114
- return data.message || "Email verified successfully";
115
- } finally {
116
- processing.value = false;
117
- }
118
- };
119
-
120
- const forgotPassword = async (payload: ForgotPayload) => {
121
- processing.value = true;
122
- try {
123
- const data = await request<unknown>("POST", "/forgot-password", payload);
124
- return data.message || "If this email exists, a reset link has been sent";
125
- } finally {
126
- processing.value = false;
127
- }
128
- };
129
-
130
- const resetPassword = async (payload: ResetPayload) => {
131
- processing.value = true;
132
- try {
133
- const data = await request<unknown>("POST", "/reset-password", payload);
134
- return data.message || "Password reset successfully";
135
- } finally {
136
- processing.value = false;
137
- }
138
- };
139
-
140
- const logout = async () => {
141
- processing.value = true;
142
- try {
143
- await request<unknown>("POST", "/logout");
144
- } finally {
145
- syncUser(null);
146
- processing.value = false;
147
- initialized.value = true;
148
- }
149
- };
150
-
151
- return {
152
- user,
153
- isAuthenticated,
154
- processing,
155
- initialized,
156
- bootstrap,
157
- register,
158
- login,
159
- forgotPassword,
160
- resetPassword,
161
- verifyEmail,
162
- logout
163
- };
164
- });
1
+ import axios from "axios";
2
+ import { defineStore } from "pinia";
3
+ import { ref } from "vue";
4
+ import { type AuthUser, clearUser, setUser } from "@/composables/useAuth";
5
+
6
+ type LoginPayload = { email?: string; phone?: string; password: string; remember?: boolean };
7
+ type VerifyEmailPayload = { email: string; token: string };
8
+ type ForgotPayload = { email: string };
9
+
10
+ type RegisterPayload = {
11
+ name: string;
12
+ email?: string;
13
+ phone?: string;
14
+ password: string;
15
+ password_confirmation: string;
16
+ };
17
+
18
+ type ResetPayload = {
19
+ email: string;
20
+ token: string;
21
+ password: string;
22
+ password_confirmation: string;
23
+ };
24
+
25
+ type ApiResponse<T> = { message: string; data?: T };
26
+ type AuthData = { user: AuthUser };
27
+
28
+ async function request<T>(method: "GET" | "POST", path: string, payload?: unknown): Promise<ApiResponse<T>> {
29
+ try {
30
+ const response = await axios.request<ApiResponse<T>>({
31
+ method,
32
+ url: `/api/auth${path}`,
33
+ data: payload
34
+ });
35
+
36
+ return response.data;
37
+ } catch (error: unknown) {
38
+ if (axios.isAxiosError(error)) {
39
+ throw new Error(String(error.response?.data?.message || error.message || "Request failed"));
40
+ }
41
+ throw new Error("Request failed");
42
+ }
43
+ }
44
+
45
+ const MAX_BOOTSTRAP_ATTEMPTS = 5;
46
+ const BOOTSTRAP_BACKOFF_MS = 500;
47
+
48
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
49
+
50
+ export const useAuthStore = defineStore("auth", () => {
51
+ const user = ref<AuthUser | null>(null);
52
+ const isAuthenticated = ref(false);
53
+ const processing = ref(false);
54
+ const initialized = ref(false);
55
+
56
+ const syncUser = (value: AuthUser | null) => {
57
+ user.value = value;
58
+ isAuthenticated.value = !!value;
59
+ if (value) setUser(value);
60
+ else clearUser();
61
+ };
62
+
63
+ let bootstrapPromise: Promise<void> | null = null;
64
+ const bootstrap = async () => {
65
+ if (initialized.value) return;
66
+
67
+ // Reuse the same request if multiple navigations call bootstrap.
68
+ if (bootstrapPromise) {
69
+ return bootstrapPromise;
70
+ }
71
+
72
+ bootstrapPromise = (async () => {
73
+ for (let attempt = 1; attempt <= MAX_BOOTSTRAP_ATTEMPTS; attempt++) {
74
+ try {
75
+ const response = await axios.get<ApiResponse<AuthUser>>("/api/auth/me");
76
+
77
+ syncUser((response.data?.data || null) as AuthUser | null);
78
+ initialized.value = true;
79
+ return;
80
+ } catch (error) {
81
+ const backendUnreachable = axios.isAxiosError(error) && !error.response;
82
+
83
+ if (!backendUnreachable || attempt === MAX_BOOTSTRAP_ATTEMPTS) {
84
+ syncUser(null);
85
+ initialized.value = true;
86
+ return;
87
+ }
88
+
89
+ await sleep(BOOTSTRAP_BACKOFF_MS * 2 ** (attempt - 1));
90
+ }
91
+ }
92
+ })();
93
+
94
+ try {
95
+ await bootstrapPromise;
96
+ } finally {
97
+ bootstrapPromise = null;
98
+ }
99
+ };
100
+
101
+ const login = async (payload: LoginPayload) => {
102
+ processing.value = true;
103
+ try {
104
+ const data = await request<AuthData>("POST", "/login", payload);
105
+ syncUser((data?.data?.user || null) as AuthUser | null);
106
+ initialized.value = true;
107
+ return data.message || "Login successful";
108
+ } finally {
109
+ processing.value = false;
110
+ }
111
+ };
112
+
113
+ const register = async (payload: RegisterPayload) => {
114
+ processing.value = true;
115
+ try {
116
+ const data = await request<AuthData>("POST", "/register", payload);
117
+ const createdUser = (data?.data?.user || null) as AuthUser | null;
118
+ if (createdUser) {
119
+ syncUser(createdUser);
120
+ initialized.value = true;
121
+ } else {
122
+ syncUser(null);
123
+ }
124
+ return data.message || "Registration successful";
125
+ } finally {
126
+ processing.value = false;
127
+ }
128
+ };
129
+
130
+ const verifyEmail = async (payload: VerifyEmailPayload) => {
131
+ processing.value = true;
132
+ try {
133
+ const data = await request<unknown>("POST", "/verify-email", payload);
134
+ return data.message || "Email verified successfully";
135
+ } finally {
136
+ processing.value = false;
137
+ }
138
+ };
139
+
140
+ const forgotPassword = async (payload: ForgotPayload) => {
141
+ processing.value = true;
142
+ try {
143
+ const data = await request<unknown>("POST", "/forgot-password", payload);
144
+ return data.message || "If this email exists, a reset link has been sent";
145
+ } finally {
146
+ processing.value = false;
147
+ }
148
+ };
149
+
150
+ const resetPassword = async (payload: ResetPayload) => {
151
+ processing.value = true;
152
+ try {
153
+ const data = await request<unknown>("POST", "/reset-password", payload);
154
+ return data.message || "Password reset successfully";
155
+ } finally {
156
+ processing.value = false;
157
+ }
158
+ };
159
+
160
+ const logout = async () => {
161
+ processing.value = true;
162
+ try {
163
+ await request<unknown>("POST", "/logout");
164
+ } finally {
165
+ syncUser(null);
166
+ processing.value = false;
167
+ initialized.value = true;
168
+ }
169
+ };
170
+
171
+ return {
172
+ user,
173
+ isAuthenticated,
174
+ processing,
175
+ initialized,
176
+ bootstrap,
177
+ register,
178
+ login,
179
+ forgotPassword,
180
+ resetPassword,
181
+ verifyEmail,
182
+ logout
183
+ };
184
+ });
@@ -20,13 +20,20 @@ export default defineConfig({
20
20
  __SOCKET_ENABLED__: socketEnabled,
21
21
  __API_URL__: JSON.stringify(apiUrl)
22
22
  },
23
- root: "src/resources",
23
+ root: fileURLToPath(new URL(".", import.meta.url)),
24
24
  cacheDir: path.join(cacheBase, "nexwire", "vite-cache", "resources"),
25
25
  plugins: [vue()],
26
26
  css: {
27
27
  preprocessorOptions: {
28
28
  scss: {
29
- quietDeps: true
29
+ quietDeps: true,
30
+ silenceDeprecations: [
31
+ "import",
32
+ "if-function",
33
+ "global-builtin",
34
+ "color-functions",
35
+ "slash-div",
36
+ ],
30
37
  }
31
38
  }
32
39
  },
@@ -0,0 +1,6 @@
1
+ {
2
+ "recommendations": [
3
+ "biomejs.biome",
4
+ "editorconfig.editorconfig"
5
+ ]
6
+ }
@@ -1,26 +1,37 @@
1
- {
2
- "editor.defaultFormatter": "biomejs.biome",
3
- "[typescript]": {
4
- "editor.defaultFormatter": "biomejs.biome"
5
- },
6
- "[typescriptreact]": {
7
- "editor.defaultFormatter": "biomejs.biome"
8
- },
9
- "[javascript]": {
10
- "editor.defaultFormatter": "biomejs.biome"
11
- },
12
- "[javascriptreact]": {
13
- "editor.defaultFormatter": "biomejs.biome"
14
- },
15
- "[json]": {
16
- "editor.defaultFormatter": "biomejs.biome"
17
- },
18
- "[jsonc]": {
19
- "editor.defaultFormatter": "biomejs.biome"
20
- },
21
- "editor.codeActionsOnSave": {
22
- "source.organizeImports.biome": "explicit",
23
- "source.fixAll.biome": "explicit"
24
- },
25
- "files.eol": "\n"
26
- }
1
+ {
2
+ "editor.defaultFormatter": "biomejs.biome",
3
+ "[typescript]": {
4
+ "editor.defaultFormatter": "biomejs.biome"
5
+ },
6
+ "[typescriptreact]": {
7
+ "editor.defaultFormatter": "biomejs.biome"
8
+ },
9
+ "[javascript]": {
10
+ "editor.defaultFormatter": "biomejs.biome"
11
+ },
12
+ "[javascriptreact]": {
13
+ "editor.defaultFormatter": "biomejs.biome"
14
+ },
15
+ "[json]": {
16
+ "editor.defaultFormatter": "biomejs.biome"
17
+ },
18
+ "[jsonc]": {
19
+ "editor.defaultFormatter": "biomejs.biome"
20
+ },
21
+ "[vue]": {
22
+ "editor.defaultFormatter": "biomejs.biome"
23
+ },
24
+ "[css]": {
25
+ "editor.defaultFormatter": "biomejs.biome"
26
+ },
27
+ "[scss]": {
28
+ "editor.defaultFormatter": "biomejs.biome"
29
+ },
30
+ "editor.codeActionsOnSave": {
31
+ "source.organizeImports.biome": "explicit",
32
+ "source.fixAll.biome": "explicit"
33
+ },
34
+ "files.eol": "\n",
35
+ "files.trimTrailingWhitespace": true,
36
+ "files.insertFinalNewline": true
37
+ }
package/hono/biome.json CHANGED
@@ -5,22 +5,45 @@
5
5
  "clientKind": "git",
6
6
  "useIgnoreFile": true
7
7
  },
8
+ "html": {
9
+ "experimentalFullSupportEnabled": true,
10
+ "formatter": {
11
+ "enabled": true
12
+ }
13
+ },
8
14
  "formatter": {
9
15
  "indentStyle": "space",
10
16
  "indentWidth": 2,
11
17
  "lineWidth": 140,
12
- "bracketSameLine": true
18
+ "bracketSameLine": false
19
+ },
20
+ "files": {
21
+ "includes": [
22
+ "**",
23
+ "!**/node_modules"
24
+ ]
13
25
  },
14
26
  "linter": {
15
27
  "rules": {
16
28
  "preset": "recommended",
29
+ "a11y": {
30
+ "noLabelWithoutControl": "off",
31
+ "useButtonType": "off",
32
+ "useSemanticElements": "off",
33
+ "noStaticElementInteractions": "off",
34
+ "useKeyWithClickEvents": "off",
35
+ "useValidAnchor": "off",
36
+ "useAnchorContent": "off",
37
+ "noDistractingElements": "off"
38
+ },
17
39
  "correctness": {
18
40
  "noUnusedVariables": "off",
19
41
  "useVueValidTemplateRoot": "off",
20
42
  "noUnusedImports": "off"
21
43
  },
22
44
  "complexity": {
23
- "noUselessEscapeInRegex": "off"
45
+ "noUselessEscapeInRegex": "off",
46
+ "noImportantStyles": "off"
24
47
  },
25
48
  "style": {
26
49
  "noNonNullAssertion": "off",
package/hono/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "nexwire-hono",
3
- "private": true,
4
- "type": "module",
5
3
  "version": "1.0.0",
4
+ "private": true,
6
5
  "bin": {
7
6
  "maker": "./src/framework/maker-cli/index.mjs"
8
7
  },
8
+ "type": "module",
9
9
  "scripts": {
10
10
  "build": "npm run build:all",
11
11
  "build:all": "npm run build:backend && npm run build:ui",
12
12
  "build:backend": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
13
- "build:ui": "node src/framework/maker-cli/runtime/build-ui.mjs",
13
+ "build:ui": "npm run typecheck:ui && node src/framework/maker-cli/runtime/build-ui.mjs",
14
+ "typecheck:ui": "vue-tsc --noEmit -p src/resources/tsconfig.json",
14
15
  "dev": "node src/framework/maker-cli/index.mjs dev",
15
16
  "backend": "node src/framework/maker-cli/index.mjs serve --src",
16
17
  "ui": "node src/framework/maker-cli/index.mjs ui:dev",
@@ -78,6 +79,19 @@
78
79
  "vue-select": "^4.0.0-beta.6",
79
80
  "zod": "^4.6.7"
80
81
  },
82
+ "devDependencies": {
83
+ "@biomejs/biome": "^2.5.7",
84
+ "@types/bootstrap": "^5.2.11",
85
+ "@types/lodash-es": "^4.17.12",
86
+ "@types/node": "^25.9.9",
87
+ "@vitest/coverage-v8": "^5.0.5",
88
+ "drizzle-kit": "^0.31.11",
89
+ "tsc-alias": "^1.9.9",
90
+ "tsx": "^4.23.15",
91
+ "typescript": "^6.0.3",
92
+ "vitest": "^5.0.5",
93
+ "vue-tsc": "^3.3.13"
94
+ },
81
95
  "overrides": {
82
96
  "@esbuild-kit/core-utils": {
83
97
  "esbuild": "^0.25.12"
@@ -96,29 +110,17 @@
96
110
  "bcryptjs": "^3.0.3"
97
111
  }
98
112
  },
113
+ "engines": {
114
+ "bun": ">=1.3",
115
+ "node": ">=22.12.0",
116
+ "pnpm": ">=11",
117
+ "yarn": ">=4"
118
+ },
99
119
  "allowScripts": {
100
120
  "@parcel/watcher": true,
101
121
  "esbuild": true,
102
122
  "msgpackr-extract": true,
103
123
  "redis-commander": true,
104
124
  "vue-demi": true
105
- },
106
- "devDependencies": {
107
- "@biomejs/biome": "^2.5.7",
108
- "@types/bootstrap": "^5.2.11",
109
- "@types/lodash-es": "^4.17.12",
110
- "@types/node": "^25.9.9",
111
- "@vitest/coverage-v8": "^5.0.5",
112
- "drizzle-kit": "^0.31.11",
113
- "tsc-alias": "^1.9.9",
114
- "tsx": "^4.23.15",
115
- "typescript": "^6.0.3",
116
- "vitest": "^5.0.5"
117
- },
118
- "engines": {
119
- "node": ">=22.12.0",
120
- "bun": ">=1.3",
121
- "pnpm": ">=11",
122
- "yarn": ">=4"
123
125
  }
124
- }
126
+ }
@@ -1,2 +1,2 @@
1
- export * from "../modules/auth/database/models/role.js";
2
- export * from "../modules/auth/database/models/user.js";
1
+ export * from "../modules/auth/database/models/role.js";
2
+ export * from "../modules/auth/database/models/user.js";
@@ -1,29 +1,32 @@
1
- <template>
2
- <div class="form-check mb-0" :class="props.topclass">
3
- <input
4
- :id="String(props.id)"
5
- v-model="model"
6
- class="form-check-input shadow-none border-secondary"
7
- type="checkbox"
8
- v-bind="$attrs" />
9
- <label v-if="props.label" class="form-check-label" :for="String(props.id)">{{ props.label }}</label>
10
- </div>
11
- </template>
12
-
13
- <script setup lang="ts">
14
- defineOptions({ name: "Checkbox", inheritAttrs: false });
15
-
16
- interface CheckboxProps {
17
- id?: string | number;
18
- label?: string;
19
- topclass?: string;
20
- }
21
-
22
- const props = defineProps<CheckboxProps>();
23
-
24
- type CheckboxModel = boolean | Array<string | number>;
25
-
26
- const model = defineModel<CheckboxModel>({ default: false });
27
- </script>
28
-
29
- <style lang="scss" scoped></style>
1
+ <template>
2
+ <div class="form-check mb-0" :class="props.topclass">
3
+ <input
4
+ :id="String(props.id)"
5
+ v-model="model"
6
+ class="form-check-input shadow-none"
7
+ type="checkbox"
8
+ v-bind="$attrs"
9
+ />
10
+ <label v-if="props.label" class="form-check-label" :for="String(props.id)">{{
11
+ props.label
12
+ }}</label>
13
+ </div>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ defineOptions({ name: "Checkbox", inheritAttrs: false });
18
+
19
+ interface CheckboxProps {
20
+ id?: string | number;
21
+ label?: string;
22
+ topclass?: string;
23
+ }
24
+
25
+ const props = defineProps<CheckboxProps>();
26
+
27
+ type CheckboxModel = boolean | Array<string | number>;
28
+
29
+ const model = defineModel<CheckboxModel>({ default: false });
30
+ </script>
31
+
32
+ <style lang="scss" scoped></style>