Init Boiler Plate

This commit is contained in:
Sandip Ghimire 2025-12-17 14:37:40 +05:45
commit 660f994036
67 changed files with 9614 additions and 0 deletions

8
.editorconfig Normal file
View File

@ -0,0 +1,8 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
max_line_length = 100

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

36
.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
# 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__/

11
.prettierignore Normal file
View File

@ -0,0 +1,11 @@
# Ignore artifacts:
build
coverage
# Ignore all HTML files:
**/*.html
**/.git
**/.svn
**/.hg
**/node_modules

10
.prettierrc.json Normal file
View File

@ -0,0 +1,10 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": false,
"printWidth": 120,
"htmlWhitespaceSensitivity": "ignore",
"vueIndentScriptAndStyle": false,
"endOfLine": "auto"
}

4
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"recommendations": ["prettier.prettier-vscode", "dbaeumer.vscode-eslint", "vue.volar"],
"unwantedRecommendations": ["octref.vetur"]
}

48
README.md Normal file
View File

@ -0,0 +1,48 @@
# vue-bolier-plate
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
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```
### Lint with [ESLint](https://eslint.org/)
```sh
npm run lint
```

85
eslint.config.js Normal file
View File

@ -0,0 +1,85 @@
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginVue from "eslint-plugin-vue";
import prettier from "eslint-plugin-prettier/recommended";
import vueConfigTypescript from "@vue/eslint-config-typescript";
import vueConfigPrettier from "@vue/eslint-config-prettier";
/** @type {import('eslint').Linter.Config[]} */
export default [
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
DOLPHIN: "readonly",
},
},
},
// js
pluginJs.configs.recommended,
{
rules: {
"no-unused-vars": "error",
"no-undef": "error",
},
},
// ts
...tseslint.configs.recommended,
{
rules: {
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^(props|_)",
varsIgnorePattern: "^(props|_)",
},
],
"@typescript-eslint/no-explicit-any": "off",
},
},
// vue
...pluginVue.configs["flat/recommended"],
{
files: ["*.vue", "**/*.vue"],
languageOptions: {
parserOptions: {
parser: tseslint.parser,
},
},
},
{
rules: {
...vueConfigTypescript.rules,
...vueConfigPrettier.rules,
"prettier/prettier": [
"warn",
{
singleQuote: false,
},
],
"vue/multi-word-component-names": "off",
"vue/attribute-hyphenation": "off",
"vue/no-v-html": "off",
"vue/v-on-event-hyphenation": "off",
"vue/require-v-for-key": "off",
"vue/no-template-shadow": "error",
"vue/no-reserved-component-names": "off",
"vue/attributes-order": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-require-imports": "off",
"vue/require-default-prop": "error",
},
},
{
ignores: ["node_modules", ".nuxt", ".output", "dist"],
},
// prettier
prettier,
{
rules: {
"prettier/prettier": ["warn", { singleQuote: false }],
},
},
];

13
index.html Normal file
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 class="font-open-sans">
<div id="app"></div>
<script type="module" src="/src/initApp.ts"></script>
</body>
</html>

5778
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

47
package.json Normal file
View File

@ -0,0 +1,47 @@
{
"name": "vue-bolier-plate",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"scripts": {
"dev": "vite --host",
"prebuild": "npm run lint",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build",
"format": "prettier --write src/",
"format:check": "prettier --check src/",
"lint": "eslint src --max-warnings 0",
"lint:fix": "eslint src --fix"
},
"dependencies": {
"axios": "^1.13.2",
"dolphin-components": "^3.1.0",
"nepali-date-library": "^1.1.11",
"pinia": "^3.0.4",
"typescript-eslint": "^8.50.0",
"vue": "^3.5.25",
"vue-router": "^4.6.4"
},
"devDependencies": {
"@tsconfig/node24": "^24.0.3",
"@types/node": "^25.0.3",
"@vitejs/plugin-vue": "^6.0.3",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/tsconfig": "^0.8.1",
"eslint": "^9.39.2",
"eslint-plugin-vue": "~10.6.2",
"jiti": "^2.6.1",
"npm-run-all2": "^8.0.4",
"prettier": "3.7.4",
"typescript": "~5.9.3",
"vite": "^7.3.0",
"vite-plugin-vue-devtools": "^8.0.5",
"vue-tsc": "^3.1.8"
}
}

View File

@ -0,0 +1,9 @@
{
"ENVIRONMENT": "PRODUCTION || DEVELOPMENT",
"PAGE_TITLE": "Dolphin",
"PAGE_TITLE_LOGO": "http://localhost:3000/img/Dolphin/dolphinfav.ico",
"CLIENT_NAME": "Mavorion Systems",
"CLIENT_LOGO": "",
"CLIENT_LOCATION": "LAZIMPAT",
"API_BASE_URL": "http://localhost:8000/api/"
}

9
public/config.json Normal file
View File

@ -0,0 +1,9 @@
{
"ENVIRONMENT": "PRODUCTION || DEVELOPMENT",
"PAGE_TITLE": "Dolphin",
"PAGE_TITLE_LOGO": "/img/Dolphin/dolphinfav.ico",
"CLIENT_NAME": "Mavorion Systems",
"CLIENT_LOCATION": "LAZIMPAT",
"CLIENT_LOGO": "",
"API_BASE_URL": "http://localhost:8000/api/v1/"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='750' height='625' viewBox='0 0 1080 900'><rect fill='#F6F8FF' width='1080' height='900'/><g fill-opacity='0.02'><polygon fill='#444' points='90 150 0 300 180 300'/><polygon points='90 150 180 0 0 0'/><polygon fill='#AAA' points='270 150 360 0 180 0'/><polygon fill='#DDD' points='450 150 360 300 540 300'/><polygon fill='#999' points='450 150 540 0 360 0'/><polygon points='630 150 540 300 720 300'/><polygon fill='#DDD' points='630 150 720 0 540 0'/><polygon fill='#444' points='810 150 720 300 900 300'/><polygon fill='#FFF' points='810 150 900 0 720 0'/><polygon fill='#DDD' points='990 150 900 300 1080 300'/><polygon fill='#444' points='990 150 1080 0 900 0'/><polygon fill='#DDD' points='90 450 0 600 180 600'/><polygon points='90 450 180 300 0 300'/><polygon fill='#666' points='270 450 180 600 360 600'/><polygon fill='#AAA' points='270 450 360 300 180 300'/><polygon fill='#DDD' points='450 450 360 600 540 600'/><polygon fill='#999' points='450 450 540 300 360 300'/><polygon fill='#999' points='630 450 540 600 720 600'/><polygon fill='#FFF' points='630 450 720 300 540 300'/><polygon points='810 450 720 600 900 600'/><polygon fill='#DDD' points='810 450 900 300 720 300'/><polygon fill='#AAA' points='990 450 900 600 1080 600'/><polygon fill='#444' points='990 450 1080 300 900 300'/><polygon fill='#222' points='90 750 0 900 180 900'/><polygon points='270 750 180 900 360 900'/><polygon fill='#DDD' points='270 750 360 600 180 600'/><polygon points='450 750 540 600 360 600'/><polygon points='630 750 540 900 720 900'/><polygon fill='#444' points='630 750 720 600 540 600'/><polygon fill='#AAA' points='810 750 720 900 900 900'/><polygon fill='#666' points='810 750 900 600 720 600'/><polygon fill='#999' points='990 750 900 900 1080 900'/><polygon fill='#999' points='180 0 90 150 270 150'/><polygon fill='#444' points='360 0 270 150 450 150'/><polygon fill='#FFF' points='540 0 450 150 630 150'/><polygon points='900 0 810 150 990 150'/><polygon fill='#222' points='0 300 -90 450 90 450'/><polygon fill='#FFF' points='0 300 90 150 -90 150'/><polygon fill='#FFF' points='180 300 90 450 270 450'/><polygon fill='#666' points='180 300 270 150 90 150'/><polygon fill='#222' points='360 300 270 450 450 450'/><polygon fill='#FFF' points='360 300 450 150 270 150'/><polygon fill='#444' points='540 300 450 450 630 450'/><polygon fill='#222' points='540 300 630 150 450 150'/><polygon fill='#AAA' points='720 300 630 450 810 450'/><polygon fill='#666' points='720 300 810 150 630 150'/><polygon fill='#FFF' points='900 300 810 450 990 450'/><polygon fill='#999' points='900 300 990 150 810 150'/><polygon points='0 600 -90 750 90 750'/><polygon fill='#666' points='0 600 90 450 -90 450'/><polygon fill='#AAA' points='180 600 90 750 270 750'/><polygon fill='#444' points='180 600 270 450 90 450'/><polygon fill='#444' points='360 600 270 750 450 750'/><polygon fill='#999' points='360 600 450 450 270 450'/><polygon fill='#666' points='540 600 630 450 450 450'/><polygon fill='#222' points='720 600 630 750 810 750'/><polygon fill='#FFF' points='900 600 810 750 990 750'/><polygon fill='#222' points='900 600 990 450 810 450'/><polygon fill='#DDD' points='0 900 90 750 -90 750'/><polygon fill='#444' points='180 900 270 750 90 750'/><polygon fill='#FFF' points='360 900 450 750 270 750'/><polygon fill='#AAA' points='540 900 630 750 450 750'/><polygon fill='#FFF' points='720 900 810 750 630 750'/><polygon fill='#222' points='900 900 990 750 810 750'/><polygon fill='#222' points='1080 300 990 450 1170 450'/><polygon fill='#FFF' points='1080 300 1170 150 990 150'/><polygon points='1080 600 990 750 1170 750'/><polygon fill='#666' points='1080 600 1170 450 990 450'/><polygon fill='#DDD' points='1080 900 1170 750 990 750'/></g></svg>

5
src/App.vue Normal file
View File

@ -0,0 +1,5 @@
<template>
<RouterView></RouterView>
</template>
<script setup lang="ts"></script>

2
src/assets/css/main.css Normal file
View File

@ -0,0 +1,2 @@
@import "./tailwind.css";
@import "dolphin-components/dolphin-components.css";

View File

@ -0,0 +1,36 @@
@import "tailwindcss";
@theme {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-modal: var(--card);
--color-modal-foreground: var(--card-foreground);
--color-modal: var(--card);
--color-modal-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-border: var(--border);
--color-border-foreground: var(--border-foreground);
--color-input: var(--input);
--color-input-focus: var(--input-focus);
--color-sidebar: var(--sidebar);
--color-scrollbar: var(--scrollbar);
--radius-default: var(--radius);
}

0
src/assets/ts/main.ts Normal file
View File

56
src/core/app/api.ts Normal file
View File

@ -0,0 +1,56 @@
import { useAuth } from "@/stores/Auth/auth.store";
import axios from "axios";
import { storeToRefs } from "pinia";
import { auth } from "./endpoints";
const apiURL: string = DOLPHIN.config.API_BASE_URL;
const api = axios.create({
baseURL: apiURL,
headers: {
"Content-Type": "application/json",
},
withCredentials: true,
});
// No need to save the Token as they are already attached to the cookie from backend
api.interceptors.request.use(
function (config) {
return config;
},
function (error) {
return Promise.reject(error);
}
);
api.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
const {
config,
response: { status },
} = error;
const originalRequest = config;
if (status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const authStore = useAuth();
const { isAuthenticated } = storeToRefs(authStore);
try {
await api.post(auth.tokenRefresh, null, {
withCredentials: true,
});
isAuthenticated.value = true;
return await axios(originalRequest);
} catch (e) {
console.error("Token refresh failed", e);
isAuthenticated.value = false;
throw e;
}
}
return Promise.reject(error);
}
);
export default api;

31
src/core/app/endpoints.ts Normal file
View File

@ -0,0 +1,31 @@
export const auth = {
login: `/login/`,
logout: `/logout/`,
tokenRefresh: `/token/refresh/`,
};
export const user = {
list: `/user/`,
create: `/user/`,
retrieve: (id: number) => `/user/${id}/`,
update: (id: number) => `/user/${id}/`,
partialUpdate: (id: number) => `/user/${id}/`,
delete: (id: number) => `/user/${id}/`,
self: `/user/self/`,
};
export const permissions = {
allPermissions: `/all-permissions/`,
allGroupsPermissions: `/all-groups-permissions/`,
createGroupsPermissions: `/create-groups-permissions/`,
assignGroupsToUser: `/assign-groups-to-user/`,
createRoleDepartmentAccess: `/create-role-department-access/`,
};
export const API_ENDPOINTS = {
auth,
user,
permissions,
};
export type ApiEndpoints = typeof API_ENDPOINTS;

15
src/core/declarations/dolphin.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
export {};
declare global {
var DOLPHIN: {
config: {
ENVIRONMENT: "PRODUCTION" | "DEVELOPMENT";
PAGE_TITLE: string;
PAGE_TITLE_LOGO: string;
CLIENT_NAME: string;
CLIENT_LOGO: string;
CLIENT_LOCATION: string;
API_BASE_URL: string;
};
};
}

View File

@ -0,0 +1,30 @@
import type { App } from "vue";
import {
ContentLayout,
DateRange,
DateSelector,
Icons,
Modal,
Switch,
Tabulator,
Toggle,
Towser,
Loader,
Wizard,
} from "dolphin-components";
const setupComponents = (app: App) => {
app.component("ContentLayout", ContentLayout);
app.component("DateRange", DateRange);
app.component("DateSelector", DateSelector);
app.component("Icons", Icons);
app.component("Modal", Modal);
app.component("Switch", Switch);
app.component("Tabulator", Tabulator);
app.component("Toggle", Toggle);
app.component("Towser", Towser);
app.component("Loader", Loader);
app.component("Wizard", Wizard);
};
export default setupComponents;

View File

@ -0,0 +1,20 @@
import type { App } from "vue";
import {
InputError,
ScrollbarScroll,
InputSelect,
InputCurrency,
ScrollbarAnimateDropdown,
Tooltip,
} from "dolphin-components";
const setupDirectives = (app: App) => {
app.directive("input-error", InputError);
app.directive("input-currency", InputCurrency);
app.directive("input-select", InputSelect);
app.directive("hide-scrollbar", ScrollbarScroll);
app.directive("animate-dropdown", ScrollbarAnimateDropdown);
app.directive("tooltip", Tooltip);
};
export default setupDirectives;

View File

@ -0,0 +1,11 @@
import type { App } from "vue";
import { FocusNextPlugin, CurrencyFormatterPlugin, NumbertoWordPlugin, ToastPlugin } from "dolphin-components";
const setupPlugins = (app: App) => {
app.use(FocusNextPlugin);
app.use(CurrencyFormatterPlugin);
app.use(NumbertoWordPlugin);
app.use(ToastPlugin);
};
export default setupPlugins;

View File

@ -0,0 +1,25 @@
import router from "@/router";
import { useAuth } from "@/stores/Auth/auth.store";
import type { App } from "vue";
const setupRouter = (app: App) => {
router.beforeEach(async (to, from, next) => {
if (to.meta.requireAuth || to.meta.onlyGuest) {
const authStore = useAuth();
await authStore.getUser();
if (authStore.isAuthenticated && to.meta.requireAuth) {
next();
} else if (!authStore.isAuthenticated && to.meta.onlyGuest) {
next();
} else if (authStore.isAuthenticated && to.meta.onlyGuest) {
next({ name: "dashboard" });
} else {
next({ name: "login" });
}
}
next();
});
app.use(router);
};
export default setupRouter;

View File

@ -0,0 +1,11 @@
import type { RouteLocationRaw } from "vue-router";
export type SidebarItem = {
label: string;
to?: RouteLocationRaw;
icon?: string;
children?: {
label: string;
to: RouteLocationRaw;
}[];
};

View File

@ -0,0 +1,15 @@
export interface LoginDetails {
username: string;
password: string;
cloudflareToken?: string;
}
export interface UserDetails {
id: number;
username: string;
full_name: string;
email: string;
phone: number | null;
department: string | null;
roles: [];
}

11
src/core/utils/Common.ts Normal file
View File

@ -0,0 +1,11 @@
const getNameInitials = (name: string): string => {
if (!name?.trim()) return "";
const parts = name.trim().split(/\s+/);
const first = parts[0]?.[0]?.toUpperCase() ?? "";
const last = (parts.length > 1 ? parts[parts.length - 1]?.[0]?.toUpperCase() : "") ?? "";
return first + last;
};
export { getNameInitials };

45
src/initApp.ts Normal file
View File

@ -0,0 +1,45 @@
globalThis.DOLPHIN = globalThis.DOLPHIN || { config: {} as any };
async function loadConfig() {
try {
const response = await fetch("/config.json");
if (!response.ok) throw new Error("Configuration Missing");
DOLPHIN.config = Object.freeze(await response.json());
document.title = DOLPHIN.config.PAGE_TITLE;
document.querySelectorAll('link[rel*="icon"]').forEach((el) => el.remove());
const link = Object.assign(document.createElement("link"), {
rel: "icon",
type: "image/png",
href: `${DOLPHIN.config.PAGE_TITLE_LOGO}`,
});
document.head.appendChild(link);
if (DOLPHIN.config.ENVIRONMENT == "PRODUCTION") {
setInterval(() => {
console.clear();
const style = `
color: #00CCFF;
font-size: 64px;
font-weight: bold;
text-shadow: 1px 1px 2px #000;
`;
console.log("%cDolphin Kiosk - Mavorion", style);
}, 1000);
}
} catch (e) {
console.error("Configuration Missing:", e);
const msg = document.createElement("div");
msg.innerText = "⚠️ Configuration Missing. Please contact support.";
msg.style.cssText = "color:red; font-family:sans-serif; text-align:center; margin-top:50px;";
document.body.appendChild(msg);
throw e;
}
}
(async () => {
await loadConfig();
const { initAPP } = await import("./main");
initAPP();
})();

148
src/layouts/AuthLayout.vue Normal file
View File

@ -0,0 +1,148 @@
<template>
<div class="app-container" :class="coreStore.isSidebarOpen ? 'sidebar-open' : 'sidebar-close'">
<Header></Header>
<div class="body-container">
<Sidebar :sidebarItems="sidebarItems"></Sidebar>
<div class="main-container">
<div class="relative">
<RouterView />
</div>
</div>
</div>
<Footer></Footer>
</div>
</template>
<script setup lang="ts">
import Footer from "./components/Footer.vue";
import Sidebar from "./components/Sidebar.vue";
import Header from "./components/Header.vue";
import { useCore } from "@/stores/App/core.store";
const coreStore = useCore();
import type { SidebarItem } from "@/core/types/app/sidebar.type";
const sidebarItems: SidebarItem[] = [
{
label: "Dashboard",
},
{
label: "Dashboard",
icon: "Home",
to: "/",
},
{
label: "Stock Verification",
icon: "Blocks",
children: [
{
label: "Opening Stock",
to: "/stock-verification/opening-stock/",
},
{
label: "Closing Stock",
to: "/stock-verification/closing-stock/",
},
],
},
{
label: "Components",
},
{
label: "Content Layout",
icon: "Package",
to: "/component/content-layout",
},
{
label: "Modal",
icon: "MessageSquareCode",
to: "/component/modal",
},
{
label: "Switch",
icon: "ToggleLeft",
to: "/component/switch",
},
{
label: "Toggle",
icon: "ToggleRight",
to: "/component/toggle",
},
{
label: "Tabulator",
icon: "Table",
to: "/component/tabulator",
},
{
label: "Towser",
icon: "Blinds",
to: "/component/towser",
},
{
label: "Date Range",
icon: "CalendarRange",
to: "/component/date-range",
},
{
label: "Date Selector",
icon: "Calendar",
to: "/component/date-selector",
},
{
label: "Wizard Stepper",
icon: "WandSparkles",
to: "/component/wizard",
},
{
label: "Loader",
icon: "Loader",
to: "/component/loader",
},
// {
// label: "ImageCropper Upload",
// icon: "Crop",
// to: "/component/image-cropper",
// },
{
label: "Directives",
},
{
label: "Input Error",
icon: "TriangleAlert",
to: "/directives/input-error",
},
{
label: "Input Currency",
icon: "CircleDollarSign",
to: "/directives/input-currency",
},
{
label: "Input Select",
icon: "TextCursorInput",
to: "/directives/input-select",
},
{
label: "Tooltip",
icon: "BookOpenText",
to: "/directives/tooltip",
},
{
label: "CSS",
},
{
label: "Buttons",
icon: "SquareArrowLeft",
to: "/css/button",
},
{
label: "Input",
icon: "TextCursor",
to: "/css/input",
},
{
label: "Font",
icon: "CaseSensitive",
to: "/css/font",
},
];
</script>

View File

@ -0,0 +1,12 @@
<template>
<div class="footer-container bg-white">
<div class="flex gap-0.5 justify-end h-full">
<span class="h-fit my-auto text-[13px]">
<span style="font-family: sans-serif" class="font-bold">©</span>
2025, Mavorion Systems. All rights reserved.
</span>
</div>
</div>
</template>
<script lang="ts" setup></script>

View File

@ -0,0 +1,101 @@
<template>
<nav class="header-container">
<div class="px-3.75 flex justify-between w-full">
<div class="w-[225px] flex justify-center">
<img src="/img/Dolphin/dolphin-logo.png" alt="" class="w-32 my-auto" />
</div>
<div class="relative select-none" id="modal-div-nav-profile">
<div
class="w-[38px] rounded-full text-white h-[38px] flex justify-center bg-primary cursor-pointer transition-all"
@click="isUserMenuOpen = !isUserMenuOpen"
:class="
isUserMenuOpen ? 'shadow-[0px_0px_3px_3px_#004cad6e]' : 'shadow-[0px_0px_0px_0px_#004cad6e]'
"
>
<span v-if="false" class="h-fit my-auto text-[16px]">
<img src="" alt="Profile photo" class="w-[32px] h-[32px] object-cover rounded-full" />
</span>
<span v-else class="h-[30px] my-auto text-[20px]">
{{ getNameInitials(user.full_name ? user.full_name : user.username) }}
</span>
</div>
<div
:class="isUserMenuOpen ? 'block' : 'hidden'"
class="absolute bg-white right-0 w-[250px] border shadow-sm transition-all"
tabindex="0"
ref="userProfileMenu"
>
<div>
<div class="px-[15px] py-[5px] border-b">
<div class="text-[12px] font-bold">Account</div>
<div class="flex gap-[5px]">
<div
class="text-white w-[42px] h-[42px] rounded-full flex justify-center item-center bg-primary"
@click="action('profile')"
>
<span v-if="false" class="h-fit my-auto text-[14px]">
<img
src=""
alt="Profile photo"
class="w-[42px] h-[42px] object-cover rounded-full"
/>
</span>
<span v-else class="h-[38px] my-auto text-[28px]">
{{ getNameInitials(user.full_name ? user.full_name : user.username) }}
</span>
</div>
<div>
<div>{{ user.full_name ? user.full_name : user.username }}</div>
<div class="text-[12px] text-secondary-foreground">{{ user.email }}</div>
</div>
</div>
</div>
<div
class="px-[15px] py-[5px] hover:bg-secondary cursor-pointer text-red-500"
@click="action('logout')"
>
Logout
</div>
</div>
</div>
</div>
</div>
</nav>
</template>
<script setup lang="ts">
import { getNameInitials } from "@/core/utils/Common";
import { useAuth } from "@/stores/Auth/auth.store";
import { storeToRefs } from "pinia";
import { ref, onMounted, onUnmounted } from "vue";
const authStore = useAuth();
const isUserMenuOpen = ref(false);
const { user } = storeToRefs(authStore);
function handleDocumentClick(event: any) {
const element = document.getElementById("modal-div-nav-profile");
if (isUserMenuOpen.value && !(event.target == element || element?.contains(event.target))) {
isUserMenuOpen.value = false;
}
}
onMounted(async () => {
document.addEventListener("click", handleDocumentClick);
});
onUnmounted(async () => {
document.removeEventListener("click", handleDocumentClick);
});
const action = (type: string) => {
switch (type) {
case "logout":
authStore.logout();
break;
}
isUserMenuOpen.value = false;
};
</script>

View File

@ -0,0 +1,104 @@
<template>
<div class="sidebar-container border-r" v-hide-scrollbar>
<button v-if="coreStore.isSidebarOpen" @click="coreStore.setSidebarStatus(false)" class="sidebar-close-item">
<Icons name="ArrowLeft" size="24" />
</button>
<button v-if="!coreStore.isSidebarOpen" class="sidebar-open-item" @click="coreStore.setSidebarStatus(true)">
<Icons name="TextAlignJustify" size="30" />
</button>
<div class="sidebar-items">
<div v-for="(item, index) in sidebarItems">
<div class="sidebar-header" v-if="!item.to && !item.children">
{{ item.label }}
</div>
<RouterLink
:to="item.to"
v-if="item.to && !item.children"
class="sidebar-default"
:class="isActive(item) ? 'sidebar-item-active' : ''"
@click="isActive(item) ? toggleDropdown(-1) : null"
>
<div>
<Icons :name="item.icon" size="20" />
</div>
<span>{{ item.label }}</span>
</RouterLink>
<div
v-if="item.children && !item.to"
class="sidebar-dropdown"
:class="activeDropdown == index ? 'sidebar-item-open' : ''"
>
<div class="sidebar-dropdown-header" @click="toggleDropdown(index)">
<div>
<Icons :name="item.icon" size="20" />
</div>
<span>{{ item.label }}</span>
<div class="sidebar-chevron">
<Icons name="ChevronRight" size="14" />
</div>
</div>
<div class="sidebar-dropdown-items" v-animate-dropdown>
<div class="sidebar-dropdown-item" v-for="childitem in item.children">
<!-- v-if="childitem.permission ? hasPermission(childitem.permission) : true" -->
<RouterLink :to="childitem.to" :class="isActive(childitem) ? 'sidebar-item-active' : ''">
<span>{{ childitem.label }}</span>
</RouterLink>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, type PropType, ref } from "vue";
import { useRouter } from "vue-router";
import type { SidebarItem } from "@/core/types/app/sidebar.type";
import { useCore } from "@/stores/App/core.store";
const router = useRouter();
const coreStore = useCore();
const props = defineProps({
sidebarItems: {
type: Array as PropType<SidebarItem[]>,
required: true,
},
});
const activeDropdown = ref(-1);
const toggleDropdown = (val: number) => {
if (activeDropdown.value == val) {
activeDropdown.value = -1;
} else {
activeDropdown.value = val;
}
};
const isActive = (item: SidebarItem) => {
if (item.to == router.currentRoute.value.path) {
return true;
}
};
onMounted(() => {
checkDropDown();
});
const checkDropDown = () => {
if (props.sidebarItems) {
props.sidebarItems.forEach((item) => {
if (item.children && !item.to) {
item.children.forEach((child) => {
if (child.to == router.currentRoute.value.path) {
toggleDropdown(props.sidebarItems.indexOf(item));
}
});
}
});
}
};
</script>

26
src/main.ts Normal file
View File

@ -0,0 +1,26 @@
// @ts-ignore
import "@/assets/css/main.css";
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
//Setups
import setupComponents from "./core/setup/components.setup";
import setupDirectives from "./core/setup/directives.setup";
import setupPlugins from "./core/setup/plugins.setup";
import setupRouter from "./core/setup/router.setup";
export const initAPP = () => {
const app = createApp(App);
app.use(createPinia());
setupComponents(app);
setupDirectives(app);
setupPlugins(app);
setupRouter(app);
app.mount("#app");
};

119
src/router/auth.router.ts Normal file
View File

@ -0,0 +1,119 @@
import type { RouteRecordRaw } from "vue-router";
const authChildren: Array<RouteRecordRaw> = [
{
path: "/",
name: "dashboard",
component: () => import("@/views/Dashboard/Index.vue"),
},
{
path: "/component",
name: "component",
children: [
{
path: "content-layout",
name: "content-layout",
component: () => import("@/views/Components/ContentLayout.vue"),
},
{
path: "modal",
name: "modal",
component: () => import("@/views/Components/Modal.vue"),
},
{
path: "switch",
name: "switch",
component: () => import("@/views/Components/Switch.vue"),
},
{
path: "toggle",
name: "toggle",
component: () => import("@/views/Components/Toggle.vue"),
},
{
path: "towser",
name: "towser",
component: () => import("@/views/Components/Towser.vue"),
},
{
path: "tabulator",
name: "tabulator",
component: () => import("@/views/Components/Tabulator.vue"),
},
{
path: "date-range",
name: "date-range",
component: () => import("@/views/Components/DateRange.vue"),
},
{
path: "date-selector",
name: "date-selector",
component: () => import("@/views/Components/DateSelector.vue"),
},
{
path: "wizard",
name: "wizard",
component: () => import("@/views/Components/Wizard.vue"),
},
{
path: "loader",
name: "loader",
component: () => import("@/views/Components/Loader.vue"),
},
{
path: "image-cropper",
name: "image-cropper",
component: () => import("@/views/Components/ImageCropper.vue"),
},
],
},
{
path: "/directives",
name: "directives",
children: [
{
path: "input-error",
name: "input-error",
component: () => import("@/views/Directives/InputError.vue"),
},
{
path: "input-currency",
name: "input-currency",
component: () => import("@/views/Directives/InputCurrency.vue"),
},
{
path: "input-select",
name: "input-select",
component: () => import("@/views/Directives/InputSelect.vue"),
},
{
path: "tooltip",
name: "tooltip",
component: () => import("@/views/Directives/Tooltip.vue"),
},
],
},
{
path: "/css",
name: "css",
children: [
{
path: "button",
name: "button",
component: () => import("@/views/CSS/Button.vue"),
},
{
path: "input",
name: "input",
component: () => import("@/views/CSS/Input.vue"),
},
{
path: "font",
name: "font",
component: () => import("@/views/CSS/Font.vue"),
},
],
},
];
export default authChildren;

27
src/router/index.ts Normal file
View File

@ -0,0 +1,27 @@
import { createRouter, createWebHistory } from "vue-router";
import authChildren from "./auth.router";
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: "/",
name: "authLayout",
component: () => import("@/layouts/AuthLayout.vue"),
meta: {
requireAuth: true,
},
children: authChildren,
},
{
path: "/login",
name: "login",
meta: {
onlyGuest: true,
},
component: () => import("@/views/Auth/Login.vue"),
},
],
});
export default router;

10
src/router/router.ts Normal file
View File

@ -0,0 +1,10 @@
import "vue-router";
export {};
declare module "vue-router" {
interface RouteMeta {
requireAuth?: boolean;
onlyGuest?: boolean;
}
}

View File

@ -0,0 +1,19 @@
import { defineStore } from "pinia";
export const useCore = defineStore("core", {
state: () => ({
sidebarOpen: true,
}),
getters: {
isSidebarOpen(state) {
return state.sidebarOpen;
},
},
actions: {
setSidebarStatus(value: boolean) {
this.sidebarOpen = value;
},
},
});

View File

@ -0,0 +1,19 @@
import { defineStore } from "pinia";
export const useLoader = defineStore("loader", {
state: () => ({
loader: false,
}),
getters: {
isLoading(state) {
return state.loader;
},
},
actions: {
setLoaderStatus(value: boolean) {
this.loader = value;
},
},
});

View File

@ -0,0 +1,68 @@
import api from "@/core/app/api";
import { auth, user } from "@/core/app/endpoints";
import type { LoginDetails, UserDetails } from "@/core/types/auth/auth.type";
import { defineStore } from "pinia";
import { useLoader } from "../App/loader.store";
import router from "@/router";
export const useAuth = defineStore("auth", {
state: () => ({
loginDetails: {} as LoginDetails,
isAuthenticated: false,
user: {} as UserDetails,
}),
getters: {
getLoginDetails(state) {
return state.loginDetails;
},
},
actions: {
async login() {
const loaderStore = useLoader();
loaderStore.setLoaderStatus(true);
await api
.post(auth.login, this.loginDetails)
.then(() => {
loaderStore.setLoaderStatus(false);
this.isAuthenticated = true;
router.push({ name: "dashboard" });
})
.catch(() => {
loaderStore.setLoaderStatus(false);
this.isAuthenticated = false;
});
},
async getUser() {
const loaderStore = useLoader();
loaderStore.setLoaderStatus(true);
await api
.get(user.self)
.then((e) => {
this.user = e.data.data;
loaderStore.setLoaderStatus(false);
this.isAuthenticated = true;
})
.catch(() => {
this.user = {} as UserDetails;
loaderStore.setLoaderStatus(false);
this.isAuthenticated = false;
});
},
async logout() {
const loaderStore = useLoader();
loaderStore.setLoaderStatus(true);
await api
.post(auth.logout)
.then(() => {
this.isAuthenticated = false;
router.push({ name: "login" });
})
.catch((e) => {
console.log(e);
});
},
},
});

12
src/stores/counter.ts Normal file
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 };
});

110
src/views/Auth/Login.vue Normal file
View File

@ -0,0 +1,110 @@
<template>
<div class="login" style="height: 100vh; background-image: url(/svg/large-triangles.svg); background-size: cover">
<div class="w-[375px] mx-auto">
<div class="flex justify-center">
<img src="/img/Dolphin/dolphin-logo.png" class="w-36 my-5" />
</div>
<div class="border border-[#99A1AF] rounded-xs px-10 py-10 bg-white select-none">
<div class="text-center" :class="getLoginError ? '' : 'mb-[10px]'">
<p class="text-[30px] text-semibold">Sign In</p>
<div class="text-sm font-normal">Fill your detail to sign in to Dolphin PIS.</div>
</div>
<div class="mt-[12px] mb-[-19px]" v-if="getLoginError">
<div class="text-sm text-red-700 font-semibold text-center flex gap-[10px]">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-5 -mr-3"
>
<path
fill-rule="evenodd"
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
clip-rule="evenodd"
/>
</svg>
<div>The username or password you entered is incorrect.</div>
</div>
</div>
<form @submit.prevent="authStore.login" class="pt-10">
<div class="">
<label for="username">Username</label>
<div class="my-1">
<input
v-model="loginDetails.username"
id="username"
name="username"
type="text"
autocomplete="username"
required
class="p-[10px] max-h-[40px]! h-[40px]! text-[14px] w-full"
/>
</div>
</div>
<div class="my-[15px]">
<label for="username">Password</label>
<div class="my-1 flex">
<!--When show password is false-->
<input
v-model="loginDetails.password"
id="password"
name="password"
:type="!showPassword ? 'password' : 'text'"
autocomplete="current-password"
required
class="p-[10px] max-h-[40px]! h-[40px]! text-[14px] w-full"
style="border-radius: 2px 0 0 2px !important"
/>
<span
class="bg-gray-100 flex w-[50px] border-r border-t border-b border-gray-300 hover:bg-gray-200 cursor-pointer transition-all"
@click="showPassword = !showPassword"
>
<div class="m-auto">
<Icons
name="EyeOff"
size="20"
class="text-lg text-gray-900 mt-1 mr-[-1px]"
v-if="!showPassword"
/>
<Icons name="Eye" class="text-lg text-gray-900 mt-1" size="20" v-else />
</div>
</span>
</div>
</div>
<button
type="submit"
class="btn btn-primary w-full max-h-[40px]! h-[40px]! text-lg!"
:class="isLoading ? 'btn-loading' : ''"
>
Sign In
</button>
</form>
</div>
<div class="text-xs text-center my-5 select-none">
<div>
© 2024
<a href="https://mavorion.com" class="text-[#224CAD] underline">Mavorion Systems</a>
. All rights reserved.
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useLoader } from "@/stores/App/loader.store";
import { useAuth } from "@/stores/Auth/auth.store";
import { storeToRefs } from "pinia";
import { ref } from "vue";
const authStore = useAuth();
const loaderStore = useLoader();
const { loginDetails } = storeToRefs(authStore);
const { isLoading } = storeToRefs(loaderStore);
const showPassword = ref(false);
const getLoginError = "";
</script>

132
src/views/CSS/Button.vue Normal file
View File

@ -0,0 +1,132 @@
<template>
<div>
<h1 class="font-bold text-[25px] text-accent-foreground">Default Buttons</h1>
<div class="flex gap-3.75">
<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-success">Success</button>
<button class="btn btn-warning">Warning</button>
<button class="btn btn-danger">Danger</button>
<button class="btn btn-info">Info</button>
<button class="btn btn-dark">Dark</button>
<button class="btn btn-purple">Purple</button>
</div>
</div>
<div class="mt-5">
<h1 class="font-bold text-[25px] text-accent-foreground">Default Disabled Buttons</h1>
<div class="flex gap-3.75">
<button class="btn btn-primary" disabled>Primary</button>
<button class="btn btn-secondary" disabled>Secondary</button>
<button class="btn btn-success" disabled>Success</button>
<button class="btn btn-warning" disabled>Warning</button>
<button class="btn btn-danger" disabled>Danger</button>
<button class="btn btn-info" disabled>Info</button>
<button class="btn btn-dark" disabled>Dark</button>
<button class="btn btn-purple" disabled>Purple</button>
</div>
</div>
<div class="mt-5">
<h1 class="font-bold text-[25px] text-accent-foreground">Outline Buttons</h1>
<div class="flex gap-3.75">
<button class="btn btn-outline-primary">Primary</button>
<button class="btn btn-outline-secondary">Secondary</button>
<button class="btn btn-outline-success">Success</button>
<button class="btn btn-outline-warning">Warning</button>
<button class="btn btn-outline-danger">Danger</button>
<button class="btn btn-outline-info">Info</button>
<button class="btn btn-outline-dark">Dark</button>
<button class="btn btn-outline-purple">Purple</button>
</div>
</div>
<div class="mt-5">
<h1 class="font-bold text-[25px] text-accent-foreground">Outline Disabled Buttons</h1>
<div class="flex gap-3.75">
<button class="btn btn-outline-primary" disabled>Primary</button>
<button class="btn btn-outline-secondary" disabled>Secondary</button>
<button class="btn btn-outline-success" disabled>Success</button>
<button class="btn btn-outline-warning" disabled>Warning</button>
<button class="btn btn-outline-danger" disabled>Danger</button>
<button class="btn btn-outline-info" disabled>Info</button>
<button class="btn btn-outline-dark" disabled>Dark</button>
<button class="btn btn-outline-purple" disabled>Purple</button>
</div>
</div>
<div class="mt-5">
<h1 class="font-bold text-[25px] text-accent-foreground">Ghost Buttons</h1>
<div class="flex gap-3.75">
<button class="btn btn-ghost-primary">Primary</button>
<button class="btn btn-ghost-secondary">Secondary</button>
<button class="btn btn-ghost-success">Success</button>
<button class="btn btn-ghost-warning">Warning</button>
<button class="btn btn-ghost-danger">Danger</button>
<button class="btn btn-ghost-info">Info</button>
<button class="btn btn-ghost-purple">Purple</button>
</div>
</div>
<div class="mt-5">
<h1 class="font-bold text-[25px] text-accent-foreground">Ghost Disabled Buttons</h1>
<div class="flex gap-3.75">
<button class="btn btn-ghost-primary" disabled>Primary</button>
<button class="btn btn-ghost-secondary" disabled>Secondary</button>
<button class="btn btn-ghost-success" disabled>Success</button>
<button class="btn btn-ghost-warning" disabled>Warning</button>
<button class="btn btn-ghost-danger" disabled>Danger</button>
<button class="btn btn-ghost-info" disabled>Info</button>
<button class="btn btn-ghost-purple" disabled>Purple</button>
</div>
</div>
<div class="mt-5">
<h1 class="font-bold text-[25px] text-accent-foreground">Button Loading</h1>
<div class="flex gap-3.75">
<button class="btn btn-primary btn-loading">Primary</button>
<button class="btn btn-secondary btn-loading">Secondary</button>
<button class="btn btn-success btn-loading">Success</button>
<button class="btn btn-warning btn-loading">Warning</button>
<button class="btn btn-danger btn-loading">Danger</button>
<button class="btn btn-info btn-loading">Info</button>
<button class="btn btn-dark btn-loading">Dark</button>
<button class="btn btn-purple btn-loading">Purple</button>
</div>
</div>
<div class="mt-5">
<h1 class="font-bold text-[25px] text-accent-foreground">Outline Button Loading</h1>
<div class="flex gap-3.75">
<button class="btn btn-outline-primary btn-loading">Primary</button>
<button class="btn btn-outline-secondary btn-loading">Secondary</button>
<button class="btn btn-outline-success btn-loading">Success</button>
<button class="btn btn-outline-warning btn-loading">Warning</button>
<button class="btn btn-outline-danger btn-loading">Danger</button>
<button class="btn btn-outline-info btn-loading">Info</button>
<button class="btn btn-outline-purple btn-loading">Purple</button>
</div>
</div>
<div class="mt-5">
<h1 class="font-bold text-[25px] text-accent-foreground">Ghost Button Loading</h1>
<div class="flex gap-3.75">
<button class="btn btn-ghost-primary btn-loading">Primary</button>
<button class="btn btn-ghost-secondary btn-loading">Secondary</button>
<button class="btn btn-ghost-success btn-loading">Success</button>
<button class="btn btn-ghost-warning btn-loading">Warning</button>
<button class="btn btn-ghost-danger btn-loading">Danger</button>
<button class="btn btn-ghost-info btn-loading">Info</button>
<button class="btn btn-ghost-purple btn-loading">Purple</button>
</div>
</div>
<div class="mt-5">
<h1 class="font-bold text-[25px] text-accent-foreground">Button Group</h1>
<div class="flex gap-3.75">
<div class="btn-group">
<button class="btn btn-primary">First</button>
<button class="btn btn-secondary">Second</button>
<button class="btn btn-warning">Third</button>
</div>
<div class="btn-group">
<button class="btn btn-outline-secondary">First</button>
<button class="btn btn-outline-secondary">Second</button>
<button class="btn btn-outline-secondary">Third</button>
</div>
</div>
</div>
</template>
<script setup lang="ts"></script>

72
src/views/CSS/Font.vue Normal file
View File

@ -0,0 +1,72 @@
<template>
<div>
<h1 class="font-bold text-[25px] text-accent-foreground">Font Overview</h1>
<div class="pl-5">
<div>
<h2 class="font-bold text-[20px] text-accent-foreground">1) Open Sans (Main Content)</h2>
<div class="pl-5">
<div>
<span class="font-bold">Alphabet (Capital):</span>
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
</div>
<div>
<span class="font-bold">Alphabet (Small):</span>
a b c d e f g h i j k l m n o p q r s t u v w x y z
</div>
<div>
<span class="font-bold">Number:</span>
1 2 3 4 5 6 7 8 9 0
</div>
<div>
<span class="font-bold">Special Character:</span>
~ ``` ! @ # $ % ^ & * () - _ + = {} [] : ; "" '' {{ "<>" }} , . ? / | \
</div>
</div>
</div>
<div>
<h2 class="font-bold text-[20px] text-accent-foreground">2) Kumbh Sans (Sidebar Content)</h2>
<div class="pl-5 font-kumbh">
<div>
<span class="font-bold">Alphabet (Capital):</span>
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
</div>
<div>
<span class="font-bold">Alphabet (Small):</span>
a b c d e f g h i j k l m n o p q r s t u v w x y z
</div>
<div>
<span class="font-bold">Number:</span>
1 2 3 4 5 6 7 8 9 0
</div>
<div>
<span class="font-bold">Special Character:</span>
~ ``` ! @ # $ % ^ & * () - _ + = {} [] : ; "" '' {{ "<>" }} , . ? / | \
</div>
</div>
</div>
<div>
<h2 class="font-bold text-[20px] text-accent-foreground">3) Roboto (Login Page)</h2>
<div class="pl-5 font-roboto">
<div>
<span class="font-bold">Alphabet (Capital):</span>
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
</div>
<div>
<span class="font-bold">Alphabet (Small):</span>
a b c d e f g h i j k l m n o p q r s t u v w x y z
</div>
<div>
<span class="font-bold">Number:</span>
1 2 3 4 5 6 7 8 9 0
</div>
<div>
<span class="font-bold">Special Character:</span>
~ ``` ! @ # $ % ^ & * () - _ + = {} [] : ; "" '' {{ "<>" }} , . ? / | \
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts"></script>

