Commit 02b9b2a8 by claraa26

horizontal navbar and color change

1 parent e6c2765b
Showing with 4828 additions and 0 deletions

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
[*.{js,py}]
charset = utf-8
# 4 space indentation
[*.py]
indent_style = space
indent_size = 4
# 2 space indentation
[*.{vue,scss,ts}]
indent_style = space
indent_size = 2
# Tab indentation (no size specified)
[Makefile]
indent_style = tab
# Indentation override for all JS under lib directory
[lib/**.js]
indent_style = space
indent_size = 2
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2
{
"globals": {
"$api": true,
"COOKIE_MAX_AGE_1_YEAR": true,
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"EffectScope": true,
"ExtractDefaultPropTypes": true,
"ExtractPropTypes": true,
"ExtractPublicPropTypes": true,
"InjectionKey": true,
"PropType": true,
"Ref": true,
"VNode": true,
"WritableComputedRef": true,
"acceptHMRUpdate": true,
"alphaDashValidator": true,
"alphaValidator": true,
"asyncComputed": true,
"autoResetRef": true,
"avatarText": true,
"betweenValidator": true,
"computed": true,
"computedAsync": true,
"computedEager": true,
"computedInject": true,
"computedWithControl": true,
"confirmedValidator": true,
"controlledComputed": true,
"controlledRef": true,
"createApp": true,
"createEventHook": true,
"createGenericProjection": true,
"createGlobalState": true,
"createInjectionState": true,
"createPinia": true,
"createProjection": true,
"createReactiveFn": true,
"createReusableTemplate": true,
"createSharedComposable": true,
"createTemplatePromise": true,
"createUnrefFn": true,
"createUrl": true,
"customRef": true,
"debouncedRef": true,
"debouncedWatch": true,
"defineAsyncComponent": true,
"defineComponent": true,
"definePage": true,
"defineStore": true,
"eagerComputed": true,
"effectScope": true,
"emailValidator": true,
"extendRef": true,
"formatDate": true,
"formatDateToMonthShort": true,
"getActivePinia": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"h": true,
"hexToRgb": true,
"ignorableWatch": true,
"inject": true,
"injectLocal": true,
"integerValidator": true,
"isDefined": true,
"isEmpty": true,
"isEmptyArray": true,
"isNullOrUndefined": true,
"isObject": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"isToday": true,
"kFormatter": true,
"lengthValidator": true,
"logicAnd": true,
"logicNot": true,
"logicOr": true,
"makeDestructurable": true,
"mapActions": true,
"mapGetters": true,
"mapState": true,
"mapStores": true,
"mapWritableState": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeRouteLeave": true,
"onBeforeRouteUpdate": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onClickOutside": true,
"onDeactivated": true,
"onErrorCaptured": true,
"onKeyStroke": true,
"onLongPress": true,
"onMounted": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onStartTyping": true,
"onUnmounted": true,
"onUpdated": true,
"paginationMeta": true,
"passwordValidator": true,
"pausableWatch": true,
"prefixWithPlus": true,
"provide": true,
"provideLocal": true,
"reactify": true,
"reactifyObject": true,
"reactive": true,
"reactiveComputed": true,
"reactiveOmit": true,
"reactivePick": true,
"readonly": true,
"ref": true,
"refAutoReset": true,
"refDebounced": true,
"refDefault": true,
"refThrottled": true,
"refWithControl": true,
"regexValidator": true,
"registerPlugins": true,
"requiredValidator": true,
"resolveComponent": true,
"resolveRef": true,
"resolveUnref": true,
"resolveVuetifyTheme": true,
"rgbaToHex": true,
"setActivePinia": true,
"setMapStoreSuffix": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"storeToRefs": true,
"syncRef": true,
"syncRefs": true,
"templateRef": true,
"throttledRef": true,
"throttledWatch": true,
"toRaw": true,
"toReactive": true,
"toRef": true,
"toRefs": true,
"toValue": true,
"triggerRef": true,
"tryOnBeforeMount": true,
"tryOnBeforeUnmount": true,
"tryOnMounted": true,
"tryOnScopeDispose": true,
"tryOnUnmounted": true,
"unref": true,
"unrefElement": true,
"until": true,
"urlValidator": true,
"useAbility": true,
"useAbs": true,
"useActiveElement": true,
"useAnimate": true,
"useApi": true,
"useArrayDifference": true,
"useArrayEvery": true,
"useArrayFilter": true,
"useArrayFind": true,
"useArrayFindIndex": true,
"useArrayFindLast": true,
"useArrayIncludes": true,
"useArrayJoin": true,
"useArrayMap": true,
"useArrayReduce": true,
"useArraySome": true,
"useArrayUnique": true,
"useAsyncQueue": true,
"useAsyncState": true,
"useAttrs": true,
"useAverage": true,
"useBase64": true,
"useBattery": true,
"useBluetooth": true,
"useBreakpoints": true,
"useBroadcastChannel": true,
"useBrowserLocation": true,
"useCached": true,
"useCeil": true,
"useClamp": true,
"useClipboard": true,
"useClipboardItems": true,
"useCloned": true,
"useColorMode": true,
"useConfirmDialog": true,
"useCookie": true,
"useCounter": true,
"useCssModule": true,
"useCssVar": true,
"useCssVars": true,
"useCurrentElement": true,
"useCycleList": true,
"useDark": true,
"useDateFormat": true,
"useDebounce": true,
"useDebounceFn": true,
"useDebouncedRefHistory": true,
"useDeviceMotion": true,
"useDeviceOrientation": true,
"useDevicePixelRatio": true,
"useDevicesList": true,
"useDisplayMedia": true,
"useDocumentVisibility": true,
"useDraggable": true,
"useDropZone": true,
"useElementBounding": true,
"useElementByPoint": true,
"useElementHover": true,
"useElementSize": true,
"useElementVisibility": true,
"useEventBus": true,
"useEventListener": true,
"useEventSource": true,
"useEyeDropper": true,
"useFavicon": true,
"useFetch": true,
"useFileDialog": true,
"useFileSystemAccess": true,
"useFloor": true,
"useFocus": true,
"useFocusWithin": true,
"useFps": true,
"useFullscreen": true,
"useGamepad": true,
"useGenerateImageVariant": true,
"useGeolocation": true,
"useI18n": true,
"useIdle": true,
"useImage": true,
"useInfiniteScroll": true,
"useIntersectionObserver": true,
"useInterval": true,
"useIntervalFn": true,
"useKeyModifier": true,
"useLastChanged": true,
"useLocalStorage": true,
"useMagicKeys": true,
"useManualRefHistory": true,
"useMath": true,
"useMax": true,
"useMediaControls": true,
"useMediaQuery": true,
"useMemoize": true,
"useMemory": true,
"useMin": true,
"useMounted": true,
"useMouse": true,
"useMouseInElement": true,
"useMousePressed": true,
"useMutationObserver": true,
"useNavigatorLanguage": true,
"useNetwork": true,
"useNow": true,
"useObjectUrl": true,
"useOffsetPagination": true,
"useOnline": true,
"usePageLeave": true,
"useParallax": true,
"useParentElement": true,
"usePerformanceObserver": true,
"usePermission": true,
"usePointer": true,
"usePointerLock": true,
"usePointerSwipe": true,
"usePrecision": true,
"usePreferredColorScheme": true,
"usePreferredContrast": true,
"usePreferredDark": true,
"usePreferredLanguages": true,
"usePreferredReducedMotion": true,
"usePrevious": true,
"useProjection": true,
"useRafFn": true,
"useRefHistory": true,
"useResizeObserver": true,
"useResponsiveLeftSidebar": true,
"useRound": true,
"useRoute": true,
"useRouter": true,
"useScreenOrientation": true,
"useScreenSafeArea": true,
"useScriptTag": true,
"useScroll": true,
"useScrollLock": true,
"useSessionStorage": true,
"useShare": true,
"useSkins": true,
"useSlots": true,
"useSorted": true,
"useSpeechRecognition": true,
"useSpeechSynthesis": true,
"useStepper": true,
"useStorageAsync": true,
"useStyleTag": true,
"useSum": true,
"useSupported": true,
"useSwipe": true,
"useTemplateRefsList": true,
"useTextDirection": true,
"useTextSelection": true,
"useTextareaAutosize": true,
"useThrottle": true,
"useThrottleFn": true,
"useThrottledRefHistory": true,
"useTimeAgo": true,
"useTimeout": true,
"useTimeoutFn": true,
"useTimeoutPoll": true,
"useTimestamp": true,
"useTitle": true,
"useToNumber": true,
"useToString": true,
"useToggle": true,
"useTransition": true,
"useTrunc": true,
"useUrlSearchParams": true,
"useUserMedia": true,
"useVModel": true,
"useVModels": true,
"useVibrate": true,
"useVirtualList": true,
"useWakeLock": true,
"useWebNotification": true,
"useWebSocket": true,
"useWebWorker": true,
"useWebWorkerFn": true,
"useWindowFocus": true,
"useWindowScroll": true,
"useWindowSize": true,
"watch": true,
"watchArray": true,
"watchAtMost": true,
"watchDebounced": true,
"watchDeep": true,
"watchEffect": true,
"watchIgnorable": true,
"watchImmediate": true,
"watchOnce": true,
"watchPausable": true,
"watchPostEffect": true,
"watchSyncEffect": true,
"watchThrottled": true,
"watchTriggerable": true,
"watchWithFilter": true,
"whenever": true,
"DirectiveBinding": true,
"MaybeRef": true,
"MaybeRefOrGetter": true,
"onWatcherCleanup": true,
"useId": true,
"useModel": true,
"useTemplateRef": true
}
}
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
'.eslintrc-auto-import.json',
'plugin:vue/vue3-recommended',
'plugin:import/recommended',
'plugin:promise/recommended',
'plugin:sonarjs/recommended',
'plugin:case-police/recommended',
'plugin:regexp/recommended',
// 'plugin:unicorn/recommended',
],
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 13,
sourceType: 'module',
},
plugins: [
'vue',
'regex',
'regexp',
],
ignorePatterns: ['src/plugins/iconify/*.js', 'node_modules', 'dist', '*.d.ts', 'vendor', '*.json'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
// indentation (Already present in TypeScript)
'comma-spacing': ['error', { before: false, after: true }],
'key-spacing': ['error', { afterColon: true }],
'n/prefer-global/process': ['off'],
'sonarjs/cognitive-complexity': ['off'],
'vue/first-attribute-linebreak': ['error', {
singleline: 'beside',
multiline: 'below',
}],
// indentation (Already present in TypeScript)
'indent': ['error', 2],
// Enforce trailing comma (Already present in TypeScript)
'comma-dangle': ['error', 'always-multiline'],
// Enforce consistent spacing inside braces of object (Already present in TypeScript)
'object-curly-spacing': ['error', 'always'],
// Enforce camelCase naming convention
'camelcase': 'error',
// Disable max-len
'max-len': 'off',
// we don't want it
'semi': ['error', 'never'],
// add parens ony when required in arrow function
'arrow-parens': ['error', 'as-needed'],
// add new line above comment
'newline-before-return': 'error',
// add new line above comment
'lines-around-comment': [
'error',
{
beforeBlockComment: true,
beforeLineComment: true,
allowBlockStart: true,
allowClassStart: true,
allowObjectStart: true,
allowArrayStart: true,
// We don't want to add extra space above closing SECTION
ignorePattern: '!SECTION',
},
],
// Ignore _ as unused variable
'array-element-newline': ['error', 'consistent'],
'array-bracket-newline': ['error', 'consistent'],
'vue/multi-word-component-names': 'off',
'padding-line-between-statements': [
'error',
{ blankLine: 'always', prev: 'expression', next: 'const' },
{ blankLine: 'always', prev: 'const', next: 'expression' },
{ blankLine: 'always', prev: 'multiline-const', next: '*' },
{ blankLine: 'always', prev: '*', next: 'multiline-const' },
],
// Plugin: eslint-plugin-import
'import/prefer-default-export': 'off',
'import/newline-after-import': ['error', { count: 1 }],
'no-restricted-imports': ['error', 'vuetify/components', {
name: 'vue3-apexcharts',
message: 'apexcharts are auto imported',
}],
// For omitting extension for ts files
'import/extensions': [
'error',
'ignorePackages',
{
js: 'never',
jsx: 'never',
ts: 'never',
tsx: 'never',
},
],
// ignore virtual files
'import/no-unresolved': [2, {
ignore: [
'~pages$',
'virtual:generated-layouts',
'#components$',
// Ignore vite's ?raw imports
'.*\?raw',
// Ignore nuxt auth in nuxt version
'#auth$',
],
}],
// Thanks: https://stackoverflow.com/a/63961972/10796681
'no-shadow': 'off',
// Plugin: eslint-plugin-promise
'promise/always-return': 'off',
'promise/catch-or-return': 'off',
// ESLint plugin vue
'vue/block-tag-newline': 'error',
'vue/component-api-style': 'error',
'vue/component-name-in-template-casing': ['error', 'PascalCase', { registeredComponentsOnly: false, ignores: ['/^swiper-/'] }],
'vue/custom-event-name-casing': ['error', 'camelCase', {
ignores: [
'/^(click):[a-z]+((\d)|([A-Z0-9][a-z0-9]+))*([A-Z])?/',
],
}],
'vue/define-macros-order': 'error',
'vue/html-comment-content-newline': 'error',
'vue/html-comment-content-spacing': 'error',
'vue/html-comment-indent': 'error',
'vue/match-component-file-name': 'error',
'vue/no-child-content': 'error',
'vue/require-default-prop': 'off',
'vue/no-duplicate-attr-inheritance': 'error',
'vue/no-empty-component-block': 'error',
'vue/no-multiple-objects-in-class': 'error',
'vue/no-reserved-component-names': 'error',
'vue/no-template-target-blank': 'error',
'vue/no-useless-mustaches': 'error',
'vue/no-useless-v-bind': 'error',
'vue/padding-line-between-blocks': 'error',
'vue/prefer-separate-static-class': 'error',
'vue/prefer-true-attribute-shorthand': 'error',
'vue/v-on-function-call': 'error',
'vue/no-restricted-class': ['error', '/^(p|m)(l|r)-/'],
'vue/valid-v-slot': ['error', {
allowModifiers: true,
}],
// -- Extension Rules
'vue/no-irregular-whitespace': 'error',
'vue/template-curly-spacing': 'error',
// -- Sonarlint
'sonarjs/no-duplicate-string': 'off',
'sonarjs/no-nested-template-literals': 'off',
// -- Unicorn
// 'unicorn/filename-case': 'off',
// 'unicorn/prevent-abbreviations': ['error', {
// replacements: {
// props: false,
// },
// }],
// https://github.com/gmullerb/eslint-plugin-regex
'regex/invalid': [
'error',
[
{
regex: '@/assets/images',
replacement: '@images',
message: 'Use \'@images\' path alias for image imports',
},
{
regex: '@/assets/styles',
replacement: '@styles',
message: 'Use \'@styles\' path alias for importing styles from \'src/assets/styles\'',
},
{
id: 'Disallow icon of icon library',
regex: '(mdi|tabler)-\\w',
message: 'Only \'remix\' icons are allowed',
},
{
regex: '@core/\\w',
message: 'You can\'t use @core when you are in @layouts module',
files: {
inspect: '@layouts/.*',
},
},
{
regex: 'useLayouts\\(',
message: '`useLayouts` composable is only allowed in @layouts & @core directory. Please use `useThemeConfig` composable instead.',
files: {
inspect: '^(?!.*(@core|@layouts)).*',
},
},
],
// Ignore files
'\.eslintrc\.cjs',
],
},
settings: {
'import/resolver': {
node: true,
typescript: { project: './jsconfig.json' },
},
},
}
## GITATTRIBUTES FOR WEB PROJECTS
#
# These settings are for any web project.
#
# Details per file setting:
# text These files should be normalized (i.e. convert CRLF to LF).
# binary These files are binary and should be left untouched.
#
# Note that binary is a macro for -text -diff.
######################################################################
# Auto detect
## Handle line endings automatically for files detected as
## text and leave all files detected as binary untouched.
## This will handle all files NOT defined below.
* text=auto
# Source code
*.bash text eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf
*.coffee text
*.css text diff=css
*.htm text diff=html
*.html text diff=html
*.inc text
*.ini text
*.js text
*.json text
*.jsx text
*.less text
*.ls text
*.map text -diff
*.od text
*.onlydata text
*.php text diff=php
*.pl text
*.ps1 text eol=crlf
*.py text diff=python
*.rb text diff=ruby
*.sass text
*.scm text
*.scss text diff=css
*.sh text eol=lf
.husky/* text eol=lf
*.sql text
*.styl text
*.tag text
*.ts text
*.tsx text
*.xml text
*.xhtml text diff=html
# Docker
Dockerfile text
# Documentation
*.ipynb text eol=lf
*.markdown text diff=markdown
*.md text diff=markdown
*.mdwn text diff=markdown
*.mdown text diff=markdown
*.mkd text diff=markdown
*.mkdn text diff=markdown
*.mdtxt text
*.mdtext text
*.txt text
AUTHORS text
CHANGELOG text
CHANGES text
CONTRIBUTING text
COPYING text
copyright text
*COPYRIGHT* text
INSTALL text
license text
LICENSE text
NEWS text
readme text
*README* text
TODO text
# Templates
*.dot text
*.ejs text
*.erb text
*.haml text
*.handlebars text
*.hbs text
*.hbt text
*.jade text
*.latte text
*.mustache text
*.njk text
*.phtml text
*.svelte text
*.tmpl text
*.tpl text
*.twig text
*.vue text
# Configs
*.cnf text
*.conf text
*.config text
.editorconfig text
.env text
.gitattributes text
.gitconfig text
.htaccess text
*.lock text -diff
package.json text eol=lf
package-lock.json text eol=lf -diff
pnpm-lock.yaml text eol=lf -diff
.prettierrc text
yarn.lock text -diff
*.toml text
*.yaml text
*.yml text
browserslist text
Makefile text
makefile text
# Heroku
Procfile text
# Graphics
*.ai binary
*.bmp binary
*.eps binary
*.gif binary
*.gifv binary
*.ico binary
*.jng binary
*.jp2 binary
*.jpg binary
*.jpeg binary
*.jpx binary
*.jxr binary
*.pdf binary
*.png binary
*.psb binary
*.psd binary
# SVG treated as an asset (binary) by default.
*.svg text
# If you want to treat it as binary,
# use the following line instead.
# *.svg binary
*.svgz binary
*.tif binary
*.tiff binary
*.wbmp binary
*.webp binary
# Audio
*.kar binary
*.m4a binary
*.mid binary
*.midi binary
*.mp3 binary
*.ogg binary
*.ra binary
# Video
*.3gpp binary
*.3gp binary
*.as binary
*.asf binary
*.asx binary
*.avi binary
*.fla binary
*.flv binary
*.m4v binary
*.mng binary
*.mov binary
*.mp4 binary
*.mpeg binary
*.mpg binary
*.ogv binary
*.swc binary
*.swf binary
*.webm binary
# Archives
*.7z binary
*.gz binary
*.jar binary
*.rar binary
*.tar binary
*.zip binary
# Fonts
*.ttf binary
*.eot binary
*.otf binary
*.woff binary
*.woff2 binary
# Executables
*.exe binary
*.pyc binary
# RC files (like .babelrc or .eslintrc)
*.*rc text
# Ignore files (like .npmignore or .gitignore)
*.*ignore text
# 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
*.local
/cypress/videos/
/cypress/screenshots/
# 👉 Custom Git ignores
# Editor directories and files
.vscode/*
!.vscode/extensions.json
!.vscode/settings.json
!.vscode/*.code-snippets
!.vscode/tours
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.yarn
# iconify dist files
src/plugins/iconify/icons.css
# Ignore MSW script
public/mockServiceWorker.js
# Env files
.env*
!.env.example
auto-install-peers=true
shamefully-hoist=true
{
"extends": [
"stylelint-config-standard-scss",
"stylelint-config-idiomatic-order",
"@stylistic/stylelint-config"
],
"plugins": [
"stylelint-use-logical-spec",
"@stylistic/stylelint-plugin"
],
"overrides": [
{
"files": [
"**/*.scss"
],
"customSyntax": "postcss-scss"
},
{
"files": [
"**/*.vue"
],
"customSyntax": "postcss-html"
}
],
"rules": {
"@stylistic/max-line-length": [
220,
{
"ignore": "comments"
}
],
"@stylistic/indentation": 2,
"liberty/use-logical-spec": true,
"selector-class-pattern": null,
"color-function-notation": null,
"annotation-no-unknown": [
true,
{
"ignoreAnnotations": [
"default"
]
}
],
"media-feature-range-notation": null
}
}
{
"Add hand emoji": {
"prefix": "cm-hand-emoji",
"body": [
"👉"
],
"description": "Add hand emoji"
},
"Add info emoji": {
"prefix": "cm-info-emoji",
"body": [
"ℹ️"
],
"description": "Add info emoji"
},
"Add warning emoji": {
"prefix": "cm-warning-emoji",
"body": [
"❗"
],
"description": "Add warning emoji"
}
}
{
"recommendations": [
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig",
"xabikos.javascriptsnippets",
"stylelint.vscode-stylelint",
"fabiospampinato.vscode-highlight",
"github.vscode-pull-request-github",
"vue.volar",
"antfu.iconify",
"cipchk.cssrem",
"matijao.vue-nuxt-snippets",
"dongido.sync-env"
]
}
{
"editor.formatOnSave": true,
"files.insertFinalNewline": true,
"javascript.updateImportsOnFileMove.enabled": "always",
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.autoClosingBrackets": "always"
},
"[markdown]": {
"editor.defaultFormatter": "DavidAnson.vscode-markdownlint"
},
"[scss]": {
"editor.defaultFormatter": "stylelint.vscode-stylelint"
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[vue]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"volar.preview.port": 3000,
"volar.completion.preferredTagNameCase": "pascal",
"eslint.options": {},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit",
"source.organizeImports": "explicit"
},
"eslint.alwaysShowStatus": true,
"eslint.format.enable": true,
"eslint.packageManager": "pnpm",
"stylelint.packageManager": "pnpm",
"stylelint.validate": [
"css",
"scss",
"vue"
],
"cSpell.words": [
"Composables",
"Customizer",
"destr",
"flagpack",
"Iconify",
"ofetch",
"psudo",
"stylelint",
"touchless",
"triggerer",
"vuetify",
"nuxt"
],
"commentAnchors.tags.anchors": {
"ℹ️": {
"scope": "hidden",
"highlightColor": "#3498DB",
"styleComment": true,
"isItalic": false
},
"👉": {
"scope": "file",
"highlightColor": "#98C379",
"styleComment": true,
"isItalic": false
},
"❗": {
"scope": "hidden",
"highlightColor": "#FF2D00",
"styleComment": true,
"isItalic": false
}
},
"highlight.regexFlags": "gi",
"highlight.regexes": {
"(100vh|translate|margin:|padding:|margin-left|margin-right|rotate|text-align|border-top|border-right|border-bottom|border-left|float|background-position|transform|width|height|top|left|bottom|right|float|clear|(p|m)(l|r)-|border-(start|end)-(start|end)-radius)": [
{
"borderWidth": "1px",
"borderColor": "tomato",
"borderStyle": "solid"
}
],
"(overflow-x:|overflow-y:)": [
{
"borderWidth": "1px",
"borderColor": "green",
"borderStyle": "solid"
}
]
}
}
{
"Vue TS - DefineProps": {
"prefix": "dprops",
"body": [
"defineProps<${1:Props}>()"
],
"description": "DefineProps in script setup"
},
"Vue TS - Props interface": {
"prefix": "iprops",
"body": [
"interface Props {",
" ${1}",
"}"
],
"description": "Create props interface in script setup"
}
}
{
"script": {
"prefix": "vue-sfc-ts",
"body": [
"<script lang=\"ts\" setup>",
"",
"</script>",
"",
"<template>",
" ",
"</template>",
"",
"<style lang=\"scss\">",
"",
"</style>",
""
],
"description": "Vue SFC Typescript"
},
"template": {
"scope": "vue",
"prefix": "template",
"body": [
"<template>",
" $1",
"</template>"
],
"description": "Create <template> block"
},
"Script setup + TS": {
"prefix": "script-setup-ts",
"body": [
"<script setup lang=\"ts\">",
"${1}",
"</script>"
],
"description": "Script setup + TS"
},
"style": {
"scope": "vue",
"prefix": "style",
"body": [
"<style lang=\"scss\">",
"$1",
"</style>"
],
"description": "Create <style> block"
},
"use composable": {
"prefix": "use-composable",
"body": [
"const { $2 } = ${1:useComposable}()"
],
"description": "We frequently uses composable in our components and writing const {} = useModule() is tedious. This snippet helps you to write it quickly."
},
"template interpolation": {
"prefix": "cc",
"body": [
"{{ ${1} }}"
],
"description": "We are just making writing template interpolation easier."
}
}
{
"Vuetify Menu -- Parent Activator": {
"prefix": "v-menu",
"body": [
"<v-btn color=\"primary\">",
" Activator",
" <v-menu activator=\"parent\">",
" <v-list>",
" <v-list-item",
" v-for=\"(item, index) in ['apple', 'banana', 'cherry']\"",
" :key=\"index\"",
" :value=\"index\"",
" >",
" <v-list-item-title>{{ item }}</v-list-item-title>",
" </v-list-item>",
" </v-list>",
" </v-menu>",
"</v-btn>"
],
"description": "We use menu component with parent activator mostly because it is compact and easy to understand."
},
"Vuetify CSS variable": {
"prefix": "v-css-var",
"body": [
"rgb(var(--v-${1:theme}))"
],
"description": "Vuetify CSS variable"
},
"Icon only button": {
"prefix": "IconBtn",
"body": [
"<IconBtn>",
" <VIcon icon=\"ri-${1}\" />",
"</IconBtn>"
],
"description": "Icon only button"
},
"Radio Group": {
"prefix": "v-radio-grp",
"body": [
"<v-radio-group v-model=\"${1:modelValue}\">",
" <v-radio",
" v-for=\"item in ['apple', 'banana', 'cherry']\"",
" :key=\"item\"",
" :label=\"item\"",
" :value=\"item\"",
" />",
"</v-radio-group>"
],
"description": "Radio Group"
}
}
# vue
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar) (and disable Vetur).
## Type Support for `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates.
However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can run `Volar: Switch TS Plugin on/off` from VS Code command palette.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.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
```
FROM node:lts
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
COPY . .
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm i; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \
# Allow install without lockfile, so example works even without Node.js installed locally
else echo "Warning: Lockfile not found. It is recommended to commit lockfiles to version control." && yarn install; \
fi
# Note: Don't expose ports here, Compose will handle that for us
# Start vue.js in development mode based on the preferred package manager
CMD \
if [ -f yarn.lock ]; then yarn dev --host; \
elif [ -f package-lock.json ]; then npm run dev -- --host; \
elif [ -f pnpm-lock.yaml ]; then pnpm dev --host; \
else yarn dev --host; \
fi
version: '3'
services:
vue-project:
container_name: vue-project_dev
build:
context: .
dockerfile: dev.Dockerfile
volumes:
- ./src:/app/src
- ./public:/app/public
restart: always
ports:
- 5173:5173
version: '3'
services:
vue-project:
container_name: vue-project_prod
build:
context: .
dockerfile: prod.Dockerfile
image: vue-app
ports:
- 8080:80
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Materio - Vuetify Vuejs Admin Template</title>
<link rel="stylesheet" type="text/css" href="/loader.css" />
</head>
<body>
<div id="app">
<div id="loading-bg">
<div class="loading-logo">
<!-- SVG Logo -->
<svg width="86" height="48" viewBox="0 0 30 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<path d="M30 21.392a2 2 0 0 1-3.027 1.716l-3.258-1.95a2 2 0 0 1-.973-1.716l-.001-6.7L15
17.178l-7.742-4.434v6.7a2 2 0 0 1-.973 1.715l-3.258 1.95A2 2 0 0 1 0 21.392V3.585l.005-.15L0 3.572a2 2 0 0
1 3.045-1.706L15 9.194l11.955-7.328A2 2 0 0 1 30 3.572Z" fill="var(--initial-loader-color)" />
<path fill="var(--initial-loader-color)" opacity=".078" d="m0 8.589 7.258 4.162v4.08z" />
<path fill="var(--initial-loader-color)" opacity=".078" d=" m0 8.589 7.258 4.056v2.492zm30 0-7.258
4.153v4.212z" />
<path fill="var(--initial-loader-color)" opacity=".078" d="m30 8.589-7.258 4.052v2.62z" />
<path d="M3.045 1.866 15 9.194v7.983L0 8.587V3.571a2 2 0 0 1 3.045-1.706Z" fill-opacity=".15" fill="
#FFF" />
<path d="M26.955 1.866 15 9.194v7.983l15-8.59V3.571a2 2 0 0 0-3.045-1.706Z" fill-opacity=".35"
fill="#FFF" />
</g>
</svg>
</div>
<div class=" loading">
<div class="effect-1 effects"></div>
<div class="effect-2 effects"></div>
<div class="effect-3 effects"></div>
</div>
</div>
</div>
<script type="module" src="/src/main.js"></script>
<script>
const loaderColor = localStorage.getItem('materio-initial-loader-bg') || '#FFFFFF'
const primaryColor = localStorage.getItem('materio-initial-loader-color') || '#a169ff'
if (loaderColor)
document.documentElement.style.setProperty('--initial-loader-bg', loaderColor)
if (loaderColor)
document.documentElement.style.setProperty('--initial-loader-bg', loaderColor)
if (primaryColor)
document.documentElement.style.setProperty('--initial-loader-color', primaryColor)
</script>
</body>
</html>
{
"include": [
"./vite.config.*",
"./src/**/*",
"./src/**/*.vue",
"./themeConfig.js"
],
"exclude": [
"./dist",
"./node_modules"
],
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "Bundler",
"jsx": "preserve",
"paths": {
"@/*": [
"./src/*"
],
"@themeConfig": [
"./themeConfig.js"
],
"@layouts/*": [
"./src/@layouts/*"
],
"@layouts": [
"./src/@layouts"
],
"@core/*": [
"./src/@core/*"
],
"@core": [
"./src/@core"
],
"@images/*": [
"./src/assets/images/*"
],
"@styles/*": [
"./src/assets/styles/*"
],
"@validators": [
"./src/@core/utils/validators"
],
"@db/*": [
"./src/plugins/fake-api/handlers/*"
],
"@api-utils/*": [
"./src/plugins/fake-api/utils/*"
]
},
"types": [
"vite/client",
"unplugin-vue-router/client",
"vite-plugin-vue-layouts/client"
]
}
}
# nginx.conf
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
# Additional configurations go here...
}
This diff could not be displayed because it is too large.
{
"name": "materio-vuetify-vuejs-admin-template",
"version": "3.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview --port 5050",
"lint": "eslint . -c .eslintrc.cjs --fix --ext .ts,.js,.cjs,.vue,.tsx,.jsx",
"build:icons": "tsx src/plugins/iconify/build-icons.js",
"msw:init": "msw init public/ --save",
"postinstall": "npm run build:icons && npm run msw:init"
},
"dependencies": {
"@casl/ability": "6.7.1",
"@casl/vue": "2.2.2",
"@floating-ui/dom": "1.6.7",
"@formkit/drag-and-drop": "0.1.6",
"@iconify-json/bxl": "1.1.10",
"@sindresorhus/is": "7.0.0",
"@tiptap/extension-highlight": "^2.5.6",
"@tiptap/extension-image": "^2.5.6",
"@tiptap/extension-link": "^2.5.6",
"@tiptap/extension-text-align": "^2.5.6",
"@tiptap/pm": "^2.5.6",
"@tiptap/starter-kit": "^2.5.6",
"@tiptap/vue-3": "^2.5.6",
"@vueuse/core": "10.11.0",
"@vueuse/math": "10.11.0",
"apexcharts": "3.51.0",
"chart.js": "4.4.3",
"cookie-es": "1.2.1",
"eslint-plugin-regexp": "2.6.0",
"jwt-decode": "4.0.0",
"mapbox-gl": "3.5.1",
"ofetch": "1.3.4",
"pinia": "2.1.7",
"prismjs": "1.29.0",
"roboto-fontface": "0.10.0",
"shepherd.js": "13.0.1",
"swiper": "11.1.7",
"ufo": "1.5.4",
"unplugin-vue-define-options": "1.4.6",
"vue": "3.4.34",
"vue-chartjs": "5.3.1",
"vue-flatpickr-component": "11.0.5",
"vue-i18n": "9.13.1",
"vue-prism-component": "2.0.0",
"vue-router": "4.4.0",
"vue3-apexcharts": "1.5.3",
"vue3-perfect-scrollbar": "2.0.0",
"vuetify": "3.6.13",
"webfontloader": "1.6.28"
},
"devDependencies": {
"@antfu/eslint-config-vue": "0.43.1",
"@antfu/utils": "0.7.10",
"@fullcalendar/core": "6.1.15",
"@fullcalendar/daygrid": "6.1.15",
"@fullcalendar/interaction": "6.1.15",
"@fullcalendar/list": "6.1.15",
"@fullcalendar/timegrid": "6.1.15",
"@fullcalendar/vue3": "6.1.15",
"@iconify-json/mdi": "1.1.67",
"@iconify-json/ri": "1.1.21",
"@iconify/tools": "4.0.4",
"@iconify/utils": "2.1.25",
"@iconify/vue": "4.1.2",
"@intlify/unplugin-vue-i18n": "4.0.0",
"@stylistic/stylelint-config": "1.0.1",
"@stylistic/stylelint-plugin": "2.1.2",
"@tiptap/extension-character-count": "^2.5.6",
"@tiptap/extension-placeholder": "^2.5.6",
"@tiptap/extension-subscript": "^2.5.6",
"@tiptap/extension-superscript": "^2.5.6",
"@tiptap/extension-underline": "^2.5.6",
"@videojs-player/vue": "1.0.0",
"@vitejs/plugin-vue": "5.1.0",
"@vitejs/plugin-vue-jsx": "4.0.0",
"eslint": "8.57.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-import-resolver-typescript": "3.6.1",
"eslint-plugin-case-police": "0.6.1",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-promise": "6.6.0",
"eslint-plugin-regex": "1.10.0",
"eslint-plugin-sonarjs": "0.24.0",
"eslint-plugin-unicorn": "51.0.1",
"eslint-plugin-vue": "9.27.0",
"msw": "2.3.4",
"postcss-html": "1.7.0",
"postcss-scss": "4.0.9",
"sass": "1.76.0",
"shiki": "1.11.1",
"stylelint": "16.6.1",
"stylelint-config-idiomatic-order": "10.0.0",
"stylelint-config-standard-scss": "13.1.0",
"stylelint-use-logical-spec": "5.0.1",
"tsx": "4.16.2",
"unplugin-auto-import": "0.18.2",
"unplugin-vue-components": "0.27.3",
"unplugin-vue-router": "0.8.8",
"video.js": "8.6.0",
"vite": "5.3.4",
"vite-plugin-vue-devtools": "7.3.5",
"vite-plugin-vue-layouts": "0.11.0",
"vite-plugin-vuetify": "2.0.3",
"vite-svg-loader": "5.1.0",
"vue-shepherd": "3.0.0"
},
"resolutions": {
"postcss": "^8",
"@tiptap/core": "^2",
"@types/video.js": "^7",
"sass": "1.76.0"
},
"overrides": {
"postcss": "^8",
"@tiptap/core": "^2",
"@types/video.js": "^7",
"sass": "1.76.0"
},
"msw": {
"workerDirectory": "public"
}
}
This diff could not be displayed because it is too large.
# Use the official Node.js image as the base image
FROM node:18 as builder
# Set the working directory in the container
WORKDIR /app
# Copy package.json and yarn.lock to the container
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
# Copy the rest of the application code
COPY . .
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm i; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
# Build vue.js based on the preferred package manager
RUN \
if [ -f yarn.lock ]; then yarn build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then pnpm run build; \
else yarn build; \
fi
# Use Nginx as the production server
FROM nginx:stable-alpine
# Copy the custom Nginx configuration file
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy the built Vue.js files to the Nginx web server directory
COPY --from=builder /app/dist /usr/share/nginx/html
# Expose port 80 for Nginx
EXPOSE 80
# Start Nginx when the container runs
CMD ["nginx", "-g", "daemon off;"]
No preview for this file type
<svg width="1em" height="1em" viewBox="0 0 59 58" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.2">
<path d="M23.2164 41.2571L20.475 46.6946C20.2986 47.0654 19.9986 47.3632 19.6265 47.5368C19.2544 47.7105 18.8334 47.7491 18.4359 47.6462C12.8852 46.2868 8.08203 43.9305 4.59297 40.8946C4.32992 40.6625 4.1389 40.3599 4.04253 40.0226C3.94616 39.6853 3.94848 39.3275 4.04922 38.9915L11.7297 13.3446C11.8044 13.0821 11.9397 12.8409 12.1247 12.6402C12.3096 12.4395 12.539 12.285 12.7945 12.1891C14.9645 11.2989 17.2085 10.6014 19.5008 10.1047C19.9413 10.0083 20.4019 10.0774 20.7947 10.2988C21.1875 10.5203 21.485 10.8787 21.6305 11.3055L23.4203 16.7204C27.3437 16.1766 31.3234 16.1766 35.2469 16.7204L37.0367 11.3055C37.1822 10.8787 37.4797 10.5203 37.8725 10.2988C38.2653 10.0774 38.7259 10.0083 39.1664 10.1047C41.4587 10.6014 43.7027 11.2989 45.8727 12.1891C46.1282 12.285 46.3576 12.4395 46.5425 12.6402C46.7275 12.8409 46.8628 13.0821 46.9375 13.3446L54.618 38.9915C54.7187 39.3275 54.721 39.6853 54.6247 40.0226C54.5283 40.3599 54.3373 40.6625 54.0742 40.8946C50.5852 43.9305 45.782 46.2868 40.2313 47.6462C39.8338 47.7491 39.4128 47.7105 39.0407 47.5368C38.6686 47.3632 38.3686 47.0654 38.1922 46.6946L35.4508 41.2571C33.4242 41.5421 31.3802 41.6859 29.3336 41.6876C27.287 41.6859 25.243 41.5421 23.2164 41.2571Z" fill="#4B465C"/>
<path d="M23.2164 41.2571L20.475 46.6946C20.2986 47.0654 19.9986 47.3632 19.6265 47.5368C19.2544 47.7105 18.8334 47.7491 18.4359 47.6462C12.8852 46.2868 8.08203 43.9305 4.59297 40.8946C4.32992 40.6625 4.1389 40.3599 4.04253 40.0226C3.94616 39.6853 3.94848 39.3275 4.04922 38.9915L11.7297 13.3446C11.8044 13.0821 11.9397 12.8409 12.1247 12.6402C12.3096 12.4395 12.539 12.285 12.7945 12.1891C14.9645 11.2989 17.2085 10.6014 19.5008 10.1047C19.9413 10.0083 20.4019 10.0774 20.7947 10.2988C21.1875 10.5203 21.485 10.8787 21.6305 11.3055L23.4203 16.7204C27.3437 16.1766 31.3234 16.1766 35.2469 16.7204L37.0367 11.3055C37.1822 10.8787 37.4797 10.5203 37.8725 10.2988C38.2653 10.0774 38.7259 10.0083 39.1664 10.1047C41.4587 10.6014 43.7027 11.2989 45.8727 12.1891C46.1282 12.285 46.3576 12.4395 46.5425 12.6402C46.7275 12.8409 46.8628 13.0821 46.9375 13.3446L54.618 38.9915C54.7187 39.3275 54.721 39.6853 54.6247 40.0226C54.5283 40.3599 54.3373 40.6625 54.0742 40.8946C50.5852 43.9305 45.782 46.2868 40.2313 47.6462C39.8338 47.7491 39.4128 47.7105 39.0407 47.5368C38.6686 47.3632 38.3686 47.0654 38.1922 46.6946L35.4508 41.2571C33.4242 41.5421 31.3802 41.6859 29.3336 41.6876C27.287 41.6859 25.243 41.5421 23.2164 41.2571Z" fill="white" fill-opacity="0.2"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.8022 32.625C24.8022 34.1265 23.585 35.3438 22.0835 35.3438C20.582 35.3438 19.3647 34.1265 19.3647 32.625C19.3647 31.1235 20.582 29.9062 22.0835 29.9062C23.585 29.9062 24.8022 31.1235 24.8022 32.625ZM39.3022 32.625C39.3022 34.1265 38.085 35.3438 36.5835 35.3438C35.082 35.3438 33.8647 34.1265 33.8647 32.625C33.8647 31.1235 35.082 29.9062 36.5835 29.9062C38.085 29.9062 39.3022 31.1235 39.3022 32.625Z" fill="#4B465C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.8022 32.625C24.8022 34.1265 23.585 35.3438 22.0835 35.3438C20.582 35.3438 19.3647 34.1265 19.3647 32.625C19.3647 31.1235 20.582 29.9062 22.0835 29.9062C23.585 29.9062 24.8022 31.1235 24.8022 32.625ZM39.3022 32.625C39.3022 34.1265 38.085 35.3438 36.5835 35.3438C35.082 35.3438 33.8647 34.1265 33.8647 32.625C33.8647 31.1235 35.082 29.9062 36.5835 29.9062C38.085 29.9062 39.3022 31.1235 39.3022 32.625Z" fill="white" fill-opacity="0.2"/>
<path d="M17.1898 18.1251C21.119 16.8936 25.2161 16.2821 29.3336 16.3126C33.4511 16.2821 37.5482 16.8936 41.4773 18.1251M41.4773 39.8751C37.5482 41.1065 33.4511 41.718 29.3336 41.6876C25.2161 41.718 21.119 41.1065 17.1898 39.8751M35.4508 41.2571L38.1922 46.6946C38.3686 47.0654 38.6686 47.3632 39.0407 47.5368C39.4128 47.7105 39.8338 47.7491 40.2313 47.6462C45.782 46.2868 50.5852 43.9305 54.0742 40.8946C54.3373 40.6625 54.5283 40.3599 54.6247 40.0226C54.721 39.6853 54.7187 39.3275 54.618 38.9915L46.9375 13.3446C46.8628 13.0821 46.7275 12.8409 46.5425 12.6402C46.3576 12.4395 46.1282 12.285 45.8727 12.1891C43.7027 11.2989 41.4587 10.6014 39.1664 10.1047C38.7259 10.0083 38.2653 10.0774 37.8725 10.2988C37.4797 10.5203 37.1822 10.8787 37.0367 11.3055L35.2469 16.7204M23.2164 41.2571L20.475 46.6946C20.2986 47.0654 19.9986 47.3632 19.6265 47.5368C19.2544 47.7105 18.8334 47.7491 18.4359 47.6462C12.8852 46.2868 8.08203 43.9305 4.59297 40.8946C4.32992 40.6625 4.1389 40.3599 4.04253 40.0226C3.94616 39.6853 3.94848 39.3275 4.04922 38.9915L11.7297 13.3446C11.8044 13.0821 11.9397 12.8409 12.1247 12.6402C12.3096 12.4395 12.539 12.285 12.7945 12.1891C14.9645 11.2989 17.2085 10.6014 19.5008 10.1047C19.9413 10.0083 20.4019 10.0774 20.7947 10.2988C21.1875 10.5203 21.485 10.8787 21.6305 11.3055L23.4203 16.7204" stroke="#4B465C" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.1898 18.1251C21.119 16.8936 25.2161 16.2821 29.3336 16.3126C33.4511 16.2821 37.5482 16.8936 41.4773 18.1251M41.4773 39.8751C37.5482 41.1065 33.4511 41.718 29.3336 41.6876C25.2161 41.718 21.119 41.1065 17.1898 39.8751M35.4508 41.2571L38.1922 46.6946C38.3686 47.0654 38.6686 47.3632 39.0407 47.5368C39.4128 47.7105 39.8338 47.7491 40.2313 47.6462C45.782 46.2868 50.5852 43.9305 54.0742 40.8946C54.3373 40.6625 54.5283 40.3599 54.6247 40.0226C54.721 39.6853 54.7187 39.3275 54.618 38.9915L46.9375 13.3446C46.8628 13.0821 46.7275 12.8409 46.5425 12.6402C46.3576 12.4395 46.1282 12.285 45.8727 12.1891C43.7027 11.2989 41.4587 10.6014 39.1664 10.1047C38.7259 10.0083 38.2653 10.0774 37.8725 10.2988C37.4797 10.5203 37.1822 10.8787 37.0367 11.3055L35.2469 16.7204M23.2164 41.2571L20.475 46.6946C20.2986 47.0654 19.9986 47.3632 19.6265 47.5368C19.2544 47.7105 18.8334 47.7491 18.4359 47.6462C12.8852 46.2868 8.08203 43.9305 4.59297 40.8946C4.32992 40.6625 4.1389 40.3599 4.04253 40.0226C3.94616 39.6853 3.94848 39.3275 4.04922 38.9915L11.7297 13.3446C11.8044 13.0821 11.9397 12.8409 12.1247 12.6402C12.3096 12.4395 12.539 12.285 12.7945 12.1891C14.9645 11.2989 17.2085 10.6014 19.5008 10.1047C19.9413 10.0083 20.4019 10.0774 20.7947 10.2988C21.1875 10.5203 21.485 10.8787 21.6305 11.3055L23.4203 16.7204" stroke="white" stroke-opacity="0.2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg width="1em" height="1em" viewBox="0 0 58 58" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.2689 8.56722C31.7876 9.05354 31.3688 9.84814 31.029 10.8704C30.6946 11.8767 30.4652 13.0152 30.31 14.1032C30.1553 15.1872 30.0776 16.197 30.0386 16.9371C30.0358 16.991 30.0332 17.0434 30.0307 17.0943C30.0816 17.0918 30.134 17.0892 30.1879 17.0864C30.928 17.0475 31.9378 16.9697 33.0218 16.815C34.1098 16.6598 35.2483 16.4304 36.2546 16.096C37.2769 15.7562 38.0715 15.3374 38.5578 14.8561C39.3907 14.0223 39.8587 12.8919 39.8587 11.7133C39.8587 10.5339 39.3901 9.4028 38.5562 8.56883C37.7222 7.73487 36.5911 7.26636 35.4117 7.26636C34.2331 7.26636 33.1027 7.73427 32.2689 8.56722ZM39.2633 15.5649L39.9704 16.272C41.1794 15.0629 41.8587 13.4231 41.8587 11.7133C41.8587 10.0035 41.1794 8.36365 39.9704 7.15462C38.7614 5.94559 37.1216 5.26636 35.4117 5.26636C33.7019 5.26636 32.0621 5.94559 30.853 7.15461L30.8499 7.15774C30.0518 7.96296 29.5111 9.09639 29.1311 10.2396C29.0855 10.3767 29.0418 10.5154 28.9999 10.6551C28.958 10.5154 28.9143 10.3767 28.8688 10.2396C28.4888 9.09639 27.9481 7.96296 27.1499 7.15774L27.1468 7.15462C25.9378 5.94559 24.298 5.26636 22.5882 5.26636C20.8783 5.26636 19.2385 5.94559 18.0295 7.15462C16.8205 8.36366 16.1412 10.0035 16.1412 11.7133C16.1412 13.4231 16.8205 15.0629 18.0295 16.272L18.7366 15.5649L18.0326 16.2751C18.3589 16.5985 18.7391 16.8797 19.152 17.125H9.0625C7.5092 17.125 6.25 18.3842 6.25 19.9375V27.1875C6.25 28.7408 7.5092 30 9.0625 30H9.875V45.3125C9.875 46.0584 10.1713 46.7738 10.6988 47.3012C11.2262 47.8287 11.9416 48.125 12.6875 48.125H29H45.3125C46.0584 48.125 46.7738 47.8287 47.3012 47.3012C47.8287 46.7738 48.125 46.0584 48.125 45.3125V30H48.9375C50.4908 30 51.75 28.7408 51.75 27.1875V19.9375C51.75 18.3842 50.4908 17.125 48.9375 17.125H38.8479C39.2608 16.8797 39.641 16.5985 39.9673 16.2751L39.2633 15.5649ZM9.0625 19.125C8.61377 19.125 8.25 19.4888 8.25 19.9375V27.1875C8.25 27.6362 8.61377 28 9.0625 28H10.875H28V19.125H9.0625ZM30 19.125V28H47.125H48.9375C49.3862 28 49.75 27.6362 49.75 27.1875V19.9375C49.75 19.4888 49.3862 19.125 48.9375 19.125H30ZM28 30H11.875V45.3125C11.875 45.528 11.9606 45.7347 12.113 45.887C12.2653 46.0394 12.472 46.125 12.6875 46.125H28V30ZM30 46.125V30H46.125V45.3125C46.125 45.528 46.0394 45.7347 45.887 45.887C45.7347 46.0394 45.528 46.125 45.3125 46.125H30ZM21.7452 16.096C20.723 15.7562 19.9284 15.3374 19.4421 14.8562C18.6091 14.0223 18.1412 12.8919 18.1412 11.7133C18.1412 10.5339 18.6097 9.4028 19.4437 8.56883C20.2777 7.73487 21.4088 7.26636 22.5882 7.26636C23.7668 7.26636 24.8972 7.73428 25.731 8.56725C26.2123 9.05357 26.6311 9.84816 26.9708 10.8704C27.3053 11.8767 27.5346 13.0152 27.6899 14.1032C27.8445 15.1872 27.9223 16.197 27.9613 16.9371C27.9641 16.991 27.9667 17.0434 27.9691 17.0943C27.9183 17.0918 27.8659 17.0892 27.812 17.0864C27.0719 17.0475 26.0621 16.9697 24.978 16.815C23.89 16.6598 22.7516 16.4304 21.7452 16.096Z" fill="#4B465C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.2689 8.56722C31.7876 9.05354 31.3688 9.84814 31.029 10.8704C30.6946 11.8767 30.4652 13.0152 30.31 14.1032C30.1553 15.1872 30.0776 16.197 30.0386 16.9371C30.0358 16.991 30.0332 17.0434 30.0307 17.0943C30.0816 17.0918 30.134 17.0892 30.1879 17.0864C30.928 17.0475 31.9378 16.9697 33.0218 16.815C34.1098 16.6598 35.2483 16.4304 36.2546 16.096C37.2769 15.7562 38.0715 15.3374 38.5578 14.8561C39.3907 14.0223 39.8587 12.8919 39.8587 11.7133C39.8587 10.5339 39.3901 9.4028 38.5562 8.56883C37.7222 7.73487 36.5911 7.26636 35.4117 7.26636C34.2331 7.26636 33.1027 7.73427 32.2689 8.56722ZM39.2633 15.5649L39.9704 16.272C41.1794 15.0629 41.8587 13.4231 41.8587 11.7133C41.8587 10.0035 41.1794 8.36365 39.9704 7.15462C38.7614 5.94559 37.1216 5.26636 35.4117 5.26636C33.7019 5.26636 32.0621 5.94559 30.853 7.15461L30.8499 7.15774C30.0518 7.96296 29.5111 9.09639 29.1311 10.2396C29.0855 10.3767 29.0418 10.5154 28.9999 10.6551C28.958 10.5154 28.9143 10.3767 28.8688 10.2396C28.4888 9.09639 27.9481 7.96296 27.1499 7.15774L27.1468 7.15462C25.9378 5.94559 24.298 5.26636 22.5882 5.26636C20.8783 5.26636 19.2385 5.94559 18.0295 7.15462C16.8205 8.36366 16.1412 10.0035 16.1412 11.7133C16.1412 13.4231 16.8205 15.0629 18.0295 16.272L18.7366 15.5649L18.0326 16.2751C18.3589 16.5985 18.7391 16.8797 19.152 17.125H9.0625C7.5092 17.125 6.25 18.3842 6.25 19.9375V27.1875C6.25 28.7408 7.5092 30 9.0625 30H9.875V45.3125C9.875 46.0584 10.1713 46.7738 10.6988 47.3012C11.2262 47.8287 11.9416 48.125 12.6875 48.125H29H45.3125C46.0584 48.125 46.7738 47.8287 47.3012 47.3012C47.8287 46.7738 48.125 46.0584 48.125 45.3125V30H48.9375C50.4908 30 51.75 28.7408 51.75 27.1875V19.9375C51.75 18.3842 50.4908 17.125 48.9375 17.125H38.8479C39.2608 16.8797 39.641 16.5985 39.9673 16.2751L39.2633 15.5649ZM9.0625 19.125C8.61377 19.125 8.25 19.4888 8.25 19.9375V27.1875C8.25 27.6362 8.61377 28 9.0625 28H10.875H28V19.125H9.0625ZM30 19.125V28H47.125H48.9375C49.3862 28 49.75 27.6362 49.75 27.1875V19.9375C49.75 19.4888 49.3862 19.125 48.9375 19.125H30ZM28 30H11.875V45.3125C11.875 45.528 11.9606 45.7347 12.113 45.887C12.2653 46.0394 12.472 46.125 12.6875 46.125H28V30ZM30 46.125V30H46.125V45.3125C46.125 45.528 46.0394 45.7347 45.887 45.887C45.7347 46.0394 45.528 46.125 45.3125 46.125H30ZM21.7452 16.096C20.723 15.7562 19.9284 15.3374 19.4421 14.8562C18.6091 14.0223 18.1412 12.8919 18.1412 11.7133C18.1412 10.5339 18.6097 9.4028 19.4437 8.56883C20.2777 7.73487 21.4088 7.26636 22.5882 7.26636C23.7668 7.26636 24.8972 7.73428 25.731 8.56725C26.2123 9.05357 26.6311 9.84816 26.9708 10.8704C27.3053 11.8767 27.5346 13.0152 27.6899 14.1032C27.8445 15.1872 27.9223 16.197 27.9613 16.9371C27.9641 16.991 27.9667 17.0434 27.9691 17.0943C27.9183 17.0918 27.8659 17.0892 27.812 17.0864C27.0719 17.0475 26.0621 16.9697 24.978 16.815C23.89 16.6598 22.7516 16.4304 21.7452 16.096Z" fill="white" fill-opacity="0.2"/>
<g opacity="0.2">
<path d="M47.125 29V45.3125C47.125 45.7932 46.934 46.2542 46.5941 46.5941C46.2542 46.934 45.7932 47.125 45.3125 47.125H12.6875C12.2068 47.125 11.7458 46.934 11.4059 46.5941C11.066 46.2542 10.875 45.7932 10.875 45.3125V29H47.125Z" fill="#4B465C"/>
<path d="M47.125 29V45.3125C47.125 45.7932 46.934 46.2542 46.5941 46.5941C46.2542 46.934 45.7932 47.125 45.3125 47.125H12.6875C12.2068 47.125 11.7458 46.934 11.4059 46.5941C11.066 46.2542 10.875 45.7932 10.875 45.3125V29H47.125Z" fill="white" fill-opacity="0.2"/>
</g>
</svg>
<svg width="1em" height="1em" viewBox="0 0 59 58" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.2">
<path d="M50.9702 12.6875H7.69678C6.6332 12.6875 5.771 13.5497 5.771 14.6133V43.3867C5.771 44.4503 6.6332 45.3125 7.69678 45.3125H50.9702C52.0338 45.3125 52.896 44.4503 52.896 43.3867V14.6133C52.896 13.5497 52.0338 12.6875 50.9702 12.6875Z" fill="#4B465C"/>
<path d="M50.9702 12.6875H7.69678C6.6332 12.6875 5.771 13.5497 5.771 14.6133V43.3867C5.771 44.4503 6.6332 45.3125 7.69678 45.3125H50.9702C52.0338 45.3125 52.896 44.4503 52.896 43.3867V14.6133C52.896 13.5497 52.0338 12.6875 50.9702 12.6875Z" fill="white" fill-opacity="0.2"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.021 14.6133C7.021 14.2401 7.32355 13.9375 7.69678 13.9375H50.9702C51.3434 13.9375 51.646 14.2401 51.646 14.6133V43.3867C51.646 43.7599 51.3434 44.0625 50.9702 44.0625H7.69678C7.32355 44.0625 7.021 43.7599 7.021 43.3867V14.6133ZM7.69678 11.4375C5.94284 11.4375 4.521 12.8593 4.521 14.6133V43.3867C4.521 45.1407 5.94284 46.5625 7.69678 46.5625H50.9702C52.7242 46.5625 54.146 45.1407 54.146 43.3867V14.6133C54.146 12.8593 52.7242 11.4375 50.9702 11.4375H7.69678ZM13.021 20.75C12.4687 20.75 12.021 21.1977 12.021 21.75C12.021 22.3023 12.4687 22.75 13.021 22.75H45.646C46.1983 22.75 46.646 22.3023 46.646 21.75C46.646 21.1977 46.1983 20.75 45.646 20.75H13.021ZM13.021 28C12.4687 28 12.021 28.4477 12.021 29C12.021 29.5523 12.4687 30 13.021 30H45.646C46.1983 30 46.646 29.5523 46.646 29C46.646 28.4477 46.1983 28 45.646 28H13.021ZM12.021 36.25C12.021 35.6977 12.4687 35.25 13.021 35.25H14.8335C15.3858 35.25 15.8335 35.6977 15.8335 36.25C15.8335 36.8023 15.3858 37.25 14.8335 37.25H13.021C12.4687 37.25 12.021 36.8023 12.021 36.25ZM22.0835 35.25C21.5312 35.25 21.0835 35.6977 21.0835 36.25C21.0835 36.8023 21.5312 37.25 22.0835 37.25H36.5835C37.1358 37.25 37.5835 36.8023 37.5835 36.25C37.5835 35.6977 37.1358 35.25 36.5835 35.25H22.0835ZM42.8335 36.25C42.8335 35.6977 43.2812 35.25 43.8335 35.25H45.646C46.1983 35.25 46.646 35.6977 46.646 36.25C46.646 36.8023 46.1983 37.25 45.646 37.25H43.8335C43.2812 37.25 42.8335 36.8023 42.8335 36.25Z" fill="#4B465C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.021 14.6133C7.021 14.2401 7.32355 13.9375 7.69678 13.9375H50.9702C51.3434 13.9375 51.646 14.2401 51.646 14.6133V43.3867C51.646 43.7599 51.3434 44.0625 50.9702 44.0625H7.69678C7.32355 44.0625 7.021 43.7599 7.021 43.3867V14.6133ZM7.69678 11.4375C5.94284 11.4375 4.521 12.8593 4.521 14.6133V43.3867C4.521 45.1407 5.94284 46.5625 7.69678 46.5625H50.9702C52.7242 46.5625 54.146 45.1407 54.146 43.3867V14.6133C54.146 12.8593 52.7242 11.4375 50.9702 11.4375H7.69678ZM13.021 20.75C12.4687 20.75 12.021 21.1977 12.021 21.75C12.021 22.3023 12.4687 22.75 13.021 22.75H45.646C46.1983 22.75 46.646 22.3023 46.646 21.75C46.646 21.1977 46.1983 20.75 45.646 20.75H13.021ZM13.021 28C12.4687 28 12.021 28.4477 12.021 29C12.021 29.5523 12.4687 30 13.021 30H45.646C46.1983 30 46.646 29.5523 46.646 29C46.646 28.4477 46.1983 28 45.646 28H13.021ZM12.021 36.25C12.021 35.6977 12.4687 35.25 13.021 35.25H14.8335C15.3858 35.25 15.8335 35.6977 15.8335 36.25C15.8335 36.8023 15.3858 37.25 14.8335 37.25H13.021C12.4687 37.25 12.021 36.8023 12.021 36.25ZM22.0835 35.25C21.5312 35.25 21.0835 35.6977 21.0835 36.25C21.0835 36.8023 21.5312 37.25 22.0835 37.25H36.5835C37.1358 37.25 37.5835 36.8023 37.5835 36.25C37.5835 35.6977 37.1358 35.25 36.5835 35.25H22.0835ZM42.8335 36.25C42.8335 35.6977 43.2812 35.25 43.8335 35.25H45.646C46.1983 35.25 46.646 35.6977 46.646 36.25C46.646 36.8023 46.1983 37.25 45.646 37.25H43.8335C43.2812 37.25 42.8335 36.8023 42.8335 36.25Z" fill="white" fill-opacity="0.2"/>
</svg>
<svg width="1em" height="1em" viewBox="0 0 59 58" fill="#4B465C" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.2">
<path d="M9.72925 39.875V16.3125C9.72925 15.3511 10.1112 14.4291 10.791 13.7492C11.4708 13.0694 12.3928 12.6875 13.3542 12.6875H45.9792C46.9407 12.6875 47.8627 13.0694 48.5425 13.7492C49.2223 14.4291 49.6042 15.3511 49.6042 16.3125V39.875H9.72925Z" fill="#4B465C"/>
<path d="M9.72925 39.875V16.3125C9.72925 15.3511 10.1112 14.4291 10.791 13.7492C11.4708 13.0694 12.3928 12.6875 13.3542 12.6875H45.9792C46.9407 12.6875 47.8627 13.0694 48.5425 13.7492C49.2223 14.4291 49.6042 15.3511 49.6042 16.3125V39.875H9.72925Z" fill="white" fill-opacity="0.2"/>
</g>
<path d="M8.72925 39.875C8.72925 40.4273 9.17696 40.875 9.72925 40.875C10.2815 40.875 10.7292 40.4273 10.7292 39.875H8.72925ZM13.3542 12.6875V11.6875V12.6875ZM45.9792 12.6875V11.6875V12.6875ZM48.6042 39.875C48.6042 40.4273 49.052 40.875 49.6042 40.875C50.1565 40.875 50.6042 40.4273 50.6042 39.875H48.6042ZM6.10425 39.875V38.875C5.55196 38.875 5.10425 39.3227 5.10425 39.875H6.10425ZM53.2292 39.875H54.2292C54.2292 39.3227 53.7815 38.875 53.2292 38.875V39.875ZM6.10425 43.5H5.10425H6.10425ZM33.2917 20.9375C33.844 20.9375 34.2917 20.4898 34.2917 19.9375C34.2917 19.3852 33.844 18.9375 33.2917 18.9375V20.9375ZM26.0417 18.9375C25.4895 18.9375 25.0417 19.3852 25.0417 19.9375C25.0417 20.4898 25.4895 20.9375 26.0417 20.9375V18.9375ZM10.7292 39.875V16.3125H8.72925V39.875H10.7292ZM10.7292 16.3125C10.7292 15.6163 11.0058 14.9486 11.4981 14.4563L10.0839 13.0421C9.21652 13.9095 8.72925 15.0859 8.72925 16.3125H10.7292ZM11.4981 14.4563C11.9904 13.9641 12.6581 13.6875 13.3542 13.6875L13.3542 11.6875C12.1276 11.6875 10.9512 12.1748 10.0839 13.0421L11.4981 14.4563ZM13.3542 13.6875H45.9792V11.6875H13.3542V13.6875ZM45.9792 13.6875C46.6754 13.6875 47.3431 13.9641 47.8354 14.4563L49.2496 13.0421C48.3823 12.1748 47.2059 11.6875 45.9792 11.6875V13.6875ZM47.8354 14.4563C48.3277 14.9486 48.6042 15.6163 48.6042 16.3125H50.6042C50.6042 15.0859 50.117 13.9095 49.2496 13.0421L47.8354 14.4563ZM48.6042 16.3125V39.875H50.6042V16.3125H48.6042ZM6.10425 40.875H53.2292V38.875H6.10425V40.875ZM52.2292 39.875V43.5H54.2292V39.875H52.2292ZM52.2292 43.5C52.2292 44.1962 51.9527 44.8639 51.4604 45.3562L52.8746 46.7704C53.742 45.903 54.2292 44.7266 54.2292 43.5H52.2292ZM51.4604 45.3562C50.9681 45.8484 50.3004 46.125 49.6042 46.125V48.125C50.8309 48.125 52.0073 47.6377 52.8746 46.7704L51.4604 45.3562ZM49.6042 46.125H9.72925V48.125H49.6042V46.125ZM9.72925 46.125C9.03305 46.125 8.36538 45.8484 7.87309 45.3562L6.45888 46.7704C7.32623 47.6377 8.50262 48.125 9.72925 48.125V46.125ZM7.87309 45.3562C7.38081 44.8639 7.10425 44.1962 7.10425 43.5H5.10425C5.10425 44.7266 5.59152 45.903 6.45888 46.7704L7.87309 45.3562ZM7.10425 43.5V39.875H5.10425V43.5H7.10425ZM33.2917 18.9375H26.0417V20.9375H33.2917V18.9375Z" fill="#4B465C"/>
<path d="M8.72925 39.875C8.72925 40.4273 9.17696 40.875 9.72925 40.875C10.2815 40.875 10.7292 40.4273 10.7292 39.875H8.72925ZM13.3542 12.6875V11.6875V12.6875ZM45.9792 12.6875V11.6875V12.6875ZM48.6042 39.875C48.6042 40.4273 49.052 40.875 49.6042 40.875C50.1565 40.875 50.6042 40.4273 50.6042 39.875H48.6042ZM6.10425 39.875V38.875C5.55196 38.875 5.10425 39.3227 5.10425 39.875H6.10425ZM53.2292 39.875H54.2292C54.2292 39.3227 53.7815 38.875 53.2292 38.875V39.875ZM6.10425 43.5H5.10425H6.10425ZM33.2917 20.9375C33.844 20.9375 34.2917 20.4898 34.2917 19.9375C34.2917 19.3852 33.844 18.9375 33.2917 18.9375V20.9375ZM26.0417 18.9375C25.4895 18.9375 25.0417 19.3852 25.0417 19.9375C25.0417 20.4898 25.4895 20.9375 26.0417 20.9375V18.9375ZM10.7292 39.875V16.3125H8.72925V39.875H10.7292ZM10.7292 16.3125C10.7292 15.6163 11.0058 14.9486 11.4981 14.4563L10.0839 13.0421C9.21652 13.9095 8.72925 15.0859 8.72925 16.3125H10.7292ZM11.4981 14.4563C11.9904 13.9641 12.6581 13.6875 13.3542 13.6875L13.3542 11.6875C12.1276 11.6875 10.9512 12.1748 10.0839 13.0421L11.4981 14.4563ZM13.3542 13.6875H45.9792V11.6875H13.3542V13.6875ZM45.9792 13.6875C46.6754 13.6875 47.3431 13.9641 47.8354 14.4563L49.2496 13.0421C48.3823 12.1748 47.2059 11.6875 45.9792 11.6875V13.6875ZM47.8354 14.4563C48.3277 14.9486 48.6042 15.6163 48.6042 16.3125H50.6042C50.6042 15.0859 50.117 13.9095 49.2496 13.0421L47.8354 14.4563ZM48.6042 16.3125V39.875H50.6042V16.3125H48.6042ZM6.10425 40.875H53.2292V38.875H6.10425V40.875ZM52.2292 39.875V43.5H54.2292V39.875H52.2292ZM52.2292 43.5C52.2292 44.1962 51.9527 44.8639 51.4604 45.3562L52.8746 46.7704C53.742 45.903 54.2292 44.7266 54.2292 43.5H52.2292ZM51.4604 45.3562C50.9681 45.8484 50.3004 46.125 49.6042 46.125V48.125C50.8309 48.125 52.0073 47.6377 52.8746 46.7704L51.4604 45.3562ZM49.6042 46.125H9.72925V48.125H49.6042V46.125ZM9.72925 46.125C9.03305 46.125 8.36538 45.8484 7.87309 45.3562L6.45888 46.7704C7.32623 47.6377 8.50262 48.125 9.72925 48.125V46.125ZM7.87309 45.3562C7.38081 44.8639 7.10425 44.1962 7.10425 43.5H5.10425C5.10425 44.7266 5.59152 45.903 6.45888 46.7704L7.87309 45.3562ZM7.10425 43.5V39.875H5.10425V43.5H7.10425ZM33.2917 18.9375H26.0417V20.9375H33.2917V18.9375Z" fill="white" fill-opacity="0.2"/>
</svg>
<svg width="1em" height="1em" viewBox="0 0 58 58" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.2">
<path d="M17.8304 37.8359C15.6726 36.1581 13.925 34.0112 12.7199 31.5579C11.5148 29.1045 10.8839 26.4091 10.8749 23.6757C10.8296 13.8429 18.7367 5.66403 28.5695 5.43747C32.375 5.34725 36.1123 6.45746 39.2514 8.61062C42.3904 10.7638 44.7719 13.8506 46.0581 17.4333C47.3442 21.016 47.4698 24.9127 46.4169 28.5707C45.364 32.2288 43.1861 35.4625 40.1921 37.8132C39.5308 38.3245 38.995 38.9802 38.6259 39.7302C38.2568 40.4802 38.0641 41.3047 38.0625 42.1406V43.5C38.0625 43.9807 37.8715 44.4417 37.5316 44.7816C37.1917 45.1215 36.7307 45.3125 36.25 45.3125H21.7499C21.2692 45.3125 20.8082 45.1215 20.4683 44.7816C20.1284 44.4417 19.9374 43.9807 19.9374 43.5V42.1406C19.9318 41.3109 19.7394 40.4932 19.3747 39.748C19.0099 39.0028 18.4821 38.3493 17.8304 37.8359Z" fill="#4B465C"/>
<path d="M17.8304 37.8359C15.6726 36.1581 13.925 34.0112 12.7199 31.5579C11.5148 29.1045 10.8839 26.4091 10.8749 23.6757C10.8296 13.8429 18.7367 5.66403 28.5695 5.43747C32.375 5.34725 36.1123 6.45746 39.2514 8.61062C42.3904 10.7638 44.7719 13.8506 46.0581 17.4333C47.3442 21.016 47.4698 24.9127 46.4169 28.5707C45.364 32.2288 43.1861 35.4625 40.1921 37.8132C39.5308 38.3245 38.995 38.9802 38.6259 39.7302C38.2568 40.4802 38.0641 41.3047 38.0625 42.1406V43.5C38.0625 43.9807 37.8715 44.4417 37.5316 44.7816C37.1917 45.1215 36.7307 45.3125 36.25 45.3125H21.7499C21.2692 45.3125 20.8082 45.1215 20.4683 44.7816C20.1284 44.4417 19.9374 43.9807 19.9374 43.5V42.1406C19.9318 41.3109 19.7394 40.4932 19.3747 39.748C19.0099 39.0028 18.4821 38.3493 17.8304 37.8359Z" fill="white" fill-opacity="0.2"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.6857 9.43527C35.7198 7.4009 32.1887 6.35195 28.5932 6.43719L28.5925 6.4372C28.3515 6.44275 28.1116 6.45338 27.8731 6.46896L28.5464 4.43773C32.5617 4.34269 36.5049 5.51414 39.817 7.78597C43.1293 10.0579 45.6422 13.3151 46.9993 17.0954C48.3564 20.8758 48.4889 24.9875 47.3779 28.8473C46.2669 32.7072 43.9688 36.1193 40.8097 38.5998L40.8037 38.6045L40.8037 38.6044C40.263 39.0224 39.8249 39.5585 39.5232 40.1717C39.2215 40.7847 39.0639 41.4585 39.0625 42.1416V42.1425V43.5C39.0625 44.2459 38.7661 44.9613 38.2387 45.4887C37.7112 46.0162 36.9959 46.3125 36.25 46.3125H21.75C21.004 46.3125 20.2887 46.0162 19.7612 45.4887C19.2338 44.9613 18.9375 44.2459 18.9375 43.5V42.1441C18.9323 41.4657 18.7748 40.7971 18.4765 40.1877C18.1782 39.5781 17.7466 39.0434 17.2138 38.6231L17.8866 36.5936C18.069 36.7483 18.255 36.8993 18.4442 37.0465L17.8304 37.836L18.4492 37.0504C19.2189 37.6567 19.8421 38.4284 20.2729 39.3084C20.7036 40.1884 20.9307 41.154 20.9374 42.1338L20.9375 42.1406L20.9375 43.5C20.9375 43.7155 21.0231 43.9221 21.1754 44.0745C21.3278 44.2269 21.5345 44.3125 21.75 44.3125H36.25C36.4654 44.3125 36.6721 44.2269 36.8245 44.0745C36.9768 43.9221 37.0625 43.7155 37.0625 43.5V42.1406V42.1387C37.0644 41.1503 37.2923 40.1754 37.7287 39.2886C38.1646 38.4029 38.7969 37.6285 39.5775 37.0244C42.4048 34.8035 44.4614 31.7492 45.4559 28.2941C46.4507 24.8379 46.3321 21.1562 45.1169 17.7712C43.9017 14.3862 41.6516 11.4696 38.6857 9.43527ZM17.8865 36.5936L17.8866 36.5936L27.8731 6.46896L27.8724 6.469L28.5458 4.43775C18.1651 4.67729 9.8275 13.3058 9.87496 23.6797C9.88451 26.5645 10.5504 29.4094 11.8223 31.9987C13.0938 34.5872 14.9375 36.8525 17.2138 38.6231L17.8865 36.5936ZM17.8865 36.5936C16.1041 35.0827 14.6499 33.2189 13.6175 31.117C12.4793 28.7998 11.8834 26.254 11.8749 23.6725L11.8749 23.6711C11.8332 14.6214 18.9246 7.05389 27.8724 6.469L17.8865 36.5936ZM18.9376 52.5625C18.9376 52.0102 19.3853 51.5625 19.9376 51.5625H38.0626C38.6149 51.5625 39.0626 52.0102 39.0626 52.5625C39.0626 53.1148 38.6149 53.5625 38.0626 53.5625H19.9376C19.3853 53.5625 18.9376 53.1148 18.9376 52.5625ZM31.0024 11.8828C30.4579 11.7905 29.9416 12.1571 29.8493 12.7016C29.757 13.2461 30.1236 13.7624 30.6681 13.8547C32.6793 14.1956 34.535 15.1524 35.9792 16.5929C37.4235 18.0334 38.385 19.8867 38.731 21.897C38.8247 22.4413 39.3419 22.8066 39.8862 22.7129C40.4304 22.6192 40.7957 22.102 40.702 21.5577C40.2857 19.1394 39.129 16.9098 37.3916 15.1769C35.6543 13.4439 33.4218 12.293 31.0024 11.8828Z" fill="#4B465C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.6857 9.43527C35.7198 7.4009 32.1887 6.35195 28.5932 6.43719L28.5925 6.4372C28.3515 6.44275 28.1116 6.45338 27.8731 6.46896L28.5464 4.43773C32.5617 4.34269 36.5049 5.51414 39.817 7.78597C43.1293 10.0579 45.6422 13.3151 46.9993 17.0954C48.3564 20.8758 48.4889 24.9875 47.3779 28.8473C46.2669 32.7072 43.9688 36.1193 40.8097 38.5998L40.8037 38.6045L40.8037 38.6044C40.263 39.0224 39.8249 39.5585 39.5232 40.1717C39.2215 40.7847 39.0639 41.4585 39.0625 42.1416V42.1425V43.5C39.0625 44.2459 38.7661 44.9613 38.2387 45.4887C37.7112 46.0162 36.9959 46.3125 36.25 46.3125H21.75C21.004 46.3125 20.2887 46.0162 19.7612 45.4887C19.2338 44.9613 18.9375 44.2459 18.9375 43.5V42.1441C18.9323 41.4657 18.7748 40.7971 18.4765 40.1877C18.1782 39.5781 17.7466 39.0434 17.2138 38.6231L17.8866 36.5936C18.069 36.7483 18.255 36.8993 18.4442 37.0465L17.8304 37.836L18.4492 37.0504C19.2189 37.6567 19.8421 38.4284 20.2729 39.3084C20.7036 40.1884 20.9307 41.154 20.9374 42.1338L20.9375 42.1406L20.9375 43.5C20.9375 43.7155 21.0231 43.9221 21.1754 44.0745C21.3278 44.2269 21.5345 44.3125 21.75 44.3125H36.25C36.4654 44.3125 36.6721 44.2269 36.8245 44.0745C36.9768 43.9221 37.0625 43.7155 37.0625 43.5V42.1406V42.1387C37.0644 41.1503 37.2923 40.1754 37.7287 39.2886C38.1646 38.4029 38.7969 37.6285 39.5775 37.0244C42.4048 34.8035 44.4614 31.7492 45.4559 28.2941C46.4507 24.8379 46.3321 21.1562 45.1169 17.7712C43.9017 14.3862 41.6516 11.4696 38.6857 9.43527ZM17.8865 36.5936L17.8866 36.5936L27.8731 6.46896L27.8724 6.469L28.5458 4.43775C18.1651 4.67729 9.8275 13.3058 9.87496 23.6797C9.88451 26.5645 10.5504 29.4094 11.8223 31.9987C13.0938 34.5872 14.9375 36.8525 17.2138 38.6231L17.8865 36.5936ZM17.8865 36.5936C16.1041 35.0827 14.6499 33.2189 13.6175 31.117C12.4793 28.7998 11.8834 26.254 11.8749 23.6725L11.8749 23.6711C11.8332 14.6214 18.9246 7.05389 27.8724 6.469L17.8865 36.5936ZM18.9376 52.5625C18.9376 52.0102 19.3853 51.5625 19.9376 51.5625H38.0626C38.6149 51.5625 39.0626 52.0102 39.0626 52.5625C39.0626 53.1148 38.6149 53.5625 38.0626 53.5625H19.9376C19.3853 53.5625 18.9376 53.1148 18.9376 52.5625ZM31.0024 11.8828C30.4579 11.7905 29.9416 12.1571 29.8493 12.7016C29.757 13.2461 30.1236 13.7624 30.6681 13.8547C32.6793 14.1956 34.535 15.1524 35.9792 16.5929C37.4235 18.0334 38.385 19.8867 38.731 21.897C38.8247 22.4413 39.3419 22.8066 39.8862 22.7129C40.4304 22.6192 40.7957 22.102 40.702 21.5577C40.2857 19.1394 39.129 16.9098 37.3916 15.1769C35.6543 13.4439 33.4218 12.293 31.0024 11.8828Z" fill="white" fill-opacity="0.2"/>
</svg>
<svg width="1em" height="1em" viewBox="0 0 59 58" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.2">
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.9019 33.6218L41.7878 25.0804C42.0597 30.314 40.7683 36.4086 36.7808 43.364L43.5777 48.8015C43.8194 48.9935 44.1061 49.1205 44.4106 49.1706C44.7151 49.2207 45.0274 49.1922 45.3178 49.0879C45.6083 48.9835 45.8673 48.8067 46.0702 48.5742C46.2732 48.3417 46.4134 48.0612 46.4777 47.7593L49.2644 35.1625C49.3316 34.8954 49.3337 34.6161 49.2706 34.348C49.2076 34.08 49.0811 33.8309 48.9019 33.6218ZM10.2956 33.7578L17.4097 25.239C17.1378 30.4726 18.4292 36.5672 22.4167 43.5L15.6198 48.9375C15.3797 49.1294 15.0947 49.257 14.7916 49.3084C14.4885 49.3598 14.1773 49.3333 13.8873 49.2314C13.5973 49.1294 13.338 48.9554 13.1338 48.7256C12.9295 48.4958 12.7871 48.2179 12.7198 47.9179L9.93313 35.2984C9.86594 35.0313 9.8638 34.7521 9.92688 34.484C9.98995 34.2159 10.1164 33.9669 10.2956 33.7578Z" fill="#4B465C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.9019 33.6218L41.7878 25.0804C42.0597 30.314 40.7683 36.4086 36.7808 43.364L43.5777 48.8015C43.8194 48.9935 44.1061 49.1205 44.4106 49.1706C44.7151 49.2207 45.0274 49.1922 45.3178 49.0879C45.6083 48.9835 45.8673 48.8067 46.0702 48.5742C46.2732 48.3417 46.4134 48.0612 46.4777 47.7593L49.2644 35.1625C49.3316 34.8954 49.3337 34.6161 49.2706 34.348C49.2076 34.08 49.0811 33.8309 48.9019 33.6218ZM10.2956 33.7578L17.4097 25.239C17.1378 30.4726 18.4292 36.5672 22.4167 43.5L15.6198 48.9375C15.3797 49.1294 15.0947 49.257 14.7916 49.3084C14.4885 49.3598 14.1773 49.3333 13.8873 49.2314C13.5973 49.1294 13.338 48.9554 13.1338 48.7256C12.9295 48.4958 12.7871 48.2179 12.7198 47.9179L9.93313 35.2984C9.86594 35.0313 9.8638 34.7521 9.92688 34.484C9.98995 34.2159 10.1164 33.9669 10.2956 33.7578Z" fill="white" fill-opacity="0.2"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.9017 3.71102C28.3979 3.30011 29.0221 3.07513 29.6666 3.07513C30.3127 3.07513 30.9383 3.30117 31.435 3.71394C33.6315 5.50224 38.386 9.93871 41.0105 16.7606C41.9219 19.1296 42.5713 21.7739 42.7735 24.6785L49.8022 33.113C50.0828 33.4423 50.2809 33.8338 50.38 34.255C50.4785 34.6735 50.4764 35.1093 50.374 35.5267L47.5901 48.1336L47.5894 48.1367C47.485 48.6022 47.264 49.0335 46.9471 49.39C46.6302 49.7465 46.2278 50.0166 45.7778 50.1748C45.3278 50.333 44.8449 50.3742 44.3746 50.2944C43.9043 50.2146 43.4621 50.0165 43.0894 49.7188L43.0889 49.7184L36.566 44.5H22.7675L16.2445 49.7184L16.2441 49.7188C15.8714 50.0165 15.4291 50.2146 14.9588 50.2944C14.4885 50.3742 14.0057 50.333 13.5556 50.1748C13.1056 50.0166 12.7032 49.7465 12.3863 49.39C12.0694 49.0335 11.8484 48.6022 11.7441 48.1367L11.7434 48.1336L8.95943 35.5267C8.85707 35.1093 8.85499 34.6735 8.95346 34.255C9.05262 33.8335 9.25088 33.4419 9.53173 33.1125L16.4274 24.8553C16.6112 21.877 17.2734 19.1695 18.2135 16.7491C20.8639 9.92541 25.6801 5.4896 27.9017 3.71102ZM40.8041 25.2385C40.7893 25.1573 40.7846 25.0748 40.7899 24.993C40.6159 22.2127 40.0004 19.7051 39.1438 17.4787C36.6951 11.1136 32.2331 6.94203 30.1682 5.26158L30.1583 5.25355L30.1584 5.25349C30.0204 5.13826 29.8464 5.07513 29.6666 5.07513C29.4869 5.07513 29.3128 5.13826 29.1748 5.25349L29.1585 5.26684C27.0721 6.93594 22.5504 11.1072 20.0778 17.4732C19.1887 19.7623 18.5587 22.3492 18.4096 25.2244C18.4102 25.2674 18.4081 25.3106 18.4032 25.3535C18.1745 30.253 19.3435 35.9842 22.9982 42.5H36.3292C39.938 35.9325 41.0647 30.1631 40.8041 25.2385ZM48.2696 34.398L42.8122 27.8492C42.6094 32.4348 41.2748 37.5835 38.2005 43.2464L44.3378 48.1563C44.4455 48.2423 44.5733 48.2995 44.7091 48.3226C44.845 48.3456 44.9845 48.3337 45.1145 48.288C45.2445 48.2423 45.3607 48.1643 45.4523 48.0613C45.5436 47.9586 45.6073 47.8344 45.6376 47.7004L45.6378 47.6992L48.4239 35.0828L48.4272 35.0682L48.4305 35.0545C48.4587 34.9425 48.4596 34.8255 48.4332 34.7131C48.4067 34.6007 48.3537 34.4963 48.2786 34.4086L48.2695 34.3981L48.2696 34.398ZM16.4139 27.9916L11.0632 34.3988L11.0549 34.4087L11.0549 34.4086C10.9798 34.4963 10.9267 34.6007 10.9003 34.7131C10.8738 34.8255 10.8747 34.9425 10.9029 35.0545C10.9053 35.0639 10.9075 35.0734 10.9096 35.0828L13.6956 47.6992L13.6959 47.7005C13.7262 47.8345 13.7899 47.9586 13.8812 48.0613C13.9727 48.1643 14.089 48.2423 14.219 48.288C14.349 48.3337 14.4885 48.3456 14.6243 48.3226C14.7602 48.2995 14.888 48.2423 14.9956 48.1563L21.1271 43.2511C18.0233 37.6471 16.6517 32.5443 16.4139 27.9916ZM25.0417 50.75C25.0417 50.1977 25.4895 49.75 26.0417 49.75H33.2917C33.844 49.75 34.2917 50.1977 34.2917 50.75C34.2917 51.3023 33.844 51.75 33.2917 51.75H26.0417C25.4895 51.75 25.0417 51.3023 25.0417 50.75ZM32.3855 21.75C32.3855 23.2515 31.1683 24.4688 29.6667 24.4688C28.1652 24.4688 26.948 23.2515 26.948 21.75C26.948 20.2485 28.1652 19.0313 29.6667 19.0313C31.1683 19.0313 32.3855 20.2485 32.3855 21.75Z" fill="#4B465C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.9017 3.71102C28.3979 3.30011 29.0221 3.07513 29.6666 3.07513C30.3127 3.07513 30.9383 3.30117 31.435 3.71394C33.6315 5.50224 38.386 9.93871 41.0105 16.7606C41.9219 19.1296 42.5713 21.7739 42.7735 24.6785L49.8022 33.113C50.0828 33.4423 50.2809 33.8338 50.38 34.255C50.4785 34.6735 50.4764 35.1093 50.374 35.5267L47.5901 48.1336L47.5894 48.1367C47.485 48.6022 47.264 49.0335 46.9471 49.39C46.6302 49.7465 46.2278 50.0166 45.7778 50.1748C45.3278 50.333 44.8449 50.3742 44.3746 50.2944C43.9043 50.2146 43.4621 50.0165 43.0894 49.7188L43.0889 49.7184L36.566 44.5H22.7675L16.2445 49.7184L16.2441 49.7188C15.8714 50.0165 15.4291 50.2146 14.9588 50.2944C14.4885 50.3742 14.0057 50.333 13.5556 50.1748C13.1056 50.0166 12.7032 49.7465 12.3863 49.39C12.0694 49.0335 11.8484 48.6022 11.7441 48.1367L11.7434 48.1336L8.95943 35.5267C8.85707 35.1093 8.85499 34.6735 8.95346 34.255C9.05262 33.8335 9.25088 33.4419 9.53173 33.1125L16.4274 24.8553C16.6112 21.877 17.2734 19.1695 18.2135 16.7491C20.8639 9.92541 25.6801 5.4896 27.9017 3.71102ZM40.8041 25.2385C40.7893 25.1573 40.7846 25.0748 40.7899 24.993C40.6159 22.2127 40.0004 19.7051 39.1438 17.4787C36.6951 11.1136 32.2331 6.94203 30.1682 5.26158L30.1583 5.25355L30.1584 5.25349C30.0204 5.13826 29.8464 5.07513 29.6666 5.07513C29.4869 5.07513 29.3128 5.13826 29.1748 5.25349L29.1585 5.26684C27.0721 6.93594 22.5504 11.1072 20.0778 17.4732C19.1887 19.7623 18.5587 22.3492 18.4096 25.2244C18.4102 25.2674 18.4081 25.3106 18.4032 25.3535C18.1745 30.253 19.3435 35.9842 22.9982 42.5H36.3292C39.938 35.9325 41.0647 30.1631 40.8041 25.2385ZM48.2696 34.398L42.8122 27.8492C42.6094 32.4348 41.2748 37.5835 38.2005 43.2464L44.3378 48.1563C44.4455 48.2423 44.5733 48.2995 44.7091 48.3226C44.845 48.3456 44.9845 48.3337 45.1145 48.288C45.2445 48.2423 45.3607 48.1643 45.4523 48.0613C45.5436 47.9586 45.6073 47.8344 45.6376 47.7004L45.6378 47.6992L48.4239 35.0828L48.4272 35.0682L48.4305 35.0545C48.4587 34.9425 48.4596 34.8255 48.4332 34.7131C48.4067 34.6007 48.3537 34.4963 48.2786 34.4086L48.2695 34.3981L48.2696 34.398ZM16.4139 27.9916L11.0632 34.3988L11.0549 34.4087L11.0549 34.4086C10.9798 34.4963 10.9267 34.6007 10.9003 34.7131C10.8738 34.8255 10.8747 34.9425 10.9029 35.0545C10.9053 35.0639 10.9075 35.0734 10.9096 35.0828L13.6956 47.6992L13.6959 47.7005C13.7262 47.8345 13.7899 47.9586 13.8812 48.0613C13.9727 48.1643 14.089 48.2423 14.219 48.288C14.349 48.3337 14.4885 48.3456 14.6243 48.3226C14.7602 48.2995 14.888 48.2423 14.9956 48.1563L21.1271 43.2511C18.0233 37.6471 16.6517 32.5443 16.4139 27.9916ZM25.0417 50.75C25.0417 50.1977 25.4895 49.75 26.0417 49.75H33.2917C33.844 49.75 34.2917 50.1977 34.2917 50.75C34.2917 51.3023 33.844 51.75 33.2917 51.75H26.0417C25.4895 51.75 25.0417 51.3023 25.0417 50.75ZM32.3855 21.75C32.3855 23.2515 31.1683 24.4688 29.6667 24.4688C28.1652 24.4688 26.948 23.2515 26.948 21.75C26.948 20.2485 28.1652 19.0313 29.6667 19.0313C31.1683 19.0313 32.3855 20.2485 32.3855 21.75Z" fill="white" fill-opacity="0.2"/>
</svg>
body {
margin: 0;
}
html {
overflow: hidden scroll;
}
#loading-bg {
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: var(--initial-loader-bg, #fff);
block-size: 100%;
gap: 1rem 0;
inline-size: 100%;
}
.loading {
position: relative;
box-sizing: border-box;
border: 3px solid transparent;
border-radius: 50%;
block-size: 55px;
inline-size: 55px;
}
.loading .effect-1,
.loading .effect-2,
.loading .effect-3 {
position: absolute;
box-sizing: border-box;
border: 3px solid transparent;
border-radius: 50%;
block-size: 100%;
border-inline-start: 3px solid var(--initial-loader-color, #eee);
inline-size: 100%;
}
.loading .effect-1 {
animation: rotate 1s ease infinite;
}
.loading .effect-2 {
animation: rotate-opacity 1s ease infinite 0.1s;
}
.loading .effect-3 {
animation: rotate-opacity 1s ease infinite 0.2s;
}
.loading .effects {
transition: all 0.3s ease;
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(1turn);
}
}
@keyframes rotate-opacity {
0% {
opacity: 0.1;
transform: rotate(0deg);
}
100% {
opacity: 1;
transform: rotate(1turn);
}
}
<script setup>
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
import {
VList,
VListItem,
} from 'vuetify/components/VList'
const props = defineProps({
isDialogVisible: {
type: Boolean,
required: true,
},
searchResults: {
type: Array,
required: true,
},
})
const emit = defineEmits([
'update:isDialogVisible',
'search',
])
// 👉 Hotkey
// eslint-disable-next-line camelcase
const { ctrl_k, meta_k } = useMagicKeys({
passive: false,
onEventFired(e) {
if (e.ctrlKey && e.key === 'k' && e.type === 'keydown')
e.preventDefault()
},
})
const refSearchList = ref()
const refSearchInput = ref()
const searchQueryLocal = ref('')
// 👉 watching control + / to open dialog
/* eslint-disable camelcase */
watch([
ctrl_k,
meta_k,
], () => {
emit('update:isDialogVisible', true)
})
/* eslint-enable */
// 👉 clear search result and close the dialog
const clearSearchAndCloseDialog = () => {
searchQueryLocal.value = ''
emit('update:isDialogVisible', false)
}
const getFocusOnSearchList = e => {
if (e.key === 'ArrowDown') {
e.preventDefault()
refSearchList.value?.focus('next')
} else if (e.key === 'ArrowUp') {
e.preventDefault()
refSearchList.value?.focus('prev')
}
}
const dialogModelValueUpdate = val => {
searchQueryLocal.value = ''
emit('update:isDialogVisible', val)
}
watch(() => props.isDialogVisible, () => {
searchQueryLocal.value = ''
})
</script>
<template>
<VDialog
max-width="600"
:model-value="props.isDialogVisible"
:height="$vuetify.display.smAndUp ? '537' : '100%'"
:fullscreen="$vuetify.display.width < 600"
class="app-bar-search-dialog"
@update:model-value="dialogModelValueUpdate"
@keyup.esc="clearSearchAndCloseDialog"
>
<VCard
height="100%"
width="100%"
class="position-relative"
>
<VCardText class="py-3 px-4">
<!-- 👉 Search Input -->
<VTextField
ref="refSearchInput"
v-model="searchQueryLocal"
autofocus
density="compact"
variant="plain"
class="app-bar-search-input"
@keyup.esc="clearSearchAndCloseDialog"
@keydown="getFocusOnSearchList"
@update:model-value="$emit('search', searchQueryLocal)"
>
<!-- 👉 Prepend Inner -->
<template #prepend-inner>
<div class="d-flex align-center text-high-emphasis me-1">
<VIcon
size="24"
icon="ri-search-line"
style=" margin-block-start: 1px; opacity: 1;"
/>
</div>
</template>
<!-- 👉 Append Inner -->
<template #append-inner>
<div class="d-flex align-start">
<div
class="text-base text-disabled cursor-pointer me-1"
@click="clearSearchAndCloseDialog"
>
[esc]
</div>
<IconBtn
class="mt-n2"
color="medium-emphasis"
@click="clearSearchAndCloseDialog"
>
<VIcon icon="ri-close-line" />
</IconBtn>
</div>
</template>
</VTextField>
</VCardText>
<!-- 👉 Divider -->
<VDivider />
<!-- 👉 Perfect Scrollbar -->
<PerfectScrollbar
:options="{ wheelPropagation: false, suppressScrollX: true }"
class="h-100"
>
<!-- 👉 Search List -->
<VList
v-show="searchQueryLocal.length && !!props.searchResults.length"
ref="refSearchList"
density="compact"
class="app-bar-search-list py-0"
>
<!-- 👉 list Item /List Sub header -->
<template
v-for="item in props.searchResults"
:key="item"
>
<slot
name="searchResult"
:item="item"
>
<VListItem>
{{ item }}
</VListItem>
</slot>
</template>
</VList>
<!-- 👉 Suggestions -->
<div
v-show="!!props.searchResults && !searchQueryLocal && $slots.suggestions"
class="h-100"
>
<slot name="suggestions" />
</div>
<!-- 👉 No Data found -->
<div
v-show="!props.searchResults.length && searchQueryLocal.length"
class="h-100"
>
<slot name="noData">
<VCardText class="h-100">
<div class="app-bar-search-suggestions d-flex flex-column align-center justify-center text-high-emphasis pa-12">
<VIcon
size="64"
icon="ri-file-forbid-line"
/>
<div class="d-flex align-center flex-wrap justify-center gap-2 text-h5 my-3">
<span>No Result For </span>
<span>"{{ searchQueryLocal }}"</span>
</div>
<slot name="noDataSuggestion" />
</div>
</VCardText>
</slot>
</div>
</PerfectScrollbar>
</VCard>
</VDialog>
</template>
<style lang="scss">
.app-bar-search-suggestions {
.app-bar-search-suggestion {
&:hover {
color: rgb(var(--v-theme-primary));
}
}
}
.app-bar-search-dialog {
.app-bar-search-input {
.v-field__input {
padding-block-start: 0.2rem;
}
}
.v-overlay__scrim {
backdrop-filter: blur(4px);
}
.app-bar-search-list {
.v-list-item,
.v-list-subheader {
font-size: 0.75rem;
padding-inline: 1rem;
}
.v-list-item {
.v-list-item__append {
.enter-icon {
visibility: hidden;
}
}
&:hover,
&:active,
&:focus {
.v-list-item__append {
.enter-icon {
visibility: visible;
}
}
}
}
.v-list-subheader {
line-height: 1;
min-block-size: auto;
padding-block: 16px 8px;
padding-inline-start: 1rem;
text-transform: uppercase;
}
}
}
@supports selector(:focus-visible) {
.app-bar-search-dialog {
.v-list-item:focus-visible::after {
content: none;
}
}
}
</style>
<style lang="scss" scoped>
.card-list {
--v-card-list-gap: 16px;
}
</style>
<script setup>
const props = defineProps({
title: {
type: String,
required: true,
},
})
const emit = defineEmits(['cancel'])
</script>
<template>
<div class="pa-5 d-flex align-center">
<h5 class="text-h5">
{{ props.title }}
</h5>
<VSpacer />
<slot name="beforeClose" />
<IconBtn
class="text-medium-emphasis"
size="x-small"
@click="$emit('cancel', $event)"
>
<VIcon
icon="ri-close-line"
size="24"
/>
</IconBtn>
</div>
</template>
<script setup>
import stepperCheck from '@images/svg/stepper-check.svg'
const props = defineProps({
items: {
type: Array,
required: true,
},
currentStep: {
type: Number,
required: false,
default: 0,
},
direction: {
type: String,
required: false,
default: 'horizontal',
},
iconSize: {
type: [
String,
Number,
],
required: false,
default: 52,
},
isActiveStepValid: {
type: Boolean,
required: false,
default: undefined,
},
align: {
type: String,
required: false,
default: 'default',
},
})
const emit = defineEmits(['update:currentStep'])
const currentStep = ref(props.currentStep || 0)
const activeOrCompletedStepsClasses = computed(() => index => index < currentStep.value ? 'stepper-steps-completed' : index === currentStep.value ? 'stepper-steps-active' : '')
const isHorizontalAndNotLastStep = computed(() => index => props.direction === 'horizontal' && props.items.length - 1 !== index)
// check if validation is enabled
const isValidationEnabled = computed(() => {
return props.isActiveStepValid !== undefined
})
watchEffect(() => {
if (props.currentStep !== undefined && props.currentStep < props.items.length && props.currentStep >= 0)
currentStep.value = props.currentStep
emit('update:currentStep', currentStep.value)
})
</script>
<template>
<VSlideGroup
v-model="currentStep"
class="app-stepper"
show-arrows
:direction="props.direction"
:class="`app-stepper-${props.align} ${props.items[0].icon ? 'app-stepper-icons' : ''}`"
>
<VSlideGroupItem
v-for="(item, index) in props.items"
:key="item.title"
:value="index"
>
<div
class="cursor-pointer app-stepper-step mx-1"
:class="[
(!props.isActiveStepValid && (isValidationEnabled)) && 'stepper-steps-invalid',
activeOrCompletedStepsClasses(index),
]"
@click="!isValidationEnabled && emit('update:currentStep', index)"
>
<!-- SECTION stepper step with icon -->
<template v-if="item.icon">
<div class="stepper-icon-step text-high-emphasis d-flex align-center gap-2">
<!-- 👉 icon and title -->
<div
class="d-flex align-center gap-3 step-wrapper"
:class="[props.direction === 'horizontal' && 'flex-column']"
>
<div class="stepper-icon">
<template v-if="typeof item.icon === 'object'">
<Component :is="item.icon" />
</template>
<VIcon
v-else
:icon="item.icon"
:size="item.size || props.iconSize"
/>
</div>
<div>
<p class="stepper-title font-weight-medium text-base mb-1">
{{ item.title }}
</p>
<p
v-if="item.subtitle"
class="stepper-subtitle text-sm mb-0"
>
{{ item.subtitle }}
</p>
</div>
</div>
<!-- 👉 append chevron -->
<VIcon
v-if="isHorizontalAndNotLastStep(index)"
class="flip-in-rtl stepper-chevron-indicator mx-6"
size="18"
icon="ri-arrow-right-s-line"
/>
</div>
</template>
<!-- !SECTION -->
<!-- SECTION stepper step without icon -->
<template v-else>
<div class="d-flex align-center gap-x-2">
<div class="d-flex align-center gap-2">
<div
class="d-flex align-center justify-center"
style="block-size: 24px; inline-size: 24px;"
>
<!-- 👉 custom circle icon -->
<template v-if="index >= currentStep">
<div
v-if="(!isValidationEnabled || props.isActiveStepValid || index !== currentStep)"
class="stepper-step-indicator"
/>
<VIcon
v-else
icon="ri-error-warning-line"
size="24"
color="error"
/>
</template>
<!-- 👉 step completed icon -->
<component
:is="stepperCheck"
v-else
class="stepper-step-icon"
/>
</div>
<!-- 👉 Step Number -->
<h4 :class="`${!item.subtitle ? 'text-h6' : 'text-h4'} step-number`">
{{ (index + 1).toString().padStart(2, '0') }}
</h4>
</div>
<!-- 👉 title and subtitle -->
<div
class="app-stepper-title-wrapper"
style="line-height: 0;"
>
<h6 class="text-base font-weight-medium step-title">
{{ item.title }}
</h6>
<p
v-if="item.subtitle"
class="text-sm step-subtitle mb-0"
>
{{ item.subtitle }}
</p>
</div>
<!-- 👉 stepper step line -->
<div
v-if="isHorizontalAndNotLastStep(index)"
class="stepper-step-line"
/>
</div>
<div
v-if="props.direction === 'vertical' && props.items.length - 1 !== index"
class="stepper-step-line vertical"
/>
</template>
<!-- !SECTION -->
</div>
</VSlideGroupItem>
</VSlideGroup>
</template>
<style lang="scss">
@use "@core/scss/base/mixins.scss";
/* stylelint-disable no-descending-specificity */
.app-stepper {
&.app-stepper-default:not(.app-stepper-icons) .app-stepper-step:not(:last-child) {
inline-size: 100%;
}
// 👉 stepper step with icon and default
.v-slide-group__content {
.stepper-step-indicator {
border: 0.1875rem solid rgb(var(--v-theme-primary));
border-radius: 50%;
background-color: rgb(var(--v-theme-surface));
block-size: 1.25rem;
inline-size: 1.25rem;
opacity: var(--v-activated-opacity);
}
.stepper-step-line {
border-radius: 0.1875rem;
background-color: rgb(var(--v-theme-primary));
block-size: 0.1875rem;
opacity: var(--v-activated-opacity);
}
.stepper-step-line:not(.vertical) {
inline-size: 100%;
min-inline-size: 3rem;
}
.stepper-step-line.vertical {
border-radius: 1.25rem;
block-size: 1.25rem;
inline-size: 0.1875rem;
margin-inline-start: 0.625rem;
}
.stepper-chevron-indicator {
color: rgba(var(--v-theme-on-surface), var(--v-disabled-opacity));
}
.stepper-steps-completed,
.stepper-steps-active {
.stepper-icon-step,
.stepper-step-icon {
color: rgb(var(--v-theme-primary)) !important;
}
.stepper-step-indicator {
border-width: 0.3125rem;
opacity: 1;
}
}
.stepper-steps-completed {
.stepper-step-line {
opacity: 1;
}
.stepper-chevron-indicator {
color: rgb(var(--v-theme-primary));
}
}
.stepper-steps-invalid.stepper-steps-active {
.stepper-icon-step,
.step-number,
.step-title,
.step-subtitle {
color: rgb(var(--v-theme-error)) !important;
}
}
.app-stepper-step:not(.stepper-steps-active,.stepper-steps-completed) {
.step-number {
color: rgba(var(--v-theme-on-surface), var(--v-disabled-opacity));
}
}
}
.app-stepper-title-wrapper {
text-wrap: nowrap;
}
// 👉 stepper step with bg color
&.stepper-icon-step-bg {
.v-slide-group__content {
row-gap: 1.5rem;
}
.stepper-icon-step {
.step-wrapper {
flex-direction: row !important;
}
.stepper-icon {
display: flex;
align-items: center;
justify-content: center;
border-radius: 0.3125rem;
background-color: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));
block-size: 2.5rem;
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
inline-size: 2.5rem;
margin-inline-end: 0.3rem;
}
.stepper-title,
.stepper-subtitle {
line-height: normal;
}
.stepper-title {
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
font-size: 0.9375rem;
line-height: 1.375rem;
}
.stepper-subtitle {
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
font-size: 0.8125rem;
font-weight: 400;
line-height: 1.25rem;
}
}
.stepper-steps-active {
.stepper-icon-step {
.stepper-icon {
background-color: rgb(var(--v-theme-primary));
color: rgba(var(--v-theme-on-primary));
@include mixins.elevation(2);
}
}
}
.stepper-steps-completed {
.stepper-icon-step {
.stepper-icon {
background: rgba(var(--v-theme-primary), 0.16);
color: rgba(var(--v-theme-primary));
}
}
}
}
// 👉 stepper alignment
&.app-stepper-default {
.v-slide-group__content {
justify-content: space-between;
}
}
&.app-stepper-center {
.v-slide-group__content {
justify-content: center;
}
}
&.app-stepper- {
.v-slide-group__content {
justify-content: start;
}
}
&.app-stepper-end {
.v-slide-group__content {
justify-content: end;
}
}
&.app-stepper-icons {
.app-stepper-step:not(.stepper-steps-active,.stepper-steps-completed) {
.stepper-title {
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
}
}
&:not(.stepper-icon-step-bg) {
.step-wrapper {
padding-block: 1.25rem;
padding-inline: 1.875rem;
}
}
&.v-slide-group--vertical {
.step-wrapper {
padding-inline: 0;
}
}
}
}
</style>
<script setup>
const buyNowUrl = typeof window !== 'undefined' && 'isMarketplace' in window && window.isMarketplace ? 'https://store.vuetifyjs.com/products/materio-vuetify-vuejs-admin-template' : 'https://themeselection.com/item/materio-vuetify-vuejs-admin-template/'
</script>
<template>
<a
class="buy-now-button d-print-none"
role="button"
rel="noopener noreferrer"
:href="buyNowUrl"
target="_blank"
>
Buy Now
<span class="button-inner" />
</a>
</template>
<style lang="scss" scoped>
.buy-now-button,
.button-inner {
display: inline-flex;
box-sizing: border-box;
align-items: center;
justify-content: center;
border: 0;
border-radius: 6px;
margin: 0;
animation: anime 12s linear infinite;
appearance: none;
background: linear-gradient(-45deg, #ffa63d, #ff3d77, #338aff, #3cf0c5);
background-size: 600%;
color: rgba(255, 255, 255, 90%);
cursor: pointer;
font-size: 0.9375rem;
font-weight: 500;
letter-spacing: 0.43px;
line-height: 1.2;
min-inline-size: 50px;
outline: 0;
padding-block: 0.625rem;
padding-inline: 1.25rem;
text-decoration: none;
text-transform: none;
vertical-align: middle;
}
.buy-now-button {
position: fixed;
z-index: 999;
inset-block-end: 5%;
inset-inline-end: 87px;
&:hover {
color: white;
text-decoration: none;
}
.button-inner {
position: absolute;
z-index: -1;
filter: blur(12px);
inset: 0;
opacity: 0;
transition: opacity 200ms ease-in-out;
}
&:not(:hover) .button-inner {
opacity: 0.8;
}
}
@keyframes anime {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
</style>
<script setup>
const props = defineProps({
title: {
type: String,
required: true,
},
divider: {
type: Boolean,
required: false,
default: true,
},
})
</script>
<template>
<VDivider v-if="props.divider" />
<div class="customizer-section">
<div>
<VChip
label
size="small"
color="primary"
rounded="sm"
>
{{ props.title }}
</VChip>
</div>
<slot />
</div>
</template>
<script setup>
const props = defineProps({
icon: {
type: String,
required: false,
default: 'ri-close-line',
},
iconSize: {
type: String,
required: false,
default: '24',
},
})
</script>
<template>
<IconBtn class="v-dialog-close-btn">
<VIcon
:icon="props.icon"
:size="props.iconSize"
/>
</IconBtn>
</template>
<script setup>
import {
useDropZone,
useFileDialog,
useObjectUrl,
} from '@vueuse/core'
const dropZoneRef = ref()
const fileData = ref([])
const { open, onChange } = useFileDialog({ accept: 'image/*' })
function onDrop(DroppedFiles) {
DroppedFiles?.forEach(file => {
if (file.type.slice(0, 6) !== 'image/') {
// eslint-disable-next-line no-alert
alert('Only image files are allowed')
return
}
fileData.value.push({
file,
url: useObjectUrl(file).value ?? '',
})
})
}
onChange(selectedFiles => {
if (!selectedFiles)
return
for (const file of selectedFiles) {
fileData.value.push({
file,
url: useObjectUrl(file).value ?? '',
})
}
})
useDropZone(dropZoneRef, onDrop)
</script>
<template>
<div class="flex">
<div class="w-full h-auto relative">
<div
ref="dropZoneRef"
class="cursor-pointer"
@click="() => open()"
>
<div
v-if="fileData.length === 0"
class="d-flex flex-column justify-center align-center gap-y-2 pa-12 drop-zone rounded"
>
<IconBtn
variant="tonal"
color="secondary"
class="rounded"
size="40"
>
<VIcon
size="24"
icon="ri-upload-2-line"
/>
</IconBtn>
<h4 class="text-h4">
Drag and drop your image here.
</h4>
<span class="text-disabled">or</span>
<VBtn
variant="outlined"
size="small"
>
Browse Images
</VBtn>
</div>
<div
v-else
class="d-flex justify-center align-center gap-3 pa-8 drop-zone flex-wrap"
>
<VRow class="match-height w-100">
<template
v-for="(item, index) in fileData"
:key="index"
>
<VCol
cols="12"
sm="4"
>
<VCard :ripple="false">
<VCardText
class="d-flex flex-column"
@click.stop
>
<VImg
:src="item.url"
width="200px"
height="150px"
class="w-100 mx-auto"
/>
<div class="mt-2">
<span class="clamp-text text-wrap">
{{ item.file.name }}
</span>
<span>
{{ item.file.size / 1000 }} KB
</span>
</div>
</VCardText>
<VCardActions>
<VBtn
variant="text"
block
@click.stop="fileData.splice(index, 1)"
>
Remove File
</VBtn>
</VCardActions>
</VCard>
</VCol>
</template>
</VRow>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.drop-zone {
border: 1px dashed rgba(var(--v-theme-on-surface), var(--v-border-opacity));
}
</style>
<script setup>
const props = defineProps({
languages: {
type: Array,
required: true,
},
location: {
type: null,
required: false,
default: 'bottom end',
},
})
const { locale } = useI18n({ useScope: 'global' })
</script>
<template>
<IconBtn>
<VIcon icon="ri-translate-2" />
<!-- Menu -->
<VMenu
activator="parent"
:location="props.location"
offset="15px"
width="160"
>
<!-- List -->
<VList
:selected="[locale]"
color="primary"
mandatory
>
<!-- List item -->
<VListItem
v-for="lang in props.languages"
:key="lang.i18nLang"
:value="lang.i18nLang"
@click="locale = lang.i18nLang"
>
<!-- Language label -->
<VListItemTitle>
{{ lang.label }}
</VListItemTitle>
</VListItem>
</VList>
</VMenu>
</IconBtn>
</template>
<script setup>
const props = defineProps({
menuList: {
type: Array,
required: false,
},
itemProps: {
type: Boolean,
required: false,
},
iconSize: {
type: String,
required: false,
},
})
</script>
<template>
<IconBtn>
<VIcon
icon="ri-more-2-line"
:size="iconSize"
/>
<VMenu
v-if="props.menuList"
activator="parent"
>
<VList
:items="props.menuList"
:item-props="props.itemProps"
/>
</VMenu>
</IconBtn>
</template>
<script setup>
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
const props = defineProps({
notifications: {
type: Array,
required: true,
},
badgeProps: {
type: Object,
required: false,
default: undefined,
},
location: {
type: null,
required: false,
default: 'bottom end',
},
})
const emit = defineEmits([
'read',
'unread',
'remove',
'click:notification',
])
const isAllMarkRead = computed(() => {
return props.notifications.some(item => item.isSeen === false)
})
const markAllReadOrUnread = () => {
const allNotificationsIds = props.notifications.map(item => item.id)
if (!isAllMarkRead.value)
emit('unread', allNotificationsIds)
else
emit('read', allNotificationsIds)
}
const totalUnreadNotifications = computed(() => props.notifications.filter(item => !item.isSeen).length)
</script>
<template>
<IconBtn id="notification-btn">
<VBadge
dot
v-bind="props.badgeProps"
:model-value="props.notifications.some(n => !n.isSeen)"
color="error"
bordered
offset-x="1"
offset-y="1"
>
<VIcon icon="ri-notification-2-line" />
</VBadge>
<VMenu
activator="parent"
width="380"
:location="props.location"
offset="15px"
:close-on-content-click="false"
>
<VCard class="d-flex flex-column">
<!-- 👉 Header -->
<VCardItem class="notification-section">
<h5 class="text-h5 text-truncate">
Notifications
</h5>
<template #append>
<VChip
v-show="!!isAllMarkRead"
size="small"
class="me-3"
variant="tonal"
color="primary"
>
{{ totalUnreadNotifications }} new
</VChip>
<IconBtn
v-show="props.notifications.length"
@click="markAllReadOrUnread"
>
<VIcon
color="high-emphasis"
:icon="!isAllMarkRead ? 'ri-mail-line' : 'ri-mail-open-line' "
/>
<VTooltip
activator="parent"
location="start"
>
{{ !isAllMarkRead ? 'Mark all as unread' : 'Mark all as read' }}
</VTooltip>
</IconBtn>
</template>
</VCardItem>
<VDivider />
<!-- 👉 Notifications list -->
<PerfectScrollbar
:options="{ wheelPropagation: false }"
style="max-block-size: 27rem;"
>
<VList class="py-0">
<template
v-for="(notification, index) in props.notifications"
:key="notification.title"
>
<VDivider v-if="index > 0" />
<VListItem
link
lines="one"
min-height="66px"
class="list-item-hover-class py-3"
@click="$emit('click:notification', notification)"
>
<!-- Slot: Prepend -->
<!-- Handles Avatar: Image, Icon, Text -->
<div class="d-flex align-start gap-3">
<div>
<VAvatar
:color="notification.color && !notification.img ? notification.color : undefined"
:variant="notification.img ? undefined : 'tonal' "
>
<span v-if="notification.text">{{ avatarText(notification.text) }}</span>
<VImg
v-if="notification.img"
:src="notification.img"
/>
<VIcon
v-if="notification.icon"
:icon="notification.icon"
/>
</VAvatar>
</div>
<div>
<h6 class="text-h6 mb-1">
{{ notification.title }}
</h6>
<p
class="text-body-2 mb-2"
style="letter-spacing: 0.4px !important; line-height: 18px;"
>
{{ notification.subtitle }}
</p>
<p
class="text-sm text-disabled mb-0"
style="letter-spacing: 0.4px !important; line-height: 18px;"
>
{{ notification.time }}
</p>
</div>
<VSpacer />
<div class="d-flex flex-column align-end gap-2">
<VIcon
:color="!notification.isSeen ? 'primary' : '#a8aaae'"
:class="`${notification.isSeen ? 'visible-in-hover' : ''} ms-1`"
size="10"
icon="ri-circle-fill"
@click.stop="$emit(notification.isSeen ? 'unread' : 'read', [notification.id])"
/>
<div style="block-size: 20px; inline-size: 20px;">
<VIcon
size="20"
icon="ri-close-line"
color="secondary"
class="visible-in-hover"
@click="$emit('remove', notification.id)"
/>
</div>
</div>
</div>
</VListItem>
</template>
<VListItem
v-show="!props.notifications.length"
class="text-center text-medium-emphasis"
style="block-size: 56px;"
>
<VListItemTitle>No Notification Found!</VListItemTitle>
</VListItem>
</VList>
</PerfectScrollbar>
<VDivider />
<!-- 👉 Footer -->
<VCardText
v-show="props.notifications.length"
class="pa-4"
>
<VBtn
block
size="small"
>
View All Notifications
</VBtn>
</VCardText>
</VCard>
</VMenu>
</IconBtn>
</template>
<style lang="scss">
.notification-section {
padding: 14px !important;
}
.list-item-hover-class {
.visible-in-hover {
display: none;
}
&:hover {
.visible-in-hover {
display: block;
}
}
}
</style>
<script setup>
import { Placeholder } from '@tiptap/extension-placeholder'
import { TextAlign } from '@tiptap/extension-text-align'
import { Underline } from '@tiptap/extension-underline'
import { StarterKit } from '@tiptap/starter-kit'
import {
EditorContent,
useEditor,
} from '@tiptap/vue-3'
const props = defineProps({
modelValue: {
type: String,
required: true,
},
placeholder: {
type: String,
required: false,
},
})
const emit = defineEmits(['update:modelValue'])
const editorRef = ref()
const editor = useEditor({
content: props.modelValue,
extensions: [
StarterKit,
TextAlign.configure({
types: [
'heading',
'paragraph',
],
}),
Placeholder.configure({ placeholder: props.placeholder ?? 'Write something here...' }),
Underline,
],
onUpdate() {
if (!editor.value)
return
emit('update:modelValue', editor.value.getHTML())
},
})
watch(() => props.modelValue, () => {
const isSame = editor.value?.getHTML() === props.modelValue
if (isSame)
return
editor.value?.commands.setContent(props.modelValue)
})
</script>
<template>
<div class="pa-6 productDescriptionEditor">
<!-- buttons -->
<div
v-if="editor"
class="d-flex gap-1 flex-wrap align-center"
>
<VBtn
size="small"
icon
rounded
:variant="editor.isActive('bold') ? 'tonal' : 'text'"
:color="editor.isActive('bold') ? 'primary' : 'default'"
@click="editor.chain().focus().toggleBold().run()"
>
<VIcon
icon="ri-bold"
class="font-weight-medium"
/>
</VBtn>
<VBtn
size="small"
icon
rounded
:variant="editor.isActive('italic') ? 'tonal' : 'text'"
:color="editor.isActive('italic') ? 'primary' : 'default'"
@click="editor.chain().focus().toggleItalic().run()"
>
<VIcon
icon="ri-italic"
class="font-weight-medium"
/>
</VBtn>
<VBtn
size="small"
icon
rounded
:variant="editor.isActive('underline') ? 'tonal' : 'text'"
:color="editor.isActive('underline') ? 'primary' : 'default'"
@click="editor.commands.toggleUnderline()"
>
<VIcon icon="ri-underline" />
</VBtn>
<VBtn
size="small"
icon
rounded
:variant="editor.isActive('strike') ? 'tonal' : 'text'"
:color="editor.isActive('strike') ? 'primary' : 'default'"
@click="editor.chain().focus().toggleStrike().run()"
>
<VIcon
icon="ri-strikethrough"
size="20"
/>
</VBtn>
<VBtn
size="small"
icon
rounded
:variant="editor.isActive({ textAlign: 'left' }) ? 'tonal' : 'text'"
:color="editor.isActive({ textAlign: 'left' }) ? 'primary' : 'default'"
@click="editor.chain().focus().setTextAlign('left').run()"
>
<VIcon
icon="ri-align-left"
size="20"
/>
</VBtn>
<VBtn
size="small"
icon
rounded
:color="editor.isActive({ textAlign: 'center' }) ? 'primary' : 'default'"
:variant="editor.isActive({ textAlign: 'center' }) ? 'tonal' : 'text'"
@click="editor.chain().focus().setTextAlign('center').run()"
>
<VIcon
icon="ri-align-center"
size="20"
/>
</VBtn>
<VBtn
size="small"
icon
rounded
:variant="editor.isActive({ textAlign: 'right' }) ? 'tonal' : 'text'"
:color="editor.isActive({ textAlign: 'right' }) ? 'primary' : 'default'"
@click="editor.chain().focus().setTextAlign('right').run()"
>
<VIcon
icon="ri-align-right"
size="20"
/>
</VBtn>
<VBtn
size="small"
icon
rounded
:variant="editor.isActive({ textAlign: 'justify' }) ? 'tonal' : 'text'"
:color="editor.isActive({ textAlign: 'justify' }) ? 'primary' : 'default'"
@click="editor.chain().focus().setTextAlign('justify').run()"
>
<VIcon
icon="ri-align-justify"
size="20"
/>
</VBtn>
</div>
<VDivider class="my-4" />
<EditorContent
ref="editorRef"
:editor="editor"
/>
</div>
</template>
<style lang="scss">
.productDescriptionEditor {
.ProseMirror {
padding: 0 !important;
min-block-size: 12vh;
p {
margin-block-end: 0;
}
p.is-editor-empty:first-child::before {
block-size: 0;
color: #adb5bd;
content: attr(data-placeholder);
float: inline-start;
pointer-events: none;
}
&-focused {
outline: none;
}
}
}
</style>
<script setup>
const { y } = useWindowScroll()
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth',
})
}
</script>
<template>
<VScaleTransition
style="transform-origin: center;"
class="scroll-to-top d-print-none"
>
<VBtn
v-show="y > 200"
icon
density="comfortable"
@click="scrollToTop"
>
<VIcon
size="22"
icon="ri-arrow-up-line"
/>
</VBtn>
</VScaleTransition>
</template>
<style lang="scss">
.scroll-to-top {
position: fixed !important;
// To keep button on top of v-layout. E.g. Email app
z-index: 999;
inset-block-end: 5%;
inset-inline-end: 25px;
}
</style>
<script setup>
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
const props = defineProps({
togglerIcon: {
type: String,
required: false,
default: 'ri-star-smile-line',
},
shortcuts: {
type: Array,
required: true,
},
})
const router = useRouter()
</script>
<template>
<IconBtn>
<VIcon :icon="props.togglerIcon" />
<VMenu
activator="parent"
offset="15px"
location="bottom end"
>
<VCard
max-width="380"
max-height="560"
class="d-flex flex-column"
>
<VCardItem class="px-4 py-2">
<h5 class="text-h5">
Shortcuts
</h5>
<template #append>
<IconBtn>
<VIcon
icon="ri-add-line"
color="high-emphasis"
/>
<VTooltip
activator="parent"
location="start"
>
Add Shortcut
</VTooltip>
</IconBtn>
</template>
</VCardItem>
<VDivider />
<PerfectScrollbar :options="{ wheelPropagation: false }">
<VRow class="ma-0 mt-n1">
<VCol
v-for="(shortcut, index) in props.shortcuts"
:key="shortcut.title"
cols="6"
class="text-center border-t cursor-pointer pa-6 shortcut-icon"
:class="(index + 1) % 2 ? 'border-e' : ''"
@click="router.push(shortcut.to)"
>
<VAvatar
variant="tonal"
size="50"
>
<VIcon
color="high-emphasis"
size="26"
:icon="shortcut.icon"
/>
</VAvatar>
<h6 class="text-h6 mt-3">
{{ shortcut.title }}
</h6>
<p class="text-sm text-medium-emphasis mb-0">
{{ shortcut.subtitle }}
</p>
</VCol>
</VRow>
</PerfectScrollbar>
</VCard>
</VMenu>
</IconBtn>
</template>
<style lang="scss">
.shortcut-icon:hover {
background-color: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));
}
</style>
<script setup>
const props = defineProps({
page: {
type: Number,
required: true,
},
itemsPerPage: {
type: Number,
required: true,
},
totalItems: {
type: Number,
required: true,
},
})
const emit = defineEmits(['update:page'])
const updatePage = value => {
emit('update:page', value)
}
</script>
<template>
<div>
<VDivider />
<div class="d-flex align-center justify-sm-space-between justify-center flex-wrap gap-3 px-6 py-3">
<p class="text-disabled mb-0">
{{ paginationMeta({ page, itemsPerPage }, totalItems) }}
</p>
<VPagination
:model-value="page"
active-color="primary"
:length="Math.ceil(totalItems / itemsPerPage)"
:total-visible="$vuetify.display.xs ? 1 : Math.min(Math.ceil(totalItems / itemsPerPage), 5)"
@update:model-value="updatePage"
/>
</div>
</div>
</template>
<script setup>
import { useConfigStore } from '@core/stores/config'
const props = defineProps({
themes: {
type: Array,
required: true,
},
})
const configStore = useConfigStore()
const selectedItem = ref([configStore.theme])
// Update icon if theme is changed from other sources
watch(() => configStore.theme, () => {
selectedItem.value = [configStore.theme]
}, { deep: true })
</script>
<template>
<IconBtn>
<VIcon :icon="props.themes.find(t => t.name === configStore.theme)?.icon" />
<VTooltip
activator="parent"
open-delay="1000"
scroll-strategy="close"
>
<span class="text-capitalize">{{ configStore.theme }}</span>
</VTooltip>
<VMenu
activator="parent"
offset="15px"
width="160"
>
<VList
v-model:selected="selectedItem"
mandatory
>
<VListItem
v-for="{ name, icon } in props.themes"
:key="name"
:value="name"
:prepend-icon="icon"
color="primary"
@click="() => { configStore.theme = name }"
>
<VListItemTitle class="text-capitalize">
{{ name }}
</VListItemTitle>
</VListItem>
</VList>
</VMenu>
</IconBtn>
</template>
<script setup>
import { Placeholder } from '@tiptap/extension-placeholder'
import { TextAlign } from '@tiptap/extension-text-align'
import { Underline } from '@tiptap/extension-underline'
import { StarterKit } from '@tiptap/starter-kit'
import {
EditorContent,
useEditor,
} from '@tiptap/vue-3'
const props = defineProps({
modelValue: {
type: String,
required: true,
},
placeholder: {
type: String,
required: false,
},
isDivider: {
type: Boolean,
required: false,
default: true,
},
})
const emit = defineEmits(['update:modelValue'])
const editorRef = ref()
const editor = useEditor({
content: props.modelValue,
extensions: [
StarterKit,
TextAlign.configure({
types: [
'heading',
'paragraph',
],
}),
Placeholder.configure({ placeholder: props.placeholder ?? 'Write something here...' }),
Underline,
],
onUpdate() {
if (!editor.value)
return
emit('update:modelValue', editor.value.getHTML())
},
})
watch(() => props.modelValue, () => {
const isSame = editor.value?.getHTML() === props.modelValue
if (isSame)
return
editor.value?.commands.setContent(props.modelValue)
})
</script>
<template>
<div>
<div
v-if="editor"
class="d-flex gap-2 px-6 py-2 flex-wrap align-center editor"
>
<VBtn
:class="{ 'is-active': editor.isActive('bold') }"
icon="ri-bold"
class="rounded"
size="small"
variant="text"
color="default"
@click="editor.chain().focus().toggleBold().run()"
/>
<VBtn
:class="{ 'is-active': editor.isActive('underline') }"
icon="ri-underline"
class="rounded"
size="small"
variant="text"
color="default"
@click="editor.commands.toggleUnderline()"
/>
<VBtn
icon="ri-italic"
class="rounded"
size="small"
variant="text"
color="default"
:class="{ 'is-active': editor.isActive('italic') }"
@click="editor.chain().focus().toggleItalic().run()"
/>
<VBtn
icon="ri-strikethrough"
class="rounded"
size="small"
variant="text"
color="default"
:class="{ 'is-active': editor.isActive('strike') }"
@click="editor.chain().focus().toggleStrike().run()"
/>
<VBtn
icon="ri-align-left"
class="rounded"
size="small"
variant="text"
color="default"
:class="{ 'is-active': editor.isActive({ textAlign: 'left' }) }"
@click="editor.chain().focus().setTextAlign('left').run()"
/>
<VBtn
icon="ri-align-center"
class="rounded"
size="small"
variant="text"
color="default"
:class="{ 'is-active': editor.isActive({ textAlign: 'center' }) }"
@click="editor.chain().focus().setTextAlign('center').run()"
/>
<VBtn
icon="ri-align-right"
class="rounded"
size="small"
variant="text"
color="default"
:class="{ 'is-active': editor.isActive({ textAlign: 'right' }) }"
@click="editor.chain().focus().setTextAlign('right').run()"
/>
<VBtn
icon="ri-align-justify"
class="rounded"
size="small"
variant="text"
color="default"
:class="{ 'is-active': editor.isActive({ textAlign: 'justify' }) }"
@click="editor.chain().focus().setTextAlign('justify').run()"
/>
</div>
<VDivider v-if="props.isDivider" />
<EditorContent
ref="editorRef"
:editor="editor"
/>
</div>
</template>
<style lang="scss">
.ProseMirror {
padding: 0.5rem;
min-block-size: 15vh;
outline: none;
p {
margin-block-end: 0;
}
p.is-editor-empty:first-child::before {
block-size: 0;
color: #adb5bd;
content: attr(data-placeholder);
float: inline-start;
pointer-events: none;
}
}
</style>
<style lang="scss">
.is-active {
border-color: rgba(var(--v-theme-primary), var(--v-border-opacity)) !important;
background-color: rgba(var(--v-theme-primary), var(--v-activated-opacity));
color: rgb(var(--v-theme-primary));
}
</style>
<script setup>
const props = defineProps({
selectedCheckbox: {
type: Array,
required: true,
},
checkboxContent: {
type: Array,
required: true,
},
gridColumn: {
type: null,
required: false,
},
})
const emit = defineEmits(['update:selectedCheckbox'])
const updateSelectedOption = value => {
if (typeof value !== 'boolean' && value !== null)
emit('update:selectedCheckbox', value)
}
</script>
<template>
<VRow v-if="props.checkboxContent && props.selectedCheckbox">
<VCol
v-for="item in props.checkboxContent"
:key="item.title"
v-bind="gridColumn"
>
<VLabel
class="custom-input custom-checkbox rounded cursor-pointer"
:class="props.selectedCheckbox.includes(item.value) ? 'active' : ''"
>
<div>
<VCheckbox
:model-value="props.selectedCheckbox"
:value="item.value"
@update:model-value="updateSelectedOption"
/>
</div>
<slot :item="item">
<div class="flex-grow-1">
<div class="d-flex align-center mb-2">
<h6 class="cr-title text-base">
{{ item.title }}
</h6>
<VSpacer />
<span
v-if="item.subtitle"
class="text-sm text-disabled"
>{{ item.subtitle }}</span>
</div>
<p class="text-sm text-medium-emphasis mb-0">
{{ item.desc }}
</p>
</div>
</slot>
</VLabel>
</VCol>
</VRow>
</template>
<style lang="scss" scoped>
.custom-checkbox {
display: flex;
align-items: flex-start;
gap: 0.25rem;
.v-checkbox {
margin-block-start: -0.375rem;
}
.cr-title {
font-weight: 500;
}
}
</style>
<script setup>
const props = defineProps({
selectedCheckbox: {
type: Array,
required: true,
},
checkboxContent: {
type: Array,
required: true,
},
gridColumn: {
type: null,
required: false,
},
})
const emit = defineEmits(['update:selectedCheckbox'])
const updateSelectedOption = value => {
if (typeof value !== 'boolean' && value !== null)
emit('update:selectedCheckbox', value)
}
</script>
<template>
<VRow v-if="props.checkboxContent && props.selectedCheckbox">
<VCol
v-for="item in props.checkboxContent"
:key="item.title"
v-bind="gridColumn"
>
<VLabel
class="custom-input custom-checkbox-icon rounded cursor-pointer"
:class="props.selectedCheckbox.includes(item.value) ? 'active' : ''"
>
<slot :item="item">
<div class="d-flex flex-column align-center text-center gap-2">
<VIcon
size="28"
:icon="item.icon"
class="text-high-emphasis"
/>
<h6 class="cr-title text-base">
{{ item.title }}
</h6>
<p class="text-sm text-medium-emphasis clamp-text mb-0">
{{ item.desc }}
</p>
</div>
</slot>
<div>
<VCheckbox
:model-value="props.selectedCheckbox"
:value="item.value"
@update:model-value="updateSelectedOption"
/>
</div>
</VLabel>
</VCol>
</VRow>
</template>
<style lang="scss" scoped>
.custom-checkbox-icon {
display: flex;
flex-direction: column;
gap: 0.5rem;
.v-checkbox {
margin-block-end: -0.375rem;
.v-selection-control__wrapper {
margin-inline-start: 0;
}
}
.cr-title {
font-weight: 500;
}
}
</style>
<style lang="scss">
.custom-checkbox-icon {
.v-checkbox {
margin-block-end: -0.375rem;
.v-selection-control__wrapper {
margin-inline-start: 0;
}
}
}
</style>
<script setup>
const props = defineProps({
selectedCheckbox: {
type: Array,
required: true,
},
checkboxContent: {
type: Array,
required: true,
},
gridColumn: {
type: null,
required: false,
},
})
const emit = defineEmits(['update:selectedCheckbox'])
const updateSelectedOption = value => {
if (typeof value !== 'boolean' && value !== null)
emit('update:selectedCheckbox', value)
}
</script>
<template>
<VRow v-if="props.checkboxContent && props.selectedCheckbox">
<VCol
v-for="item in props.checkboxContent"
:key="item.value"
v-bind="gridColumn"
>
<VLabel
class="custom-input custom-checkbox rounded cursor-pointer w-100"
:class="props.selectedCheckbox.includes(item.value) ? 'active' : ''"
>
<div>
<VCheckbox
:id="`custom-checkbox-with-img-${item.value}`"
:model-value="props.selectedCheckbox"
:value="item.value"
@update:model-value="updateSelectedOption"
/>
</div>
<img
:src="item.bgImage"
alt="bg-img"
class="custom-checkbox-image"
>
</VLabel>
<VLabel
v-if="item.label || $slots.label"
:for="`custom-checkbox-with-img-${item.value}`"
class="cursor-pointer"
>
<slot
name="label"
:label="item.label"
>
{{ item.label }}
</slot>
</VLabel>
</VCol>
</VRow>
</template>
<style lang="scss" scoped>
.custom-checkbox {
position: relative;
padding: 0;
.custom-checkbox-image {
block-size: 100%;
inline-size: 100%;
min-inline-size: 100%;
}
.v-checkbox {
position: absolute;
inset-block-start: 0;
inset-inline-end: 0;
visibility: hidden;
}
&.active {
border-width: 1px;
}
&:hover,
&.active {
.v-checkbox {
visibility: visible;
}
}
}
</style>
<script setup>
const props = defineProps({
selectedRadio: {
type: String,
required: true,
},
radioContent: {
type: Array,
required: true,
},
gridColumn: {
type: null,
required: false,
},
})
const emit = defineEmits(['update:selectedRadio'])
const updateSelectedOption = value => {
if (value !== null)
emit('update:selectedRadio', value)
}
</script>
<template>
<VRadioGroup
v-if="props.radioContent"
:model-value="props.selectedRadio"
@update:model-value="updateSelectedOption"
>
<VRow>
<VCol
v-for="item in props.radioContent"
:key="item.title"
v-bind="gridColumn"
>
<VLabel
class="custom-input custom-radio rounded cursor-pointer"
:class="props.selectedRadio === item.value ? 'active' : ''"
>
<div>
<VRadio :value="item.value" />
</div>
<slot :item="item">
<div class="flex-grow-1">
<div class="d-flex align-center mb-2">
<h6 class="cr-title text-base">
{{ item.title }}
</h6>
<VSpacer />
<span
v-if="item.subtitle"
class="text-disabled text-sm"
>{{ item.subtitle }}</span>
</div>
<p class="text-sm text-medium-emphasis mb-0">
{{ item.desc }}
</p>
</div>
</slot>
</VLabel>
</VCol>
</VRow>
</VRadioGroup>
</template>
<style lang="scss" scoped>
.custom-radio {
display: flex;
align-items: flex-start;
gap: 0.25rem;
.v-radio {
margin-block-start: -0.45rem;
}
.cr-title {
font-weight: 500;
}
}
</style>
<script setup>
const props = defineProps({
selectedRadio: {
type: String,
required: true,
},
radioContent: {
type: Array,
required: true,
},
gridColumn: {
type: null,
required: false,
},
})
const emit = defineEmits(['update:selectedRadio'])
const updateSelectedOption = value => {
if (value !== null)
emit('update:selectedRadio', value)
}
</script>
<template>
<VRadioGroup
v-if="props.radioContent"
:model-value="props.selectedRadio"
@update:model-value="updateSelectedOption"
>
<VRow>
<VCol
v-for="item in props.radioContent"
:key="item.title"
v-bind="gridColumn"
>
<VLabel
class="custom-input custom-radio-icon rounded cursor-pointer"
:class="props.selectedRadio === item.value ? 'active' : ''"
>
<slot :item="item">
<div class="d-flex flex-column align-center text-center gap-2">
<VIcon
size="28"
:icon="item.icon"
class="text-high-emphasis"
/>
<h6 class="cr-title text-base">
{{ item.title }}
</h6>
<p class="text-sm text-medium-emphasis mb-0 clamp-text">
{{ item.desc }}
</p>
</div>
</slot>
<div>
<VRadio :value="item.value" />
</div>
</VLabel>
</VCol>
</VRow>
</VRadioGroup>
</template>
<style lang="scss" scoped>
.custom-radio-icon {
display: flex;
flex-direction: column;
gap: 0.5rem;
.v-radio {
margin-block-end: -0.25rem;
}
.cr-title {
font-weight: 500;
}
}
</style>
<style lang="scss">
.custom-radio-icon {
.v-radio {
margin-block-end: -0.25rem;
.v-selection-control__wrapper {
margin-inline-start: 0;
}
}
}
</style>
<script setup>
const props = defineProps({
selectedRadio: {
type: String,
required: true,
},
radioContent: {
type: Array,
required: true,
},
gridColumn: {
type: null,
required: false,
},
})
const emit = defineEmits(['update:selectedRadio'])
const updateSelectedOption = value => {
if (value !== null)
emit('update:selectedRadio', value)
}
</script>
<template>
<VRadioGroup
v-if="props.radioContent"
:model-value="props.selectedRadio"
@update:model-value="updateSelectedOption"
>
<VRow>
<VCol
v-for="item in props.radioContent"
:key="item.bgImage"
v-bind="gridColumn"
>
<VLabel
class="custom-input custom-radio rounded cursor-pointer w-100"
:class="props.selectedRadio === item.value ? 'active' : ''"
>
<slot
name="content"
:item="item"
>
<template v-if="typeof item.bgImage === 'object'">
<Component
:is="item.bgImage"
class="custom-radio-image"
/>
</template>
<img
v-else
:src="item.bgImage"
alt="bg-img"
class="custom-radio-image"
>
</slot>
<VRadio
:id="`custom-radio-with-img-${item.value}`"
:value="item.value"
/>
</VLabel>
<VLabel
v-if="item.label || $slots.label"
:for="`custom-radio-with-img-${item.value}`"
class="cursor-pointer"
>
<slot
name="label"
:label="item.label"
>
{{ item.label }}
</slot>
</VLabel>
</VCol>
</VRow>
</VRadioGroup>
</template>
<style lang="scss" scoped>
.custom-radio {
padding: 0 !important;
&.active {
border-width: 1px;
}
.custom-radio-image {
block-size: 100%;
inline-size: 100%;
min-inline-size: 100%;
}
.v-radio {
visibility: hidden;
}
}
</style>
<script setup>
const props = defineProps({
collapsed: {
type: Boolean,
required: false,
default: false,
},
noActions: {
type: Boolean,
required: false,
default: false,
},
actionCollapsed: {
type: Boolean,
required: false,
default: false,
},
actionRefresh: {
type: Boolean,
required: false,
default: false,
},
actionRemove: {
type: Boolean,
required: false,
default: false,
},
loading: {
type: Boolean,
required: false,
skipCheck: true,
default: undefined,
},
title: {
type: String,
required: false,
default: undefined,
},
})
const emit = defineEmits([
'collapsed',
'refresh',
'trash',
'initialLoad',
'update:loading',
])
defineOptions({
inheritAttrs: false,
})
const _loading = ref(false)
const $loading = computed({
get() {
return props.loading !== undefined ? props.loading : _loading.value
},
set(value) {
props.loading !== undefined ? emit('update:loading', value) : _loading.value = value
},
})
const isContentCollapsed = ref(props.collapsed)
const isCardRemoved = ref(false)
// stop loading
const stopLoading = () => {
$loading.value = false
}
// trigger collapse
const triggerCollapse = () => {
isContentCollapsed.value = !isContentCollapsed.value
emit('collapsed', isContentCollapsed.value)
}
// trigger refresh
const triggerRefresh = () => {
$loading.value = true
emit('refresh', stopLoading)
}
// trigger removal
const triggeredRemove = () => {
isCardRemoved.value = true
emit('trash')
}
</script>
<template>
<VExpandTransition>
<!-- TODO remove div when transition work with v-card components: https://github.com/vuetifyjs/vuetify/issues/15111 -->
<div v-if="!isCardRemoved">
<VCard v-bind="$attrs">
<VCardItem>
<VCardTitle v-if="props.title || $slots.title">
<!-- 👉 Title slot and prop -->
<slot name="title">
{{ props.title }}
</slot>
</VCardTitle>
<template #append>
<!-- 👉 Before actions slot -->
<div>
<slot name="before-actions" />
<!-- SECTION Actions buttons -->
<!-- 👉 Collapse button -->
<IconBtn
v-if="(!(actionRemove || actionRefresh) || actionCollapsed) && !noActions"
@click="triggerCollapse"
>
<VIcon
size="20"
icon="ri-arrow-up-s-line"
:style="{ transform: isContentCollapsed ? 'rotate(-180deg)' : undefined }"
style="transition-duration: 0.28s;"
/>
</IconBtn>
<!-- 👉 Overlay button -->
<IconBtn
v-if="(!(actionRemove || actionCollapsed) || actionRefresh) && !noActions"
@click="triggerRefresh"
>
<VIcon
size="20"
icon="ri-refresh-line"
/>
</IconBtn>
<!-- 👉 Close button -->
<IconBtn
v-if="(!(actionRefresh || actionCollapsed) || actionRemove) && !noActions"
@click="triggeredRemove"
>
<VIcon
size="20"
icon="ri-close-line"
/>
</IconBtn>
</div>
<!-- !SECTION -->
</template>
</VCardItem>
<!-- 👉 card content -->
<VExpandTransition>
<div
v-show="!isContentCollapsed"
class="v-card-content"
>
<slot />
</div>
</VExpandTransition>
<!-- 👉 Overlay -->
<VOverlay
v-model="$loading"
contained
persistent
scroll-strategy="none"
class="align-center justify-center"
>
<VProgressCircular indeterminate />
</VOverlay>
</VCard>
</div>
</VExpandTransition>
</template>
<style lang="scss">
.v-card-item {
+.v-card-content {
.v-card-text:first-child {
padding-block-start: 0;
}
}
}
</style>
<script setup>
import { getSingletonHighlighter } from 'shiki'
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
const props = defineProps({
title: {
type: String,
required: true,
},
code: {
type: Object,
required: true,
},
codeLanguage: {
type: String,
required: false,
default: 'markup',
},
noPadding: {
type: Boolean,
required: false,
default: false,
},
})
const preferredCodeLanguage = useCookie('preferredCodeLanguage', {
default: () => 'ts',
maxAge: COOKIE_MAX_AGE_1_YEAR,
})
const isCodeShown = ref(false)
const { copy, copied } = useClipboard({ source: computed(() => props.code[preferredCodeLanguage.value]) })
const highlighter = await getSingletonHighlighter({
themes: [
'dracula',
'dracula-soft',
],
langs: ['vue'],
})
const codeSnippet = highlighter.codeToHtml(props.code[preferredCodeLanguage.value], {
lang: 'vue',
theme: 'dracula',
})
</script>
<template>
<VCard class="app-card-code">
<VCardItem>
<VCardTitle>{{ props.title }}</VCardTitle>
<template #append>
<IconBtn
:color="isCodeShown ? 'primary' : 'default'"
:class="isCodeShown ? '' : 'text-disabled'"
@click="isCodeShown = !isCodeShown"
>
<VIcon
size="20"
icon="ri-code-s-line"
/>
</IconBtn>
</template>
</VCardItem>
<slot v-if="noPadding" />
<VCardText v-else>
<slot />
</VCardText>
<VExpandTransition>
<div v-show="isCodeShown">
<VDivider />
<VCardText class="d-flex gap-y-3 flex-column">
<div class="d-flex justify-end">
<VBtnToggle
v-model="preferredCodeLanguage"
mandatory
density="compact"
>
<VBtn value="ts">
<!-- eslint-disable-next-line regex/invalid -->
<VIcon icon="mdi-language-typescript" />
</VBtn>
<VBtn value="js">
<!-- eslint-disable-next-line regex/invalid -->
<VIcon icon="mdi-language-javascript" />
</VBtn>
</VBtnToggle>
</div>
<div class="position-relative">
<PerfectScrollbar
style="border-radius: 6px;max-block-size: 500px;"
:options="{ wheelPropagation: false }"
>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-html="codeSnippet" />
</PerfectScrollbar>
<IconBtn
class="position-absolute app-card-code-copy-icon"
color="white"
@click="() => { copy() }"
>
<VIcon
:icon="copied ? 'ri-check-line' : 'ri-file-copy-line'"
size="20"
/>
</IconBtn>
</div>
</VCardText>
</div>
</VExpandTransition>
</VCard>
</template>
<style lang="scss">
@use "@styles/variables/vuetify";
code[class*="language-"],
pre[class*="language-"] {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 14px;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
border-radius: vuetify.$card-border-radius;
max-block-size: 500px;
}
.app-card-code-copy-icon {
inset-block-start: 1.2em;
inset-inline-end: 0.8em;
}
.app-card-code {
.shiki {
padding: 0.75rem;
text-wrap: wrap;
}
}
</style>
<script setup>
import { kFormatter } from '@core/utils/formatters'
const props = defineProps({
title: {
type: String,
required: true,
},
color: {
type: String,
required: false,
default: 'primary',
},
icon: {
type: String,
required: true,
},
stats: {
type: Number,
required: true,
},
change: {
type: Number,
required: true,
},
})
const isPositive = computed(() => Math.sign(props.change) === 1)
</script>
<template>
<VCard
variant="text"
border
>
<VCardText class="d-flex align-center">
<VAvatar
size="40"
rounded
class="elevation-2 me-4"
style="background-color: rgb(var(--v-theme-surface));"
>
<VIcon
:color="props.color"
:icon="props.icon"
:size="24"
/>
</VAvatar>
<div>
<div class="text-body-1">
{{ props.title }}
</div>
<div class="d-flex align-center flex-wrap">
<h5 class="text-h5">
{{ kFormatter(props.stats) }}
</h5>
<div
v-if="props.change"
:class="`${isPositive ? 'text-success' : 'text-error'} mt-1`"
>
<VIcon
:icon="isPositive ? 'ri-arrow-up-s-line' : 'ri-arrow-down-s-line'"
size="24"
/>
<span class="text-base">
{{ Math.abs(props.change) }}%
</span>
</div>
</div>
</div>
</VCardText>
</VCard>
</template>
<style lang="scss">
.skin--bordered {
.v-avatar {
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)) !important;
box-shadow: none !important;
}
}
</style>
<script setup>
const props = defineProps({
title: {
type: String,
required: true,
},
color: {
type: String,
required: false,
default: 'primary',
},
icon: {
type: String,
required: true,
},
stats: {
type: String,
required: true,
},
change: {
type: Number,
required: true,
},
subtitle: {
type: String,
required: true,
},
})
const isPositive = computed(() => Math.sign(props.change) === 1)
</script>
<template>
<VCard>
<VCardText class="d-flex align-center">
<VAvatar
v-if="props.icon"
size="40"
:color="props.color"
class="elevation-2"
>
<VIcon
:icon="props.icon"
size="24"
/>
</VAvatar>
<VSpacer />
<MoreBtn class="me-n3 mt-n1" />
</VCardText>
<VCardText>
<h6 class="text-h6 mb-1">
{{ props.title }}
</h6>
<div
v-if="props.change"
class="d-flex align-center mb-1 flex-wrap"
>
<h4 class="text-h4 me-2">
{{ props.stats }}
</h4>
<div
:class="isPositive ? 'text-success' : 'text-error'"
class="text-body-1"
>
{{ isPositive ? `+${props.change}` : props.change }}%
</div>
</div>
<div class="text-body-2">
{{ props.subtitle }}
</div>
</VCardText>
</VCard>
</template>
<script setup>
const props = defineProps({
title: {
type: String,
required: true,
},
value: {
type: String,
required: true,
},
change: {
type: Number,
required: true,
},
desc: {
type: String,
required: true,
},
icon: {
type: String,
required: true,
},
iconColor: {
type: String,
required: true,
},
})
</script>
<template>
<VCard>
<VCardText>
<div class="d-flex justify-space-between">
<div class="d-flex flex-column gap-y-1">
<div class="text-body-1 text-high-emphasis">
{{ title }}
</div>
<div>
<h5 class="text-h5">
{{ value }}
<span
class="text-base"
:class="change > 0 ? 'text-success' : 'text-error'"
>({{ prefixWithPlus(change) }}%)</span>
</h5>
</div>
<div class="text-body-2">
{{ desc }}
</div>
</div>
<VAvatar
:color="iconColor"
variant="tonal"
rounded
size="42"
>
<VIcon
:icon="icon"
size="26"
/>
</VAvatar>
</div>
</VCardText>
</VCard>
</template>
<script setup>
const props = defineProps({
title: {
type: String,
required: true,
},
subtitle: {
type: String,
required: true,
},
stats: {
type: String,
required: true,
},
change: {
type: Number,
required: true,
},
image: {
type: String,
required: true,
},
color: {
type: String,
required: false,
default: 'primary',
},
})
const isPositive = computed(() => Math.sign(props.change) === 1)
</script>
<template>
<VCard class="overflow-visible position-relative">
<div class="d-flex">
<VCardText>
<h6 class="text-h6 mb-5">
{{ props.title }}
</h6>
<div class="d-flex align-center flex-wrap mb-3">
<h4 class="text-h4 me-2">
{{ props.stats }}
</h4>
<div
class="text-body-1"
:class="isPositive ? 'text-success' : 'text-error'"
>
{{ isPositive ? `+${props.change}` : props.change }}%
</div>
</div>
<VChip
v-if="props.subtitle"
:color="props.color"
size="small"
>
{{ props.subtitle }}
</VChip>
</VCardText>
<VSpacer />
<div class="illustrator-img">
<VImg
v-if="props.image"
:src="props.image"
:width="110"
/>
</div>
</div>
</VCard>
</template>
<style lang="scss">
.illustrator-img {
position: absolute;
inset-block-end: 0;
inset-inline-end: 5%;
}
@media (max-width: 1200px) and (min-width: 960px) {
.illustrator-img {
inset-block-end: 0;
inset-inline-end: 0;
}
}
</style>
import { stringifyQuery } from 'ufo'
export const createUrl = (url, options) => computed(() => {
if (!options?.query)
return toValue(url)
const _url = toValue(url)
const _query = toValue(options?.query)
const queryObj = Object.fromEntries(Object.entries(_query).map(([key, val]) => [key, toValue(val)]))
return `${_url}${queryObj ? `?${stringifyQuery(queryObj)}` : ''}`
})
// Ported from [Nuxt](https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/cookie.ts)
import { parse, serialize } from 'cookie-es'
import { destr } from 'destr'
const CookieDefaults = {
path: '/',
watch: true,
decode: val => destr(decodeURIComponent(val)),
encode: val => encodeURIComponent(typeof val === 'string' ? val : JSON.stringify(val)),
}
export const useCookie = (name, _opts) => {
const opts = { ...CookieDefaults, ..._opts || {} }
const cookies = parse(document.cookie, opts)
const cookie = ref(cookies[name] ?? opts.default?.())
watch(cookie, () => {
document.cookie = serializeCookie(name, cookie.value, opts)
})
return cookie
}
function serializeCookie(name, value, opts = {}) {
if (value === null || value === undefined)
return serialize(name, value, { ...opts, maxAge: -1 })
return serialize(name, value, { ...opts, maxAge: 60 * 60 * 24 * 30 })
}
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!