Add new Version
2
.dockerignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
38
.editorconfig
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# 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
|
||||||
1
.env.example
Normal file
@ -0,0 +1 @@
|
|||||||
|
VITE_API_BASE_URL=
|
||||||
356
.eslintrc-auto-import.json
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
{
|
||||||
|
"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,
|
||||||
|
"defineLoader": 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,
|
||||||
|
"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,
|
||||||
|
"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,
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
228
.eslintrc.cjs
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
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'],
|
||||||
|
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',
|
||||||
|
'#auth$',
|
||||||
|
|
||||||
|
// Ignore vite's ?raw imports
|
||||||
|
'.*\?raw',
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
|
||||||
|
// 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\'',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
43
.gitignore
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# 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
|
||||||
46
.stylelintrc.json
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
23
.vscode/anchor-comments.code-snippets
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
96
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
{
|
||||||
|
"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": [
|
||||||
|
"addressline",
|
||||||
|
"Composables",
|
||||||
|
"Customizer",
|
||||||
|
"destr",
|
||||||
|
"flagpack",
|
||||||
|
"Iconify",
|
||||||
|
"nuxt",
|
||||||
|
"ofetch",
|
||||||
|
"psudo",
|
||||||
|
"stylelint",
|
||||||
|
"touchless",
|
||||||
|
"triggerer",
|
||||||
|
"vuetify"
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
18
.vscode/vue-ts.code-snippets
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
63
.vscode/vue.code-snippets
vendored
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"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."
|
||||||
|
}
|
||||||
|
}
|
||||||
52
.vscode/vuetify.code-snippets
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"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=\"tabler-${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"
|
||||||
|
}
|
||||||
|
}
|
||||||
35
README.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# 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
|
||||||
|
```
|
||||||
1049
auto-imports.d.ts
vendored
Normal file
67
components.d.ts
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// Generated by unplugin-vue-components
|
||||||
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
|
export {}
|
||||||
|
|
||||||
|
declare module 'vue' {
|
||||||
|
export interface GlobalComponents {
|
||||||
|
AddAuthenticatorAppDialog: typeof import('./src/components/dialogs/AddAuthenticatorAppDialog.vue')['default']
|
||||||
|
AddEditAddressDialog: typeof import('./src/components/dialogs/AddEditAddressDialog.vue')['default']
|
||||||
|
AddEditPermissionDialog: typeof import('./src/components/dialogs/AddEditPermissionDialog.vue')['default']
|
||||||
|
AddEditRoleDialog: typeof import('./src/components/dialogs/AddEditRoleDialog.vue')['default']
|
||||||
|
AddPaymentMethodDialog: typeof import('./src/components/dialogs/AddPaymentMethodDialog.vue')['default']
|
||||||
|
AppAutocomplete: typeof import('./src/@core/components/app-form-elements/AppAutocomplete.vue')['default']
|
||||||
|
AppBarSearch: typeof import('./src/@core/components/AppBarSearch.vue')['default']
|
||||||
|
AppCardActions: typeof import('./src/@core/components/cards/AppCardActions.vue')['default']
|
||||||
|
AppCardCode: typeof import('./src/@core/components/cards/AppCardCode.vue')['default']
|
||||||
|
AppCombobox: typeof import('./src/@core/components/app-form-elements/AppCombobox.vue')['default']
|
||||||
|
AppDateTimePicker: typeof import('./src/@core/components/app-form-elements/AppDateTimePicker.vue')['default']
|
||||||
|
AppDrawerHeaderSection: typeof import('./src/@core/components/AppDrawerHeaderSection.vue')['default']
|
||||||
|
AppLoadingIndicator: typeof import('./src/components/AppLoadingIndicator.vue')['default']
|
||||||
|
AppPricing: typeof import('./src/components/AppPricing.vue')['default']
|
||||||
|
AppSearchHeader: typeof import('./src/components/AppSearchHeader.vue')['default']
|
||||||
|
AppSelect: typeof import('./src/@core/components/app-form-elements/AppSelect.vue')['default']
|
||||||
|
AppStepper: typeof import('./src/@core/components/AppStepper.vue')['default']
|
||||||
|
AppTextarea: typeof import('./src/@core/components/app-form-elements/AppTextarea.vue')['default']
|
||||||
|
AppTextField: typeof import('./src/@core/components/app-form-elements/AppTextField.vue')['default']
|
||||||
|
BuyNow: typeof import('./src/@core/components/BuyNow.vue')['default']
|
||||||
|
CardAddEditDialog: typeof import('./src/components/dialogs/CardAddEditDialog.vue')['default']
|
||||||
|
CardStatisticsHorizontal: typeof import('./src/@core/components/cards/CardStatisticsHorizontal.vue')['default']
|
||||||
|
CardStatisticsVertical: typeof import('./src/@core/components/cards/CardStatisticsVertical.vue')['default']
|
||||||
|
CardStatisticsVerticalSimple: typeof import('./src/@core/components/CardStatisticsVerticalSimple.vue')['default']
|
||||||
|
ConfirmDialog: typeof import('./src/components/dialogs/ConfirmDialog.vue')['default']
|
||||||
|
CreateAppDialog: typeof import('./src/components/dialogs/CreateAppDialog.vue')['default']
|
||||||
|
CustomCheckboxes: typeof import('./src/@core/components/app-form-elements/CustomCheckboxes.vue')['default']
|
||||||
|
CustomCheckboxesWithIcon: typeof import('./src/@core/components/app-form-elements/CustomCheckboxesWithIcon.vue')['default']
|
||||||
|
CustomCheckboxesWithImage: typeof import('./src/@core/components/app-form-elements/CustomCheckboxesWithImage.vue')['default']
|
||||||
|
CustomizerSection: typeof import('./src/@core/components/CustomizerSection.vue')['default']
|
||||||
|
CustomRadios: typeof import('./src/@core/components/app-form-elements/CustomRadios.vue')['default']
|
||||||
|
CustomRadiosWithIcon: typeof import('./src/@core/components/app-form-elements/CustomRadiosWithIcon.vue')['default']
|
||||||
|
CustomRadiosWithImage: typeof import('./src/@core/components/app-form-elements/CustomRadiosWithImage.vue')['default']
|
||||||
|
DialogCloseBtn: typeof import('./src/@core/components/DialogCloseBtn.vue')['default']
|
||||||
|
DropZone: typeof import('./src/@core/components/DropZone.vue')['default']
|
||||||
|
EnableOneTimePasswordDialog: typeof import('./src/components/dialogs/EnableOneTimePasswordDialog.vue')['default']
|
||||||
|
ErrorHeader: typeof import('./src/components/ErrorHeader.vue')['default']
|
||||||
|
I18n: typeof import('./src/@core/components/I18n.vue')['default']
|
||||||
|
MoreBtn: typeof import('./src/@core/components/MoreBtn.vue')['default']
|
||||||
|
Notifications: typeof import('./src/@core/components/Notifications.vue')['default']
|
||||||
|
PaymentProvidersDialog: typeof import('./src/components/dialogs/PaymentProvidersDialog.vue')['default']
|
||||||
|
PricingPlanDialog: typeof import('./src/components/dialogs/PricingPlanDialog.vue')['default']
|
||||||
|
ProductDescriptionEditor: typeof import('./src/@core/components/ProductDescriptionEditor.vue')['default']
|
||||||
|
ReferAndEarnDialog: typeof import('./src/components/dialogs/ReferAndEarnDialog.vue')['default']
|
||||||
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
ScrollToTop: typeof import('./src/@core/components/ScrollToTop.vue')['default']
|
||||||
|
ShareProjectDialog: typeof import('./src/components/dialogs/ShareProjectDialog.vue')['default']
|
||||||
|
Shortcuts: typeof import('./src/@core/components/Shortcuts.vue')['default']
|
||||||
|
TablePagination: typeof import('./src/@core/components/TablePagination.vue')['default']
|
||||||
|
TheCustomizer: typeof import('./src/@core/components/TheCustomizer.vue')['default']
|
||||||
|
ThemeSwitcher: typeof import('./src/@core/components/ThemeSwitcher.vue')['default']
|
||||||
|
TiptapEditor: typeof import('./src/@core/components/TiptapEditor.vue')['default']
|
||||||
|
TwoFactorAuthDialog: typeof import('./src/components/dialogs/TwoFactorAuthDialog.vue')['default']
|
||||||
|
UserInfoEditDialog: typeof import('./src/components/dialogs/UserInfoEditDialog.vue')['default']
|
||||||
|
UserUpgradePlanDialog: typeof import('./src/components/dialogs/UserUpgradePlanDialog.vue')['default']
|
||||||
|
}
|
||||||
|
}
|
||||||
25
dev.Dockerfile
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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
|
||||||
14
docker-compose.dev.yml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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
|
||||||
11
docker-compose.prod.yml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
vue-project:
|
||||||
|
container_name: vue-project_prod
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: prod.Dockerfile
|
||||||
|
image: vue-app
|
||||||
|
ports:
|
||||||
|
- 8080:80
|
||||||
54
index.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<!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>Skoutz - Vorbeischauen – Vergleichen – Verlieben</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 34 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
|
d="M0.00183571 0.3125V7.59485C0.00183571 7.59485 -0.141502 9.88783 2.10473 11.8288L14.5469 23.6837L21.0172 23.6005L19.9794 10.8126L17.5261 7.93369L9.81536 0.3125H0.00183571Z"
|
||||||
|
fill="var(--initial-loader-color)"
|
||||||
|
/>
|
||||||
|
<path opacity="0.06" fill-rule="evenodd" clip-rule="evenodd"
|
||||||
|
d="M8.17969 17.7762L13.3027 3.75173L17.589 8.02192L8.17969 17.7762Z" fill="#161616"
|
||||||
|
/>
|
||||||
|
<path opacity="0.06" fill-rule="evenodd" clip-rule="evenodd"
|
||||||
|
d="M8.58203 17.2248L14.8129 5.24231L17.6211 8.05247L8.58203 17.2248Z" fill="#161616"
|
||||||
|
/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
|
d="M8.25781 17.6914L25.1339 0.3125H33.9991V7.62657C33.9991 7.62657 33.8144 10.0645 32.5743 11.3686L21.0179 23.6875H14.5487L8.25781 17.6914Z"
|
||||||
|
fill="var(--initial-loader-color)"
|
||||||
|
/>
|
||||||
|
</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('vuexy-initial-loader-bg') || '#FFFFFF'
|
||||||
|
const primaryColor = localStorage.getItem('vuexy-initial-loader-color') || '#7367F0'
|
||||||
|
|
||||||
|
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>
|
||||||
57
jsconfig.json
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"include": [
|
||||||
|
"./vite.config.*",
|
||||||
|
"./src/**/*",
|
||||||
|
"./src/**/*.vue",
|
||||||
|
"./themeConfig.js"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"./dist",
|
||||||
|
"./node_modules"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"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",
|
||||||
|
"vite-plugin-vue-layouts/client"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
14
nginx.conf
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# 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...
|
||||||
|
}
|
||||||
13114
package-lock.json
generated
Normal file
125
package.json
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
{
|
||||||
|
"name": "vuexy-vuejs-admin-template",
|
||||||
|
"version": "9.1.1",
|
||||||
|
"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.0",
|
||||||
|
"@casl/vue": "2.2.2",
|
||||||
|
"@floating-ui/dom": "1.6.1",
|
||||||
|
"@iconify-json/fa": "1.1.8",
|
||||||
|
"@iconify-json/tabler": "1.1.106",
|
||||||
|
"@sindresorhus/is": "6.2.0",
|
||||||
|
"@tiptap/extension-highlight": "2.2.4",
|
||||||
|
"@tiptap/extension-image": "2.2.4",
|
||||||
|
"@tiptap/extension-link": "2.2.4",
|
||||||
|
"@tiptap/extension-text-align": "2.2.4",
|
||||||
|
"@tiptap/pm": "2.2.4",
|
||||||
|
"@tiptap/starter-kit": "2.2.4",
|
||||||
|
"@tiptap/vue-3": "2.2.4",
|
||||||
|
"@vueuse/core": "10.9.0",
|
||||||
|
"@vueuse/math": "10.9.0",
|
||||||
|
"apexcharts": "3.46.0",
|
||||||
|
"chart.js": "4.4.2",
|
||||||
|
"cookie-es": "1.0.0",
|
||||||
|
"eslint-plugin-regexp": "2.2.0",
|
||||||
|
"jwt-decode": "4.0.0",
|
||||||
|
"mapbox-gl": "3.1.2",
|
||||||
|
"ofetch": "1.3.3",
|
||||||
|
"pinia": "2.1.7",
|
||||||
|
"prismjs": "1.29.0",
|
||||||
|
"roboto-fontface": "0.10.0",
|
||||||
|
"shepherd.js": "11.2.0",
|
||||||
|
"swiper": "11.0.7",
|
||||||
|
"ufo": "1.4.0",
|
||||||
|
"unplugin-vue-define-options": "1.4.2",
|
||||||
|
"vue": "3.4.21",
|
||||||
|
"vue-chartjs": "5.3.0",
|
||||||
|
"vue-flatpickr-component": "11.0.3",
|
||||||
|
"vue-i18n": "9.10.1",
|
||||||
|
"vue-prism-component": "2.0.0",
|
||||||
|
"vue-router": "4.3.0",
|
||||||
|
"vue3-apexcharts": "1.5.2",
|
||||||
|
"vue3-perfect-scrollbar": "1.6.1",
|
||||||
|
"vuetify": "3.5.2",
|
||||||
|
"webfontloader": "1.6.28"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@antfu/eslint-config-vue": "0.43.1",
|
||||||
|
"@antfu/utils": "0.7.7",
|
||||||
|
"@fullcalendar/core": "6.1.11",
|
||||||
|
"@fullcalendar/daygrid": "6.1.11",
|
||||||
|
"@fullcalendar/interaction": "6.1.11",
|
||||||
|
"@fullcalendar/list": "6.1.11",
|
||||||
|
"@fullcalendar/timegrid": "6.1.11",
|
||||||
|
"@fullcalendar/vue3": "6.1.11",
|
||||||
|
"@iconify-json/mdi": "1.1.64",
|
||||||
|
"@iconify/tools": "4.0.2",
|
||||||
|
"@iconify/utils": "2.1.22",
|
||||||
|
"@iconify/vue": "4.1.1",
|
||||||
|
"@intlify/unplugin-vue-i18n": "2.0.0",
|
||||||
|
"@stylistic/stylelint-config": "1.0.1",
|
||||||
|
"@stylistic/stylelint-plugin": "2.1.0",
|
||||||
|
"@tiptap/extension-character-count": "2.2.4",
|
||||||
|
"@tiptap/extension-placeholder": "2.2.4",
|
||||||
|
"@tiptap/extension-subscript": "2.2.4",
|
||||||
|
"@tiptap/extension-superscript": "2.2.4",
|
||||||
|
"@tiptap/extension-underline": "2.2.4",
|
||||||
|
"@videojs-player/vue": "1.0.0",
|
||||||
|
"@vitejs/plugin-vue": "5.0.4",
|
||||||
|
"@vitejs/plugin-vue-jsx": "3.1.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.1.1",
|
||||||
|
"eslint-plugin-regex": "1.10.0",
|
||||||
|
"eslint-plugin-sonarjs": "0.23.0",
|
||||||
|
"eslint-plugin-unicorn": "50.0.1",
|
||||||
|
"eslint-plugin-vue": "9.22.0",
|
||||||
|
"msw": "2.2.2",
|
||||||
|
"postcss-html": "1.6.0",
|
||||||
|
"postcss-scss": "4.0.9",
|
||||||
|
"sass": "1.71.1",
|
||||||
|
"shikiji": "0.10.2",
|
||||||
|
"stylelint": "16.2.1",
|
||||||
|
"stylelint-config-idiomatic-order": "10.0.0",
|
||||||
|
"stylelint-config-standard-scss": "13.0.0",
|
||||||
|
"stylelint-use-logical-spec": "5.0.1",
|
||||||
|
"tsx": "4.7.1",
|
||||||
|
"unplugin-auto-import": "0.17.5",
|
||||||
|
"unplugin-vue-components": "0.26.0",
|
||||||
|
"unplugin-vue-router": "0.7.0",
|
||||||
|
"video.js": "8.6.0",
|
||||||
|
"vite": "5.1.4",
|
||||||
|
"vite-plugin-vue-devtools": "7.0.16",
|
||||||
|
"vite-plugin-vue-layouts": "0.11.0",
|
||||||
|
"vite-plugin-vuetify": "2.0.1",
|
||||||
|
"vite-svg-loader": "5.1.0",
|
||||||
|
"vue-shepherd": "3.0.0"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"postcss": "^8",
|
||||||
|
"@tiptap/core": "^2",
|
||||||
|
"@types/video.js": "^7"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"postcss": "^8",
|
||||||
|
"@tiptap/core": "^2",
|
||||||
|
"@types/video.js": "^7"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@8.6.2",
|
||||||
|
"msw": {
|
||||||
|
"workerDirectory": "public"
|
||||||
|
}
|
||||||
|
}
|
||||||
7963
pnpm-lock.yaml
generated
Normal file
41
prod.Dockerfile
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# 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;"]
|
||||||
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/images/avatars/avatar-1.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/images/avatars/avatar-2.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
10
public/images/svg/discord.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg width="59" height="58" 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>
|
||||||
|
After Width: | Height: | Size: 6.2 KiB |
8
public/images/svg/gift.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg width="58" height="58" 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>
|
||||||
|
After Width: | Height: | Size: 6.3 KiB |
8
public/images/svg/keyboard.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 3.6 KiB |
8
public/images/svg/laptop.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 5.0 KiB |
8
public/images/svg/lightbulb.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 6.6 KiB |
8
public/images/svg/rocket.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 7.7 KiB |
79
public/loader.css
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
288
src/@core/components/AppBarSearch.vue
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
<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,
|
||||||
|
},
|
||||||
|
isLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
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 ? '531' : '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="px-4"
|
||||||
|
style="padding-block: 1rem 1.2rem;"
|
||||||
|
>
|
||||||
|
<!-- 👉 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="tabler-search"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 👉 Append Inner -->
|
||||||
|
<template #append-inner>
|
||||||
|
<div class="d-flex align-start">
|
||||||
|
<div
|
||||||
|
class="text-base text-disabled cursor-pointer me-3"
|
||||||
|
@click="clearSearchAndCloseDialog"
|
||||||
|
>
|
||||||
|
[esc]
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VIcon
|
||||||
|
icon="tabler-x"
|
||||||
|
size="24"
|
||||||
|
@click="clearSearchAndCloseDialog"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</VTextField>
|
||||||
|
</VCardText>
|
||||||
|
|
||||||
|
<!-- 👉 Divider -->
|
||||||
|
<VDivider />
|
||||||
|
|
||||||
|
<!-- 👉 Perfect Scrollbar -->
|
||||||
|
<PerfectScrollbar
|
||||||
|
:options="{ wheelPropagation: false, suppressScrollX: true }"
|
||||||
|
class="h-100"
|
||||||
|
>
|
||||||
|
<!-- 👉 Suggestions -->
|
||||||
|
<div
|
||||||
|
v-show="!!props.searchResults && !searchQueryLocal && $slots.suggestions"
|
||||||
|
class="h-100"
|
||||||
|
>
|
||||||
|
<slot name="suggestions" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-if="!isLoading">
|
||||||
|
<!-- 👉 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>
|
||||||
|
|
||||||
|
<!-- 👉 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="tabler-file-alert"
|
||||||
|
/>
|
||||||
|
<div class="d-flex align-center flex-wrap justify-center gap-2 text-h5 mt-3">
|
||||||
|
<span>No Result For </span>
|
||||||
|
<span>"{{ searchQueryLocal }}"</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<slot name="noDataSuggestion" />
|
||||||
|
</div>
|
||||||
|
</VCardText>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 👉 Loading -->
|
||||||
|
<template v-if="isLoading">
|
||||||
|
<VSkeletonLoader
|
||||||
|
v-for="i in 3"
|
||||||
|
:key="i"
|
||||||
|
type="list-item-two-line"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-bar-search-list {
|
||||||
|
.v-list-item,
|
||||||
|
.v-list-subheader {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding-inline: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-list-item {
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-block-end: 0.125rem;
|
||||||
|
margin-inline: 0.5rem;
|
||||||
|
|
||||||
|
.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>
|
||||||
31
src/@core/components/AppDrawerHeaderSection.vue
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['cancel'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="pa-6 d-flex align-center">
|
||||||
|
<h5 class="text-h5">
|
||||||
|
{{ props.title }}
|
||||||
|
</h5>
|
||||||
|
<VSpacer />
|
||||||
|
|
||||||
|
<slot name="beforeClose" />
|
||||||
|
|
||||||
|
<IconBtn
|
||||||
|
size="small"
|
||||||
|
@click="$emit('cancel', $event)"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
size="24"
|
||||||
|
icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</IconBtn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
359
src/@core/components/AppStepper.vue
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
<script setup>
|
||||||
|
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: 60,
|
||||||
|
},
|
||||||
|
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 pa-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 ">
|
||||||
|
<!-- 👉 icon and title -->
|
||||||
|
<div
|
||||||
|
class="d-flex align-center gap-x-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 mb-0">
|
||||||
|
{{ item.title }}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
v-if="item.subtitle"
|
||||||
|
class="stepper-subtitle mb-0"
|
||||||
|
>
|
||||||
|
{{ item.subtitle }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 👉 append chevron -->
|
||||||
|
<VIcon
|
||||||
|
v-if="isHorizontalAndNotLastStep(index)"
|
||||||
|
class="flip-in-rtl stepper-chevron-indicator mx-6"
|
||||||
|
size="20"
|
||||||
|
icon="tabler-chevron-right"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- !SECTION -->
|
||||||
|
|
||||||
|
<!-- SECTION stepper step without icon -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="d-flex align-center gap-x-3">
|
||||||
|
<div>
|
||||||
|
<!-- 👉 custom circle icon -->
|
||||||
|
<template v-if="index >= currentStep">
|
||||||
|
<VAvatar
|
||||||
|
v-if="(!isValidationEnabled || props.isActiveStepValid || index !== currentStep)"
|
||||||
|
size="38"
|
||||||
|
rounded
|
||||||
|
:variant="index === currentStep ? 'elevated' : 'tonal'"
|
||||||
|
:color="index === currentStep ? 'primary' : 'default'"
|
||||||
|
>
|
||||||
|
<h5
|
||||||
|
class="text-h5"
|
||||||
|
:style="index === currentStep ? { color: '#fff' } : ''"
|
||||||
|
>
|
||||||
|
{{ index + 1 }}
|
||||||
|
</h5>
|
||||||
|
</VAvatar>
|
||||||
|
|
||||||
|
<VAvatar
|
||||||
|
v-else
|
||||||
|
color="error"
|
||||||
|
size="38"
|
||||||
|
rounded
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
|
||||||
|
icon="tabler-alert-circle"
|
||||||
|
size="22"
|
||||||
|
/>
|
||||||
|
</VAvatar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 👉 step completed icon -->
|
||||||
|
|
||||||
|
<VAvatar
|
||||||
|
v-else
|
||||||
|
class="stepper-icon"
|
||||||
|
variant="tonal"
|
||||||
|
color="primary"
|
||||||
|
size="38"
|
||||||
|
rounded
|
||||||
|
>
|
||||||
|
<h5
|
||||||
|
class="text-h5"
|
||||||
|
style="color: rgb(var(--v-theme-primary));"
|
||||||
|
>
|
||||||
|
{{ index + 1 }}
|
||||||
|
</h5>
|
||||||
|
</VAvatar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 👉 title and subtitle -->
|
||||||
|
<div class="d-flex flex-column justify-center">
|
||||||
|
<div class="stepper-title font-weight-medium">
|
||||||
|
{{ item.title }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="item.subtitle"
|
||||||
|
class="stepper-subtitle text-sm text-disabled"
|
||||||
|
>
|
||||||
|
{{ item.subtitle }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 👉 stepper step icon -->
|
||||||
|
<div
|
||||||
|
v-if="isHorizontalAndNotLastStep(index)"
|
||||||
|
class="stepper-step-line stepper-chevron-indicator mx-6"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
icon="tabler-chevron-right"
|
||||||
|
size="20"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- !SECTION -->
|
||||||
|
</div>
|
||||||
|
</VSlideGroupItem>
|
||||||
|
</VSlideGroup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use "@core/scss/template/mixins" as templateMixins;
|
||||||
|
|
||||||
|
.app-stepper {
|
||||||
|
// 👉 stepper step with bg color
|
||||||
|
&.stepper-icon-step-bg {
|
||||||
|
.stepper-icon-step {
|
||||||
|
.step-wrapper {
|
||||||
|
flex-direction: row !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepper-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
background-color: rgba(var(--v-theme-on-surface), var(--v-selected-opacity));
|
||||||
|
block-size: 2.375rem;
|
||||||
|
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
|
||||||
|
inline-size: 2.375rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepper-steps-active {
|
||||||
|
.stepper-icon-step {
|
||||||
|
.stepper-icon {
|
||||||
|
@include templateMixins.custom-elevation(var(--v-theme-primary), "sm");
|
||||||
|
|
||||||
|
background-color: rgb(var(--v-theme-primary));
|
||||||
|
color: rgba(var(--v-theme-on-primary));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepper-steps-completed {
|
||||||
|
.stepper-icon-step {
|
||||||
|
.stepper-icon {
|
||||||
|
background: rgba(var(--v-theme-primary), var(--v-activated-opacity));
|
||||||
|
color: rgba(var(--v-theme-primary));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.app-stepper-icons:not(.stepper-icon-step-bg) {
|
||||||
|
/* stylelint-disable-next-line no-descending-specificity */
|
||||||
|
.stepper-icon {
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-wrapper {
|
||||||
|
padding: 1.25rem;
|
||||||
|
gap: 0.5rem;
|
||||||
|
min-inline-size: 9.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepper-chevron-indicator {
|
||||||
|
margin-inline: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepper-steps-completed,
|
||||||
|
.stepper-steps-active {
|
||||||
|
.stepper-icon-step,
|
||||||
|
.stepper-step-icon,
|
||||||
|
.stepper-title,
|
||||||
|
.stepper-subtitle {
|
||||||
|
color: rgb(var(--v-theme-primary)) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 stepper step with icon and default
|
||||||
|
.v-slide-group__content {
|
||||||
|
row-gap: 1rem;
|
||||||
|
|
||||||
|
/* stylelint-disable-next-line no-descending-specificity */
|
||||||
|
.stepper-title {
|
||||||
|
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable-next-line no-descending-specificity */
|
||||||
|
.stepper-subtitle {
|
||||||
|
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable-next-line no-descending-specificity */
|
||||||
|
.stepper-chevron-indicator {
|
||||||
|
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable-next-line no-descending-specificity */
|
||||||
|
.stepper-steps-completed {
|
||||||
|
/* stylelint-disable-next-line no-descending-specificity */
|
||||||
|
.stepper-title,
|
||||||
|
.stepper-subtitle {
|
||||||
|
color: rgba(var(--v-theme-on-surface), var(--v-disabled-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepper-chevron-indicator {
|
||||||
|
color: rgb(var(--v-theme-primary));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable-next-line no-descending-specificity */
|
||||||
|
.stepper-steps-active {
|
||||||
|
.v-avatar.bg-primary {
|
||||||
|
@include templateMixins.custom-elevation(var(--v-theme-primary), "sm");
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-avatar.bg-error {
|
||||||
|
@include templateMixins.custom-elevation(var(--v-theme-error), "sm");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepper-steps-invalid.stepper-steps-active {
|
||||||
|
.stepper-icon-step,
|
||||||
|
.step-number,
|
||||||
|
.stepper-title,
|
||||||
|
.stepper-subtitle {
|
||||||
|
color: rgb(var(--v-theme-error)) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-stepper-step {
|
||||||
|
&:not(.stepper-steps-active,.stepper-steps-completed) .v-avatar--variant-tonal {
|
||||||
|
--v-activated-opacity: 0.06;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 stepper alignment
|
||||||
|
&.app-stepper-center {
|
||||||
|
.v-slide-group__content {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.app-stepper-start {
|
||||||
|
.v-slide-group__content {
|
||||||
|
justify-content: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.app-stepper-end {
|
||||||
|
.v-slide-group__content {
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
91
src/@core/components/BuyNow.vue
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<script setup>
|
||||||
|
const vm = getCurrentInstance()
|
||||||
|
const buyNowUrl = ref(vm?.appContext.config.globalProperties.buyNowUrl || 'https://1.envato.market/vuexy_admin')
|
||||||
|
|
||||||
|
watch(buyNowUrl, val => {
|
||||||
|
if (vm)
|
||||||
|
vm.appContext.config.globalProperties.buyNowUrl = val
|
||||||
|
})
|
||||||
|
</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>
|
||||||
44
src/@core/components/CardStatisticsVerticalSimple.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VCard>
|
||||||
|
<VCardText class="d-flex flex-column align-center justify-center">
|
||||||
|
<VAvatar
|
||||||
|
v-if="props.icon"
|
||||||
|
size="40"
|
||||||
|
variant="tonal"
|
||||||
|
rounded
|
||||||
|
:color="props.color"
|
||||||
|
>
|
||||||
|
<VIcon :icon="props.icon" />
|
||||||
|
</VAvatar>
|
||||||
|
|
||||||
|
<h5 class="text-h5 pt-2 mb-1">
|
||||||
|
{{ props.stats }}
|
||||||
|
</h5>
|
||||||
|
<div class="text-body-1">
|
||||||
|
{{ props.title }}
|
||||||
|
</div>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</template>
|
||||||
30
src/@core/components/CustomizerSection.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<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
|
||||||
|
size="small"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
<span class="font-weight-medium">{{ props.title }}</span>
|
||||||
|
</VChip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
28
src/@core/components/DialogCloseBtn.vue
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'tabler-x',
|
||||||
|
},
|
||||||
|
iconSize: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '20',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<IconBtn
|
||||||
|
variant="elevated"
|
||||||
|
size="30"
|
||||||
|
:ripple="false"
|
||||||
|
class="v-dialog-close-btn"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
:icon="props.icon"
|
||||||
|
:size="props.iconSize"
|
||||||
|
/>
|
||||||
|
</IconBtn>
|
||||||
|
</template>
|
||||||
129
src/@core/components/DropZone.vue
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<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"
|
||||||
|
class="rounded-sm"
|
||||||
|
>
|
||||||
|
<VIcon icon="tabler-upload" />
|
||||||
|
</IconBtn>
|
||||||
|
<h4 class="text-h4">
|
||||||
|
Drag and drop your image here.
|
||||||
|
</h4>
|
||||||
|
<span class="text-disabled">or</span>
|
||||||
|
|
||||||
|
<VBtn
|
||||||
|
variant="tonal"
|
||||||
|
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"
|
||||||
|
border
|
||||||
|
>
|
||||||
|
<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>
|
||||||
49
src/@core/components/I18n.vue
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<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
|
||||||
|
size="24"
|
||||||
|
icon="tabler-language"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Menu -->
|
||||||
|
<VMenu
|
||||||
|
activator="parent"
|
||||||
|
:location="props.location"
|
||||||
|
offset="12px"
|
||||||
|
>
|
||||||
|
<!-- List -->
|
||||||
|
<VList
|
||||||
|
:selected="[locale]"
|
||||||
|
color="primary"
|
||||||
|
min-width="175px"
|
||||||
|
>
|
||||||
|
<!-- 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>
|
||||||
28
src/@core/components/MoreBtn.vue
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
menuList: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
itemProps: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<IconBtn color="disabled">
|
||||||
|
<VIcon icon="tabler-dots-vertical" />
|
||||||
|
|
||||||
|
<VMenu
|
||||||
|
v-if="props.menuList"
|
||||||
|
activator="parent"
|
||||||
|
>
|
||||||
|
<VList
|
||||||
|
:items="props.menuList"
|
||||||
|
:item-props="props.itemProps"
|
||||||
|
/>
|
||||||
|
</VMenu>
|
||||||
|
</IconBtn>
|
||||||
|
</template>
|
||||||
247
src/@core/components/Notifications.vue
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
<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(() => 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 totalUnseenNotifications = computed(() => {
|
||||||
|
return props.notifications.filter(item => item.isSeen === false).length
|
||||||
|
})
|
||||||
|
|
||||||
|
const toggleReadUnread = (isSeen, Id) => {
|
||||||
|
if (isSeen)
|
||||||
|
emit('unread', [Id])
|
||||||
|
else
|
||||||
|
emit('read', [Id])
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<IconBtn id="notification-btn">
|
||||||
|
<VBadge
|
||||||
|
v-bind="props.badgeProps"
|
||||||
|
:model-value="props.notifications.some(n => !n.isSeen)"
|
||||||
|
color="error"
|
||||||
|
dot
|
||||||
|
offset-x="2"
|
||||||
|
offset-y="3"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
size="24"
|
||||||
|
icon="tabler-bell"
|
||||||
|
/>
|
||||||
|
</VBadge>
|
||||||
|
|
||||||
|
<VMenu
|
||||||
|
activator="parent"
|
||||||
|
width="380px"
|
||||||
|
:location="props.location"
|
||||||
|
offset="12px"
|
||||||
|
:close-on-content-click="false"
|
||||||
|
>
|
||||||
|
<VCard class="d-flex flex-column">
|
||||||
|
<!-- 👉 Header -->
|
||||||
|
<VCardItem class="notification-section">
|
||||||
|
<VCardTitle class="text-h6">
|
||||||
|
Notifications
|
||||||
|
</VCardTitle>
|
||||||
|
|
||||||
|
<template #append>
|
||||||
|
<VChip
|
||||||
|
v-show="props.notifications.some(n => !n.isSeen)"
|
||||||
|
size="small"
|
||||||
|
color="primary"
|
||||||
|
class="me-2"
|
||||||
|
>
|
||||||
|
{{ totalUnseenNotifications }} New
|
||||||
|
</VChip>
|
||||||
|
<IconBtn
|
||||||
|
v-show="props.notifications.length"
|
||||||
|
size="34"
|
||||||
|
@click="markAllReadOrUnread"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
size="20"
|
||||||
|
color="high-emphasis"
|
||||||
|
:icon="!isAllMarkRead ? 'tabler-mail' : 'tabler-mail-opened' "
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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: 23.75rem;"
|
||||||
|
>
|
||||||
|
<VList class="notification-list rounded-0 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"
|
||||||
|
@click="$emit('click:notification', notification)"
|
||||||
|
>
|
||||||
|
<!-- Slot: Prepend -->
|
||||||
|
<!-- Handles Avatar: Image, Icon, Text -->
|
||||||
|
<div class="d-flex align-start gap-3">
|
||||||
|
<VAvatar
|
||||||
|
size="40"
|
||||||
|
:color="notification.color && notification.icon ? notification.color : undefined"
|
||||||
|
:image="notification.img || undefined"
|
||||||
|
:icon="notification.icon || undefined"
|
||||||
|
:variant="notification.img ? undefined : 'tonal' "
|
||||||
|
>
|
||||||
|
<span v-if="notification.text">{{ avatarText(notification.text) }}</span>
|
||||||
|
</VAvatar>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-weight-medium mb-1">
|
||||||
|
{{ notification.title }}
|
||||||
|
</p>
|
||||||
|
<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">
|
||||||
|
<VIcon
|
||||||
|
size="10"
|
||||||
|
icon="tabler-circle-filled"
|
||||||
|
:color="!notification.isSeen ? 'primary' : '#a8aaae'"
|
||||||
|
:class="`${notification.isSeen ? 'visible-in-hover' : ''}`"
|
||||||
|
class="mb-2"
|
||||||
|
@click.stop="toggleReadUnread(notification.isSeen, notification.id)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<VIcon
|
||||||
|
size="20"
|
||||||
|
icon="tabler-x"
|
||||||
|
class="visible-in-hover"
|
||||||
|
@click="$emit('remove', notification.id)"
|
||||||
|
/>
|
||||||
|
</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-block: 0.75rem;
|
||||||
|
padding-inline: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item-hover-class {
|
||||||
|
.visible-in-hover {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.visible-in-hover {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-list.v-list {
|
||||||
|
.v-list-item {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding-block: 0.75rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Badge Style Override for Notification Badge
|
||||||
|
.notification-badge {
|
||||||
|
.v-badge__badge {
|
||||||
|
/* stylelint-disable-next-line liberty/use-logical-spec */
|
||||||
|
min-width: 18px;
|
||||||
|
padding: 0;
|
||||||
|
block-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
203
src/@core/components/ProductDescriptionEditor.vue
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
<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="tabler-bold"
|
||||||
|
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="tabler-underline" />
|
||||||
|
</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="tabler-italic"
|
||||||
|
class="font-weight-medium"
|
||||||
|
/>
|
||||||
|
</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="tabler-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="tabler-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="tabler-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="tabler-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="tabler-align-justified"
|
||||||
|
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>
|
||||||
41
src/@core/components/ScrollToTop.vue
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<script setup>
|
||||||
|
const { y } = useWindowScroll()
|
||||||
|
|
||||||
|
const scrollToTop = () => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Transition
|
||||||
|
name="app-transition-zoom-fade"
|
||||||
|
style="transform-origin: center;"
|
||||||
|
>
|
||||||
|
<VBtn
|
||||||
|
v-show="y > 200"
|
||||||
|
icon
|
||||||
|
density="comfortable"
|
||||||
|
class="scroll-to-top d-print-none"
|
||||||
|
@click="scrollToTop"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
size="22"
|
||||||
|
icon="tabler-arrow-up"
|
||||||
|
/>
|
||||||
|
</VBtn>
|
||||||
|
</Transition>
|
||||||
|
</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>
|
||||||
95
src/@core/components/Shortcuts.vue
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<script setup>
|
||||||
|
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
togglerIcon: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'tabler-layout-grid-add',
|
||||||
|
},
|
||||||
|
shortcuts: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<IconBtn>
|
||||||
|
<VIcon
|
||||||
|
size="24"
|
||||||
|
:icon="props.togglerIcon"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<VMenu
|
||||||
|
activator="parent"
|
||||||
|
offset="12px"
|
||||||
|
location="bottom end"
|
||||||
|
>
|
||||||
|
<VCard
|
||||||
|
:width="$vuetify.display.smAndDown ? 330 : 380"
|
||||||
|
max-height="560"
|
||||||
|
class="d-flex flex-column"
|
||||||
|
>
|
||||||
|
<VCardItem class="py-3">
|
||||||
|
<h6 class="text-base font-weight-medium">
|
||||||
|
Shortcuts
|
||||||
|
</h6>
|
||||||
|
|
||||||
|
<template #append>
|
||||||
|
<IconBtn
|
||||||
|
size="small"
|
||||||
|
color="high-emphasis"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
size="20"
|
||||||
|
icon="tabler-plus"
|
||||||
|
/>
|
||||||
|
</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
|
||||||
|
size="26"
|
||||||
|
color="high-emphasis"
|
||||||
|
:icon="shortcut.icon"
|
||||||
|
/>
|
||||||
|
</VAvatar>
|
||||||
|
|
||||||
|
<h6 class="text-base font-weight-medium mt-3 mb-0">
|
||||||
|
{{ shortcut.title }}
|
||||||
|
</h6>
|
||||||
|
<p class="text-sm 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>
|
||||||
42
src/@core/components/TablePagination.vue
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<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>
|
||||||
612
src/@core/components/TheCustomizer.vue
Normal file
@ -0,0 +1,612 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
|
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
|
||||||
|
import { useTheme } from 'vuetify'
|
||||||
|
import {
|
||||||
|
staticPrimaryColor,
|
||||||
|
staticPrimaryDarkenColor,
|
||||||
|
} from '@/plugins/vuetify/theme'
|
||||||
|
import {
|
||||||
|
Direction,
|
||||||
|
Layout,
|
||||||
|
Skins,
|
||||||
|
Theme,
|
||||||
|
} from '@core/enums'
|
||||||
|
import { useConfigStore } from '@core/stores/config'
|
||||||
|
import horizontalLight from '@images/customizer-icons/horizontal-light.svg'
|
||||||
|
import {
|
||||||
|
AppContentLayoutNav,
|
||||||
|
ContentWidth,
|
||||||
|
} from '@layouts/enums'
|
||||||
|
import {
|
||||||
|
cookieRef,
|
||||||
|
namespaceConfig,
|
||||||
|
} from '@layouts/stores/config'
|
||||||
|
import { themeConfig } from '@themeConfig'
|
||||||
|
import borderSkin from '@images/customizer-icons/border-light.svg'
|
||||||
|
import collapsed from '@images/customizer-icons/collapsed-light.svg'
|
||||||
|
import compact from '@images/customizer-icons/compact-light.svg'
|
||||||
|
import defaultSkin from '@images/customizer-icons/default-light.svg'
|
||||||
|
import ltrSvg from '@images/customizer-icons/ltr-light.svg'
|
||||||
|
import rtlSvg from '@images/customizer-icons/rtl-light.svg'
|
||||||
|
import wideSvg from '@images/customizer-icons/wide-light.svg'
|
||||||
|
|
||||||
|
const isNavDrawerOpen = ref(false)
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
const vuetifyTheme = useTheme()
|
||||||
|
|
||||||
|
const colors = [
|
||||||
|
{
|
||||||
|
main: staticPrimaryColor,
|
||||||
|
darken: '#675DD8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
main: '#0D9394',
|
||||||
|
darken: '#0C8485',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
main: '#FFB400',
|
||||||
|
darken: '#E6A200',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
main: '#FF4C51',
|
||||||
|
darken: '#E64449',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
main: '#16B1FF',
|
||||||
|
darken: '#149FE6',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const customPrimaryColor = ref('#663131')
|
||||||
|
|
||||||
|
watch(() => configStore.theme, () => {
|
||||||
|
const cookiePrimaryColor = cookieRef(`${ vuetifyTheme.name.value }ThemePrimaryColor`, null).value
|
||||||
|
if (cookiePrimaryColor && !colors.some(color => color.main === cookiePrimaryColor))
|
||||||
|
customPrimaryColor.value = cookiePrimaryColor
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
const setPrimaryColor = useDebounceFn(color => {
|
||||||
|
vuetifyTheme.themes.value[vuetifyTheme.name.value].colors.primary = color.main
|
||||||
|
vuetifyTheme.themes.value[vuetifyTheme.name.value].colors['primary-darken-1'] = color.darken
|
||||||
|
cookieRef(`${ vuetifyTheme.name.value }ThemePrimaryColor`, null).value = color.main
|
||||||
|
cookieRef(`${ vuetifyTheme.name.value }ThemePrimaryDarkenColor`, null).value = color.darken
|
||||||
|
useStorage(namespaceConfig('initial-loader-color'), null).value = color.main
|
||||||
|
}, 100)
|
||||||
|
|
||||||
|
const themeMode = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
bgImage: 'tabler-sun',
|
||||||
|
value: Theme.Light,
|
||||||
|
label: 'Light',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bgImage: 'tabler-moon-stars',
|
||||||
|
value: Theme.Dark,
|
||||||
|
label: 'Dark',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bgImage: 'tabler-device-desktop-analytics',
|
||||||
|
value: Theme.System,
|
||||||
|
label: 'System',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const themeSkin = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
bgImage: defaultSkin,
|
||||||
|
value: Skins.Default,
|
||||||
|
label: 'Default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bgImage: borderSkin,
|
||||||
|
value: Skins.Bordered,
|
||||||
|
label: 'Bordered',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentLayout = ref(configStore.isVerticalNavCollapsed ? 'collapsed' : configStore.appContentLayoutNav)
|
||||||
|
|
||||||
|
const layouts = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
bgImage: defaultSkin,
|
||||||
|
value: Layout.Vertical,
|
||||||
|
label: 'Vertical',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bgImage: collapsed,
|
||||||
|
value: Layout.Collapsed,
|
||||||
|
label: 'Collapsed',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bgImage: horizontalLight,
|
||||||
|
value: Layout.Horizontal,
|
||||||
|
label: 'Horizontal',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(currentLayout, () => {
|
||||||
|
if (currentLayout.value === 'collapsed') {
|
||||||
|
configStore.isVerticalNavCollapsed = true
|
||||||
|
configStore.appContentLayoutNav = AppContentLayoutNav.Vertical
|
||||||
|
} else {
|
||||||
|
configStore.isVerticalNavCollapsed = false
|
||||||
|
configStore.appContentLayoutNav = currentLayout.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
watch(() => configStore.isVerticalNavCollapsed, () => {
|
||||||
|
currentLayout.value = configStore.isVerticalNavCollapsed ? 'collapsed' : configStore.appContentLayoutNav
|
||||||
|
})
|
||||||
|
|
||||||
|
const contentWidth = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
bgImage: compact,
|
||||||
|
value: ContentWidth.Boxed,
|
||||||
|
label: 'Compact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bgImage: wideSvg,
|
||||||
|
value: ContentWidth.Fluid,
|
||||||
|
label: 'Wide',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentDir = ref(configStore.isAppRTL ? 'rtl' : 'ltr')
|
||||||
|
|
||||||
|
const direction = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
bgImage: ltrSvg,
|
||||||
|
value: Direction.Ltr,
|
||||||
|
label: 'Left to right',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bgImage: rtlSvg,
|
||||||
|
value: Direction.Rtl,
|
||||||
|
label: 'Right to left',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(currentDir, () => {
|
||||||
|
if (currentDir.value === 'rtl')
|
||||||
|
configStore.isAppRTL = true
|
||||||
|
else
|
||||||
|
configStore.isAppRTL = false
|
||||||
|
})
|
||||||
|
|
||||||
|
const isCookieHasAnyValue = ref(false)
|
||||||
|
const { locale } = useI18n({ useScope: 'global' })
|
||||||
|
|
||||||
|
const isActiveLangRTL = computed(() => {
|
||||||
|
const lang = themeConfig.app.i18n.langConfig.find(l => l.i18nLang === locale.value)
|
||||||
|
|
||||||
|
return lang?.isRTL ?? false
|
||||||
|
})
|
||||||
|
|
||||||
|
watch([
|
||||||
|
() => vuetifyTheme.current.value.colors.primary,
|
||||||
|
configStore.$state,
|
||||||
|
locale,
|
||||||
|
], () => {
|
||||||
|
const initialConfigValue = [
|
||||||
|
staticPrimaryColor,
|
||||||
|
staticPrimaryColor,
|
||||||
|
themeConfig.app.theme,
|
||||||
|
themeConfig.app.skin,
|
||||||
|
themeConfig.verticalNav.isVerticalNavSemiDark,
|
||||||
|
themeConfig.verticalNav.isVerticalNavCollapsed,
|
||||||
|
themeConfig.app.contentWidth,
|
||||||
|
isActiveLangRTL.value,
|
||||||
|
themeConfig.app.contentLayoutNav,
|
||||||
|
]
|
||||||
|
|
||||||
|
const themeConfigValue = [
|
||||||
|
vuetifyTheme.themes.value.light.colors.primary,
|
||||||
|
vuetifyTheme.themes.value.dark.colors.primary,
|
||||||
|
configStore.theme,
|
||||||
|
configStore.skin,
|
||||||
|
configStore.isVerticalNavSemiDark,
|
||||||
|
configStore.isVerticalNavCollapsed,
|
||||||
|
configStore.appContentWidth,
|
||||||
|
configStore.isAppRTL,
|
||||||
|
configStore.appContentLayoutNav,
|
||||||
|
]
|
||||||
|
|
||||||
|
currentDir.value = configStore.isAppRTL ? 'rtl' : 'ltr'
|
||||||
|
isCookieHasAnyValue.value = JSON.stringify(themeConfigValue) !== JSON.stringify(initialConfigValue)
|
||||||
|
}, {
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const resetCustomizer = async () => {
|
||||||
|
if (isCookieHasAnyValue.value) {
|
||||||
|
vuetifyTheme.themes.value.light.colors.primary = staticPrimaryColor
|
||||||
|
vuetifyTheme.themes.value.dark.colors.primary = staticPrimaryColor
|
||||||
|
vuetifyTheme.themes.value.light.colors['primary-darken-1'] = staticPrimaryDarkenColor
|
||||||
|
vuetifyTheme.themes.value.dark.colors['primary-darken-1'] = staticPrimaryDarkenColor
|
||||||
|
configStore.theme = themeConfig.app.theme
|
||||||
|
configStore.skin = themeConfig.app.skin
|
||||||
|
configStore.isVerticalNavSemiDark = themeConfig.verticalNav.isVerticalNavSemiDark
|
||||||
|
configStore.appContentLayoutNav = themeConfig.app.contentLayoutNav
|
||||||
|
configStore.appContentWidth = themeConfig.app.contentWidth
|
||||||
|
configStore.isAppRTL = isActiveLangRTL.value
|
||||||
|
configStore.isVerticalNavCollapsed = themeConfig.verticalNav.isVerticalNavCollapsed
|
||||||
|
useStorage(namespaceConfig('initial-loader-color'), null).value = staticPrimaryColor
|
||||||
|
currentLayout.value = themeConfig.app.contentLayoutNav
|
||||||
|
cookieRef('lightThemePrimaryColor', null).value = null
|
||||||
|
cookieRef('darkThemePrimaryColor', null).value = null
|
||||||
|
cookieRef('lightThemePrimaryDarkenColor', null).value = null
|
||||||
|
cookieRef('darkThemePrimaryDarkenColor', null).value = null
|
||||||
|
await nextTick()
|
||||||
|
isCookieHasAnyValue.value = false
|
||||||
|
customPrimaryColor.value = '#ffffff'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="d-lg-block d-none">
|
||||||
|
<VBtn
|
||||||
|
icon
|
||||||
|
class="app-customizer-toggler rounded-s-lg rounded-0"
|
||||||
|
style="z-index: 1001;"
|
||||||
|
@click="isNavDrawerOpen = true"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
size="22"
|
||||||
|
icon="tabler-settings"
|
||||||
|
/>
|
||||||
|
</VBtn>
|
||||||
|
|
||||||
|
<VNavigationDrawer
|
||||||
|
v-model="isNavDrawerOpen"
|
||||||
|
temporary
|
||||||
|
touchless
|
||||||
|
border="none"
|
||||||
|
location="end"
|
||||||
|
width="400"
|
||||||
|
elevation="10"
|
||||||
|
:scrim="false"
|
||||||
|
class="app-customizer"
|
||||||
|
>
|
||||||
|
<!-- 👉 Header -->
|
||||||
|
<div class="customizer-heading d-flex align-center justify-space-between">
|
||||||
|
<div>
|
||||||
|
<h6 class="text-h6">
|
||||||
|
Theme Customizer
|
||||||
|
</h6>
|
||||||
|
<p class="text-body-2 mb-0">
|
||||||
|
Customize & Preview in Real Time
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex align-center gap-1">
|
||||||
|
<VBtn
|
||||||
|
icon
|
||||||
|
variant="text"
|
||||||
|
size="small"
|
||||||
|
color="medium-emphasis"
|
||||||
|
@click="resetCustomizer"
|
||||||
|
>
|
||||||
|
<VBadge
|
||||||
|
v-show="isCookieHasAnyValue"
|
||||||
|
dot
|
||||||
|
color="error"
|
||||||
|
offset-x="-29"
|
||||||
|
offset-y="-14"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<VIcon
|
||||||
|
size="24"
|
||||||
|
color="high-emphasis"
|
||||||
|
icon="tabler-refresh"
|
||||||
|
/>
|
||||||
|
</VBtn>
|
||||||
|
|
||||||
|
<VBtn
|
||||||
|
icon
|
||||||
|
variant="text"
|
||||||
|
color="medium-emphasis"
|
||||||
|
size="small"
|
||||||
|
@click="isNavDrawerOpen = false"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
icon="tabler-x"
|
||||||
|
color="high-emphasis"
|
||||||
|
size="24"
|
||||||
|
/>
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VDivider />
|
||||||
|
|
||||||
|
<PerfectScrollbar
|
||||||
|
tag="ul"
|
||||||
|
:options="{ wheelPropagation: false }"
|
||||||
|
>
|
||||||
|
<!-- SECTION Theming -->
|
||||||
|
<CustomizerSection
|
||||||
|
title="Theming"
|
||||||
|
:divider="false"
|
||||||
|
>
|
||||||
|
<!-- 👉 Primary Color -->
|
||||||
|
<div class="d-flex flex-column gap-2">
|
||||||
|
<h6 class="text-h6">
|
||||||
|
Primary Color
|
||||||
|
</h6>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="d-flex app-customizer-primary-colors"
|
||||||
|
style="column-gap: 0.75rem; margin-block-start: 2px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="color in colors"
|
||||||
|
:key="color.main"
|
||||||
|
style="
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
outline: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||||
|
padding-block: 0.5rem;
|
||||||
|
padding-inline: 0.625rem;"
|
||||||
|
class="primary-color-wrapper cursor-pointer"
|
||||||
|
:class="vuetifyTheme.current.value.colors.primary === color.main ? 'active' : ''"
|
||||||
|
:style="vuetifyTheme.current.value.colors.primary === color.main ? `outline-color: ${color.main}; outline-width:2px;` : `--v-color:${color.main}`"
|
||||||
|
@click="setPrimaryColor(color)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="border-radius: 0.375rem;block-size: 2.125rem; inline-size: 1.8938rem;"
|
||||||
|
:style="{ backgroundColor: color.main }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="primary-color-wrapper cursor-pointer d-flex align-center"
|
||||||
|
style="
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
outline: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||||
|
padding-block: 0.5rem;
|
||||||
|
padding-inline: 0.625rem;"
|
||||||
|
:class="vuetifyTheme.current.value.colors.primary === customPrimaryColor ? 'active' : ''"
|
||||||
|
:style="vuetifyTheme.current.value.colors.primary === customPrimaryColor ? `outline-color: ${customPrimaryColor}; outline-width:2px;` : ''"
|
||||||
|
>
|
||||||
|
<VBtn
|
||||||
|
icon
|
||||||
|
size="30"
|
||||||
|
:color="vuetifyTheme.current.value.colors.primary === customPrimaryColor ? customPrimaryColor : $vuetify.theme.current.dark ? '#8692d029' : '#4b465c29'"
|
||||||
|
variant="flat"
|
||||||
|
style="border-radius: 0.375rem;"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
size="20"
|
||||||
|
icon="tabler-color-picker"
|
||||||
|
:color="vuetifyTheme.current.value.colors.primary === customPrimaryColor ? 'rgb(var(--v-theme-on-primary))' : ''"
|
||||||
|
/>
|
||||||
|
</VBtn>
|
||||||
|
|
||||||
|
<VMenu
|
||||||
|
activator="parent"
|
||||||
|
:close-on-content-click="false"
|
||||||
|
>
|
||||||
|
<VList>
|
||||||
|
<VListItem>
|
||||||
|
<VColorPicker
|
||||||
|
v-model="customPrimaryColor"
|
||||||
|
mode="hex"
|
||||||
|
:modes="['hex']"
|
||||||
|
@update:model-value="setPrimaryColor({ main: customPrimaryColor, darken: customPrimaryColor })"
|
||||||
|
/>
|
||||||
|
</VListItem>
|
||||||
|
</VList>
|
||||||
|
</VMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 👉 Theme -->
|
||||||
|
<div class="d-flex flex-column gap-2">
|
||||||
|
<h6 class="text-h6">
|
||||||
|
Theme
|
||||||
|
</h6>
|
||||||
|
|
||||||
|
<CustomRadiosWithImage
|
||||||
|
:key="configStore.theme"
|
||||||
|
v-model:selected-radio="configStore.theme"
|
||||||
|
:radio-content="themeMode"
|
||||||
|
:grid-column="{ cols: '4' }"
|
||||||
|
class="customizer-skins"
|
||||||
|
>
|
||||||
|
<template #label="item">
|
||||||
|
<span class="text-sm text-medium-emphasis mt-1">{{ item?.label }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #content="{ item }">
|
||||||
|
<div
|
||||||
|
class="customizer-skins-icon-wrapper d-flex align-center justify-center py-3 w-100"
|
||||||
|
style="min-inline-size: 100%;"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
size="30"
|
||||||
|
:icon="item.bgImage"
|
||||||
|
color="high-emphasis"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</CustomRadiosWithImage>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 👉 Skin -->
|
||||||
|
<div class="d-flex flex-column gap-2">
|
||||||
|
<h6 class="text-h6">
|
||||||
|
Skins
|
||||||
|
</h6>
|
||||||
|
|
||||||
|
<CustomRadiosWithImage
|
||||||
|
:key="configStore.skin"
|
||||||
|
v-model:selected-radio="configStore.skin"
|
||||||
|
:radio-content="themeSkin"
|
||||||
|
:grid-column="{ cols: '4' }"
|
||||||
|
>
|
||||||
|
<template #label="item">
|
||||||
|
<span class="text-sm text-medium-emphasis">{{ item?.label }}</span>
|
||||||
|
</template>
|
||||||
|
</CustomRadiosWithImage>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 👉 Semi Dark -->
|
||||||
|
<div
|
||||||
|
class="align-center justify-space-between"
|
||||||
|
:class="vuetifyTheme.global.name.value === 'light' && configStore.appContentLayoutNav === AppContentLayoutNav.Vertical ? 'd-flex' : 'd-none'"
|
||||||
|
>
|
||||||
|
<VLabel
|
||||||
|
for="customizer-semi-dark"
|
||||||
|
class="text-h6 text-high-emphasis"
|
||||||
|
>
|
||||||
|
Semi Dark Menu
|
||||||
|
</VLabel>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<VSwitch
|
||||||
|
id="customizer-semi-dark"
|
||||||
|
v-model="configStore.isVerticalNavSemiDark"
|
||||||
|
class="ms-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CustomizerSection>
|
||||||
|
<!-- !SECTION -->
|
||||||
|
|
||||||
|
<!-- SECTION LAYOUT -->
|
||||||
|
<CustomizerSection title="Layout">
|
||||||
|
<!-- 👉 Layouts -->
|
||||||
|
<div class="d-flex flex-column gap-2">
|
||||||
|
<h6 class="text-base font-weight-medium">
|
||||||
|
Layout
|
||||||
|
</h6>
|
||||||
|
|
||||||
|
<CustomRadiosWithImage
|
||||||
|
:key="currentLayout"
|
||||||
|
v-model:selected-radio="currentLayout"
|
||||||
|
:radio-content="layouts"
|
||||||
|
:grid-column="{ cols: '4' }"
|
||||||
|
>
|
||||||
|
<template #label="item">
|
||||||
|
<span class="text-sm text-medium-emphasis">{{ item.label }}</span>
|
||||||
|
</template>
|
||||||
|
</CustomRadiosWithImage>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 👉 Content Width -->
|
||||||
|
<div class="d-flex flex-column gap-2">
|
||||||
|
<h6 class="text-base font-weight-medium">
|
||||||
|
Content
|
||||||
|
</h6>
|
||||||
|
|
||||||
|
<CustomRadiosWithImage
|
||||||
|
:key="configStore.appContentWidth"
|
||||||
|
v-model:selected-radio="configStore.appContentWidth"
|
||||||
|
:radio-content="contentWidth"
|
||||||
|
:grid-column="{ cols: '4' }"
|
||||||
|
>
|
||||||
|
<template #label="item">
|
||||||
|
<span class="text-sm text-medium-emphasis">{{ item.label }}</span>
|
||||||
|
</template>
|
||||||
|
</CustomRadiosWithImage>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 👉 Direction -->
|
||||||
|
<div class="d-flex flex-column gap-2">
|
||||||
|
<h6 class="text-base font-weight-medium">
|
||||||
|
Direction
|
||||||
|
</h6>
|
||||||
|
|
||||||
|
<CustomRadiosWithImage
|
||||||
|
:key="currentDir"
|
||||||
|
v-model:selected-radio="currentDir"
|
||||||
|
:radio-content="direction"
|
||||||
|
:grid-column="{ cols: '4' }"
|
||||||
|
>
|
||||||
|
<template #label="item">
|
||||||
|
<span class="text-sm text-medium-emphasis">{{ item?.label }}</span>
|
||||||
|
</template>
|
||||||
|
</CustomRadiosWithImage>
|
||||||
|
</div>
|
||||||
|
</CustomizerSection>
|
||||||
|
<!-- !SECTION -->
|
||||||
|
</PerfectScrollbar>
|
||||||
|
</VNavigationDrawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.app-customizer {
|
||||||
|
.customizer-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 1.5rem;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customizer-heading {
|
||||||
|
padding-block: 1rem;
|
||||||
|
padding-inline: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-input-wrapper {
|
||||||
|
.v-col {
|
||||||
|
padding-inline: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-label.custom-input {
|
||||||
|
border: none;
|
||||||
|
color: rgb(var(--v-theme-on-surface));
|
||||||
|
outline: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-navigation-drawer__content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-label.custom-input.active {
|
||||||
|
border-color: transparent;
|
||||||
|
outline: 2px solid rgb(var(--v-theme-primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-label.custom-input:not(.active):hover {
|
||||||
|
border-color: rgba(var(--v-border-color), 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.customizer-skins {
|
||||||
|
.custom-input.active {
|
||||||
|
.customizer-skins-icon-wrapper {
|
||||||
|
background-color: rgba(var(--v-global-theme-primary), var(--v-selected-opacity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-customizer-primary-colors {
|
||||||
|
.primary-color-wrapper:not(.active) {
|
||||||
|
&:hover {
|
||||||
|
outline-color: rgba(var(--v-border-color), 0.22) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-customizer-toggler {
|
||||||
|
position: fixed !important;
|
||||||
|
inset-block-start: 20%;
|
||||||
|
inset-inline-end: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
58
src/@core/components/ThemeSwitcher.vue
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<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 color="rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity))">
|
||||||
|
<VIcon
|
||||||
|
:icon="props.themes.find(t => t.name === configStore.theme)?.icon"
|
||||||
|
size="24"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<VTooltip
|
||||||
|
activator="parent"
|
||||||
|
open-delay="1000"
|
||||||
|
scroll-strategy="close"
|
||||||
|
>
|
||||||
|
<span class="text-capitalize">{{ configStore.theme }}</span>
|
||||||
|
</VTooltip>
|
||||||
|
|
||||||
|
<VMenu
|
||||||
|
activator="parent"
|
||||||
|
offset="12px"
|
||||||
|
:width="180"
|
||||||
|
>
|
||||||
|
<VList
|
||||||
|
v-model:selected="selectedItem"
|
||||||
|
mandatory
|
||||||
|
>
|
||||||
|
<VListItem
|
||||||
|
v-for="{ name, icon } in props.themes"
|
||||||
|
:key="name"
|
||||||
|
:value="name"
|
||||||
|
:prepend-icon="icon"
|
||||||
|
color="primary"
|
||||||
|
class="text-capitalize"
|
||||||
|
@click="() => { configStore.theme = name }"
|
||||||
|
>
|
||||||
|
{{ name }}
|
||||||
|
</VListItem>
|
||||||
|
</VList>
|
||||||
|
</VMenu>
|
||||||
|
</IconBtn>
|
||||||
|
</template>
|
||||||
171
src/@core/components/TiptapEditor.vue
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
<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>
|
||||||
|
<div
|
||||||
|
v-if="editor"
|
||||||
|
class="d-flex gap-2 py-2 px-6 flex-wrap align-center editor"
|
||||||
|
>
|
||||||
|
<IconBtn
|
||||||
|
size="small"
|
||||||
|
rounded
|
||||||
|
:variant="editor.isActive('bold') ? 'tonal' : 'text'"
|
||||||
|
:color="editor.isActive('bold') ? 'primary' : 'default'"
|
||||||
|
@click="editor.chain().focus().toggleBold().run()"
|
||||||
|
>
|
||||||
|
<VIcon icon="tabler-bold" />
|
||||||
|
</IconBtn>
|
||||||
|
|
||||||
|
<IconBtn
|
||||||
|
size="small"
|
||||||
|
rounded
|
||||||
|
:variant="editor.isActive('underline') ? 'tonal' : 'text'"
|
||||||
|
:color="editor.isActive('underline') ? 'primary' : 'default'"
|
||||||
|
@click="editor.commands.toggleUnderline()"
|
||||||
|
>
|
||||||
|
<VIcon icon="tabler-underline" />
|
||||||
|
</IconBtn>
|
||||||
|
|
||||||
|
<IconBtn
|
||||||
|
size="small"
|
||||||
|
rounded
|
||||||
|
:variant="editor.isActive('italic') ? 'tonal' : 'text'"
|
||||||
|
:color="editor.isActive('italic') ? 'primary' : 'default'"
|
||||||
|
@click="editor.chain().focus().toggleItalic().run()"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
icon="tabler-italic"
|
||||||
|
class="font-weight-medium"
|
||||||
|
/>
|
||||||
|
</IconBtn>
|
||||||
|
|
||||||
|
<IconBtn
|
||||||
|
size="small"
|
||||||
|
rounded
|
||||||
|
:variant="editor.isActive('strike') ? 'tonal' : 'text'"
|
||||||
|
:color="editor.isActive('strike') ? 'primary' : 'default'"
|
||||||
|
@click="editor.chain().focus().toggleStrike().run()"
|
||||||
|
>
|
||||||
|
<VIcon icon="tabler-strikethrough" />
|
||||||
|
</IconBtn>
|
||||||
|
|
||||||
|
<IconBtn
|
||||||
|
size="small"
|
||||||
|
rounded
|
||||||
|
:variant="editor.isActive({ textAlign: 'left' }) ? 'tonal' : 'text'"
|
||||||
|
:color="editor.isActive({ textAlign: 'left' }) ? 'primary' : 'default'"
|
||||||
|
@click="editor.chain().focus().setTextAlign('left').run()"
|
||||||
|
>
|
||||||
|
<VIcon icon="tabler-align-left" />
|
||||||
|
</IconBtn>
|
||||||
|
|
||||||
|
<IconBtn
|
||||||
|
size="small"
|
||||||
|
rounded
|
||||||
|
:color="editor.isActive({ textAlign: 'center' }) ? 'primary' : 'default'"
|
||||||
|
:variant="editor.isActive({ textAlign: 'center' }) ? 'tonal' : 'text'"
|
||||||
|
@click="editor.chain().focus().setTextAlign('center').run()"
|
||||||
|
>
|
||||||
|
<VIcon icon="tabler-align-center" />
|
||||||
|
</IconBtn>
|
||||||
|
|
||||||
|
<IconBtn
|
||||||
|
size="small"
|
||||||
|
rounded
|
||||||
|
:variant="editor.isActive({ textAlign: 'right' }) ? 'tonal' : 'text'"
|
||||||
|
:color="editor.isActive({ textAlign: 'right' }) ? 'primary' : 'default'"
|
||||||
|
@click="editor.chain().focus().setTextAlign('right').run()"
|
||||||
|
>
|
||||||
|
<VIcon icon="tabler-align-right" />
|
||||||
|
</IconBtn>
|
||||||
|
|
||||||
|
<IconBtn
|
||||||
|
size="small"
|
||||||
|
rounded
|
||||||
|
:variant="editor.isActive({ textAlign: 'justify' }) ? 'tonal' : 'text'"
|
||||||
|
:color="editor.isActive({ textAlign: 'justify' }) ? 'primary' : 'default'"
|
||||||
|
@click="editor.chain().focus().setTextAlign('justify').run()"
|
||||||
|
>
|
||||||
|
<VIcon icon="tabler-align-justified" />
|
||||||
|
</IconBtn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VDivider />
|
||||||
|
|
||||||
|
<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>
|
||||||
57
src/@core/components/app-form-elements/AppAutocomplete.vue
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<script setup>
|
||||||
|
defineOptions({
|
||||||
|
name: 'AppAutocomplete',
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// const { class: _class, label, variant: _, ...restAttrs } = useAttrs()
|
||||||
|
const elementId = computed(() => {
|
||||||
|
const attrs = useAttrs()
|
||||||
|
const _elementIdToken = attrs.id || attrs.label
|
||||||
|
|
||||||
|
return _elementIdToken ? `app-autocomplete-${ _elementIdToken }-${ Math.random().toString(36).slice(2, 7) }` : undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const label = computed(() => useAttrs().label)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="app-autocomplete flex-grow-1"
|
||||||
|
:class="$attrs.class"
|
||||||
|
>
|
||||||
|
<VLabel
|
||||||
|
v-if="label"
|
||||||
|
:for="elementId"
|
||||||
|
class="mb-1 text-body-2"
|
||||||
|
:text="label"
|
||||||
|
/>
|
||||||
|
<VAutocomplete
|
||||||
|
v-bind="{
|
||||||
|
...$attrs,
|
||||||
|
class: null,
|
||||||
|
label: undefined,
|
||||||
|
id: elementId,
|
||||||
|
variant: 'outlined',
|
||||||
|
menuProps: {
|
||||||
|
contentClass: [
|
||||||
|
'app-inner-list',
|
||||||
|
'app-autocomplete__content',
|
||||||
|
'v-autocomplete__content',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-for="(_, name) in $slots"
|
||||||
|
#[name]="slotProps"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
:name="name"
|
||||||
|
v-bind="slotProps || {}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VAutocomplete>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
57
src/@core/components/app-form-elements/AppCombobox.vue
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<script setup>
|
||||||
|
defineOptions({
|
||||||
|
name: 'AppCombobox',
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const elementId = computed(() => {
|
||||||
|
const attrs = useAttrs()
|
||||||
|
const _elementIdToken = attrs.id || attrs.label
|
||||||
|
|
||||||
|
return _elementIdToken ? `app-combobox-${ _elementIdToken }-${ Math.random().toString(36).slice(2, 7) }` : undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const label = computed(() => useAttrs().label)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="app-combobox flex-grow-1"
|
||||||
|
:class="$attrs.class"
|
||||||
|
>
|
||||||
|
<VLabel
|
||||||
|
v-if="label"
|
||||||
|
:for="elementId"
|
||||||
|
class="mb-1 text-body-2"
|
||||||
|
:text="label"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<VCombobox
|
||||||
|
v-bind="{
|
||||||
|
...$attrs,
|
||||||
|
class: null,
|
||||||
|
label: undefined,
|
||||||
|
variant: 'outlined',
|
||||||
|
id: elementId,
|
||||||
|
menuProps: {
|
||||||
|
contentClass: [
|
||||||
|
'app-inner-list',
|
||||||
|
'app-combobox__content',
|
||||||
|
'v-combobox__content',
|
||||||
|
$attrs.multiple !== undefined ? 'v-list-select-multiple' : '',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-for="(_, name) in $slots"
|
||||||
|
#[name]="slotProps"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
:name="name"
|
||||||
|
v-bind="slotProps || {}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VCombobox>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
537
src/@core/components/app-form-elements/AppDateTimePicker.vue
Normal file
@ -0,0 +1,537 @@
|
|||||||
|
<script setup>
|
||||||
|
import FlatPickr from 'vue-flatpickr-component'
|
||||||
|
import { useTheme } from 'vuetify'
|
||||||
|
import {
|
||||||
|
VField,
|
||||||
|
filterFieldProps,
|
||||||
|
makeVFieldProps,
|
||||||
|
} from 'vuetify/lib/components/VField/VField'
|
||||||
|
import {
|
||||||
|
VInput,
|
||||||
|
makeVInputProps,
|
||||||
|
} from 'vuetify/lib/components/VInput/VInput'
|
||||||
|
|
||||||
|
|
||||||
|
import { filterInputAttrs } from 'vuetify/lib/util/helpers'
|
||||||
|
import { useConfigStore } from '@core/stores/config'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
autofocus: Boolean,
|
||||||
|
counter: [
|
||||||
|
Boolean,
|
||||||
|
Number,
|
||||||
|
String,
|
||||||
|
],
|
||||||
|
counterValue: Function,
|
||||||
|
prefix: String,
|
||||||
|
placeholder: String,
|
||||||
|
persistentPlaceholder: Boolean,
|
||||||
|
persistentCounter: Boolean,
|
||||||
|
suffix: String,
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'text',
|
||||||
|
},
|
||||||
|
modelModifiers: Object,
|
||||||
|
...makeVInputProps({
|
||||||
|
density: 'comfortable',
|
||||||
|
hideDetails: 'auto',
|
||||||
|
}),
|
||||||
|
...makeVFieldProps({
|
||||||
|
variant: 'outlined',
|
||||||
|
color: 'primary',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits([
|
||||||
|
'click:control',
|
||||||
|
'mousedown:control',
|
||||||
|
'update:focused',
|
||||||
|
'update:modelValue',
|
||||||
|
'click:clear',
|
||||||
|
])
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
const attrs = useAttrs()
|
||||||
|
const [rootAttrs, compAttrs] = filterInputAttrs(attrs)
|
||||||
|
const inputProps = ref(VInput.filterProps(props))
|
||||||
|
const fieldProps = ref(filterFieldProps(props))
|
||||||
|
const refFlatPicker = ref()
|
||||||
|
const { focused } = useFocus(refFlatPicker)
|
||||||
|
const isCalendarOpen = ref(false)
|
||||||
|
const isInlinePicker = ref(false)
|
||||||
|
|
||||||
|
// flat picker prop manipulation
|
||||||
|
if (compAttrs.config && compAttrs.config.inline) {
|
||||||
|
isInlinePicker.value = compAttrs.config.inline
|
||||||
|
Object.assign(compAttrs, { altInputClass: 'inlinePicker' })
|
||||||
|
}
|
||||||
|
compAttrs.config = {
|
||||||
|
...compAttrs.config,
|
||||||
|
prevArrow: '<i class="tabler-chevron-left v-icon" style="font-size: 20px; height: 20px; width: 20px;"></i>',
|
||||||
|
nextArrow: '<i class="tabler-chevron-right v-icon" style="font-size: 20px; height: 20px; width: 20px;"></i>',
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClear = el => {
|
||||||
|
el.stopPropagation()
|
||||||
|
nextTick(() => {
|
||||||
|
emit('update:modelValue', '')
|
||||||
|
emit('click:clear', el)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const vuetifyTheme = useTheme()
|
||||||
|
const vuetifyThemesName = Object.keys(vuetifyTheme.themes.value)
|
||||||
|
|
||||||
|
// Themes class added to flat-picker component for light and dark support
|
||||||
|
const updateThemeClassInCalendar = () => {
|
||||||
|
|
||||||
|
// ℹ️ Flatpickr don't render it's instance in mobile and device simulator
|
||||||
|
if (!refFlatPicker.value.fp.calendarContainer)
|
||||||
|
return
|
||||||
|
vuetifyThemesName.forEach(t => {
|
||||||
|
refFlatPicker.value.fp.calendarContainer.classList.remove(`v-theme--${ t }`)
|
||||||
|
})
|
||||||
|
refFlatPicker.value.fp.calendarContainer.classList.add(`v-theme--${ vuetifyTheme.global.name.value }`)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => configStore.theme, updateThemeClassInCalendar)
|
||||||
|
onMounted(() => {
|
||||||
|
updateThemeClassInCalendar()
|
||||||
|
})
|
||||||
|
|
||||||
|
const emitModelValue = val => {
|
||||||
|
emit('update:modelValue', val)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props, () => {
|
||||||
|
fieldProps.value = filterFieldProps(props)
|
||||||
|
inputProps.value = VInput.filterProps(props)
|
||||||
|
}, {
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const elementId = computed(() => {
|
||||||
|
|
||||||
|
|
||||||
|
const _elementIdToken = fieldProps.id || fieldProps.label
|
||||||
|
|
||||||
|
return _elementIdToken ? `app-picker-field-${ _elementIdToken }-${ Math.random().toString(36).slice(2, 7) }` : undefined
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="app-picker-field">
|
||||||
|
<!-- v-input -->
|
||||||
|
<VLabel
|
||||||
|
v-if="fieldProps.label"
|
||||||
|
class="mb-1 text-body-2"
|
||||||
|
:for="elementId"
|
||||||
|
:text="fieldProps.label"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<VInput
|
||||||
|
v-bind="{ ...inputProps, ...rootAttrs }"
|
||||||
|
:model-value="modelValue"
|
||||||
|
:hide-details="props.hideDetails"
|
||||||
|
:class="[{
|
||||||
|
'v-text-field--prefixed': props.prefix,
|
||||||
|
'v-text-field--suffixed': props.suffix,
|
||||||
|
'v-text-field--flush-details': ['plain', 'underlined'].includes(props.variant),
|
||||||
|
}, props.class]"
|
||||||
|
class="position-relative v-text-field"
|
||||||
|
:style="props.style"
|
||||||
|
>
|
||||||
|
<template #default="{ id, isDirty, isValid, isDisabled, isReadonly }">
|
||||||
|
<!-- v-field -->
|
||||||
|
<VField
|
||||||
|
v-bind="{ ...fieldProps, label: undefined }"
|
||||||
|
:id="id.value"
|
||||||
|
role="textbox"
|
||||||
|
:active="focused || isDirty.value || isCalendarOpen"
|
||||||
|
:focused="focused || isCalendarOpen"
|
||||||
|
:dirty="isDirty.value || props.dirty"
|
||||||
|
:error="isValid.value === false"
|
||||||
|
:disabled="isDisabled.value"
|
||||||
|
@click:clear="onClear"
|
||||||
|
>
|
||||||
|
<template #default="{ props: vFieldProps }">
|
||||||
|
<div v-bind="vFieldProps">
|
||||||
|
<!-- flat-picker -->
|
||||||
|
<FlatPickr
|
||||||
|
v-if="!isInlinePicker"
|
||||||
|
v-bind="compAttrs"
|
||||||
|
ref="refFlatPicker"
|
||||||
|
:model-value="modelValue"
|
||||||
|
:placeholder="props.placeholder"
|
||||||
|
:readonly="isReadonly.value"
|
||||||
|
class="flat-picker-custom-style h-100 w-100"
|
||||||
|
:disabled="isReadonly.value"
|
||||||
|
@on-open="isCalendarOpen = true"
|
||||||
|
@on-close="isCalendarOpen = false"
|
||||||
|
@update:model-value="emitModelValue"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- simple input for inline prop -->
|
||||||
|
<input
|
||||||
|
v-if="isInlinePicker"
|
||||||
|
:value="modelValue"
|
||||||
|
:placeholder="props.placeholder"
|
||||||
|
:readonly="isReadonly.value"
|
||||||
|
class="flat-picker-custom-style h-100 w-100"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</VField>
|
||||||
|
</template>
|
||||||
|
</VInput>
|
||||||
|
|
||||||
|
<!-- flat picker for inline props -->
|
||||||
|
<FlatPickr
|
||||||
|
v-if="isInlinePicker"
|
||||||
|
v-bind="compAttrs"
|
||||||
|
ref="refFlatPicker"
|
||||||
|
:model-value="modelValue"
|
||||||
|
@update:model-value="emitModelValue"
|
||||||
|
@on-open="isCalendarOpen = true"
|
||||||
|
@on-close="isCalendarOpen = false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use "@core/scss/template/mixins" as templateMixins;
|
||||||
|
|
||||||
|
/* stylelint-disable no-descending-specificity */
|
||||||
|
@use "flatpickr/dist/flatpickr.css";
|
||||||
|
@use "@core/scss/base/mixins";
|
||||||
|
|
||||||
|
.flat-picker-custom-style {
|
||||||
|
position: absolute;
|
||||||
|
color: inherit;
|
||||||
|
inline-size: 100%;
|
||||||
|
inset: 0;
|
||||||
|
outline: none;
|
||||||
|
padding-block: 0;
|
||||||
|
padding-inline: var(--v-field-padding-start);
|
||||||
|
}
|
||||||
|
|
||||||
|
$heading-color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
|
||||||
|
$body-color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
|
||||||
|
$disabled-color: rgba(var(--v-theme-on-background), var(--v-disabled-opacity));
|
||||||
|
|
||||||
|
// hide the input when your picker is inline
|
||||||
|
input[altinputclass="inlinePicker"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-time input.flatpickr-hour {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-calendar {
|
||||||
|
@include mixins.elevation(6);
|
||||||
|
|
||||||
|
background-color: rgb(var(--v-theme-surface));
|
||||||
|
inline-size: 16.875rem;
|
||||||
|
|
||||||
|
.flatpickr-day:focus {
|
||||||
|
border-color: rgba(var(--v-border-color), var(--v-border-opacity));
|
||||||
|
background: rgba(var(--v-border-color), var(--v-border-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-rContainer {
|
||||||
|
.flatpickr-weekdays {
|
||||||
|
block-size: 1.25rem;
|
||||||
|
padding-inline: 0.5625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-days {
|
||||||
|
min-inline-size: 16.875rem;
|
||||||
|
|
||||||
|
.dayContainer {
|
||||||
|
justify-content: center !important;
|
||||||
|
inline-size: 16.875rem;
|
||||||
|
min-inline-size: 16.875rem;
|
||||||
|
padding-block: 0.75rem 0.5rem;
|
||||||
|
|
||||||
|
.flatpickr-day {
|
||||||
|
block-size: 2.25rem;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
line-height: 2.25rem;
|
||||||
|
margin-block-start: 0 !important;
|
||||||
|
max-inline-size: 2.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-day {
|
||||||
|
color: $body-color;
|
||||||
|
|
||||||
|
&.today {
|
||||||
|
&:not(.selected) {
|
||||||
|
border: none !important;
|
||||||
|
background: rgba(var(--v-theme-primary), 0.24);
|
||||||
|
color: rgb(var(--v-theme-primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: none !important;
|
||||||
|
background: rgba(var(--v-theme-primary), 0.24);
|
||||||
|
color: rgb(var(--v-theme-primary));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected,
|
||||||
|
&.selected:hover {
|
||||||
|
border-color: rgb(var(--v-theme-primary));
|
||||||
|
background: rgb(var(--v-theme-primary));
|
||||||
|
color: rgb(var(--v-theme-on-primary));
|
||||||
|
|
||||||
|
@include templateMixins.custom-elevation(var(--v-theme-primary), "sm");
|
||||||
|
}
|
||||||
|
|
||||||
|
&.inRange,
|
||||||
|
&.inRange:hover {
|
||||||
|
border: none;
|
||||||
|
background: rgba(var(--v-theme-primary), var(--v-activated-opacity)) !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
color: rgb(var(--v-theme-primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.startRange {
|
||||||
|
@include templateMixins.custom-elevation(var(--v-theme-primary), "sm");
|
||||||
|
}
|
||||||
|
|
||||||
|
&.endRange {
|
||||||
|
@include templateMixins.custom-elevation(var(--v-theme-primary), "sm");
|
||||||
|
}
|
||||||
|
|
||||||
|
&.startRange,
|
||||||
|
&.endRange,
|
||||||
|
&.startRange:hover,
|
||||||
|
&.endRange:hover {
|
||||||
|
border-color: rgb(var(--v-theme-primary));
|
||||||
|
background: rgb(var(--v-theme-primary));
|
||||||
|
color: rgb(var(--v-theme-on-primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected.startRange + .endRange:not(:nth-child(7n + 1)),
|
||||||
|
&.startRange.startRange + .endRange:not(:nth-child(7n + 1)),
|
||||||
|
&.endRange.startRange + .endRange:not(:nth-child(7n + 1)) {
|
||||||
|
box-shadow: -10px 0 0 rgb(var(--v-theme-primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.flatpickr-disabled,
|
||||||
|
&.prevMonthDay:not(.startRange,.inRange),
|
||||||
|
&.nextMonthDay:not(.endRange,.inRange) {
|
||||||
|
opacity: var(--v-disabled-opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: transparent;
|
||||||
|
background: rgba(var(--v-theme-on-surface), 0.06);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-weekday {
|
||||||
|
color: $heading-color;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 400;
|
||||||
|
inline-size: 2.25rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-days {
|
||||||
|
inline-size: 16.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after,
|
||||||
|
&::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-months {
|
||||||
|
.flatpickr-prev-month,
|
||||||
|
.flatpickr-next-month {
|
||||||
|
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
|
||||||
|
fill: $body-color;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover i,
|
||||||
|
&:hover svg {
|
||||||
|
fill: $body-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-current-month span.cur-month {
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
// Open calendar above overlay
|
||||||
|
z-index: 2401;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hasTime.open {
|
||||||
|
.flatpickr-innerContainer + .flatpickr-time {
|
||||||
|
block-size: auto;
|
||||||
|
border-block-start: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-time {
|
||||||
|
border-block-start: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-hour,
|
||||||
|
.flatpickr-minute,
|
||||||
|
.flatpickr-am-pm {
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-theme--dark .flatpickr-calendar {
|
||||||
|
box-shadow: 0 3px 14px 0 rgb(15 20 34 / 38%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time picker hover & focus bg color
|
||||||
|
.flatpickr-time input:hover,
|
||||||
|
.flatpickr-time .flatpickr-am-pm:hover,
|
||||||
|
.flatpickr-time input:focus,
|
||||||
|
.flatpickr-time .flatpickr-am-pm:focus {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time picker
|
||||||
|
.flatpickr-time {
|
||||||
|
.flatpickr-am-pm,
|
||||||
|
.flatpickr-time-separator,
|
||||||
|
input {
|
||||||
|
color: $body-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.numInputWrapper {
|
||||||
|
span {
|
||||||
|
&.arrowUp {
|
||||||
|
&::after {
|
||||||
|
border-block-end-color: rgb(var(--v-border-color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.arrowDown {
|
||||||
|
&::after {
|
||||||
|
border-block-start-color: rgb(var(--v-border-color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Added bg color for flatpickr input only as it has default readonly attribute
|
||||||
|
.flatpickr-input[readonly],
|
||||||
|
.flatpickr-input ~ .form-control[readonly],
|
||||||
|
.flatpickr-human-friendly[readonly] {
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// week sections
|
||||||
|
.flatpickr-weekdays {
|
||||||
|
margin-block: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Month and year section
|
||||||
|
.flatpickr-current-month {
|
||||||
|
.flatpickr-monthDropdown-months {
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-monthDropdown-months,
|
||||||
|
.numInputWrapper {
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: $heading-color;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.375rem;
|
||||||
|
transition: all 0.15s ease-out;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-monthDropdown-month {
|
||||||
|
background-color: rgb(var(--v-theme-surface));
|
||||||
|
}
|
||||||
|
|
||||||
|
.numInput.cur-year {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-day.flatpickr-disabled,
|
||||||
|
.flatpickr-day.flatpickr-disabled:hover {
|
||||||
|
color: $body-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-months {
|
||||||
|
padding-block: 0.75rem;
|
||||||
|
padding-inline: 1rem;
|
||||||
|
|
||||||
|
.flatpickr-prev-month,
|
||||||
|
.flatpickr-next-month {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 5rem;
|
||||||
|
background: rgba(var(--v-theme-on-surface), var(--v-selected-opacity));
|
||||||
|
block-size: 1.875rem;
|
||||||
|
inline-size: 1.875rem;
|
||||||
|
inset-block-start: 15px !important;
|
||||||
|
|
||||||
|
&.flatpickr-disabled {
|
||||||
|
display: inline;
|
||||||
|
opacity: var(--v-disabled-opacity);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-next-month {
|
||||||
|
inset-inline-end: 1.05rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-prev-month {
|
||||||
|
/* stylelint-disable-next-line liberty/use-logical-spec */
|
||||||
|
right: 3.65rem;
|
||||||
|
left: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-month {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
block-size: 2.125rem;
|
||||||
|
|
||||||
|
.flatpickr-current-month {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0;
|
||||||
|
block-size: 1.75rem;
|
||||||
|
inset-inline-start: 0;
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
50
src/@core/components/app-form-elements/AppSelect.vue
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<script setup>
|
||||||
|
defineOptions({
|
||||||
|
name: 'AppSelect',
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const elementId = computed(() => {
|
||||||
|
const attrs = useAttrs()
|
||||||
|
const _elementIdToken = attrs.id || attrs.label
|
||||||
|
|
||||||
|
return _elementIdToken ? `app-select-${ _elementIdToken }-${ Math.random().toString(36).slice(2, 7) }` : undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const label = computed(() => useAttrs().label)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="app-select flex-grow-1"
|
||||||
|
:class="$attrs.class"
|
||||||
|
>
|
||||||
|
<VLabel
|
||||||
|
v-if="label"
|
||||||
|
:for="elementId"
|
||||||
|
class="mb-1 text-body-2"
|
||||||
|
style="line-height: 15px;"
|
||||||
|
:text="label"
|
||||||
|
/>
|
||||||
|
<VSelect
|
||||||
|
v-bind="{
|
||||||
|
...$attrs,
|
||||||
|
class: null,
|
||||||
|
label: undefined,
|
||||||
|
variant: 'outlined',
|
||||||
|
id: elementId,
|
||||||
|
menuProps: { contentClass: ['app-inner-list', 'app-select__content', 'v-select__content', $attrs.multiple !== undefined ? 'v-list-select-multiple' : ''] },
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-for="(_, name) in $slots"
|
||||||
|
#[name]="slotProps"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
:name="name"
|
||||||
|
v-bind="slotProps || {}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VSelect>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
49
src/@core/components/app-form-elements/AppTextField.vue
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<script setup>
|
||||||
|
defineOptions({
|
||||||
|
name: 'AppTextField',
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const elementId = computed(() => {
|
||||||
|
const attrs = useAttrs()
|
||||||
|
const _elementIdToken = attrs.id || attrs.label
|
||||||
|
|
||||||
|
return _elementIdToken ? `app-text-field-${ _elementIdToken }-${ Math.random().toString(36).slice(2, 7) }` : undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const label = computed(() => useAttrs().label)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="app-text-field flex-grow-1"
|
||||||
|
:class="$attrs.class"
|
||||||
|
>
|
||||||
|
<VLabel
|
||||||
|
v-if="label"
|
||||||
|
:for="elementId"
|
||||||
|
class="mb-1 text-body-2 text-wrap"
|
||||||
|
style="line-height: 15px;"
|
||||||
|
:text="label"
|
||||||
|
/>
|
||||||
|
<VTextField
|
||||||
|
v-bind="{
|
||||||
|
...$attrs,
|
||||||
|
class: null,
|
||||||
|
label: undefined,
|
||||||
|
variant: 'outlined',
|
||||||
|
id: elementId,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-for="(_, name) in $slots"
|
||||||
|
#[name]="slotProps"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
:name="name"
|
||||||
|
v-bind="slotProps || {}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VTextField>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
50
src/@core/components/app-form-elements/AppTextarea.vue
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<script setup>
|
||||||
|
defineOptions({
|
||||||
|
name: 'AppTextarea',
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// const { class: _class, label, variant: _, ...restAttrs } = useAttrs()
|
||||||
|
const elementId = computed(() => {
|
||||||
|
const attrs = useAttrs()
|
||||||
|
const _elementIdToken = attrs.id || attrs.label
|
||||||
|
|
||||||
|
return _elementIdToken ? `app-textarea-${ _elementIdToken }-${ Math.random().toString(36).slice(2, 7) }` : undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const label = computed(() => useAttrs().label)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="app-textarea flex-grow-1"
|
||||||
|
:class="$attrs.class"
|
||||||
|
>
|
||||||
|
<VLabel
|
||||||
|
v-if="label"
|
||||||
|
:for="elementId"
|
||||||
|
class="mb-1 text-body-2"
|
||||||
|
:text="label"
|
||||||
|
/>
|
||||||
|
<VTextarea
|
||||||
|
v-bind="{
|
||||||
|
...$attrs,
|
||||||
|
class: null,
|
||||||
|
label: undefined,
|
||||||
|
variant: 'outlined',
|
||||||
|
id: elementId,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-for="(_, name) in $slots"
|
||||||
|
#[name]="slotProps"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
:name="name"
|
||||||
|
v-bind="slotProps || {}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VTextarea>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
83
src/@core/components/app-form-elements/CustomCheckboxes.vue
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<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"
|
||||||
|
class="custom-input-wrapper"
|
||||||
|
>
|
||||||
|
<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-disabled text-body-2"
|
||||||
|
>{{ item.subtitle }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm 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.5rem;
|
||||||
|
|
||||||
|
.v-checkbox {
|
||||||
|
margin-block-start: -0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cr-title {
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.375rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
<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"
|
||||||
|
class="custom-input-wrapper"
|
||||||
|
>
|
||||||
|
<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
|
||||||
|
v-bind="item.icon"
|
||||||
|
class="text-high-emphasis"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h6 class="cr-title text-base">
|
||||||
|
{{ item.title }}
|
||||||
|
</h6>
|
||||||
|
<p class="text-sm 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;
|
||||||
|
line-height: 1.375rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.custom-checkbox-icon {
|
||||||
|
.v-checkbox {
|
||||||
|
margin-block-end: -0.375rem;
|
||||||
|
|
||||||
|
.v-selection-control__wrapper {
|
||||||
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
<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"
|
||||||
|
class="custom-input-wrapper"
|
||||||
|
>
|
||||||
|
<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
.v-checkbox {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
83
src/@core/components/app-form-elements/CustomRadios.vue
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<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"
|
||||||
|
class="custom-input-wrapper"
|
||||||
|
@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-body-2"
|
||||||
|
>{{ item.subtitle }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-body-2 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;
|
||||||
|
line-height: 1.375rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
<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"
|
||||||
|
class="custom-input-wrapper"
|
||||||
|
@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
|
||||||
|
v-bind="item.icon"
|
||||||
|
class="text-high-emphasis"
|
||||||
|
/>
|
||||||
|
<h6 class="text-h6">
|
||||||
|
{{ item.title }}
|
||||||
|
</h6>
|
||||||
|
|
||||||
|
<p class="text-body-2 mb-0">
|
||||||
|
{{ 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.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.custom-radio-icon {
|
||||||
|
.v-radio {
|
||||||
|
margin-block-end: -0.25rem;
|
||||||
|
|
||||||
|
.v-selection-control__wrapper {
|
||||||
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
101
src/@core/components/app-form-elements/CustomRadiosWithImage.vue
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<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"
|
||||||
|
class="custom-input-wrapper"
|
||||||
|
@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>
|
||||||
183
src/@core/components/cards/AppCardActions.vue
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<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="tabler-chevron-up"
|
||||||
|
: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="tabler-refresh"
|
||||||
|
/>
|
||||||
|
</IconBtn>
|
||||||
|
|
||||||
|
<!-- 👉 Close button -->
|
||||||
|
<IconBtn
|
||||||
|
v-if="(!(actionRefresh || actionCollapsed) || actionRemove) && !noActions"
|
||||||
|
@click="triggeredRemove"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
size="20"
|
||||||
|
icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
159
src/@core/components/cards/AppCardCode.vue
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<script setup>
|
||||||
|
import { getHighlighter } from 'shikiji'
|
||||||
|
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 getHighlighter({
|
||||||
|
themes: [
|
||||||
|
'dracula',
|
||||||
|
'dracula-soft',
|
||||||
|
],
|
||||||
|
langs: ['vue'],
|
||||||
|
})
|
||||||
|
|
||||||
|
const codeSnippet = highlighter.codeToHtml(props.code[preferredCodeLanguage.value], {
|
||||||
|
lang: 'vue',
|
||||||
|
theme: 'dracula',
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- eslint-disable regex/invalid -->
|
||||||
|
<VCard class="app-card-code">
|
||||||
|
<VCardItem>
|
||||||
|
<VCardTitle>{{ props.title }}</VCardTitle>
|
||||||
|
<template #append>
|
||||||
|
<IconBtn
|
||||||
|
size="small"
|
||||||
|
:color="isCodeShown ? 'primary' : 'default'"
|
||||||
|
:class="isCodeShown ? '' : 'text-disabled'"
|
||||||
|
@click="isCodeShown = !isCodeShown"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
size="20"
|
||||||
|
icon="tabler-code"
|
||||||
|
/>
|
||||||
|
</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"
|
||||||
|
icon
|
||||||
|
:variant="preferredCodeLanguage === 'ts' ? 'tonal' : 'text'"
|
||||||
|
:color="preferredCodeLanguage === 'ts' ? 'primary' : ''"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
icon="mdi-language-typescript"
|
||||||
|
:color="preferredCodeLanguage === 'ts' ? 'primary' : 'secondary'"
|
||||||
|
/>
|
||||||
|
</VBtn>
|
||||||
|
|
||||||
|
<VBtn
|
||||||
|
value="js"
|
||||||
|
icon
|
||||||
|
:variant="preferredCodeLanguage === 'js' ? 'tonal' : 'text'"
|
||||||
|
:color="preferredCodeLanguage === 'js' ? 'primary' : ''"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
icon="mdi-language-javascript"
|
||||||
|
:color="preferredCodeLanguage === 'js' ? 'primary' : 'secondary'"
|
||||||
|
/>
|
||||||
|
</VBtn>
|
||||||
|
</VBtnToggle>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="position-relative">
|
||||||
|
<PerfectScrollbar
|
||||||
|
style="border-radius: 6px;max-block-size: 500px;"
|
||||||
|
:options="{ wheelPropagation: false, suppressScrollX: false }"
|
||||||
|
>
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
|
<span v-html="codeSnippet" />
|
||||||
|
</PerfectScrollbar>
|
||||||
|
<IconBtn
|
||||||
|
class="position-absolute app-card-code-copy-icon"
|
||||||
|
color="white"
|
||||||
|
@click="() => { copy() }"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
:icon="copied ? 'tabler-check' : 'tabler-copy'"
|
||||||
|
size="20"
|
||||||
|
/>
|
||||||
|
</IconBtn>
|
||||||
|
</div>
|
||||||
|
</VCardText>
|
||||||
|
</div>
|
||||||
|
</VExpandTransition>
|
||||||
|
</VCard>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use "@styles/variables/vuetify.scss";
|
||||||
|
|
||||||
|
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>
|
||||||
50
src/@core/components/cards/CardStatisticsHorizontal.vue
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VCard>
|
||||||
|
<VCardText class="d-flex align-center justify-space-between">
|
||||||
|
<div>
|
||||||
|
<div class="d-flex align-center flex-wrap">
|
||||||
|
<h5 class="text-h5">
|
||||||
|
{{ props.stats }}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="text-subtitle-1">
|
||||||
|
{{ props.title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VAvatar
|
||||||
|
:color="props.color"
|
||||||
|
:size="42"
|
||||||
|
rounded
|
||||||
|
variant="tonal"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
:icon="props.icon"
|
||||||
|
size="26"
|
||||||
|
/>
|
||||||
|
</VAvatar>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</template>
|
||||||
66
src/@core/components/cards/CardStatisticsVertical.vue
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<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,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
series: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
chartOptions: {
|
||||||
|
type: null,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VCard>
|
||||||
|
<VCardText class="d-flex flex-column pb-0">
|
||||||
|
<VAvatar
|
||||||
|
v-if="props.icon"
|
||||||
|
size="42"
|
||||||
|
variant="tonal"
|
||||||
|
:color="props.color"
|
||||||
|
rounded
|
||||||
|
class="mb-2"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
:icon="props.icon"
|
||||||
|
size="26"
|
||||||
|
/>
|
||||||
|
</VAvatar>
|
||||||
|
|
||||||
|
<h5 class="text-h5">
|
||||||
|
{{ props.stats }}
|
||||||
|
</h5>
|
||||||
|
<div class="text-sm">
|
||||||
|
{{ props.title }}
|
||||||
|
</div>
|
||||||
|
</VCardText>
|
||||||
|
|
||||||
|
<VueApexCharts
|
||||||
|
:series="props.series"
|
||||||
|
:options="props.chartOptions"
|
||||||
|
:height="props.height"
|
||||||
|
/>
|
||||||
|
</VCard>
|
||||||
|
</template>
|
||||||
11
src/@core/composable/createUrl.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
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)}` : ''}`
|
||||||
|
})
|
||||||
28
src/@core/composable/useCookie.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
23
src/@core/composable/useGenerateImageVariant.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { useTheme } from 'vuetify'
|
||||||
|
import { useConfigStore } from '@core/stores/config'
|
||||||
|
|
||||||
|
// composable function to return the image variant as per the current theme and skin
|
||||||
|
export const useGenerateImageVariant = (imgLight, imgDark, imgLightBordered, imgDarkBordered, bordered = false) => {
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
const { global } = useTheme()
|
||||||
|
|
||||||
|
return computed(() => {
|
||||||
|
if (global.name.value === 'light') {
|
||||||
|
if (configStore.skin === 'bordered' && bordered)
|
||||||
|
return imgLightBordered
|
||||||
|
else
|
||||||
|
return imgLight
|
||||||
|
}
|
||||||
|
if (global.name.value === 'dark') {
|
||||||
|
if (configStore.skin === 'bordered' && bordered)
|
||||||
|
return imgDarkBordered
|
||||||
|
else
|
||||||
|
return imgDark
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
23
src/@core/composable/useResponsiveSidebar.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { useDisplay } from 'vuetify'
|
||||||
|
|
||||||
|
export const useResponsiveLeftSidebar = (mobileBreakpoint = undefined) => {
|
||||||
|
const { mdAndDown, name: currentBreakpoint } = useDisplay()
|
||||||
|
const _mobileBreakpoint = mobileBreakpoint || mdAndDown
|
||||||
|
const isLeftSidebarOpen = ref(true)
|
||||||
|
|
||||||
|
const setInitialValue = () => {
|
||||||
|
isLeftSidebarOpen.value = !_mobileBreakpoint.value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Set the initial value of sidebar
|
||||||
|
setInitialValue()
|
||||||
|
watch(currentBreakpoint, () => {
|
||||||
|
// Reset left sidebar
|
||||||
|
isLeftSidebarOpen.value = !_mobileBreakpoint.value
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLeftSidebarOpen,
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/@core/composable/useSkins.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { VThemeProvider } from 'vuetify/components/VThemeProvider'
|
||||||
|
import { useConfigStore } from '@core/stores/config'
|
||||||
|
import { AppContentLayoutNav } from '@layouts/enums'
|
||||||
|
|
||||||
|
// TODO: Use `VThemeProvider` from dist instead of lib (Using this component from dist causes navbar to loose sticky positioning)
|
||||||
|
export const useSkins = () => {
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
|
const layoutAttrs = computed(() => ({
|
||||||
|
verticalNavAttrs: {
|
||||||
|
wrapper: h(VThemeProvider, { tag: 'aside' }),
|
||||||
|
wrapperProps: {
|
||||||
|
withBackground: true,
|
||||||
|
theme: (configStore.isVerticalNavSemiDark && configStore.appContentLayoutNav === AppContentLayoutNav.Vertical)
|
||||||
|
? 'dark'
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const injectSkinClasses = () => {
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
const bodyClasses = document.body.classList
|
||||||
|
const genSkinClass = _skin => `skin--${_skin}`
|
||||||
|
|
||||||
|
watch(() => configStore.skin, (val, oldVal) => {
|
||||||
|
bodyClasses.remove(genSkinClass(oldVal))
|
||||||
|
bodyClasses.add(genSkinClass(val))
|
||||||
|
}, { immediate: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
injectSkinClasses,
|
||||||
|
layoutAttrs,
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/@core/enums.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export const Skins = {
|
||||||
|
Default: 'default',
|
||||||
|
Bordered: 'bordered',
|
||||||
|
}
|
||||||
|
export const Theme = {
|
||||||
|
Light: 'light',
|
||||||
|
Dark: 'dark',
|
||||||
|
System: 'system',
|
||||||
|
}
|
||||||
|
export const Layout = {
|
||||||
|
Vertical: 'vertical',
|
||||||
|
Horizontal: 'horizontal',
|
||||||
|
Collapsed: 'collapsed',
|
||||||
|
}
|
||||||
|
export const Direction = {
|
||||||
|
Ltr: 'ltr',
|
||||||
|
Rtl: 'rtl',
|
||||||
|
}
|
||||||
40
src/@core/index.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
export const defineThemeConfig = userConfig => {
|
||||||
|
return {
|
||||||
|
themeConfig: userConfig,
|
||||||
|
layoutConfig: {
|
||||||
|
app: {
|
||||||
|
title: userConfig.app.title,
|
||||||
|
logo: userConfig.app.logo,
|
||||||
|
contentWidth: userConfig.app.contentWidth,
|
||||||
|
contentLayoutNav: userConfig.app.contentLayoutNav,
|
||||||
|
overlayNavFromBreakpoint: userConfig.app.overlayNavFromBreakpoint,
|
||||||
|
i18n: {
|
||||||
|
enable: userConfig.app.i18n.enable,
|
||||||
|
},
|
||||||
|
iconRenderer: userConfig.app.iconRenderer,
|
||||||
|
},
|
||||||
|
navbar: {
|
||||||
|
type: userConfig.navbar.type,
|
||||||
|
navbarBlur: userConfig.navbar.navbarBlur,
|
||||||
|
},
|
||||||
|
footer: { type: userConfig.footer.type },
|
||||||
|
verticalNav: {
|
||||||
|
isVerticalNavCollapsed: userConfig.verticalNav.isVerticalNavCollapsed,
|
||||||
|
defaultNavItemIconProps: userConfig.verticalNav.defaultNavItemIconProps,
|
||||||
|
},
|
||||||
|
horizontalNav: {
|
||||||
|
type: userConfig.horizontalNav.type,
|
||||||
|
transition: userConfig.horizontalNav.transition,
|
||||||
|
popoverOffset: userConfig.horizontalNav.popoverOffset,
|
||||||
|
},
|
||||||
|
icons: {
|
||||||
|
chevronDown: userConfig.icons.chevronDown,
|
||||||
|
chevronRight: userConfig.icons.chevronRight,
|
||||||
|
close: userConfig.icons.close,
|
||||||
|
verticalNavPinned: userConfig.icons.verticalNavPinned,
|
||||||
|
verticalNavUnPinned: userConfig.icons.verticalNavUnPinned,
|
||||||
|
sectionTitlePlaceholder: userConfig.icons.sectionTitlePlaceholder,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/@core/initCore.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
|
import { useTheme } from 'vuetify'
|
||||||
|
import { useConfigStore } from '@core/stores/config'
|
||||||
|
import { cookieRef, namespaceConfig } from '@layouts/stores/config'
|
||||||
|
import { themeConfig } from '@themeConfig'
|
||||||
|
|
||||||
|
const _syncAppRtl = () => {
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
const storedLang = cookieRef('language', null)
|
||||||
|
const { locale } = useI18n({ useScope: 'global' })
|
||||||
|
|
||||||
|
// TODO: Handle case where i18n can't read persisted value
|
||||||
|
if (locale.value !== storedLang.value && storedLang.value)
|
||||||
|
locale.value = storedLang.value
|
||||||
|
|
||||||
|
// watch and change lang attribute of html on language change
|
||||||
|
watch(locale, val => {
|
||||||
|
// Update lang attribute of html tag
|
||||||
|
if (typeof document !== 'undefined')
|
||||||
|
document.documentElement.setAttribute('lang', val)
|
||||||
|
|
||||||
|
// Store selected language in cookie
|
||||||
|
storedLang.value = val
|
||||||
|
|
||||||
|
// set isAppRtl value based on selected language
|
||||||
|
if (themeConfig.app.i18n.langConfig && themeConfig.app.i18n.langConfig.length) {
|
||||||
|
themeConfig.app.i18n.langConfig.forEach(lang => {
|
||||||
|
if (lang.i18nLang === storedLang.value)
|
||||||
|
configStore.isAppRTL = lang.isRTL
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const _handleSkinChanges = () => {
|
||||||
|
const { themes } = useTheme()
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
|
|
||||||
|
// Create skin default color so that we can revert back to original (default skin) color when switch to default skin from bordered skin
|
||||||
|
Object.values(themes.value).forEach(t => {
|
||||||
|
t.colors['skin-default-background'] = t.colors.background
|
||||||
|
t.colors['skin-default-surface'] = t.colors.surface
|
||||||
|
})
|
||||||
|
watch(() => configStore.skin, val => {
|
||||||
|
Object.values(themes.value).forEach(t => {
|
||||||
|
t.colors.background = t.colors[`skin-${val}-background`]
|
||||||
|
t.colors.surface = t.colors[`skin-${val}-surface`]
|
||||||
|
})
|
||||||
|
}, { immediate: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
ℹ️ Set current theme's surface color in localStorage
|
||||||
|
|
||||||
|
Why? Because when initial loader is shown (before vue is ready) we need to what's the current theme's surface color.
|
||||||
|
We will use color stored in localStorage to set the initial loader's background color.
|
||||||
|
|
||||||
|
With this we will be able to show correct background color for the initial loader even before vue identify the current theme.
|
||||||
|
*/
|
||||||
|
const _syncInitialLoaderTheme = () => {
|
||||||
|
const vuetifyTheme = useTheme()
|
||||||
|
|
||||||
|
watch(() => useConfigStore().theme, () => {
|
||||||
|
// ℹ️ We are not using theme.current.colors.surface because watcher is independent and when this watcher is ran `theme` computed is not updated
|
||||||
|
useStorage(namespaceConfig('initial-loader-bg'), null).value = vuetifyTheme.current.value.colors.surface
|
||||||
|
useStorage(namespaceConfig('initial-loader-color'), null).value = vuetifyTheme.current.value.colors.primary
|
||||||
|
}, { immediate: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const initCore = () => {
|
||||||
|
_syncInitialLoaderTheme()
|
||||||
|
_handleSkinChanges()
|
||||||
|
|
||||||
|
// ℹ️ We don't want to trigger i18n in SK
|
||||||
|
if (themeConfig.app.i18n.enable)
|
||||||
|
_syncAppRtl()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default initCore
|
||||||
689
src/@core/libs/apex-chart/apexCharConfig.js
Normal file
@ -0,0 +1,689 @@
|
|||||||
|
import { hexToRgb } from '@layouts/utils'
|
||||||
|
|
||||||
|
|
||||||
|
// 👉 Colors variables
|
||||||
|
const colorVariables = themeColors => {
|
||||||
|
const themeSecondaryTextColor = `rgba(${hexToRgb(themeColors.colors['on-surface'])},${themeColors.variables['medium-emphasis-opacity']})`
|
||||||
|
const themeDisabledTextColor = `rgba(${hexToRgb(themeColors.colors['on-surface'])},${themeColors.variables['disabled-opacity']})`
|
||||||
|
const themeBorderColor = `rgba(${hexToRgb(String(themeColors.variables['border-color']))},${themeColors.variables['border-opacity']})`
|
||||||
|
const themePrimaryTextColor = `rgba(${hexToRgb(themeColors.colors['on-surface'])},${themeColors.variables['high-emphasis-opacity']})`
|
||||||
|
|
||||||
|
return { themeSecondaryTextColor, themeDisabledTextColor, themeBorderColor, themePrimaryTextColor }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getScatterChartConfig = themeColors => {
|
||||||
|
const scatterColors = {
|
||||||
|
series1: '#ff9f43',
|
||||||
|
series2: '#7367f0',
|
||||||
|
series3: '#28c76f',
|
||||||
|
}
|
||||||
|
|
||||||
|
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
chart: {
|
||||||
|
parentHeightOffset: 0,
|
||||||
|
toolbar: { show: false },
|
||||||
|
zoom: {
|
||||||
|
type: 'xy',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'top',
|
||||||
|
horizontalAlign: 'left',
|
||||||
|
markers: { offsetX: -3 },
|
||||||
|
fontSize: '13px',
|
||||||
|
labels: { colors: themeSecondaryTextColor },
|
||||||
|
itemMargin: {
|
||||||
|
vertical: 3,
|
||||||
|
horizontal: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
colors: [scatterColors.series1, scatterColors.series2, scatterColors.series3],
|
||||||
|
grid: {
|
||||||
|
borderColor: themeBorderColor,
|
||||||
|
xaxis: {
|
||||||
|
lines: { show: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
style: { fontSize: '0.8125rem', colors: themeDisabledTextColor },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
tickAmount: 10,
|
||||||
|
axisBorder: { show: false },
|
||||||
|
axisTicks: { color: themeBorderColor },
|
||||||
|
crosshairs: {
|
||||||
|
stroke: { color: themeBorderColor },
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
style: { colors: themeDisabledTextColor },
|
||||||
|
formatter: val => Number.parseFloat(val).toFixed(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const getLineChartSimpleConfig = themeColors => {
|
||||||
|
const { themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
chart: {
|
||||||
|
parentHeightOffset: 0,
|
||||||
|
zoom: { enabled: false },
|
||||||
|
toolbar: { show: false },
|
||||||
|
},
|
||||||
|
colors: ['#ff9f43'],
|
||||||
|
stroke: { curve: 'straight' },
|
||||||
|
dataLabels: { enabled: false },
|
||||||
|
markers: {
|
||||||
|
strokeWidth: 7,
|
||||||
|
strokeOpacity: 1,
|
||||||
|
colors: ['#ff9f43'],
|
||||||
|
strokeColors: ['#fff'],
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
padding: { top: -10 },
|
||||||
|
borderColor: themeBorderColor,
|
||||||
|
xaxis: {
|
||||||
|
lines: { show: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
custom(data) {
|
||||||
|
return `<div class='bar-chart pa-2'>
|
||||||
|
<span>${data.series[data.seriesIndex][data.dataPointIndex]}%</span>
|
||||||
|
</div>`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
style: { colors: themeDisabledTextColor, fontSize: '0.8125rem' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
axisBorder: { show: false },
|
||||||
|
axisTicks: { color: themeBorderColor },
|
||||||
|
crosshairs: {
|
||||||
|
stroke: { color: themeBorderColor },
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
style: { colors: themeDisabledTextColor, fontSize: '0.8125rem' },
|
||||||
|
},
|
||||||
|
categories: [
|
||||||
|
'7/12',
|
||||||
|
'8/12',
|
||||||
|
'9/12',
|
||||||
|
'10/12',
|
||||||
|
'11/12',
|
||||||
|
'12/12',
|
||||||
|
'13/12',
|
||||||
|
'14/12',
|
||||||
|
'15/12',
|
||||||
|
'16/12',
|
||||||
|
'17/12',
|
||||||
|
'18/12',
|
||||||
|
'19/12',
|
||||||
|
'20/12',
|
||||||
|
'21/12',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const getBarChartConfig = themeColors => {
|
||||||
|
const { themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
chart: {
|
||||||
|
parentHeightOffset: 0,
|
||||||
|
toolbar: { show: false },
|
||||||
|
},
|
||||||
|
colors: ['#00cfe8'],
|
||||||
|
dataLabels: { enabled: false },
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
borderRadius: 8,
|
||||||
|
barHeight: '30%',
|
||||||
|
horizontal: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
borderColor: themeBorderColor,
|
||||||
|
xaxis: {
|
||||||
|
lines: { show: false },
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
top: -10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
style: { colors: themeDisabledTextColor, fontSize: '0.8125rem' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
axisBorder: { show: false },
|
||||||
|
axisTicks: { color: themeBorderColor },
|
||||||
|
categories: ['MON, 11', 'THU, 14', 'FRI, 15', 'MON, 18', 'WED, 20', 'FRI, 21', 'MON, 23'],
|
||||||
|
labels: {
|
||||||
|
style: { colors: themeDisabledTextColor, fontSize: '0.8125rem' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const getCandlestickChartConfig = themeColors => {
|
||||||
|
const candlestickColors = {
|
||||||
|
series1: '#28c76f',
|
||||||
|
series2: '#ea5455',
|
||||||
|
}
|
||||||
|
|
||||||
|
const { themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
chart: {
|
||||||
|
parentHeightOffset: 0,
|
||||||
|
toolbar: { show: false },
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: { columnWidth: '40%' },
|
||||||
|
candlestick: {
|
||||||
|
colors: {
|
||||||
|
upward: candlestickColors.series1,
|
||||||
|
downward: candlestickColors.series2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
padding: { top: -10 },
|
||||||
|
borderColor: themeBorderColor,
|
||||||
|
xaxis: {
|
||||||
|
lines: { show: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
tooltip: { enabled: true },
|
||||||
|
crosshairs: {
|
||||||
|
stroke: { color: themeBorderColor },
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
style: { colors: themeDisabledTextColor, fontSize: '0.8125rem' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
axisBorder: { show: false },
|
||||||
|
axisTicks: { color: themeBorderColor },
|
||||||
|
crosshairs: {
|
||||||
|
stroke: { color: themeBorderColor },
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
style: { colors: themeDisabledTextColor, fontSize: '0.8125rem' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const getRadialBarChartConfig = themeColors => {
|
||||||
|
const radialBarColors = {
|
||||||
|
series1: '#fdd835',
|
||||||
|
series2: '#32baff',
|
||||||
|
series3: '#00d4bd',
|
||||||
|
series4: '#7367f0',
|
||||||
|
series5: '#FFA1A1',
|
||||||
|
}
|
||||||
|
|
||||||
|
const { themeSecondaryTextColor, themePrimaryTextColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
stroke: { lineCap: 'round' },
|
||||||
|
labels: ['Comments', 'Replies', 'Shares'],
|
||||||
|
legend: {
|
||||||
|
show: true,
|
||||||
|
fontSize: '13px',
|
||||||
|
position: 'bottom',
|
||||||
|
labels: {
|
||||||
|
colors: themeSecondaryTextColor,
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
offsetX: -3,
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
vertical: 3,
|
||||||
|
horizontal: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
colors: [radialBarColors.series1, radialBarColors.series2, radialBarColors.series4],
|
||||||
|
plotOptions: {
|
||||||
|
radialBar: {
|
||||||
|
hollow: { size: '30%' },
|
||||||
|
track: {
|
||||||
|
margin: 15,
|
||||||
|
background: themeColors.variables['track-bg'],
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
name: {
|
||||||
|
fontSize: '2rem',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
fontSize: '0.9375rem',
|
||||||
|
color: themeSecondaryTextColor,
|
||||||
|
},
|
||||||
|
total: {
|
||||||
|
show: true,
|
||||||
|
fontWeight: 400,
|
||||||
|
label: 'Comments',
|
||||||
|
fontSize: '1.125rem',
|
||||||
|
color: themePrimaryTextColor,
|
||||||
|
formatter(w) {
|
||||||
|
const totalValue = w.globals.seriesTotals.reduce((a, b) => {
|
||||||
|
return a + b
|
||||||
|
}, 0) / w.globals.series.length
|
||||||
|
|
||||||
|
if (totalValue % 1 === 0)
|
||||||
|
return `${totalValue}%`
|
||||||
|
else
|
||||||
|
return `${totalValue.toFixed(2)}%`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
padding: {
|
||||||
|
top: -30,
|
||||||
|
bottom: -25,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const getDonutChartConfig = themeColors => {
|
||||||
|
const donutColors = {
|
||||||
|
series1: '#fdd835',
|
||||||
|
series2: '#00d4bd',
|
||||||
|
series3: '#826bf8',
|
||||||
|
series4: '#32baff',
|
||||||
|
series5: '#ffa1a1',
|
||||||
|
}
|
||||||
|
|
||||||
|
const { themeSecondaryTextColor, themePrimaryTextColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
stroke: { width: 0 },
|
||||||
|
labels: ['Operational', 'Networking', 'Hiring', 'R&D'],
|
||||||
|
colors: [donutColors.series1, donutColors.series5, donutColors.series3, donutColors.series2],
|
||||||
|
dataLabels: {
|
||||||
|
enabled: true,
|
||||||
|
formatter: val => `${Number.parseInt(val, 10)}%`,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'bottom',
|
||||||
|
markers: { offsetX: -3 },
|
||||||
|
fontSize: '13px',
|
||||||
|
labels: { colors: themeSecondaryTextColor },
|
||||||
|
itemMargin: {
|
||||||
|
vertical: 3,
|
||||||
|
horizontal: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
pie: {
|
||||||
|
donut: {
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
name: {
|
||||||
|
fontSize: '1.125rem',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
fontSize: '1.125rem',
|
||||||
|
color: themeSecondaryTextColor,
|
||||||
|
formatter: val => `${Number.parseInt(val, 10)}`,
|
||||||
|
},
|
||||||
|
total: {
|
||||||
|
show: true,
|
||||||
|
fontSize: '1.125rem',
|
||||||
|
label: 'Operational',
|
||||||
|
formatter: () => '31%',
|
||||||
|
color: themePrimaryTextColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responsive: [
|
||||||
|
{
|
||||||
|
breakpoint: 992,
|
||||||
|
options: {
|
||||||
|
chart: {
|
||||||
|
height: 380,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'bottom',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: 576,
|
||||||
|
options: {
|
||||||
|
chart: {
|
||||||
|
height: 320,
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
pie: {
|
||||||
|
donut: {
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
name: {
|
||||||
|
fontSize: '0.9375rem',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
fontSize: '0.9375rem',
|
||||||
|
},
|
||||||
|
total: {
|
||||||
|
fontSize: '0.9375rem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const getAreaChartSplineConfig = themeColors => {
|
||||||
|
const areaColors = {
|
||||||
|
series3: '#e0cffe',
|
||||||
|
series2: '#b992fe',
|
||||||
|
series1: '#ab7efd',
|
||||||
|
}
|
||||||
|
|
||||||
|
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
chart: {
|
||||||
|
parentHeightOffset: 0,
|
||||||
|
toolbar: { show: false },
|
||||||
|
},
|
||||||
|
tooltip: { shared: false },
|
||||||
|
dataLabels: { enabled: false },
|
||||||
|
stroke: {
|
||||||
|
show: false,
|
||||||
|
curve: 'straight',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'top',
|
||||||
|
horizontalAlign: 'left',
|
||||||
|
fontSize: '13px',
|
||||||
|
labels: { colors: themeSecondaryTextColor },
|
||||||
|
markers: {
|
||||||
|
offsetY: 1,
|
||||||
|
offsetX: -3,
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
vertical: 3,
|
||||||
|
horizontal: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
colors: [areaColors.series3, areaColors.series2, areaColors.series1],
|
||||||
|
fill: {
|
||||||
|
opacity: 1,
|
||||||
|
type: 'solid',
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
show: true,
|
||||||
|
borderColor: themeBorderColor,
|
||||||
|
xaxis: {
|
||||||
|
lines: { show: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
style: { colors: themeDisabledTextColor, fontSize: '0.8125rem' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
axisBorder: { show: false },
|
||||||
|
axisTicks: { color: themeBorderColor },
|
||||||
|
crosshairs: {
|
||||||
|
stroke: { color: themeBorderColor },
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
style: { colors: themeDisabledTextColor, fontSize: '0.8125rem' },
|
||||||
|
},
|
||||||
|
categories: [
|
||||||
|
'7/12',
|
||||||
|
'8/12',
|
||||||
|
'9/12',
|
||||||
|
'10/12',
|
||||||
|
'11/12',
|
||||||
|
'12/12',
|
||||||
|
'13/12',
|
||||||
|
'14/12',
|
||||||
|
'15/12',
|
||||||
|
'16/12',
|
||||||
|
'17/12',
|
||||||
|
'18/12',
|
||||||
|
'19/12',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const getColumnChartConfig = themeColors => {
|
||||||
|
const columnColors = {
|
||||||
|
series1: '#826af9',
|
||||||
|
series2: '#d2b0ff',
|
||||||
|
bg: '#f8d3ff',
|
||||||
|
}
|
||||||
|
|
||||||
|
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
chart: {
|
||||||
|
offsetX: -10,
|
||||||
|
stacked: true,
|
||||||
|
parentHeightOffset: 0,
|
||||||
|
toolbar: { show: false },
|
||||||
|
},
|
||||||
|
fill: { opacity: 1 },
|
||||||
|
dataLabels: { enabled: false },
|
||||||
|
colors: [columnColors.series1, columnColors.series2],
|
||||||
|
legend: {
|
||||||
|
position: 'top',
|
||||||
|
horizontalAlign: 'left',
|
||||||
|
fontSize: '13px',
|
||||||
|
labels: { colors: themeSecondaryTextColor },
|
||||||
|
markers: {
|
||||||
|
offsetY: 1,
|
||||||
|
offsetX: -3,
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
vertical: 3,
|
||||||
|
horizontal: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
show: true,
|
||||||
|
colors: ['transparent'],
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
columnWidth: '15%',
|
||||||
|
colors: {
|
||||||
|
backgroundBarRadius: 10,
|
||||||
|
backgroundBarColors: [columnColors.bg, columnColors.bg, columnColors.bg, columnColors.bg, columnColors.bg],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
borderColor: themeBorderColor,
|
||||||
|
xaxis: {
|
||||||
|
lines: { show: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
style: { colors: themeDisabledTextColor, fontSize: '0.8125rem' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
axisBorder: { show: false },
|
||||||
|
axisTicks: { color: themeBorderColor },
|
||||||
|
categories: ['7/12', '8/12', '9/12', '10/12', '11/12', '12/12', '13/12', '14/12', '15/12'],
|
||||||
|
crosshairs: {
|
||||||
|
stroke: { color: themeBorderColor },
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
style: { colors: themeDisabledTextColor, fontSize: '0.8125rem' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responsive: [
|
||||||
|
{
|
||||||
|
breakpoint: 600,
|
||||||
|
options: {
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
columnWidth: '35%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const getHeatMapChartConfig = themeColors => {
|
||||||
|
const { themeSecondaryTextColor, themeDisabledTextColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
chart: {
|
||||||
|
parentHeightOffset: 0,
|
||||||
|
toolbar: { show: false },
|
||||||
|
},
|
||||||
|
dataLabels: { enabled: false },
|
||||||
|
stroke: {
|
||||||
|
colors: [themeColors.colors.surface],
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'bottom',
|
||||||
|
fontSize: '13px',
|
||||||
|
labels: {
|
||||||
|
colors: themeSecondaryTextColor,
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
offsetY: 0,
|
||||||
|
offsetX: -3,
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
vertical: 3,
|
||||||
|
horizontal: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
heatmap: {
|
||||||
|
enableShades: false,
|
||||||
|
colorScale: {
|
||||||
|
ranges: [
|
||||||
|
{ to: 10, from: 0, name: '0-10', color: '#b9b3f8' },
|
||||||
|
{ to: 20, from: 11, name: '10-20', color: '#aba4f6' },
|
||||||
|
{ to: 30, from: 21, name: '20-30', color: '#9d95f5' },
|
||||||
|
{ to: 40, from: 31, name: '30-40', color: '#8f85f3' },
|
||||||
|
{ to: 50, from: 41, name: '40-50', color: '#8176f2' },
|
||||||
|
{ to: 60, from: 51, name: '50-60', color: '#7367f0' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
padding: { top: -20 },
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: themeDisabledTextColor,
|
||||||
|
fontSize: '0.8125rem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
labels: { show: false },
|
||||||
|
axisTicks: { show: false },
|
||||||
|
axisBorder: { show: false },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const getRadarChartConfig = themeColors => {
|
||||||
|
const radarColors = {
|
||||||
|
series1: '#9b88fa',
|
||||||
|
series2: '#ffa1a1',
|
||||||
|
}
|
||||||
|
|
||||||
|
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
chart: {
|
||||||
|
parentHeightOffset: 0,
|
||||||
|
toolbar: { show: false },
|
||||||
|
dropShadow: {
|
||||||
|
top: 1,
|
||||||
|
blur: 8,
|
||||||
|
left: 1,
|
||||||
|
opacity: 0.2,
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
markers: { size: 0 },
|
||||||
|
fill: { opacity: [1, 0.8] },
|
||||||
|
colors: [radarColors.series1, radarColors.series2],
|
||||||
|
stroke: {
|
||||||
|
width: 0,
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
fontSize: '13px',
|
||||||
|
labels: {
|
||||||
|
colors: themeSecondaryTextColor,
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
offsetX: -3,
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
vertical: 3,
|
||||||
|
horizontal: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
radar: {
|
||||||
|
polygons: {
|
||||||
|
strokeColors: themeBorderColor,
|
||||||
|
connectorColors: themeBorderColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
show: false,
|
||||||
|
padding: {
|
||||||
|
top: -20,
|
||||||
|
bottom: -20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: { show: false },
|
||||||
|
xaxis: {
|
||||||
|
categories: ['Battery', 'Brand', 'Camera', 'Memory', 'Storage', 'Display', 'OS', 'Price'],
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
fontSize: '0.8125rem',
|
||||||
|
colors: [
|
||||||
|
themeDisabledTextColor,
|
||||||
|
themeDisabledTextColor,
|
||||||
|
themeDisabledTextColor,
|
||||||
|
themeDisabledTextColor,
|
||||||
|
themeDisabledTextColor,
|
||||||
|
themeDisabledTextColor,
|
||||||
|
themeDisabledTextColor,
|
||||||
|
themeDisabledTextColor,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
372
src/@core/libs/chartjs/chartjsConfig.js
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
import { hexToRgb } from '@layouts/utils'
|
||||||
|
|
||||||
|
|
||||||
|
// 👉 Colors variables
|
||||||
|
const colorVariables = themeColors => {
|
||||||
|
const themeSecondaryTextColor = `rgba(${hexToRgb(themeColors.colors['on-surface'])},${themeColors.variables['medium-emphasis-opacity']})`
|
||||||
|
const themeDisabledTextColor = `rgba(${hexToRgb(themeColors.colors['on-surface'])},${themeColors.variables['disabled-opacity']})`
|
||||||
|
const themeBorderColor = `rgba(${hexToRgb(String(themeColors.variables['border-color']))},${themeColors.variables['border-opacity']})`
|
||||||
|
|
||||||
|
return { labelColor: themeDisabledTextColor, borderColor: themeBorderColor, legendColor: themeSecondaryTextColor }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// SECTION config
|
||||||
|
// 👉 Latest Bar Chart Config
|
||||||
|
export const getLatestBarChartConfig = themeColors => {
|
||||||
|
const { borderColor, labelColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
animation: { duration: 500 },
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
borderColor,
|
||||||
|
drawBorder: false,
|
||||||
|
color: borderColor,
|
||||||
|
},
|
||||||
|
ticks: { color: labelColor },
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
min: 0,
|
||||||
|
max: 400,
|
||||||
|
grid: {
|
||||||
|
borderColor,
|
||||||
|
drawBorder: false,
|
||||||
|
color: borderColor,
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
stepSize: 100,
|
||||||
|
color: labelColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Horizontal Bar Chart Config
|
||||||
|
export const getHorizontalBarChartConfig = themeColors => {
|
||||||
|
const { borderColor, labelColor, legendColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
indexAxis: 'y',
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
animation: { duration: 500 },
|
||||||
|
elements: {
|
||||||
|
bar: {
|
||||||
|
borderRadius: {
|
||||||
|
topRight: 15,
|
||||||
|
bottomRight: 15,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
padding: { top: -4 },
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
min: 0,
|
||||||
|
grid: {
|
||||||
|
drawTicks: false,
|
||||||
|
drawBorder: false,
|
||||||
|
color: borderColor,
|
||||||
|
},
|
||||||
|
ticks: { color: labelColor },
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
grid: {
|
||||||
|
borderColor,
|
||||||
|
display: false,
|
||||||
|
drawBorder: false,
|
||||||
|
},
|
||||||
|
ticks: { color: labelColor },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
align: 'end',
|
||||||
|
position: 'top',
|
||||||
|
labels: { color: legendColor },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Line Chart Config
|
||||||
|
export const getLineChartConfig = themeColors => {
|
||||||
|
const { borderColor, labelColor, legendColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
ticks: { color: labelColor },
|
||||||
|
grid: {
|
||||||
|
borderColor,
|
||||||
|
drawBorder: false,
|
||||||
|
color: borderColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
min: 0,
|
||||||
|
max: 400,
|
||||||
|
ticks: {
|
||||||
|
stepSize: 100,
|
||||||
|
color: labelColor,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
borderColor,
|
||||||
|
drawBorder: false,
|
||||||
|
color: borderColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
align: 'end',
|
||||||
|
position: 'top',
|
||||||
|
labels: {
|
||||||
|
padding: 25,
|
||||||
|
boxWidth: 10,
|
||||||
|
color: legendColor,
|
||||||
|
usePointStyle: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Radar Chart Config
|
||||||
|
export const getRadarChartConfig = themeColors => {
|
||||||
|
const { borderColor, labelColor, legendColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
animation: { duration: 500 },
|
||||||
|
layout: {
|
||||||
|
padding: { top: -20 },
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
r: {
|
||||||
|
ticks: {
|
||||||
|
display: false,
|
||||||
|
maxTicksLimit: 1,
|
||||||
|
color: labelColor,
|
||||||
|
},
|
||||||
|
grid: { color: borderColor },
|
||||||
|
pointLabels: { color: labelColor },
|
||||||
|
angleLines: { color: borderColor },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'top',
|
||||||
|
labels: {
|
||||||
|
padding: 25,
|
||||||
|
color: legendColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Polar Chart Config
|
||||||
|
export const getPolarChartConfig = themeColors => {
|
||||||
|
const { legendColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
animation: { duration: 500 },
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
top: -5,
|
||||||
|
bottom: -45,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
r: {
|
||||||
|
grid: { display: false },
|
||||||
|
ticks: { display: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'right',
|
||||||
|
labels: {
|
||||||
|
padding: 25,
|
||||||
|
boxWidth: 9,
|
||||||
|
color: legendColor,
|
||||||
|
usePointStyle: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Bubble Chart Config
|
||||||
|
export const getBubbleChartConfig = themeColors => {
|
||||||
|
const { borderColor, labelColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
min: 0,
|
||||||
|
max: 140,
|
||||||
|
grid: {
|
||||||
|
borderColor,
|
||||||
|
drawBorder: false,
|
||||||
|
color: borderColor,
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
stepSize: 10,
|
||||||
|
color: labelColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
min: 0,
|
||||||
|
max: 400,
|
||||||
|
grid: {
|
||||||
|
borderColor,
|
||||||
|
drawBorder: false,
|
||||||
|
color: borderColor,
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
stepSize: 100,
|
||||||
|
color: labelColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Doughnut Chart Config
|
||||||
|
export const getDoughnutChartConfig = () => {
|
||||||
|
return {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
animation: { duration: 500 },
|
||||||
|
cutout: 80,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Scatter Chart Config
|
||||||
|
export const getScatterChartConfig = themeColors => {
|
||||||
|
const { borderColor, labelColor, legendColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
animation: { duration: 800 },
|
||||||
|
layout: {
|
||||||
|
padding: { top: -20 },
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
min: 0,
|
||||||
|
max: 140,
|
||||||
|
grid: {
|
||||||
|
borderColor,
|
||||||
|
drawTicks: false,
|
||||||
|
drawBorder: false,
|
||||||
|
color: borderColor,
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
stepSize: 10,
|
||||||
|
color: labelColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
min: 0,
|
||||||
|
max: 400,
|
||||||
|
grid: {
|
||||||
|
borderColor,
|
||||||
|
drawTicks: false,
|
||||||
|
drawBorder: false,
|
||||||
|
color: borderColor,
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
stepSize: 100,
|
||||||
|
color: labelColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
align: 'start',
|
||||||
|
position: 'top',
|
||||||
|
labels: {
|
||||||
|
padding: 25,
|
||||||
|
boxWidth: 9,
|
||||||
|
color: legendColor,
|
||||||
|
usePointStyle: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Line Area Chart Config
|
||||||
|
export const getLineAreaChartConfig = themeColors => {
|
||||||
|
const { borderColor, labelColor, legendColor } = colorVariables(themeColors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
layout: {
|
||||||
|
padding: { top: -20 },
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
borderColor,
|
||||||
|
color: 'transparent',
|
||||||
|
},
|
||||||
|
ticks: { color: labelColor },
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
min: 0,
|
||||||
|
max: 400,
|
||||||
|
grid: {
|
||||||
|
borderColor,
|
||||||
|
color: 'transparent',
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
stepSize: 100,
|
||||||
|
color: labelColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
align: 'start',
|
||||||
|
position: 'top',
|
||||||
|
labels: {
|
||||||
|
padding: 25,
|
||||||
|
boxWidth: 9,
|
||||||
|
color: legendColor,
|
||||||
|
usePointStyle: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// !SECTION
|
||||||
54
src/@core/libs/chartjs/components/BarChart.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { BarElement, CategoryScale, Chart as ChartJS, Legend, LinearScale, Title, Tooltip } from 'chart.js'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { Bar } from 'vue-chartjs'
|
||||||
|
|
||||||
|
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'BarChart',
|
||||||
|
props: {
|
||||||
|
chartId: {
|
||||||
|
type: String,
|
||||||
|
default: 'bar-chart',
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 400,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 400,
|
||||||
|
},
|
||||||
|
cssClasses: {
|
||||||
|
default: '',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ([]),
|
||||||
|
},
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
chartOptions: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
return () => h(h(Bar), {
|
||||||
|
data: props.chartData,
|
||||||
|
options: props.chartOptions,
|
||||||
|
chartId: props.chartId,
|
||||||
|
width: props.width,
|
||||||
|
height: props.height,
|
||||||
|
cssClasses: props.cssClasses,
|
||||||
|
styles: props.styles,
|
||||||
|
plugins: props.plugins,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
54
src/@core/libs/chartjs/components/BubbleChart.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { Chart as ChartJS, Legend, LinearScale, PointElement, Title, Tooltip } from 'chart.js'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { Bubble } from 'vue-chartjs'
|
||||||
|
|
||||||
|
ChartJS.register(Title, Tooltip, Legend, PointElement, LinearScale)
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'BubbleChart',
|
||||||
|
props: {
|
||||||
|
chartId: {
|
||||||
|
type: String,
|
||||||
|
default: 'bubble-chart',
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 400,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 400,
|
||||||
|
},
|
||||||
|
cssClasses: {
|
||||||
|
default: '',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
chartOptions: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
return () => h(h(Bubble), {
|
||||||
|
data: props.chartData,
|
||||||
|
options: props.chartOptions,
|
||||||
|
chartId: props.chartId,
|
||||||
|
width: props.width,
|
||||||
|
height: props.height,
|
||||||
|
cssClasses: props.cssClasses,
|
||||||
|
styles: props.styles,
|
||||||
|
plugins: props.plugins,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
54
src/@core/libs/chartjs/components/DoughnutChart.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { ArcElement, CategoryScale, Chart as ChartJS, Legend, Title, Tooltip } from 'chart.js'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { Doughnut } from 'vue-chartjs'
|
||||||
|
|
||||||
|
ChartJS.register(Title, Tooltip, Legend, ArcElement, CategoryScale)
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'DoughnutChart',
|
||||||
|
props: {
|
||||||
|
chartId: {
|
||||||
|
type: String,
|
||||||
|
default: 'doughnut-chart',
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 400,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 400,
|
||||||
|
},
|
||||||
|
cssClasses: {
|
||||||
|
default: '',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
chartOptions: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
return () => h(h(Doughnut), {
|
||||||
|
data: props.chartData,
|
||||||
|
options: props.chartOptions,
|
||||||
|
chartId: props.chartId,
|
||||||
|
width: props.width,
|
||||||
|
height: props.height,
|
||||||
|
cssClasses: props.cssClasses,
|
||||||
|
styles: props.styles,
|
||||||
|
plugins: props.plugins,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
54
src/@core/libs/chartjs/components/LineChart.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { CategoryScale, Chart as ChartJS, Legend, LineElement, LinearScale, PointElement, Title, Tooltip } from 'chart.js'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { Line } from 'vue-chartjs'
|
||||||
|
|
||||||
|
ChartJS.register(Title, Tooltip, Legend, LineElement, LinearScale, PointElement, CategoryScale)
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'LineChart',
|
||||||
|
props: {
|
||||||
|
chartId: {
|
||||||
|
type: String,
|
||||||
|
default: 'line-chart',
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 400,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 400,
|
||||||
|
},
|
||||||
|
cssClasses: {
|
||||||
|
default: '',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
chartOptions: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
return () => h(h(Line), {
|
||||||
|
chartId: props.chartId,
|
||||||
|
width: props.width,
|
||||||
|
height: props.height,
|
||||||
|
cssClasses: props.cssClasses,
|
||||||
|
styles: props.styles,
|
||||||
|
plugins: props.plugins,
|
||||||
|
options: props.chartOptions,
|
||||||
|
data: props.chartData,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
54
src/@core/libs/chartjs/components/PolarAreaChart.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { ArcElement, Chart as ChartJS, Legend, RadialLinearScale, Title, Tooltip } from 'chart.js'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { PolarArea } from 'vue-chartjs'
|
||||||
|
|
||||||
|
ChartJS.register(Title, Tooltip, Legend, ArcElement, RadialLinearScale)
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'PolarAreaChart',
|
||||||
|
props: {
|
||||||
|
chartId: {
|
||||||
|
type: String,
|
||||||
|
default: 'line-chart',
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 400,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 400,
|
||||||
|
},
|
||||||
|
cssClasses: {
|
||||||
|
default: '',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
chartOptions: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
return () => h(h(PolarArea), {
|
||||||
|
data: props.chartData,
|
||||||
|
options: props.chartOptions,
|
||||||
|
chartId: props.chartId,
|
||||||
|
width: props.width,
|
||||||
|
height: props.height,
|
||||||
|
cssClasses: props.cssClasses,
|
||||||
|
styles: props.styles,
|
||||||
|
plugins: props.plugins,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
54
src/@core/libs/chartjs/components/RadarChart.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { Chart as ChartJS, Filler, Legend, LineElement, PointElement, RadialLinearScale, Title, Tooltip } from 'chart.js'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { Radar } from 'vue-chartjs'
|
||||||
|
|
||||||
|
ChartJS.register(Title, Tooltip, Legend, PointElement, RadialLinearScale, LineElement, Filler)
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'RadarChart',
|
||||||
|
props: {
|
||||||
|
chartId: {
|
||||||
|
type: String,
|
||||||
|
default: 'radar-chart',
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 400,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 400,
|
||||||
|
},
|
||||||
|
cssClasses: {
|
||||||
|
default: '',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
chartOptions: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
return () => h(h(Radar), {
|
||||||
|
data: props.chartData,
|
||||||
|
options: props.chartOptions,
|
||||||
|
chartId: props.chartId,
|
||||||
|
width: props.width,
|
||||||
|
height: props.height,
|
||||||
|
cssClasses: props.cssClasses,
|
||||||
|
styles: props.styles,
|
||||||
|
plugins: props.plugins,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
54
src/@core/libs/chartjs/components/ScatterChart.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { CategoryScale, Chart as ChartJS, Legend, LineElement, LinearScale, PointElement, Title, Tooltip } from 'chart.js'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { Scatter } from 'vue-chartjs'
|
||||||
|
|
||||||
|
ChartJS.register(Title, Tooltip, Legend, PointElement, LineElement, CategoryScale, LinearScale)
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ScatterChart',
|
||||||
|
props: {
|
||||||
|
chartId: {
|
||||||
|
type: String,
|
||||||
|
default: 'scatter-chart',
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 400,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 400,
|
||||||
|
},
|
||||||
|
cssClasses: {
|
||||||
|
default: '',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
chartOptions: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
return () => h(h(Scatter), {
|
||||||
|
data: props.chartData,
|
||||||
|
options: props.chartOptions,
|
||||||
|
chartId: props.chartId,
|
||||||
|
width: props.width,
|
||||||
|
height: props.height,
|
||||||
|
cssClasses: props.cssClasses,
|
||||||
|
styles: props.styles,
|
||||||
|
plugins: props.plugins,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
185
src/@core/scss/base/_components.scss
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
@use "@core/scss/base/mixins";
|
||||||
|
@use "@layouts/styles/placeholders";
|
||||||
|
@use "@layouts/styles/mixins" as layoutMixins;
|
||||||
|
@use "@configured-variables" as variables;
|
||||||
|
@use "@styles/variables/_vuetify.scss" as vuetify;
|
||||||
|
|
||||||
|
// 👉 Avatar group
|
||||||
|
.v-avatar-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-inline-start: -0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
transition: transform 0.25s ease, box-shadow 0.15s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
z-index: 2;
|
||||||
|
transform: translateY(-5px) scale(1.05);
|
||||||
|
|
||||||
|
@include mixins.elevation(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .v-avatar {
|
||||||
|
border: 2px solid rgb(var(--v-theme-surface));
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Button outline with default color border color
|
||||||
|
.v-alert--variant-outlined,
|
||||||
|
.v-avatar--variant-outlined,
|
||||||
|
.v-btn.v-btn--variant-outlined,
|
||||||
|
.v-card--variant-outlined,
|
||||||
|
.v-chip--variant-outlined,
|
||||||
|
.v-list-item--variant-outlined {
|
||||||
|
&:not([class*="text-"]) {
|
||||||
|
border-color: rgba(var(--v-border-color), var(--v-border-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.text-default {
|
||||||
|
border-color: rgba(var(--v-border-color), var(--v-border-opacity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Custom Input
|
||||||
|
.v-label.custom-input {
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||||
|
opacity: 1;
|
||||||
|
white-space: normal;
|
||||||
|
|
||||||
|
+.v-label {
|
||||||
|
letter-spacing: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: rgba(var(--v-border-color), 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: rgb(var(--v-theme-primary));
|
||||||
|
|
||||||
|
.v-icon {
|
||||||
|
color: rgb(var(--v-theme-primary)) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Datatable
|
||||||
|
.v-data-table-footer__pagination {
|
||||||
|
@include layoutMixins.rtl {
|
||||||
|
.v-btn {
|
||||||
|
.v-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dialog responsive width
|
||||||
|
.v-dialog {
|
||||||
|
// dialog custom close btn
|
||||||
|
.v-dialog-close-btn {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
color: rgba(var(--v-theme-on-surface), var(--v-disabled-opacity)) !important;
|
||||||
|
inset-block-start: 0.5rem;
|
||||||
|
inset-inline-end: 0.5rem;
|
||||||
|
|
||||||
|
.v-btn__overlay {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-card {
|
||||||
|
@extend %style-scroll-bar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 600px) {
|
||||||
|
.v-dialog {
|
||||||
|
&.v-dialog-sm,
|
||||||
|
&.v-dialog-lg,
|
||||||
|
&.v-dialog-xl {
|
||||||
|
.v-overlay__content {
|
||||||
|
inline-size: 565px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.v-dialog {
|
||||||
|
&.v-dialog-lg,
|
||||||
|
&.v-dialog-xl {
|
||||||
|
.v-overlay__content {
|
||||||
|
inline-size: 865px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1264px) {
|
||||||
|
.v-dialog.v-dialog-xl {
|
||||||
|
.v-overlay__content {
|
||||||
|
inline-size: 1165px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Expansion panel
|
||||||
|
.v-expansion-panels.customized-panels {
|
||||||
|
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||||
|
border-radius: vuetify.$border-radius-root;
|
||||||
|
|
||||||
|
.v-expansion-panel-title {
|
||||||
|
background-color: rgb(var(--v-theme-expansion-panel-text-custom-bg));
|
||||||
|
border-block-end: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||||
|
margin-block-end: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-expansion-panel-text__wrapper {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// v-tab with pill support
|
||||||
|
.v-tabs.v-tabs-pill {
|
||||||
|
.v-tab.v-btn {
|
||||||
|
border-radius: 6px !important;
|
||||||
|
transition: none;
|
||||||
|
|
||||||
|
.v-tab__slider {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop for all colors bg
|
||||||
|
@each $color-name in variables.$theme-colors-name {
|
||||||
|
body .v-tabs.v-tabs-pill {
|
||||||
|
.v-slide-group-item--active.v-tab--selected.text-#{$color-name} {
|
||||||
|
background-color: rgb(var(--v-theme-#{$color-name}));
|
||||||
|
color: rgb(var(--v-theme-on-#{$color-name})) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ℹ️ We are make even width of all v-timeline body
|
||||||
|
.v-timeline--vertical.v-timeline {
|
||||||
|
.v-timeline-item {
|
||||||
|
.v-timeline-item__body {
|
||||||
|
justify-self: stretch !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Switch
|
||||||
|
.v-switch .v-selection-control:not(.v-selection-control--dirty) .v-switch__thumb {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
16
src/@core/scss/base/_dark.scss
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
@use "@configured-variables" as variables;
|
||||||
|
|
||||||
|
// ————————————————————————————————————
|
||||||
|
// * ——— Perfect Scrollbar
|
||||||
|
// ————————————————————————————————————
|
||||||
|
|
||||||
|
body.v-theme--dark {
|
||||||
|
.ps__rail-y,
|
||||||
|
.ps__rail-x {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ps__thumb-y {
|
||||||
|
background-color: variables.$plugin-ps-thumb-y-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/@core/scss/base/_default-layout-w-horizontal-nav.scss
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
@use "@core/scss/base/placeholders" as *;
|
||||||
|
@use "@core/scss/template/placeholders" as *;
|
||||||
|
@use "@core/scss/base/mixins";
|
||||||
|
|
||||||
|
.layout-wrapper.layout-nav-type-horizontal {
|
||||||
|
.layout-navbar-and-nav-container {
|
||||||
|
@extend %default-layout-horizontal-nav-navbar-and-nav-container;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Navbar
|
||||||
|
.layout-navbar {
|
||||||
|
@extend %default-layout-horizontal-nav-navbar;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Layout content container
|
||||||
|
.navbar-content-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
block-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-horizontal-nav {
|
||||||
|
@extend %default-layout-horizontal-nav-nav;
|
||||||
|
|
||||||
|
.nav-items {
|
||||||
|
@extend %default-layout-horizontal-nav-nav-items-list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 App footer
|
||||||
|
.layout-footer {
|
||||||
|
@at-root {
|
||||||
|
.layout-footer-sticky#{&} {
|
||||||
|
background-color: rgb(var(--v-theme-surface));
|
||||||
|
|
||||||
|
@include mixins.elevation(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Use Vuetify grid sass variable here
|
||||||
|
.layout-page-content {
|
||||||
|
padding-block: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/@core/scss/base/_default-layout-w-vertical-nav.scss
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
@use "@configured-variables" as variables;
|
||||||
|
@use "@core/scss/base/placeholders" as *;
|
||||||
|
@use "@core/scss/template/placeholders" as *;
|
||||||
|
@use "misc";
|
||||||
|
@use "@core/scss/base/mixins";
|
||||||
|
|
||||||
|
$header: ".layout-navbar";
|
||||||
|
|
||||||
|
@if variables.$layout-vertical-nav-navbar-is-contained {
|
||||||
|
$header: ".layout-navbar .navbar-content-container";
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-wrapper.layout-nav-type-vertical {
|
||||||
|
// SECTION Layout Navbar
|
||||||
|
// 👉 Elevated navbar
|
||||||
|
@if variables.$vertical-nav-navbar-style == "elevated" {
|
||||||
|
// Add transition
|
||||||
|
#{$header} {
|
||||||
|
transition: padding 0.2s ease, background-color 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If navbar is contained => Add border radius to header
|
||||||
|
@if variables.$layout-vertical-nav-navbar-is-contained {
|
||||||
|
#{$header} {
|
||||||
|
border-radius: 0 0 variables.$default-layout-with-vertical-nav-navbar-footer-roundness variables.$default-layout-with-vertical-nav-navbar-footer-roundness;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scrolled styles for sticky navbar
|
||||||
|
@at-root {
|
||||||
|
/* ℹ️ This html selector with not selector is required when:
|
||||||
|
dialog is opened and window don't have any scroll. This removes window-scrolled class from layout and our style broke
|
||||||
|
*/
|
||||||
|
html.v-overlay-scroll-blocked:not([style*="--v-body-scroll-y: 0px;"]) .layout-navbar-sticky,
|
||||||
|
&.window-scrolled.layout-navbar-sticky {
|
||||||
|
|
||||||
|
#{$header} {
|
||||||
|
@extend %default-layout-vertical-nav-scrolled-sticky-elevated-nav;
|
||||||
|
@extend %default-layout-vertical-nav-floating-navbar-and-sticky-elevated-navbar-scrolled;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-blur#{$header} {
|
||||||
|
@extend %blurry-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Floating navbar
|
||||||
|
@else if variables.$vertical-nav-navbar-style == "floating" {
|
||||||
|
// ℹ️ Regardless of navbar is contained or not => Apply overlay to .layout-navbar
|
||||||
|
.layout-navbar {
|
||||||
|
&.navbar-blur {
|
||||||
|
@extend %default-layout-vertical-nav-floating-navbar-overlay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.layout-navbar-sticky) {
|
||||||
|
#{$header} {
|
||||||
|
margin-block-start: variables.$vertical-nav-floating-navbar-top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#{$header} {
|
||||||
|
@if variables.$layout-vertical-nav-navbar-is-contained {
|
||||||
|
border-radius: variables.$default-layout-with-vertical-nav-navbar-footer-roundness;
|
||||||
|
}
|
||||||
|
|
||||||
|
background-color: rgb(var(--v-theme-surface));
|
||||||
|
|
||||||
|
@extend %default-layout-vertical-nav-floating-navbar-and-sticky-elevated-navbar-scrolled;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-blur#{$header} {
|
||||||
|
@extend %blurry-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// !SECTION
|
||||||
|
|
||||||
|
// 👉 Layout footer
|
||||||
|
.layout-footer {
|
||||||
|
$ele-layout-footer: &;
|
||||||
|
|
||||||
|
.footer-content-container {
|
||||||
|
border-radius: variables.$default-layout-with-vertical-nav-navbar-footer-roundness variables.$default-layout-with-vertical-nav-navbar-footer-roundness 0 0;
|
||||||
|
|
||||||
|
// Sticky footer
|
||||||
|
@at-root {
|
||||||
|
// ℹ️ .layout-footer-sticky#{$ele-layout-footer} => .layout-footer-sticky.layout-wrapper.layout-nav-type-vertical .layout-footer
|
||||||
|
.layout-footer-sticky#{$ele-layout-footer} {
|
||||||
|
.footer-content-container {
|
||||||
|
background-color: rgb(var(--v-theme-surface));
|
||||||
|
padding-block: 0;
|
||||||
|
padding-inline: 1.2rem;
|
||||||
|
|
||||||
|
@include mixins.elevation(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/@core/scss/base/_default-layout.scss
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
@use "@core/scss/base/placeholders";
|
||||||
|
@use "@core/scss/base/variables";
|
||||||
|
|
||||||
|
.layout-vertical-nav,
|
||||||
|
.layout-horizontal-nav {
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-navbar {
|
||||||
|
@if variables.$navbar-high-emphasis-text {
|
||||||
|
@extend %layout-navbar;
|
||||||
|
}
|
||||||
|
}
|
||||||
194
src/@core/scss/base/_horizontal-nav.scss
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
@use "@core/scss/base/placeholders" as *;
|
||||||
|
@use "@core/scss/template/placeholders" as *;
|
||||||
|
@use "@configured-variables" as variables;
|
||||||
|
@use "@layouts/styles/mixins" as layoutsMixins;
|
||||||
|
@use "@core/scss/base/mixins";
|
||||||
|
@use "vuetify/lib/styles/tools/states" as vuetifyStates;
|
||||||
|
|
||||||
|
.layout-horizontal-nav {
|
||||||
|
@extend %nav;
|
||||||
|
|
||||||
|
// 👉 Icon styles
|
||||||
|
.nav-item-icon {
|
||||||
|
@extend %horizontal-nav-item-icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Common styles for nav group & nav link
|
||||||
|
.nav-link,
|
||||||
|
.nav-group {
|
||||||
|
// 👉 Disabled nav items
|
||||||
|
&.disabled {
|
||||||
|
@extend %horizontal-nav-disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set width of inner nav group and link
|
||||||
|
&.sub-item {
|
||||||
|
@extend %horizontal-nav-subitem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SECTION Nav Link
|
||||||
|
.nav-link {
|
||||||
|
@extend %nav-link;
|
||||||
|
|
||||||
|
a {
|
||||||
|
@extend %horizontal-nav-item;
|
||||||
|
|
||||||
|
// Adds before psudo element to style hover state
|
||||||
|
@include mixins.before-pseudo;
|
||||||
|
|
||||||
|
// Adds vuetify states
|
||||||
|
@include vuetifyStates.states($active: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Top level nav link
|
||||||
|
&:not(.sub-item) {
|
||||||
|
a {
|
||||||
|
@extend %horizontal-nav-top-level-item;
|
||||||
|
|
||||||
|
&.router-link-active {
|
||||||
|
@extend %nav-link-active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Sub link
|
||||||
|
&.sub-item {
|
||||||
|
a {
|
||||||
|
&.router-link-active {
|
||||||
|
// ℹ️ We will not use active styles from material here because we want to use primary color for active link
|
||||||
|
@extend %horizontal-nav-sub-nav-link-active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// !SECTION
|
||||||
|
|
||||||
|
// SECTION Nav Group
|
||||||
|
.nav-group {
|
||||||
|
.popper-triggerer {
|
||||||
|
.nav-group-label {
|
||||||
|
@extend %horizontal-nav-item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .popper-triggerer > .nav-group-label {
|
||||||
|
// Adds before psudo element to style hover state
|
||||||
|
@include mixins.before-pseudo;
|
||||||
|
|
||||||
|
// Adds vuetify states
|
||||||
|
@include vuetifyStates.states($active: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popper-content {
|
||||||
|
@extend %horizontal-nav-popper-content-hidden;
|
||||||
|
@extend %horizontal-nav-popper-content;
|
||||||
|
|
||||||
|
background-color: rgb(var(--v-theme-surface));
|
||||||
|
|
||||||
|
// Set max-height for the popper content
|
||||||
|
> div {
|
||||||
|
max-block-size: variables.$horizontal-nav-popper-content-max-height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Top level group
|
||||||
|
&:not(.sub-item) {
|
||||||
|
> .popper-triggerer {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
/*
|
||||||
|
ℹ️ The Bridge
|
||||||
|
This after pseudo will work as bridge when we have space between popper triggerer and popper content
|
||||||
|
Initially it will have pointer events none for normal behavior and once the content is shown it will
|
||||||
|
work as bridge by setting pointer events to `auto`
|
||||||
|
*/
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
block-size: variables.$horizontal-nav-popper-content-top;
|
||||||
|
content: "";
|
||||||
|
inline-size: 100%;
|
||||||
|
inset-block-start: 100%;
|
||||||
|
inset-inline-start: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable the pseudo bridge when content is shown by setting pointer events to `auto`
|
||||||
|
&.show-content > .popper-triggerer::after {
|
||||||
|
/*
|
||||||
|
ℹ️ We have added `z-index: 2` because when there is horizontal nav item below the popper trigger (group)
|
||||||
|
without this style nav item below popper trigger (group) gets focus hence closes the popper content
|
||||||
|
*/
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .popper-triggerer > .nav-group-label {
|
||||||
|
@extend %horizontal-nav-top-level-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
> .popper-triggerer > .nav-group-label {
|
||||||
|
@extend %nav-link-active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ℹ️ Add space between popper wrapper & content
|
||||||
|
> .popper-content {
|
||||||
|
margin-block-start: variables.$horizontal-nav-popper-content-top !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Sub group
|
||||||
|
&.sub-item {
|
||||||
|
&.active {
|
||||||
|
@include mixins.selected-states("> .popper-triggerer > .nav-group-label::before");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce the icon's size of nested group's nav links (Top level group > Sub group > [Nav links])
|
||||||
|
.sub-item {
|
||||||
|
.nav-item-icon {
|
||||||
|
@extend %third-level-nav-item-icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-group-arrow {
|
||||||
|
font-size: variables.$horizontal-nav-group-arrow-icon-size;
|
||||||
|
|
||||||
|
/*
|
||||||
|
ℹ️ ml-auto won't matter in top level group (because we haven't specified fixed width for top level groups)
|
||||||
|
but we wrote generally because we don't want to become so specific
|
||||||
|
*/
|
||||||
|
margin-inline-start: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.popper-inline-end {
|
||||||
|
.nav-group-arrow {
|
||||||
|
transform: rotateZ(270deg);
|
||||||
|
|
||||||
|
@include layoutsMixins.rtl {
|
||||||
|
transform: rotateZ(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item-title {
|
||||||
|
@extend %horizontal-nav-item-title;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.show-content {
|
||||||
|
> .popper-content {
|
||||||
|
@extend %horizontal-nav-popper-content-visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.active) {
|
||||||
|
@include mixins.selected-states("> .popper-triggerer > .nav-group-label::before");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// !SECTION
|
||||||
|
}
|
||||||
48
src/@core/scss/base/_index.scss
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
@use "sass:map";
|
||||||
|
|
||||||
|
// Layout
|
||||||
|
@use "vertical-nav";
|
||||||
|
@use "horizontal-nav";
|
||||||
|
@use "default-layout";
|
||||||
|
@use "default-layout-w-vertical-nav";
|
||||||
|
@use "default-layout-w-horizontal-nav";
|
||||||
|
|
||||||
|
// Layouts package
|
||||||
|
@use "layouts";
|
||||||
|
|
||||||
|
// Skins
|
||||||
|
@use "skins";
|
||||||
|
|
||||||
|
// Components
|
||||||
|
@use "components";
|
||||||
|
|
||||||
|
// Utilities
|
||||||
|
@use "utilities";
|
||||||
|
|
||||||
|
// Route Transitions
|
||||||
|
@use "route-transitions";
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
@use "misc";
|
||||||
|
|
||||||
|
// Dark
|
||||||
|
@use "dark";
|
||||||
|
|
||||||
|
// libs
|
||||||
|
@use "libs/perfect-scrollbar";
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgb(var(--v-theme-primary));
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vuetify 3 don't provide margin bottom style like vuetify 2
|
||||||
|
p {
|
||||||
|
margin-block-end: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iconify icon size
|
||||||
|
svg.iconify {
|
||||||
|
block-size: 1em;
|
||||||
|
inline-size: 1em;
|
||||||
|
}
|
||||||
63
src/@core/scss/base/_layouts.scss
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
@use "@configured-variables" as variables;
|
||||||
|
|
||||||
|
/* ℹ️ This styles extends the existing layout package's styles for handling cases that aren't related to layouts package */
|
||||||
|
|
||||||
|
/*
|
||||||
|
ℹ️ When we use v-layout as immediate first child of `.page-content-container`, it adds display:flex and page doesn't get contained height
|
||||||
|
*/
|
||||||
|
// .layout-wrapper.layout-nav-type-vertical {
|
||||||
|
// &.layout-content-height-fixed {
|
||||||
|
// .page-content-container {
|
||||||
|
// > .v-layout:first-child > :not(.v-navigation-drawer):first-child {
|
||||||
|
// flex-grow: 1;
|
||||||
|
// block-size: 100%;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
.layout-wrapper.layout-nav-type-vertical {
|
||||||
|
&.layout-content-height-fixed {
|
||||||
|
.page-content-container {
|
||||||
|
> .v-layout:first-child {
|
||||||
|
overflow: hidden;
|
||||||
|
min-block-size: 100%;
|
||||||
|
|
||||||
|
> .v-main {
|
||||||
|
// overflow-y: auto;
|
||||||
|
|
||||||
|
.v-main__wrap > :first-child {
|
||||||
|
block-size: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ℹ️ Let div/v-layout take full height. E.g. Email App
|
||||||
|
.layout-wrapper.layout-nav-type-horizontal {
|
||||||
|
&.layout-content-height-fixed {
|
||||||
|
> .layout-page-content {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Floating navbar styles
|
||||||
|
@if variables.$vertical-nav-navbar-style == "floating" {
|
||||||
|
// ℹ️ Add spacing above navbar if navbar is floating (was in %layout-navbar-sticky placeholder)
|
||||||
|
body .layout-wrapper.layout-nav-type-vertical.layout-navbar-sticky {
|
||||||
|
.layout-navbar {
|
||||||
|
inset-block-start: variables.$vertical-nav-floating-navbar-top;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ℹ️ If it's floating navbar
|
||||||
|
Add `vertical-nav-floating-navbar-top` as margin top to .layout-page-content
|
||||||
|
*/
|
||||||
|
.layout-page-content {
|
||||||
|
margin-block-start: variables.$vertical-nav-floating-navbar-top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/@core/scss/base/_misc.scss
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// ℹ️ scrollable-content allows creating fixed header and scrollable content for VNavigationDrawer (Used when perfect scrollbar is used)
|
||||||
|
.scrollable-content {
|
||||||
|
&.v-navigation-drawer {
|
||||||
|
.v-navigation-drawer__content {
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ℹ️ adding styling for code tag
|
||||||
|
code {
|
||||||
|
border-radius: 3px;
|
||||||
|
color: rgb(var(--v-code-color));
|
||||||
|
font-size: 90%;
|
||||||
|
font-weight: 400;
|
||||||
|
padding-block: 0.2em;
|
||||||
|
padding-inline: 0.4em;
|
||||||
|
}
|
||||||
63
src/@core/scss/base/_mixins.scss
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
@use "sass:map";
|
||||||
|
@use "@styles/variables/vuetify.scss";
|
||||||
|
|
||||||
|
@mixin elevation($z, $important: false) {
|
||||||
|
box-shadow: map.get(vuetify.$shadow-key-umbra, $z), map.get(vuetify.$shadow-key-penumbra, $z), map.get(vuetify.$shadow-key-ambient, $z) if($important, !important, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// #region before-pseudo
|
||||||
|
// ℹ️ This mixin is inspired from vuetify for adding hover styles via before pseudo element
|
||||||
|
@mixin before-pseudo() {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: inherit;
|
||||||
|
background: currentcolor;
|
||||||
|
block-size: 100%;
|
||||||
|
content: "";
|
||||||
|
inline-size: 100%;
|
||||||
|
inset: 0;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endregion before-pseudo
|
||||||
|
|
||||||
|
@mixin bordered-skin($component, $border-property: "border", $important: false) {
|
||||||
|
#{$component} {
|
||||||
|
box-shadow: none !important;
|
||||||
|
// stylelint-disable-next-line annotation-no-unknown
|
||||||
|
#{$border-property}: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)) if($important, !important, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #region selected-states
|
||||||
|
// ℹ️ Inspired from vuetify's active-states mixin
|
||||||
|
// focus => 0.12 & selected => 0.08
|
||||||
|
@mixin selected-states($selector) {
|
||||||
|
#{$selector} {
|
||||||
|
opacity: calc(var(--v-selected-opacity) * var(--v-theme-overlay-multiplier));
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
#{$selector} {
|
||||||
|
opacity: calc(var(--v-selected-opacity) + var(--v-hover-opacity) * var(--v-theme-overlay-multiplier));
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible
|
||||||
|
#{$selector} {
|
||||||
|
opacity: calc(var(--v-selected-opacity) + var(--v-focus-opacity) * var(--v-theme-overlay-multiplier));
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports not selector(:focus-visible) {
|
||||||
|
&:focus {
|
||||||
|
#{$selector} {
|
||||||
|
opacity: calc(var(--v-selected-opacity) + var(--v-focus-opacity) * var(--v-theme-overlay-multiplier));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endregion selected-states
|
||||||
70
src/@core/scss/base/_route-transitions.scss
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// 👉 Zoom fade
|
||||||
|
.app-transition-zoom-fade-enter-active,
|
||||||
|
.app-transition-zoom-fade-leave-active {
|
||||||
|
transition: transform 0.35s, opacity 0.28s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-transition-zoom-fade-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-transition-zoom-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Fade
|
||||||
|
.app-transition-fade-enter-active,
|
||||||
|
.app-transition-fade-leave-active {
|
||||||
|
transition: opacity 0.25s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-transition-fade-enter-from,
|
||||||
|
.app-transition-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Fade bottom
|
||||||
|
.app-transition-fade-bottom-enter-active,
|
||||||
|
.app-transition-fade-bottom-leave-active {
|
||||||
|
transition: opacity 0.3s, transform 0.35s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-transition-fade-bottom-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-0.6rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-transition-fade-bottom-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(0.6rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Slide fade
|
||||||
|
.app-transition-slide-fade-enter-active,
|
||||||
|
.app-transition-slide-fade-leave-active {
|
||||||
|
transition: opacity 0.3s, transform 0.35s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-transition-slide-fade-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-0.6rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-transition-slide-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(0.6rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 👉 Zoom out
|
||||||
|
.app-transition-zoom-out-enter-active,
|
||||||
|
.app-transition-zoom-out-leave-active {
|
||||||
|
transition: opacity 0.26s ease-in-out, transform 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-transition-zoom-out-enter-from,
|
||||||
|
.app-transition-zoom-out-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||