501
src/views/CSS/Input.vue Normal file
View File

@ -0,0 +1,501 @@
<template>
<div>
<h1 class="text-3xl font-bold mb-8">Input Component Examples</h1>
<!-- Basic Inputs -->
<section class="space-y-4">
<h2 class="text-2xl font-semibold">Basic Inputs</h2>
<div class="form-field">
<label>Default Input</label>
<input type="text" v-model="basicInput" placeholder="Enter text..." class="input-full" />
</div>
<div class="form-field">
<label>Disabled Input</label>
<input type="text" value="Disabled input" disabled class="input-full" />
</div>
<div class="form-field">
<label>Readonly Input</label>
<input type="text" value="Readonly input" readonly class="input-full" />
</div>
<div class="form-field">
<label>Textarea</label>
<textarea v-model="textareaValue" placeholder="Enter multiple lines..." class="input-full"></textarea>
</div>
</section>
<!-- Text Alignment -->
<section class="space-y-4">
<h2 class="text-2xl font-semibold">Text Alignment</h2>
<div class="grid grid-cols-3 gap-4">
<div class="form-field">
<label>Left Aligned</label>
<input type="text" value="Left" class="input-left input-full" />
</div>
<div class="form-field">
<label>Center Aligned</label>
<input type="text" value="Center" class="input-center input-full" />
</div>
<div class="form-field">
<label>Right Aligned</label>
<input type="text" value="Right" class="input-right input-full" />
</div>
</div>
</section>
<!-- Input States -->
<section class="space-y-4">
<h2 class="text-2xl font-semibold">Input States</h2>
<div class="grid grid-cols-2 gap-4">
<div class="form-field">
<label>Error State</label>
<input type="text" value="Invalid input" class="input-error input-full" />
<span class="field-error">This field is required</span>
</div>
<div class="form-field">
<label>Success State</label>
<input type="text" value="Valid input" class="input-success input-full" />
</div>
<div class="form-field">
<label>Warning State</label>
<input type="text" value="Warning input" class="input-warning input-full" />
<span class="text-xs text-yellow-600">This might cause issues</span>
</div>
<div class="form-field">
<label>Info State</label>
<input type="text" value="Info input" class="input-info input-full" />
<span class="field-hint">Additional information here</span>
</div>
</div>
</section>
<!-- Input with Icons -->
<section class="space-y-4">
<h2 class="text-2xl font-semibold">Input with Icons</h2>
<div class="grid grid-cols-2 gap-4">
<div class="form-field">
<label>Left Icon</label>
<div class="input-icon-wrapper">
<input type="text" placeholder="Search..." class="input-full" />
<svg class="input-icon-left w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
></path>
</svg>
</div>
</div>
<div class="form-field">
<label>Right Icon</label>
<div class="input-icon-wrapper has-icon-right">
<input type="email" v-model="email" placeholder="Email address" class="input-full" />
<svg class="input-icon-right w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
></path>
</svg>
</div>
</div>
<div class="form-field">
<label>Both Icons</label>
<div class="input-icon-wrapper has-icon-both">
<input type="text" placeholder="Username" class="input-full" />
<svg class="input-icon-left w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
></path>
</svg>
<svg
class="input-icon-right input-icon-clickable w-4 h-4"
@click="showPassword = !showPassword"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
></path>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
></path>
</svg>
</div>
</div>
<div class="form-field">
<label>Clearable Input</label>
<div class="input-clearable-wrapper">
<input
type="text"
v-model="clearableInput"
placeholder="Type something..."
class="input-full"
/>
<svg
class="input-clear-btn w-4 h-4"
@click="clearableInput = ''"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
></path>
</svg>
</div>
</div>
</div>
</section>
<!-- Input with Buttons -->
<section class="space-y-4">
<h2 class="text-2xl font-semibold">Input with Buttons</h2>
<div class="space-y-3">
<div class="form-field">
<label>Right Button</label>
<div class="input-side-btn">
<input type="text" v-model="searchQuery" placeholder="Search..." class="flex-1" />
<button class="btn btn-primary" @click="handleSearch">Search</button>
</div>
</div>
<div class="form-field">
<label>Left Button</label>
<div class="input-side-btn-left">
<button class="btn btn-secondary">$</button>
<input type="number" v-model="price" placeholder="0.00" class="flex-1 input-right" />
</div>
</div>
<div class="form-field">
<label>Both Buttons</label>
<div class="input-both-btn">
<button class="btn btn-outline-secondary" @click="decrementCounter">-</button>
<input type="number" v-model="counter" class="flex-1 input-center" readonly />
<button class="btn btn-outline-secondary" @click="incrementCounter">+</button>
</div>
</div>
</div>
</section>
<!-- Input Groups -->
<section class="space-y-4">
<h2 class="text-2xl font-semibold">Input Groups</h2>
<div class="space-y-3">
<div class="form-field">
<label>Multiple Inputs</label>
<div class="input-group">
<input type="text" v-model="firstName" placeholder="First name" class="flex-1" />
<input type="text" v-model="lastName" placeholder="Last name" class="flex-1" />
</div>
</div>
<div class="form-field">
<label>With Addons</label>
<div class="flex">
<span class="input-addon-left">https://</span>
<input
type="text"
v-model="website"
placeholder="example.com"
class="flex-1 rounded-none border-x-0"
/>
<span class="input-addon-right">.com</span>
</div>
</div>
</div>
</section>
<!-- Floating Label -->
<section class="space-y-4">
<h2 class="text-2xl font-semibold">Floating Labels</h2>
<div class="grid grid-cols-2 gap-4">
<div class="input-floating-wrapper">
<input type="text" v-model="floatingName" placeholder=" " class="input-full" />
<label>Full Name</label>
</div>
<div class="input-floating-wrapper">
<input type="email" v-model="floatingEmail" placeholder=" " class="input-full" />
<label>Email Address</label>
</div>
<div class="input-floating-wrapper col-span-2">
<textarea v-model="floatingMessage" placeholder=" " class="input-full"></textarea>
<label>Your Message</label>
</div>
</div>
</section>
<!-- Special Input Types -->
<section class="space-y-4">
<h2 class="text-2xl font-semibold">Special Input Types</h2>
<div class="grid grid-cols-2 gap-4">
<div class="form-field">
<label>Number Input (No Spinner)</label>
<input
type="number"
v-model="numberValue"
class="input-no-spinner input-full"
placeholder="Enter number"
/>
</div>
<div class="form-field">
<label>Time Input</label>
<input type="time" v-model="timeValue" class="input-full" />
</div>
<div class="form-field">
<label>Color Picker</label>
<input type="color" v-model="colorValue" />
</div>
<div class="form-field col-span-2">
<label>Range Slider: {{ rangeValue }}</label>
<input type="range" v-model="rangeValue" min="0" max="100" class="w-full" />
</div>
<div class="form-field col-span-2">
<label>File Upload</label>
<input type="file" @change="handleFileUpload" class="input-file input-full" />
</div>
</div>
</section>
<!-- Checkboxes and Radio -->
<section class="space-y-4">
<h2 class="text-2xl font-semibold">Checkboxes & Radio Buttons</h2>
<div class="space-y-4">
<div>
<label class="text-sm font-medium text-gray-700 mb-2 block">Checkbox Sizes</label>
<div class="flex items-center gap-4">
<label class="flex items-center gap-2">
<input type="checkbox" v-model="checkSmall" class="input-sm" />
<span class="text-sm">Small</span>
</label>
<label class="flex items-center gap-2">
<input type="checkbox" v-model="checkDefault" />
<span>Default</span>
</label>
<label class="flex items-center gap-2">
<input type="checkbox" v-model="checkLarge" class="input-lg" />
<span class="text-lg">Large</span>
</label>
</div>
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-2 block">Radio Buttons</label>
<div class="flex items-center gap-4">
<label class="flex items-center gap-2">
<input type="radio" v-model="radioValue" value="option1" />
<span>Option 1</span>
</label>
<label class="flex items-center gap-2">
<input type="radio" v-model="radioValue" value="option2" />
<span>Option 2</span>
</label>
<label class="flex items-center gap-2">
<input type="radio" v-model="radioValue" value="option3" />
<span>Option 3</span>
</label>
</div>
</div>
</div>
</section>
<!-- Input Variants -->
<section class="space-y-4">
<h2 class="text-2xl font-semibold">Input Style Variants</h2>
<div class="space-y-3">
<div class="form-field">
<label>Borderless Input</label>
<input type="text" placeholder="No borders..." class="input-borderless input-full bg-gray-50" />
</div>
<div class="form-field">
<label>Underline Input</label>
<input type="text" placeholder="Underline style..." class="input-underline input-full" />
</div>
</div>
</section>
<!-- Loading State -->
<section class="space-y-4">
<h2 class="text-2xl font-semibold">Loading State</h2>
<div class="form-field">
<label>Loading Input</label>
<input type="text" value="Processing..." class="input-loading input-full" readonly />
</div>
</section>
<!-- Form Field with Required -->
<section class="space-y-4">
<h2 class="text-2xl font-semibold">Complete Form Field</h2>
<div class="form-field field-required">
<label>Username</label>
<input
type="text"
v-model="username"
placeholder="Enter username"
class="input-full"
:class="{ 'input-error': usernameError }"
/>
<span v-if="usernameError" class="field-error">{{ usernameError }}</span>
<span v-else class="field-hint">Must be at least 3 characters</span>
</div>
<div class="form-field field-required">
<label>Password</label>
<input
:type="showPassword ? 'text' : 'password'"
v-model="password"
placeholder="Enter password"
class="input-full"
:class="{ 'input-error': passwordError }"
/>
<span v-if="passwordError" class="field-error">{{ passwordError }}</span>
<span v-else class="field-hint">Must contain at least 8 characters</span>
</div>
</section>
<!-- Form Actions -->
<section class="flex gap-4">
<button class="btn btn-primary" @click="handleSubmit">Submit Form</button>
<button class="btn btn-outline-secondary" @click="handleReset">Reset</button>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
// Basic inputs
const basicInput = ref("");
const textareaValue = ref("");
const email = ref("");
const clearableInput = ref("");
// Search
const searchQuery = ref("");
// Counter
const counter = ref(0);
const incrementCounter = () => counter.value++;
const decrementCounter = () => counter.value--;
// Price
const price = ref("");
// Input groups
const firstName = ref("");
const lastName = ref("");
const website = ref("");
// Floating labels
const floatingName = ref("");
const floatingEmail = ref("");
const floatingMessage = ref("");
// Special inputs
const numberValue = ref("");
const timeValue = ref("");
const colorValue = ref("#3b82f6");
const rangeValue = ref(50);
// Checkboxes
const checkSmall = ref(false);
const checkDefault = ref(true);
const checkLarge = ref(false);
const radioValue = ref("option1");
// Form validation
const username = ref("");
const password = ref("");
const showPassword = ref(false);
const usernameError = computed(() => {
if (username.value && username.value.length < 3) {
return "Username must be at least 3 characters";
}
return "";
});
const passwordError = computed(() => {
if (password.value && password.value.length < 8) {
return "Password must be at least 8 characters";
}
return "";
});
// Handlers
const handleSearch = () => {
console.log("Searching for:", searchQuery.value);
alert(`Searching for: ${searchQuery.value}`);
};
const handleFileUpload = (event: any) => {
const file = event.target.files[0];
if (file) {
console.log("File uploaded:", file.name);
alert(`File uploaded: ${file.name}`);
}
};
const handleSubmit = () => {
if (usernameError.value || passwordError.value) {
alert("Please fix the errors before submitting");
return;
}
console.log("Form submitted:", { username: username.value, password: password.value });
alert("Form submitted successfully!");
};
const handleReset = () => {
username.value = "";
password.value = "";
basicInput.value = "";
textareaValue.value = "";
searchQuery.value = "";
counter.value = 0;
alert("Form reset!");
};
</script>
<style scoped>
/* Ensure the CSS from the input styles document is imported in your main CSS file */
</style>

View File

@ -0,0 +1,59 @@
<template>
<Loader :show="isLoading" />
<ContentLayout
:title="breadcrumb"
:actions="actions"
bodyClass="p-4 bg-white"
titleClass="justify-between items-center"
>
<template #body>
<p>This is the main content body section.</p>
</template>
<template #action-before>
<button class="btn btn-secondary" @click="startLoading">Loader</button>
</template>
<template #action-after>
<button class="btn btn-warning" @click="handleAfterAction">After Action</button>
</template>
</ContentLayout>
</template>
<script setup lang="ts">
import { ref } from "vue";
const breadcrumb = ref([
{ name: "Home", link: "#" },
{ name: "Dashboard", link: "#" },
{ name: "Reports", count: 3 },
]);
const actions = ref([
{
title: "Add Report",
emit: "add-report",
class: "btn btn-primary",
},
{
title: "Export",
emit: "export-data",
class: "btn btn-outline-primary",
},
]);
const handleAfterAction = () => {
console.log("After action clicked");
};
const isLoading = ref(false);
const startLoading = () => {
isLoading.value = true;
// Simulate async operation
setTimeout(() => {
isLoading.value = false;
}, 3000);
};
</script>

View File

@ -0,0 +1,23 @@
<template>
<DateRange v-model="dateRange" @search="handleSearch" />
</template>
<script setup lang="ts">
import { ref } from "vue";
const dateRange = ref({
startDate: "",
endDate: "",
dateType: "today",
isBS: false,
});
function handleSearch(dateObject: any) {
console.log("Searching with dates:", dateObject);
fetchReportData(dateObject.startDate, dateObject.endDate);
}
function fetchReportData(startDate: string, endDate: string) {
console.log(`Fetching data from ${startDate} to ${endDate}`);
}
</script>

View File

@ -0,0 +1,29 @@
<template>
<div>
<!-- Basic usage -->
<h1 class="font-bold text-[25px] text-accent-foreground">Basic</h1>
<DateSelector v-model="date" />
<!-- With custom width -->
<h1 class="font-bold text-[25px] text-accent-foreground">Custom Width</h1>
<DateSelector v-model="date" classValue="w-full" />
<!-- Start in BS mode -->
<h1 class="font-bold text-[25px] text-accent-foreground">Default BS</h1>
<DateSelector v-model="date" :isBS="true" />
<!-- Without toggle button -->
<h1 class="font-bold text-[25px] text-accent-foreground">Toggle Removed</h1>
<DateSelector v-model="date" :showToggle="false" />
<!-- Disabled state -->
<h1 class="font-bold text-[25px] text-accent-foreground">Disabled</h1>
<DateSelector v-model="date" :disabled="true" />
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const date = ref("2025-04-06");
</script>

View File

@ -0,0 +1,19 @@
<template>
<button class="btn btn-primary" @click="showModal = true">Open Modal</button>
<UploadImage :show="showModal" @onClose="showModal = false" @onCrop="handleCrop" :uploadSize="2" />
<img :src="croppedImageVal" alt="" />
</template>
<script setup lang="ts">
import { UploadImage } from "dolphin-components";
import { ref } from "vue";
const showModal = ref(false);
const croppedImageVal = ref("");
const handleCrop = (croppedImage: string) => {
croppedImageVal.value = croppedImage;
console.log("Cropped Image:", croppedImage);
showModal.value = false;
};
</script>

View File

@ -0,0 +1,26 @@
<template>
<div>
<!-- Your main content -->
<button @click="startLoading" class="btn btn-ghost-primary">Load Data</button>
<!-- Loader component -->
<div class="w-full h-[500px]">
<Loader :show="isLoading" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const isLoading = ref(false);
const startLoading = () => {
isLoading.value = true;
// Simulate async operation
setTimeout(() => {
isLoading.value = false;
}, 3000);
};
</script>

View File

@ -0,0 +1,35 @@
<template>
<div>
<button class="btn btn-primary" @click="showModal = true">Open Modal</button>
<Modal
:title="[{ name: 'Home', link: '/' }, { name: 'Settings' }]"
:actions="[
{ title: 'Close', emit: 'onClose', class: 'btn btn-outline-primary' },
{ title: 'Save', emit: 'saveAction', class: 'btn btn-primary' },
]"
:show="showModal"
width="700px"
@onClose="handleClose"
@saveAction="handleSave"
>
<template #default>
<p>This is the content of the modal.</p>
</template>
</Modal>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const showModal = ref(false);
const handleClose = () => {
showModal.value = false;
};
const handleSave = () => {
alert("Saving data...");
showModal.value = false;
};
</script>

View File

@ -0,0 +1,22 @@
<template>
<div>
<h1 class="font-bold text-[25px] text-accent-foreground">Default Switch</h1>
<Switch v-model="isEnabled" />
<h1 class="font-bold text-[25px] text-accent-foreground">Custom Background Color Switch</h1>
<div class="flex gap-[15px]">
<Switch v-model="isEnabled" background="bg-black" />
<Switch v-model="isEnabled" background="bg-red-500" />
<Switch v-model="isEnabled" background="bg-yellow-500" />
<Switch v-model="isEnabled" background="bg-green-500" />
<Switch v-model="isEnabled" background="bg-primary" />
</div>
<h1 class="font-bold text-[25px] text-accent-foreground">Disabled Switch</h1>
<Switch v-model="isEnabled" :disable="true" />
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const isEnabled = ref(false);
</script>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
<template>
<div>
<h1 class="font-bold text-[25px] text-accent-foreground">Toggle Button</h1>
<div class="flex gap-3.75">
<Toggle v-model="isToggled" onText="Subscribed" offText="Not Subscribed" width="200px" />
<Toggle v-model="isToggled" onText="On" offText="Off" width="60px" />
<Toggle v-model="isToggled" onText="BS" offText="AD" width="60px" />
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const isToggled = ref(false);
</script>

View File

@ -0,0 +1,39 @@
<template>
<div class="flex gap-3.75">
<button class="btn btn-primary" @click="showTowser = true">Show Towser</button>
<button class="btn btn-primary" @click="showTab = true">Show Double Column Towser</button>
</div>
<Towser
:show="showTowser"
:title="[{ name: 'Home', link: '/' }, { name: 'Settings' }]"
:actions="[{ title: 'Close', emit: 'onClose', class: 'btn btn-outline-primary' }]"
@onClose="showTowser = false"
>
<p>This is the content of the modal.</p>
</Towser>
<Towser
:show="showTab"
:double="true"
:title="[{ name: 'Dashboard' }, { name: 'Details' }]"
:actions="[
{ title: 'Cancel', emit: 'onClose', class: 'btn btn-outline-primary' },
{ title: 'Save', emit: 'saveAction', class: 'btn btn-primary' },
]"
@onClose="showTab = false"
>
<template #default>
<p>This is the main content.</p>
</template>
<template #body2>
<p>This is the second column.</p>
</template>
</Towser>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const showTowser = ref(false);
const showTab = ref(false);
</script>

View File

@ -0,0 +1,42 @@
<template>
<div class="w-[900px] mx-auto mt-[100px]">
<Wizard
:total-steps="5"
:current-step="currentStep"
:step-labels="['Setup', 'Config', 'Review', 'Deploy', 'Done']"
@step-change="handleLabeledStepChange"
/>
<div class="mt-4 flex gap-2">
<button @click="prevLabeledStep" :disabled="currentStep <= 1" class="btn btn-outline-primary">
Previous
</button>
<button @click="nextLabeledStep" :disabled="currentStep >= 5" class="btn btn-outline-primary">Next</button>
</div>
<p class="mt-2 text-sm text-gray-600">
Current Step: {{ currentStep }} -
{{ ["Setup", "Config", "Review", "Deploy", "Done"][currentStep - 1] }}
</p>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const currentStep = ref(1);
const handleLabeledStepChange = (step: number) => {
currentStep.value = step;
};
const nextLabeledStep = () => {
if (currentStep.value < 5) {
currentStep.value++;
}
};
const prevLabeledStep = () => {
if (currentStep.value > 1) {
currentStep.value--;
}
};
</script>

View File

@ -0,0 +1,5 @@
<template>
<div>test</div>
</template>
<script lang="ts" setup></script>

View File

@ -0,0 +1,32 @@
<template>
<div>
<div>
<h1 class="font-bold text-[25px] text-accent-foreground">English (US Decimal) - ({{ intValue1 }})</h1>
<input type="text" v-input-currency:foreign v-input-select v-model.number="intValue1" id="input1" />
</div>
<div>
<h1 class="font-bold text-[25px] text-accent-foreground">Nepali (US Decimal) - ({{ intValue2 }})</h1>
<input type="text" v-input-currency v-input-select v-model.number="intValue2" id="input2" />
</div>
<div>
<h1 class="font-bold text-[25px] text-accent-foreground">English (Euro Decimal) - ({{ intValue3 }})</h1>
<input type="text" v-input-currency.euro v-input-select v-model.number="intValue3" id="input3" />
</div>
<div>
<h1 class="font-bold text-[25px] text-accent-foreground">Nepali (Euro Decimal) - ({{ intValue4 }})</h1>
<input type="text" v-input-currency:foreign.euro v-input-select v-model.number="intValue4" id="input4" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const intValue1 = ref(1210034.56);
const intValue2 = ref(7180090.12);
const intValue3 = ref(3400156.78);
const intValue4 = ref(9100012.34);
</script>

View File

@ -0,0 +1,14 @@
<template>
<div class="form-field" v-input-error="errorMessage">
<label>Username</label>
<input v-model="username" type="text" placeholder="Enter username" />
</div>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
const username = ref("");
const hasError = computed(() => username.value.length < 3);
const errorMessage = computed(() => (hasError.value ? "Username must be at least 3 characters" : ""));
</script>

View File

@ -0,0 +1,16 @@
<template>
<div class="w-full">
<input v-input-select type="text" v-model="name" class="w-full" />
</div>
<br />
<div class="w-full">
<textarea v-input-select v-model="description" class="w-full"></textarea>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const name = ref("John Doe");
const description = ref("Enter your description here");
</script>

View File

@ -0,0 +1,34 @@
//HMTL Element Load Example
<template>
<button v-tooltip="{ content: 'Save changes', preferredPosition: 'right' }">Save</button>
<button
v-tooltip="{
content: tooltipHtml,
raw: true,
preferredPosition: 'right',
}"
class="btn"
>
Hover Me
</button>
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
const tooltipHtml = ref("");
onMounted(() => {
tooltipHtml.value = `
<div>
<strong>Tooltip Title</strong><br/>
<em>Some italic text</em><br/>
<ul>
<li>Item One</li>
<li>Item Two</li>
<li>Item Three</li>
</ul>
</div>
`;
});
</script>

12
tsconfig.app.json Normal file
View File

@ -0,0 +1,12 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["src/**/*.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"paths": {
"@/*": ["./src/*"]
}
}
}

11
tsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

19
tsconfig.node.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "@tsconfig/node24/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*",
"eslint.config.*"
],
"compilerOptions": {
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

21
vite.config.ts Normal file
View File

@ -0,0 +1,21 @@
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueDevTools from "vite-plugin-vue-devtools";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
define: {
global: "window",
},
plugins: [vue(), vueDevTools(), tailwindcss()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
server: {
port: 3000,
},
});