├── .browserslistrc ├── .editorconfig ├── .eslintrc-auto-import.json ├── .github ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.yaml │ └── FEATURE_REQUEST.yaml └── workflows │ ├── inactive-issues.yaml │ └── main.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmignore ├── .npmrc ├── .nvmrc ├── .prettierrc.js ├── .release-it.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── dist ├── VResizeDrawer-BeVN9VIZ.mjs ├── VResizeDrawer-CKGjiLW1.js ├── plugin │ ├── VResizeDrawer.vue.d.ts │ ├── composables │ │ ├── classes.d.ts │ │ ├── colors.d.ts │ │ ├── helpers.d.ts │ │ ├── icons.d.ts │ │ ├── storage.d.ts │ │ └── styles.d.ts │ ├── index.d.ts │ ├── types │ │ └── index.d.ts │ └── utils │ │ ├── globals.d.ts │ │ └── props.d.ts ├── scss │ ├── _mixins.scss │ └── main.scss ├── vuetify-resize-drawer.cjs.js └── vuetify-resize-drawer.es.js ├── eslint.config.mjs ├── index.html ├── package.json ├── playground └── index.html ├── pnpm-lock.yaml ├── public └── vuetify-logo.svg ├── src ├── App.vue ├── assets │ └── vuetify-logo.svg ├── documentation │ ├── DocsPage.vue │ ├── components │ │ ├── MenuComponent.vue │ │ ├── PropsTable.vue │ │ └── VuetifyGridExamples.vue │ ├── layout │ │ └── AppBar.vue │ ├── sections │ │ ├── DependenciesSection.vue │ │ ├── DescriptionSection.vue │ │ ├── EventsSection.vue │ │ ├── ExampleSection.vue │ │ ├── GridSection.vue │ │ ├── LegalSection.vue │ │ ├── LicenseSection.vue │ │ ├── PlaygroundSection.vue │ │ ├── PropsSection.vue │ │ ├── SassVariablesSection.vue │ │ ├── SlotsSection.vue │ │ ├── UsageSection.vue │ │ └── index.js │ └── types │ │ └── docs.d.ts ├── libraries │ └── fontawesome.ts ├── main.ts ├── playground │ ├── .gitignore │ └── configs │ │ ├── PlaygroundApp.vue │ │ ├── build.sh │ │ ├── playground.ts │ │ └── templates │ │ └── PlaygroundPage.vue ├── plugin │ ├── VResizeDrawer.vue │ ├── __tests__ │ │ └── index.test.ts │ ├── composables │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── classes.test.ts.snap │ │ │ ├── classes.test.ts │ │ │ ├── colors.test.ts │ │ │ ├── helpers.test.ts │ │ │ ├── icons.test.ts │ │ │ ├── storage.test.ts │ │ │ └── styles.test.ts │ │ ├── classes.ts │ │ ├── colors.ts │ │ ├── helpers.ts │ │ ├── icons.ts │ │ ├── storage.ts │ │ └── styles.ts │ ├── index.ts │ ├── styles │ │ ├── _mixins.scss │ │ └── main.scss │ ├── types │ │ ├── auto-imports.d.ts │ │ ├── index.ts │ │ ├── vite-env.d.ts │ │ └── vue-shim.d.ts │ └── utils │ │ ├── globals.ts │ │ └── props.ts ├── plugins │ ├── index.ts │ ├── theme.ts │ ├── vuetify.ts │ └── webfontloader.ts ├── stores │ ├── index.ts │ ├── menu.ts │ └── props.ts └── style.css ├── stylelint.config.js ├── tsconfig.build.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.build.config.mts ├── vite.config.mts └── vitest.config.mts /.browserslistrc: -------------------------------------------------------------------------------- 1 | # Browsers that we support 2 | 3 | > 1% 4 | last 2 versions 5 | not dead 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = tab 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | indent_size = 2 13 | indent_style = space 14 | trim_trailing_whitespace = false 15 | 16 | [*.{yml,yaml}] 17 | indent_size = 2 18 | 19 | [*.{js,ts,mts,vue}] 20 | indent_size = 2 21 | indent_style = tab 22 | 23 | [*.{scss,css}] 24 | indent_size = 2 25 | indent_style = space 26 | -------------------------------------------------------------------------------- /.eslintrc-auto-import.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "CSSProperties": true, 4 | "Component": true, 5 | "ComponentPublicInstance": true, 6 | "ComputedRef": true, 7 | "EffectScope": true, 8 | "InjectionKey": true, 9 | "PropType": true, 10 | "Ref": true, 11 | "VNode": true, 12 | "computed": true, 13 | "createApp": true, 14 | "customRef": true, 15 | "defineAsyncComponent": true, 16 | "defineComponent": true, 17 | "effectScope": true, 18 | "getCurrentInstance": true, 19 | "getCurrentScope": true, 20 | "h": true, 21 | "inject": true, 22 | "isProxy": true, 23 | "isReactive": true, 24 | "isReadonly": true, 25 | "isRef": true, 26 | "markRaw": true, 27 | "nextTick": true, 28 | "onActivated": true, 29 | "onBeforeMount": true, 30 | "onBeforeUnmount": true, 31 | "onBeforeUpdate": true, 32 | "onDeactivated": true, 33 | "onErrorCaptured": true, 34 | "onMounted": true, 35 | "onRenderTracked": true, 36 | "onRenderTriggered": true, 37 | "onScopeDispose": true, 38 | "onServerPrefetch": true, 39 | "onUnmounted": true, 40 | "onUpdated": true, 41 | "provide": true, 42 | "reactive": true, 43 | "readonly": true, 44 | "ref": true, 45 | "resolveComponent": true, 46 | "shallowReactive": true, 47 | "shallowReadonly": true, 48 | "shallowRef": true, 49 | "toRaw": true, 50 | "toRef": true, 51 | "toRefs": true, 52 | "triggerRef": true, 53 | "unref": true, 54 | "useAttrs": true, 55 | "useCssModule": true, 56 | "useCssVars": true, 57 | "useSlots": true, 58 | "useTheme": true, 59 | "watch": true, 60 | "watchEffect": true, 61 | "watchPostEffect": true, 62 | "watchSyncEffect": true 63 | } 64 | } -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this repository 2 | 3 | ## Getting started 4 | 5 | Before you begin: 6 | - Have you read the [code of conduct](CODE_OF_CONDUCT.md)? 7 | - Check out the [existing issues](https://github.com/webdevnerdstuff/vuetify-resize-drawer/issues). 8 | 9 | ### Don't see your issue? Open one 10 | 11 | If you spot something new, open an issue using a [template](https://github.com/webdevnerdstuff/vuetify-resize-drawer/issues/new/choose). We'll use the issue to have a conversation about the problem you want to fix. 12 | 13 | ### Ready to make a change? Fork the repo 14 | 15 | Fork using the command line: 16 | 17 | - [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them. 18 | 19 | ### Make your update: 20 | 21 | Make your changes to the file(s) you'd like to update. 22 | Update the CHANGELOG.md with the updates you made, please include the date and Github username. 23 | 24 | ### Open a pull request 25 | When you're done making changes and you'd like to propose them for review, open your PR (pull request). 26 | 27 | ### Submit your PR & get it reviewed 28 | - Once you submit your PR, others from the Vue Unicorn Log community will review it with you. 29 | - After that, we may have questions, check back on your PR to keep up with the conversation. 30 | 31 | ### Your PR is merged! 32 | Congratulations! The whole GitHub community thanks you. :sparkles: 33 | 34 | Once your PR is merged, you will be proudly listed as a contributor in the [contributor chart](https://github.com/webdevnerdstuff/vuetify-resize-drawer/graphs/contributors). 35 | 36 | ### Keep contributing as you use Vue Unicorn Log 37 | 38 | Now that you're a part of the Vue Unicorn Log community, you can keep participating in many ways. 39 | 40 | ## Types of contributions 41 | You can contribute to the Vue Unicorn Log content and site in several ways. This repo is a place to discuss and collaborate on Vue Unicorn Log! Our small, but mighty team is maintaining this repo, to preserve our bandwidth, off topic conversations will be closed. 42 | 43 | ### :mega: Discussions 44 | Discussions are where we have conversations. 45 | 46 | If you'd like help troubleshooting a Vue Unicorn Log PR you're working on, have a great new idea, or want to share something amazing you've learned, join us in [discussions](https://github.com/webdevnerdstuff/vuetify-resize-drawer/discussions). 47 | 48 | ### :beetle: Issues 49 | [Issues](https://docs.github.com/en/github/managing-your-work-on-github/about-issues) are used to track tasks that contributors can help with. 50 | 51 | If you've found something in the content or the website that should be updated, search open issues to see if someone else has reported the same thing. If it's something new, open an issue using a [template](https://github.com/webdevnerdstuff/vuetify-resize-drawer/issues/new/choose). We'll use the issue to have a conversation about the problem you want to fix. 52 | 53 | ### :hammer_and_wrench: Pull requests 54 | A [pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) is a way to suggest changes in our repository. 55 | 56 | To learn more about opening a pull request in this repo, see [Opening a pull request](#opening-a-pull-request) below. 57 | 58 | ### :question: Support 59 | We are a small team working hard to keep up with the documentation demands of a continuously changing product. Unfortunately, we just can't help with support questions in this repository. If you are experiencing a problem with GitHub, unrelated to our documentation, please [contact GitHub Support directly](https://support.github.com/contact). Any issues, discussions, or pull requests opened here requesting support will be given information about how to contact GitHub Support, then closed and locked. 60 | 61 | If you're having trouble with your GitHub account, contact [Support](https://support.github.com/contact). 62 | 63 | ## Starting with an issue 64 | You can browse existing issues to find something that needs help! 65 | 66 | ### Labels 67 | Labels can help you find an issue you'd like to help with. 68 | 69 | - The [`help wanted` label](https://github.com/webdevnerdstuff/vuetify-resize-drawer/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement) is for problems or updates that anyone in the community can start working on. 70 | - The [`documentation` label](https://github.com/webdevnerdstuff/vuetify-resize-drawer/issues?q=is%3Aopen+is%3Aissue+label%3Adocumentation) is for problems or updates relating to the README.MD documentation. 71 | - The [`bug` label](https://github.com/webdevnerdstuff/vuetify-resize-drawer/issues?q=is%3Aopen+is%3Aissue+label%3Abug) is for problems with the code and bugs. 72 | - The [`enhancement` label](https://github.com/webdevnerdstuff/vuetify-resize-drawer/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement) is for 73 | suggestions to improve the code or adding of additional features. 74 | 75 | ## Opening a pull request 76 | You can use the GitHub user interface for some small changes, like fixing a typo or updating a readme. You can also fork the repo and then clone it locally, to view changes and run your tests on your machine. 77 | 78 | ## Working in the Vue Unicorn Log repository 79 | Here's some information that might be helpful while working on a Vue Unicorn Log PR: 80 | 81 | 82 | 83 | ## Reviewing 84 | We (usually the Vue Unicorn Log team) review every single PR. The purpose of reviews is to create the best content we can for people who use GitHub. 85 | 86 | - Reviews are always respectful, acknowledging that everyone did the best possible job with the knowledge they had at the time. 87 | - Reviews discuss content, not the person who created it. 88 | - Reviews are constructive and start conversation around feedback. 89 | 90 | ### Self review 91 | You should always review your own PR first. 92 | 93 | 95 | 96 | ### Suggested changes 97 | We may ask for changes to be made before a PR can be merged, either using [suggested changes](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request) or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch. 98 | 99 | As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations). 100 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | buy_me_a_coffee: webdevnerdstuff 2 | custom: ["paypal.me/webdevnerdstuff"] 3 | patreon: webdevnerdstuff 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: ["bug", "triage"] 5 | assignees: 6 | - webdevnerdstuff 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for taking the time to fill out this bug report! 12 | - type: textarea 13 | id: bug-description 14 | attributes: 15 | label: Bug description 16 | description: What happened? 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: steps 21 | attributes: 22 | label: Steps to reproduce 23 | description: Which steps do we need to take to reproduce this error? 24 | placeholder: "Steps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'" 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: logs 29 | attributes: 30 | label: Relevant log output 31 | description: If applicable, please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 32 | render: shell 33 | - type: textarea 34 | id: additional-context 35 | attributes: 36 | label: Additional context 37 | description: Add any other context about the problem here. 38 | - type: checkboxes 39 | id: terms 40 | attributes: 41 | label: Code of Conduct 42 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/webdevnerdstuff/vuetify3-resize-drawer/blob/main/CODE_OF_CONDUCT.md) 43 | options: 44 | - label: I agree to follow this project's Code of Conduct 45 | required: true 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yaml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this project 3 | title: "[Feature Request]: " 4 | labels: ["feature request"] 5 | assignees: 6 | - webdevnerdstuff 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for taking the time to fill out this request! 12 | - type: textarea 13 | id: related-to-a-problem 14 | attributes: 15 | label: Is your feature request related to a problem? Please describe. 16 | description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: feature-desc 21 | attributes: 22 | label: Describe the solution you'd like 23 | description: A clear and concise description of what you want to happen. 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: alternatives 28 | attributes: 29 | label: Describe alternatives you've considered 30 | description: A clear and concise description of any alternative solutions or features you've considered. 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: additional-context 35 | attributes: 36 | label: Additional context 37 | description: Add any other context about the problem here. 38 | - type: checkboxes 39 | id: terms 40 | attributes: 41 | label: Code of Conduct 42 | description: By submitting this request, you agree to follow our [Code of Conduct](https://github.com/webdevnerdstuff/vuetify3-resize-drawer/blob/main/CODE_OF_CONDUCT.md) 43 | options: 44 | - label: I agree to follow this project's Code of Conduct 45 | required: true 46 | -------------------------------------------------------------------------------- /.github/workflows/inactive-issues.yaml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v3 11 | with: 12 | days-before-issue-stale: 30 13 | days-before-issue-close: 14 14 | stale-issue-label: "stale" 15 | stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." 16 | close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." 17 | days-before-pr-stale: -1 18 | days-before-pr-close: -1 19 | repo-token: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy to GitHub Pages 3 | 4 | on: 5 | push: 6 | branches: [ main ] 7 | 8 | permissions: 9 | contents: write 10 | pages: write 11 | id-token: write 12 | 13 | jobs: 14 | # Build job 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup pnpm 22 | uses: pnpm/action-setup@v4 23 | with: 24 | version: 9 25 | 26 | - name: Install dependencies 27 | run: pnpm install --g gh-pages && pnpm install 28 | 29 | - name: Build Docs 30 | run: pnpm build:docs 31 | 32 | - name: Display structure of built files 33 | run: ls -R ./docs 34 | 35 | - name: Upload artifact 36 | uses: actions/upload-pages-artifact@v3 37 | with: 38 | name: github-pages 39 | path: ./docs 40 | 41 | # Deploy job 42 | deploy: 43 | environment: 44 | name: github-pages 45 | url: ${{ steps.deployment.outputs.page_url }} 46 | runs-on: ubuntu-latest 47 | needs: build 48 | 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v4 52 | 53 | - name: Download the build folder 54 | uses: actions/download-artifact@v4 55 | with: 56 | name: github-pages 57 | path: ./docs 58 | 59 | - name: Display structure of downloaded files 60 | run: ls -R ./docs 61 | 62 | - name: Deploy to GitHub Pages 63 | id: deployment 64 | uses: actions/deploy-pages@v4 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | docs 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | .history/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | 27 | .eslintcache 28 | 29 | src/plugin/**/*.bk.* 30 | 31 | 32 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | npx lint-staged && npm run test:build 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .github 3 | .history 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | node-version=22.15.0 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.15.0 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | singleQuote: true, 4 | trailingComma: 'all', 5 | }; 6 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "commitMessage": "chore: release ${version}", 4 | "tagName": "v${version}" 5 | }, 6 | "npm": { 7 | "publish": true 8 | }, 9 | "github": { 10 | "release": true, 11 | "releaseName": "v${version}" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to the "vuetify-resize-drawer" plugin will be documented in this file. 3 | 4 | ## v3.2.0 5 | 2024-10-14 6 | [main] (@webdevnerdstuff) 7 | * Adding `bottom` and `top` location support 8 | * Fix issue with handle mouse position 9 | * Change `width-snap-back` prop to `snap-back` (`width-snap-back` is backwards compatible) 10 | * Add `max-height` and `min-height` props 11 | * Update packages 12 | 13 | ## v3.1.6 14 | 2024-03-14 15 | [main] (@webdevnerdstuff) 16 | * Add another null check 17 | 18 | ## v3.1.5 19 | 2024-03-14 20 | [main] (@webdevnerdstuff) 21 | * Add null check 22 | 23 | ## v3.1.4 24 | 2024-03-13 25 | [main] (@webdevnerdstuff) 26 | * Change component to use `defineAsyncComponent` 27 | 28 | ## v3.1.3 29 | 2024-02-26 30 | [main] (@webdevnerdstuff) 31 | * Fix external rollup config 32 | * Update packages 33 | 34 | ## v3.1.2 35 | 2024-01-11 36 | [main] (@webdevnerdstuff) 37 | * Minor style adjustments 38 | * Update packages 39 | 40 | ## v3.1.0 41 | 2024-01-09 42 | [main] (@webdevnerdstuff) 43 | * Fix typescript issues 44 | * Reorganizing 45 | * Add vitest 46 | * Update node version to v20.10.0 47 | 48 | ## v3.0.0 49 | 2023-11-10 50 | [main] (@webdevnerdstuff) 51 | * Merging in vuetify3-resize-drawer 52 | * Changing package to an npm organization package 53 | * Updating release version to be more inline with vue/vuetify 3 versions 54 | * The change from v2.1.1 to v3.0.0 is not a breaking change, only a version change 55 | 56 | ## v2.1.1 57 | 2023-10-02 58 | [main] (@webdevnerdstuff) 59 | * Fix build config to exclude Vuetify styles from being included in the build 60 | 61 | ## v2.1.0 62 | 2023-08-24 63 | [main] (@webdevnerdstuff) 64 | * Add @container classes to provide Vuetify grid class support for the drawer content, enhancing responsiveness without being constrained by screen size. 65 | * Add Grid System section to documentation 66 | * Add touch support for mobile devices 67 | * Add emitted events for `drawer:mouseenter`, `drawer:mouseleave`, `handle:touchend`, `handle:touchmove`, and `handle:touchstart` 68 | * Add `touchless` support to hide handle on mobile devices. This prop does not function the same as the prop which is not supported. 69 | * Fix offsetWidth value that was returning the resized value (possible breaking change if you used emitted events to get the offsetWidth value) 70 | * Fix issue with resized values being negative, returning positive values 71 | * Fix width values that passed min/max width boundaries 72 | * Fix issue with incorrect widths when min/max values set as percentage 73 | * Fix issue with widthSnapBack prop preventing drawer resizing 74 | * Fix `close` event not being emitted when drawer is closed 75 | * Fix `update:modelValue` event not being emitted when model value was updated 76 | * Update Vuetify package to v3.3.14 77 | * Update other packages to latest versions 78 | * Update Playground template to include grid drawer for testing 79 | 80 | ## v2.0.3 81 | 2023-08-01 82 | [main] (@webdevnerdstuff) 83 | * Add check for fasvg 84 | * Add important to fa icon size 85 | 86 | ## v2.0.2 87 | 2023-06-02 88 | [main] (@webdevnerdstuff) 89 | * Update to turn off transition duration when resizing 90 | 91 | ## v2.0.0 92 | 2023-06-02 93 | [main] (@webdevnerdstuff) 94 | * Update `handleColor` prop to a string from an object. Now accepts various color values (variables, theme color names, color names, hex, rgb, rgba, etc) 95 | * Add `maxWidth` and `minWidth` props to allow for a minimum and maximum width for the drawer. 96 | * Add `snapBackWidth` prop to allow for the drawer to snap back to the `maxWidth`/`minWidth` when the drawer is closed. 97 | * Add `location` prop to allow `start` and `end` drawer locations 98 | * Add `v-icon` to be used as handle with a new prop `handleIcon` 99 | * Add better support for `mdi` and `fa` icons 100 | * Remove `top-icon` slot 101 | * Update styles 102 | * Add stronger typings 103 | * Update how typings and props are defined 104 | * Update Vuetify to current version `3.3.6` 105 | * Improve organization of code 106 | * Update documentation 107 | * Add Developer Playground 108 | 109 | ## v1.1.2 110 | 2023-07-27 111 | [main] Initial release (WebDevNerdStuff) 112 | * Fix: Add missing transform to fix drawer right direction 113 | 114 | ## v1.1.1 115 | 2023-07-27 116 | [main] Initial release (WebDevNerdStuff) 117 | * Fix build missing vuetify imports 118 | 119 | ## v1.1.0 120 | 2023-07-26 121 | [main] Initial release (WebDevNerdStuff) 122 | * Update component to be more like v3 version 123 | * Change handle to v-icon 124 | * Change handle positions to be more like v3 version 125 | * Add handle props 126 | * Add min/max width props 127 | * Remove overflow prop 128 | * Update npm packages 129 | * There may be some breaking changes in this release 130 | 131 | ## v1.0.0 132 | 2022-05-23 133 | [main] Initial release (WebDevNerdStuff) 134 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | webdevnerdstuff@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 WebDevNerdStuff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Vuetify Logo 3 |

4 | 5 |

6 |

Vuetify Resize Drawer

7 |

8 | 9 |

10 | 11 | NPM Package 12 | 13 |   14 | 15 | @WebDevNerdStuff 16 | 17 |

18 | 19 | 20 | ## Description 21 | 22 | The `vuetify-resize-drawer` component extends the functionality of the [v-navigation-drawer](https://vuetifyjs.com/en/components/navigation-drawers/) so that it is resizable by the user. 23 | 24 | 25 | ## Installation 26 | 27 | Using [pnpm](https://pnpm.io/): 28 | ``` 29 | pnpm add @wdns/vuetify-resize-drawer 30 | ``` 31 | 32 | Using npm: 33 | ``` 34 | npm i @wdns/vuetify-resize-drawer 35 | ``` 36 | 37 | ## Documentation 38 | 39 | [Documentation & Demo](https://webdevnerdstuff.github.io/vuetify-resize-drawer/) 40 | 41 | ## Dependencies 42 | 43 | [Vuetify v3](https://vuetifyjs.com/) 44 | [Vue 3](https://vuejs.org/) 45 | 46 | 47 | ## Change Log 48 | 49 | [CHANGELOG](https://github.com/webdevnerdstuff/vuetify-resize-drawer/blob/master/CHANGELOG.md) 50 | 51 | 52 | ## License 53 | 54 | Copyright (c) 2023 WebDevNerdStuff 55 | Licensed under the [MIT license](https://github.com/webdevnerdstuff/vuetify-resize-drawer/blob/master/LICENSE.md). 56 | 57 | 58 | ## Legal 59 | 60 | Vuetify and the Vuetify logo are trademarks of Vuetify. This component was not created or endorsed by Vuetify. 61 | -------------------------------------------------------------------------------- /dist/VResizeDrawer-BeVN9VIZ.mjs: -------------------------------------------------------------------------------- 1 | import o from "./vuetify-resize-drawer.es.js"; 2 | /** 3 | * @name @wdns/vuetify-resize-drawer 4 | * @version 3.2.1 5 | * @description The vuetify-resize-drawer component extends the functionality of the v-navigation-drawer so that it is resizable by the user. 6 | * @author WebDevNerdStuff & Bunnies... lots and lots of bunnies! (https://webdevnerdstuff.com) 7 | * @copyright Copyright 2025, WebDevNerdStuff 8 | * @homepage https://webdevnerdstuff.github.io/vuetify-resize-drawer/ 9 | * @repository https://github.com/webdevnerdstuff/vuetify-resize-drawer 10 | * @license MIT License 11 | */ 12 | export { 13 | o as default 14 | }; 15 | -------------------------------------------------------------------------------- /dist/VResizeDrawer-CKGjiLW1.js: -------------------------------------------------------------------------------- 1 | "use strict";/** 2 | * @name @wdns/vuetify-resize-drawer 3 | * @version 3.2.1 4 | * @description The vuetify-resize-drawer component extends the functionality of the v-navigation-drawer so that it is resizable by the user. 5 | * @author WebDevNerdStuff & Bunnies... lots and lots of bunnies! (https://webdevnerdstuff.com) 6 | * @copyright Copyright 2025, WebDevNerdStuff 7 | * @homepage https://webdevnerdstuff.github.io/vuetify-resize-drawer/ 8 | * @repository https://github.com/webdevnerdstuff/vuetify-resize-drawer 9 | * @license MIT License 10 | */Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./vuetify-resize-drawer.cjs.js");exports.default=e.default; 11 | -------------------------------------------------------------------------------- /dist/plugin/composables/classes.d.ts: -------------------------------------------------------------------------------- 1 | import { UseDrawerClasses, UseHandleContainerClasses, UseHandleIconClasses } from '../types'; 2 | export declare const useDrawerClasses: UseDrawerClasses; 3 | export declare const useHandleContainerClasses: UseHandleContainerClasses; 4 | export declare const useHandleIconClasses: UseHandleIconClasses; 5 | -------------------------------------------------------------------------------- /dist/plugin/composables/colors.d.ts: -------------------------------------------------------------------------------- 1 | import { ThemeInstance } from 'vuetify'; 2 | /** 3 | * Converts single color 4 | */ 5 | export declare const useGetColor: (color: string, theme: ThemeInstance) => string; 6 | -------------------------------------------------------------------------------- /dist/plugin/composables/helpers.d.ts: -------------------------------------------------------------------------------- 1 | import { Props, UseConvertToUnit } from '../types'; 2 | export declare const useConvertToUnit: UseConvertToUnit; 3 | export declare const useConvertToNumber: (val: string | number) => number; 4 | export declare const useUnitToPx: (valueWithUnit: Props["handleIconSize"]) => number; 5 | -------------------------------------------------------------------------------- /dist/plugin/composables/icons.d.ts: -------------------------------------------------------------------------------- 1 | import { UseGetIcon } from '../types'; 2 | export declare const useGetIcon: UseGetIcon; 3 | -------------------------------------------------------------------------------- /dist/plugin/composables/storage.d.ts: -------------------------------------------------------------------------------- 1 | import { UseSetStorage } from '../types'; 2 | export declare function useGetStorage(storageType: string, storageName: string): string | null; 3 | export declare const useSetStorage: UseSetStorage; 4 | -------------------------------------------------------------------------------- /dist/plugin/composables/styles.d.ts: -------------------------------------------------------------------------------- 1 | import { UseDrawerStyles, UseHandleContainerStyles, UseHandleIconStyles } from '../types'; 2 | export declare const iconSizes: { 3 | default: string; 4 | large: string; 5 | small: string; 6 | 'x-large': string; 7 | 'x-small': string; 8 | }; 9 | export declare const useDrawerStyles: UseDrawerStyles; 10 | export declare const useHandleContainerStyles: UseHandleContainerStyles; 11 | export declare const useHandleIconStyles: UseHandleIconStyles; 12 | -------------------------------------------------------------------------------- /dist/plugin/index.d.ts: -------------------------------------------------------------------------------- 1 | import { GlobalOptions } from './types'; 2 | import { App } from 'vue'; 3 | import { default as VResizeDrawer } from './VResizeDrawer.vue'; 4 | export declare const globalOptions: unique symbol; 5 | export declare function createVResizeDrawer(options?: GlobalOptions): { 6 | install: (app: App) => void; 7 | }; 8 | export default VResizeDrawer; 9 | export { VResizeDrawer, }; 10 | -------------------------------------------------------------------------------- /dist/plugin/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties, MaybeRef } from 'vue'; 2 | import { IconOptions, ThemeInstance } from 'vuetify'; 3 | import { VIcon, VNavigationDrawer } from 'vuetify/components'; 4 | import { default as VResizeDrawer } from '../VResizeDrawer.vue'; 5 | export * from '../index'; 6 | export type Classes = { 7 | [key: string]: boolean | undefined; 8 | }; 9 | export type EmitEventNames = 'handle:click' | 'handle:dblclick' | 'handle:drag' | 'handle:mousedown' | 'handle:mouseup' | 'handle:touchend' | 'handle:touchmove' | 'handle:touchstart'; 10 | export type StorageType = 'local' | 'session'; 11 | export type HandlePositions = 'bottom' | 'border' | 'center' | 'top'; 12 | export type DrawerLocations = 'bottom' | 'end' | 'start' | 'left' | 'right' | 'top' | undefined; 13 | type Height = number | string | undefined; 14 | export type HEXColor = string; 15 | export type HSLColor = [number, number, number]; 16 | export type RGBColor = [number, number, number]; 17 | export interface Props { 18 | absolute?: VNavigationDrawer['absolute']; 19 | expandOnHover?: VNavigationDrawer['expandOnHover']; 20 | floating?: VNavigationDrawer['floating']; 21 | handleBorderWidth?: number | string; 22 | handleColor?: string | undefined; 23 | handleIcon?: string | undefined; 24 | handleIconSize?: VIcon['size']; 25 | handlePosition?: HandlePositions; 26 | height?: Height; 27 | image?: VNavigationDrawer['image']; 28 | location?: DrawerLocations; 29 | maxHeight?: Height; 30 | maxWidth?: VNavigationDrawer['width']; 31 | minHeight?: Height; 32 | minWidth?: VNavigationDrawer['width']; 33 | modelValue?: VNavigationDrawer['modelValue']; 34 | name?: VNavigationDrawer['name']; 35 | rail?: VNavigationDrawer['rail']; 36 | railWidth?: VNavigationDrawer['railWidth']; 37 | resizable?: boolean | undefined; 38 | saveHeight?: boolean | undefined; 39 | saveWidth?: boolean | undefined; 40 | snapBack?: boolean | undefined; 41 | storageName?: string | undefined; 42 | storageType?: StorageType; 43 | tag?: VNavigationDrawer['tag']; 44 | temporary?: VNavigationDrawer['temporary']; 45 | theme?: VNavigationDrawer['theme']; 46 | touchless?: boolean | undefined; 47 | width?: VNavigationDrawer['width']; 48 | widthSnapBack?: boolean | undefined; 49 | } 50 | export interface GlobalOptions extends Props { 51 | } 52 | export interface UseConvertToUnit { 53 | (options: { 54 | unit?: string; 55 | value: string | number; 56 | }): string | void; 57 | } 58 | export interface UseSetStorage { 59 | (options: { 60 | action?: string; 61 | resizedAmount?: MaybeRef; 62 | resizedWidth?: MaybeRef; 63 | storageType?: StorageType; 64 | storageName?: Props['storageName']; 65 | saveAmount?: Props['saveWidth'] | Props['saveHeight']; 66 | rail?: Props['rail']; 67 | }): void; 68 | } 69 | export interface UseDrawerClasses { 70 | (options: { 71 | absolute?: Props['absolute']; 72 | expandOnHover?: Props['expandOnHover']; 73 | floating?: Props['floating']; 74 | isMouseover?: MaybeRef; 75 | location?: Props['location']; 76 | rail?: Props['rail']; 77 | railWidth?: Props['railWidth']; 78 | temporary?: Props['temporary']; 79 | }): object; 80 | } 81 | export interface UseHandleContainerClasses { 82 | (options: { 83 | handlePosition?: Props['handlePosition']; 84 | drawerLocation?: Props['location']; 85 | }): object; 86 | } 87 | export interface UseHandleIconClasses { 88 | (options: { 89 | handlePosition?: Props['handlePosition']; 90 | iconOptions?: IconOptions; 91 | isUserIcon?: boolean; 92 | drawerLocation?: Props['location']; 93 | }): object; 94 | } 95 | export interface UseDrawerStyles { 96 | (options: { 97 | isMouseDown?: MaybeRef; 98 | location?: Props['location']; 99 | maxHeight?: Props['maxHeight']; 100 | minHeight?: Props['minHeight']; 101 | maxWidth?: Props['maxWidth']; 102 | minWidth?: Props['minWidth']; 103 | rail?: Props['rail']; 104 | railWidth?: Props['railWidth']; 105 | resizedAmount: MaybeRef; 106 | snapBack?: Props['snapBack']; 107 | }): CSSProperties; 108 | } 109 | export interface UseHandleContainerStyles { 110 | (options: { 111 | borderWidth?: Props['handleBorderWidth']; 112 | handleColor?: Props['handleColor']; 113 | iconSize?: Props['handleIconSize']; 114 | iconSizeUnit?: number; 115 | location?: Props['location']; 116 | position?: Props['handlePosition']; 117 | theme: ThemeInstance; 118 | }): CSSProperties; 119 | } 120 | export interface UseHandleIconStyles { 121 | (options: { 122 | color?: Props['handleColor']; 123 | theme: ThemeInstance; 124 | }): CSSProperties; 125 | } 126 | export interface UseGetIcon { 127 | (options: { 128 | icon: Props['handleIcon']; 129 | iconOptions: IconOptions | undefined; 130 | name: Props['handlePosition']; 131 | }): Props['handleIcon']; 132 | } 133 | declare module 'vue' { 134 | interface ComponentCustomProperties { 135 | } 136 | interface GlobalComponents { 137 | VResizeDrawer: typeof VResizeDrawer; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /dist/plugin/utils/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare const defaultWidth = 256; 2 | declare const componentName = "v-resize-drawer"; 3 | export { defaultWidth, componentName, }; 4 | -------------------------------------------------------------------------------- /dist/plugin/utils/props.d.ts: -------------------------------------------------------------------------------- 1 | import { Props } from '../types'; 2 | export declare const AllProps: Props; 3 | -------------------------------------------------------------------------------- /dist/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:map'; 2 | @use 'sass:math'; 3 | @use 'vuetify/tools'; 4 | @use 'vuetify/settings'; 5 | 6 | /* 7 | * @source vuetify/packages/vuetify/src/styles/tools/_functions.sass 8 | */ 9 | @function breakpoint-min($name, $breakpoints) { 10 | $min: map.get($breakpoints, $name); 11 | @return if($min !=0, $min, null); 12 | } 13 | 14 | /* 15 | * @source vuetify/packages/vuetify/src/styles/tools/_display.sass 16 | */ 17 | @mixin media-breakpoint-up($name, $breakpoints: settings.$grid-breakpoints) { 18 | $min: breakpoint-min($name, $breakpoints); 19 | 20 | @if $min { 21 | @container v-resize-drawer (min-width: #{$min}) { 22 | @content; 23 | } 24 | } 25 | } 26 | 27 | /* 28 | * @source vuetify/packages/vuetify/src/components/VGrid/_mixins.sass 29 | */ 30 | @mixin make-col($size, $columns: settings.$grid-columns) { 31 | flex: 0 0 math.percentage(math.div($size, $columns)); 32 | max-width: math.percentage(math.div($size, $columns)); 33 | } 34 | 35 | @mixin make-grid-columns($columns: settings.$grid-columns, $gutter: settings.$grid-gutter, $breakpoints: settings.$grid-breakpoints) { 36 | @each $breakpoint in map.keys($breakpoints) { 37 | $infix: tools.breakpoint-infix($breakpoint, $breakpoints); 38 | 39 | @include media-breakpoint-up($breakpoint, $breakpoints) { 40 | @for $i from 1 through $columns { 41 | .v-col#{$infix}-#{$i} { 42 | @include make-col($i, $columns); 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | /* 50 | * @source vuetify/packages/vuetify/src/styles/utilities/_display.sass 51 | */ 52 | @mixin make-grid-columns-hidden() { 53 | $display: map.get(settings.$utilities, 'display'); 54 | 55 | @each $size, 56 | $media_query in settings.$display-breakpoints { 57 | @container v-resize-drawer #{$media_query} { 58 | .hidden-#{$size} { 59 | display: none !important; 60 | } 61 | } 62 | 63 | @container v-resize-drawer #{$media_query} { 64 | @each $val in map.get($display, 'values') { 65 | .#{map.get($display, 'class')}-#{$size}-#{$val} { 66 | display: $val !important; 67 | } 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /dist/scss/main.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:map'; 2 | @use './mixins' as *; 3 | @use 'vuetify/settings'; 4 | 5 | .v-resize-drawer { 6 | container-name: v-resize-drawer; 7 | container-type: inline-size; 8 | 9 | &--handle { 10 | &-container { 11 | align-items: center; 12 | cursor: grab; 13 | display: flex; 14 | justify-content: center; 15 | position: absolute; 16 | z-index: 1; 17 | 18 | &-icon { 19 | &-parent { 20 | &-end, 21 | &-right { 22 | left: initial; 23 | right: 0; 24 | } 25 | } 26 | 27 | &-center { 28 | &-end, 29 | &-right { 30 | transform: rotate(180deg); 31 | } 32 | } 33 | 34 | &-user-icon { 35 | transform: none; 36 | } 37 | 38 | &-fa { 39 | font-size: .7rem !important; 40 | } 41 | } 42 | 43 | &-parent { 44 | &-left, 45 | &-start, 46 | &-undefined { 47 | right: 0; 48 | } 49 | 50 | &-end, 51 | &-right { 52 | left: 0; 53 | } 54 | } 55 | 56 | &-position { 57 | &-top { 58 | top: 0; 59 | } 60 | 61 | &-center { 62 | top: 50%; 63 | transform: translateY(-50%); 64 | } 65 | 66 | &-bottom { 67 | bottom: 0; 68 | } 69 | 70 | &-border { 71 | cursor: col-resize; 72 | height: 100%; 73 | top: 0; 74 | width: 8px; 75 | } 76 | } 77 | } 78 | } 79 | 80 | &--bottom { 81 | transition: min-height 0.3s; 82 | 83 | .v-resize-drawer--handle-container { 84 | &-position { 85 | &-center { 86 | left: 50%; 87 | top: 0; 88 | transform: translateX(-50%); 89 | } 90 | 91 | &-border { 92 | cursor: row-resize; 93 | left: 0%; 94 | top: 0 !important; 95 | width: 100% !important; 96 | } 97 | } 98 | } 99 | } 100 | 101 | &--top { 102 | top: 0 !important; 103 | transition: min-height 0.3s; 104 | 105 | .v-resize-drawer--handle-container { 106 | &-position { 107 | &-center { 108 | bottom: 1px; 109 | left: 50%; 110 | top: unset; 111 | transform: translateX(-50%); 112 | } 113 | 114 | &-border { 115 | bottom: 0 !important; 116 | cursor: row-resize; 117 | left: 0%; 118 | top: unset; 119 | width: 100% !important; 120 | } 121 | } 122 | } 123 | } 124 | 125 | &--bottom, 126 | &--top { 127 | .v-navigation-drawer__content { 128 | flex: 1 1 auto; 129 | position: relative; 130 | } 131 | } 132 | } 133 | 134 | 135 | @container v-resize-drawer (width > #{map.get(settings.$grid-breakpoints, 'xs')}) and (max-width: #{map.get(settings.$grid-breakpoints, 'sm') - 0.02}) { 136 | .v-col { 137 | &-xs-12 { 138 | flex: 0 0 100% !important; 139 | flex-basis: 0; 140 | flex-grow: 1; 141 | max-width: 100% !important; 142 | } 143 | } 144 | } 145 | 146 | // Grid Columns // 147 | @include make-grid-columns; 148 | 149 | // Hidden & d-#{size}-#{display-value} Columns // 150 | @include make-grid-columns-hidden; 151 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // eslint.config.js 2 | import eslint from '@eslint/js'; 3 | import wdnsConfig from '@wdns/eslint-config-wdns'; 4 | import tseslint from 'typescript-eslint'; 5 | import AutoImportJson from './.eslintrc-auto-import.json' with { type: 'json' }; 6 | 7 | 8 | export default tseslint.config( 9 | eslint.configs.recommended, 10 | ...tseslint.configs.recommended, 11 | ...wdnsConfig, 12 | { 13 | ignores: [ 14 | '**/cypress/**', 15 | '**/*.cy.ts', 16 | 'cypress.config.ts', 17 | 'vite.build.config.mts', 18 | 'vite.config.mts', 19 | 'src/playground/configs/templates/PlaygroundPage.vue', 20 | ], 21 | }, 22 | { 23 | name: 'app/files-to-lint', 24 | files: ['**/*.{ts,mts,tsx,vue}'], 25 | languageOptions: { 26 | ...AutoImportJson, 27 | ecmaVersion: 'latest', 28 | sourceType: 'module', 29 | parserOptions: { 30 | parser: tseslint.parser, 31 | project: ['./tsconfig.json', './tsconfig.node.json'], 32 | extraFileExtensions: ['.vue'], 33 | sourceType: 'module', 34 | }, 35 | }, 36 | plugins: { 37 | 'typescript-eslint': tseslint.plugin, 38 | }, 39 | settings: { 40 | 'import/resolver': { 41 | typescript: { 42 | alwaysTryTypes: true, 43 | project: './tsconfig.json', 44 | }, 45 | }, 46 | }, 47 | }, 48 | ); 49 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 17 | 18 | 19 | 24 | 28 | Vuetify 3 Resize Drawer 29 | 30 | 34 | 38 | 42 | 46 | 50 | 54 | 55 | 56 | 57 |
58 | 62 | 63 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wdns/vuetify-resize-drawer", 3 | "version": "3.2.1", 4 | "description": "The vuetify-resize-drawer component extends the functionality of the v-navigation-drawer so that it is resizable by the user.", 5 | "private": false, 6 | "publishConfig": { 7 | "access": "public" 8 | }, 9 | "main": "dist/vuetify-resize-drawer.cjs.js", 10 | "module": "dist/vuetify-resize-drawer.es.js", 11 | "types": "dist/plugin/types/index.d.ts", 12 | "scripts": { 13 | "dev": "NODE_OPTIONS='--no-warnings' vite", 14 | "watch": "pnpm dev", 15 | "play": "sh src/playground/configs/build.sh && NODE_ENV=playground pnpm dev", 16 | "build": "tsc -p tsconfig.build.json && npm run test:build && vite build --config vite.build.config.mts", 17 | "build:docs": "vite build", 18 | "predeploy": "npm run build", 19 | "deploy": "gh-pages -d docs", 20 | "prepublishOnly": "npm run build", 21 | "lint": "eslint src/**/*.{ts,vue} --max-warnings 20", 22 | "prepare": "husky install", 23 | "test:dev": "NODE_OPTIONS='--no-warnings' vitest", 24 | "test:all": "vitest --run", 25 | "test:build": "vitest --run --bail 1", 26 | "cy:run": "npx cypress run --headless --browser electron", 27 | "cy:run:dev": "cypress run --browser chrome", 28 | "cy:open": "cypress open --browser chrome" 29 | }, 30 | "lint-staged": { 31 | "src/**/*.{js,ts,vue}": [ 32 | "npm run lint" 33 | ] 34 | }, 35 | "author": "WebDevNerdStuff & Bunnies... lots and lots of bunnies! (https://webdevnerdstuff.com)", 36 | "funding": [ 37 | { 38 | "type": "paypal", 39 | "url": "https://paypal.me/webdevnerdstuff" 40 | }, 41 | { 42 | "type": "patreon", 43 | "url": "https://www.patreon.com/WebDevNerdStuff" 44 | } 45 | ], 46 | "license": "MIT", 47 | "files": [ 48 | "dist/*", 49 | "LICENSE.md", 50 | "README.md" 51 | ], 52 | "repository": "https://github.com/webdevnerdstuff/vuetify-resize-drawer", 53 | "bugs": { 54 | "url": "https://github.com/webdevnerdstuff/vuetify-resize-drawer/issues" 55 | }, 56 | "homepage": "https://webdevnerdstuff.github.io/vuetify-resize-drawer/", 57 | "keywords": [ 58 | "vuetify-resize-drawer", 59 | "vuetifyResizeDrawer", 60 | "v-resize-drawer", 61 | "vResizeDrawer", 62 | "vuetify", 63 | "vuetify3", 64 | "navigation drawer", 65 | "v-navigation-drawer", 66 | "VNavigationDrawer", 67 | "api", 68 | "drawer", 69 | "resize", 70 | "resizable", 71 | "vue", 72 | "vue2", 73 | "component", 74 | "javascript", 75 | "webdevnerdstuff", 76 | "wdns" 77 | ], 78 | "dependencies": { 79 | "vue": "^3.5.12", 80 | "vuetify": "^3.7.4" 81 | }, 82 | "devDependencies": { 83 | "@eslint/js": "^9.14.0", 84 | "@fortawesome/fontawesome-svg-core": "^6.6.0", 85 | "@fortawesome/free-brands-svg-icons": "^6.6.0", 86 | "@fortawesome/free-regular-svg-icons": "^6.6.0", 87 | "@fortawesome/free-solid-svg-icons": "^6.6.0", 88 | "@fortawesome/vue-fontawesome": "^3.0.8", 89 | "@mdi/font": "^7.4.47", 90 | "@rollup/plugin-commonjs": "^28.0.1", 91 | "@rollup/plugin-node-resolve": "^15.3.0", 92 | "@rollup/plugin-terser": "^0.4.4", 93 | "@stylistic/stylelint-plugin": "^3.1.1", 94 | "@types/jest": "^29.5.14", 95 | "@types/node": "^22.9.0", 96 | "@typescript-eslint/eslint-plugin": "^8.13.0", 97 | "@typescript-eslint/parser": "^8.13.0", 98 | "@vitejs/plugin-vue": "^5.1.4", 99 | "@vue/cli-service": "^5.0.8", 100 | "@vue/eslint-config-typescript": "^14.1.3", 101 | "@vue/test-utils": "^2.4.6", 102 | "@wdns/eslint-config-wdns": "^1.0.11", 103 | "@wdns/stylelint-config-wdns": "^1.0.0", 104 | "@wdns/vue-code-block": "^2.3.3", 105 | "autoprefixer": "^10.4.20", 106 | "cypress": "^13.15.2", 107 | "eslint": "^9.14.0", 108 | "eslint-config-prettier": "^9.1.0", 109 | "eslint-import-resolver-typescript": "^3.6.3", 110 | "eslint-plugin-vue": "^9.30.0", 111 | "gh-pages": "^6.2.0", 112 | "husky": "^9.1.6", 113 | "jsdom": "^25.0.1", 114 | "lint-staged": "^15.2.10", 115 | "pinia": "^2.2.6", 116 | "postcss": "^8.4.48", 117 | "postcss-html": "^1.7.0", 118 | "postcss-scss": "^4.0.9", 119 | "prettier": "^3.3.3", 120 | "prismjs": "^1.29.0", 121 | "roboto-fontface": "^0.10.0", 122 | "rollup": "^4.25.0", 123 | "rollup-plugin-polyfill-node": "^0.13.0", 124 | "rollup-plugin-postcss": "^4.0.2", 125 | "rollup-plugin-scss": "^4.0.0", 126 | "rollup-plugin-typescript2": "^0.36.0", 127 | "sass": "^1.80.6", 128 | "stylelint": "^16.10.0", 129 | "stylelint-config-standard": "^36.0.1", 130 | "stylelint-order": "^6.0.4", 131 | "stylelint-scss": "^6.8.1", 132 | "typescript": "^5.6.3", 133 | "typescript-eslint": "^8.13.0", 134 | "unplugin-auto-import": "^0.18.3", 135 | "vite": "^5.4.10", 136 | "vite-plugin-css-injected-by-js": "^3.5.2", 137 | "vite-plugin-dts": "^4.3.0", 138 | "vite-plugin-eslint2": "^5.0.2", 139 | "vite-plugin-static-copy": "^2.1.0", 140 | "vite-plugin-stylelint": "^5.3.0", 141 | "vite-plugin-vue-devtools": "^7.6.3", 142 | "vite-plugin-vuetify": "^2.0.4", 143 | "vitest": "^2.1.4", 144 | "vue-tsc": "^2.1.10", 145 | "webfontloader": "^1.6.28" 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vuetify 3 Resize Drawer 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /public/vuetify-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 146 | 147 | 304 | 305 | 383 | 384 | 405 | -------------------------------------------------------------------------------- /src/assets/vuetify-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/documentation/DocsPage.vue: -------------------------------------------------------------------------------- 1 | 107 | 108 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /src/documentation/components/MenuComponent.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 71 | 72 | 83 | -------------------------------------------------------------------------------- /src/documentation/components/PropsTable.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/documentation/layout/AppBar.vue: -------------------------------------------------------------------------------- 1 | 107 | 108 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /src/documentation/sections/DependenciesSection.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 40 | -------------------------------------------------------------------------------- /src/documentation/sections/DescriptionSection.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 35 | -------------------------------------------------------------------------------- /src/documentation/sections/EventsSection.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 72 | 157 | -------------------------------------------------------------------------------- /src/documentation/sections/ExampleSection.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 93 | -------------------------------------------------------------------------------- /src/documentation/sections/GridSection.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/documentation/sections/LegalSection.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /src/documentation/sections/LicenseSection.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 38 | -------------------------------------------------------------------------------- /src/documentation/sections/PlaygroundSection.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 91 | -------------------------------------------------------------------------------- /src/documentation/sections/PropsSection.vue: -------------------------------------------------------------------------------- 1 | 254 | 255 | 303 | 304 | 344 | -------------------------------------------------------------------------------- /src/documentation/sections/SassVariablesSection.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 107 | -------------------------------------------------------------------------------- /src/documentation/sections/SlotsSection.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 89 | -------------------------------------------------------------------------------- /src/documentation/sections/UsageSection.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 94 | -------------------------------------------------------------------------------- /src/documentation/sections/index.js: -------------------------------------------------------------------------------- 1 | import DependenciesSection from './DependenciesSection.vue'; 2 | import DescriptionSection from './DescriptionSection.vue'; 3 | import EventsSection from './EventsSection.vue'; 4 | import ExampleSection from './ExampleSection.vue'; 5 | import GridSection from './GridSection.vue'; 6 | import LegalSection from './LegalSection.vue'; 7 | import LicenseSection from './LicenseSection.vue'; 8 | import PlaygroundSection from './PlaygroundSection.vue'; 9 | import PropsSection from './PropsSection.vue'; 10 | import SassVariablesSection from './SassVariablesSection.vue'; 11 | import SlotsSection from './SlotsSection.vue'; 12 | import UsageSection from './UsageSection.vue'; 13 | 14 | 15 | export { 16 | DependenciesSection, 17 | DescriptionSection, 18 | EventsSection, 19 | ExampleSection, 20 | GridSection, 21 | LegalSection, 22 | LicenseSection, 23 | PlaygroundSection, 24 | PropsSection, 25 | SassVariablesSection, 26 | SlotsSection, 27 | UsageSection, 28 | }; 29 | -------------------------------------------------------------------------------- /src/documentation/types/docs.d.ts: -------------------------------------------------------------------------------- 1 | import { Props } from '../../plugin/types'; 2 | 3 | 4 | declare namespace Docs { 5 | export interface KeyStringAny { 6 | [key: string]: T; 7 | } 8 | 9 | export interface DrawerOptions extends Props { 10 | absolute: boolean; 11 | color: string; 12 | dark: boolean; 13 | elevation: number; 14 | widthSnapBack?: boolean | undefined; 15 | } 16 | 17 | export interface Links { 18 | changeLog: string; 19 | github: string; 20 | githubProfile: string; 21 | license: string; 22 | npm: string; 23 | pnpm: string; 24 | vResizeDrawer: string; 25 | vue: string; 26 | vuetify: string; 27 | vuetifyGithub: string; 28 | } 29 | 30 | export interface CodeBlockSettings { 31 | plugin: string; 32 | theme: string; 33 | } 34 | 35 | export interface GlobalClasses extends KeyStringAny { 36 | appLink: string; 37 | h2: string; 38 | h3: string; 39 | h4: string; 40 | headerA: string; 41 | } 42 | 43 | export interface MenuItem { 44 | href?: string; 45 | icon?: string; 46 | items?: MenuItem[]; 47 | key?: string; 48 | link?: string; 49 | title: string; 50 | topTitle?: string; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/libraries/fontawesome.ts: -------------------------------------------------------------------------------- 1 | import { library } from '@fortawesome/fontawesome-svg-core'; 2 | import { fab } from '@fortawesome/free-brands-svg-icons'; 3 | import { far } from '@fortawesome/free-regular-svg-icons'; 4 | import { fas } from '@fortawesome/free-solid-svg-icons'; 5 | 6 | library.add( 7 | fab, 8 | fas, 9 | far, 10 | ); 11 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import '@/libraries/fontawesome'; 2 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; 3 | import { createVCodeBlock } from '@wdns/vue-code-block'; 4 | import { createPinia } from 'pinia'; 5 | import { createApp } from 'vue'; 6 | import App from './App.vue'; 7 | import { createVResizeDrawer } from './plugin/index'; 8 | import { registerPlugins } from './plugins'; 9 | 10 | 11 | const app = createApp(App); 12 | 13 | app.use(createVCodeBlock()); 14 | app.use(createVResizeDrawer()); 15 | app.use(createPinia()); 16 | 17 | app.component('font-awesome-icon', FontAwesomeIcon); 18 | app.component('FaIcon', FontAwesomeIcon); 19 | 20 | registerPlugins(app); 21 | 22 | app.mount('#app'); 23 | -------------------------------------------------------------------------------- /src/playground/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | 3 | !.gitignore 4 | 5 | !configs/ 6 | !configs/**/* 7 | -------------------------------------------------------------------------------- /src/playground/configs/PlaygroundApp.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 55 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/playground/configs/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export WHITE="$(printf '\033[0;37m')" 4 | export BOLD_WHITE="$(printf '\033[1;37m')" 5 | export BOLD_GREEN="$(printf '\033[1;32m')" 6 | export CHECKMARK="$(printf '\e[1;32m\xE2\x9C\x94\e[0m')" 7 | 8 | # Playground path and template # 9 | PLAYGROUND_VUE_DIR=src/playground 10 | 11 | PLAYGROUND_VUE_FILE=PlaygroundPage.vue 12 | 13 | 14 | # Check if Playground.vue file exists before trying to create it # 15 | if [ ! -f "$PLAYGROUND_VUE_DIR/$PLAYGROUND_VUE_FILE" ]; then 16 | cp "$PLAYGROUND_VUE_DIR/configs/templates/$PLAYGROUND_VUE_FILE" "$PLAYGROUND_VUE_DIR/$PLAYGROUND_VUE_FILE" 17 | 18 | echo "" 19 | echo " ${BOLD_GREEN}${CHECKMARK}${BOLD_WHITE} $PLAYGROUND_VUE_FILE file has been created.${WHITE}" 20 | echo "" 21 | fi 22 | 23 | -------------------------------------------------------------------------------- /src/playground/configs/playground.ts: -------------------------------------------------------------------------------- 1 | import '@/libraries/fontawesome'; 2 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; 3 | import { createPinia } from 'pinia'; 4 | import { createApp } from 'vue'; 5 | import { createVResizeDrawer } from '../../plugin/index'; 6 | import { registerPlugins } from '../../plugins'; 7 | import PlaygroundApp from './PlaygroundApp.vue'; 8 | 9 | 10 | const app = createApp(PlaygroundApp); 11 | 12 | app.use(createVResizeDrawer()); 13 | 14 | app.use(createPinia()); 15 | app.component('font-awesome-icon', FontAwesomeIcon); 16 | 17 | app.component('FaIcon', FontAwesomeIcon); 18 | 19 | registerPlugins(app); 20 | 21 | app.mount('#app'); 22 | -------------------------------------------------------------------------------- /src/playground/configs/templates/PlaygroundPage.vue: -------------------------------------------------------------------------------- 1 | 86 | 87 | 88 | 248 | 249 | 254 | -------------------------------------------------------------------------------- /src/plugin/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { createVResizeDrawer } from '../'; 3 | 4 | 5 | describe('Plugin Index', () => { 6 | describe('install', () => { 7 | it('should return install function', () => { 8 | const VResizeDrawer = createVResizeDrawer(); 9 | 10 | expect('install' in VResizeDrawer).toBe(true); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/plugin/composables/__tests__/__snapshots__/classes.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Classes Composable > useDrawerClasses > should return class object 1`] = ` 4 | { 5 | "v-navigation-drawer--absolute": false, 6 | "v-navigation-drawer--custom-rail": true, 7 | "v-navigation-drawer--fixed": true, 8 | "v-navigation-drawer--floating": null, 9 | "v-navigation-drawer--is-mouseover": false, 10 | "v-navigation-drawer--left": true, 11 | "v-navigation-drawer--open-on-hover": false, 12 | "v-navigation-drawer--rail": false, 13 | "v-navigation-drawer--right": false, 14 | "v-navigation-drawer--temporary": false, 15 | "v-resize-drawer": true, 16 | "v-resize-drawer--start": false, 17 | } 18 | `; 19 | 20 | exports[`Classes Composable > useHandleContainerClasses > should return class object 1`] = ` 21 | { 22 | "v-resize-drawer--handle-container": true, 23 | "v-resize-drawer--handle-container-parent-start": true, 24 | "v-resize-drawer--handle-container-position-center": true, 25 | } 26 | `; 27 | 28 | exports[`Classes Composable > useHandleContainerClasses > should return class object 2`] = ` 29 | { 30 | "v-resize-drawer--handle-container": true, 31 | "v-resize-drawer--handle-container-parent-left": true, 32 | "v-resize-drawer--handle-container-position-top": true, 33 | } 34 | `; 35 | 36 | exports[`Classes Composable > useHandleIconClasses > should return class object with fa classes as false 1`] = ` 37 | { 38 | "v-resize-drawer--handle-container-icon": true, 39 | "v-resize-drawer--handle-container-icon-fa": false, 40 | "v-resize-drawer--handle-container-icon-fa-top": false, 41 | "v-resize-drawer--handle-container-icon-top-start": true, 42 | "v-resize-drawer--handle-container-icon-user-icon": false, 43 | } 44 | `; 45 | 46 | exports[`Classes Composable > useHandleIconClasses > should return class object with fa classes as true 1`] = ` 47 | { 48 | "v-resize-drawer--handle-container-icon": true, 49 | "v-resize-drawer--handle-container-icon-center-left": true, 50 | "v-resize-drawer--handle-container-icon-fa": true, 51 | "v-resize-drawer--handle-container-icon-fa-center": true, 52 | "v-resize-drawer--handle-container-icon-user-icon": false, 53 | } 54 | `; 55 | -------------------------------------------------------------------------------- /src/plugin/composables/__tests__/classes.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { 3 | useDrawerClasses, 4 | useHandleContainerClasses, 5 | useHandleIconClasses, 6 | } from '../classes'; 7 | 8 | 9 | describe('Classes Composable', () => { 10 | describe('useDrawerClasses', () => { 11 | it('should return class object', () => { 12 | const classes = useDrawerClasses({ 13 | absolute: false, 14 | expandOnHover: false, 15 | floating: null, 16 | isMouseover: false, 17 | location: 'start', 18 | rail: false, 19 | railWidth: '8', 20 | temporary: undefined, 21 | }); 22 | 23 | expect(classes).toMatchSnapshot(); 24 | }); 25 | }); 26 | 27 | describe('useHandleContainerClasses', () => { 28 | it('should return class object', () => { 29 | const classes = useHandleContainerClasses({ 30 | drawerLocation: 'start', 31 | handlePosition: 'center', 32 | }); 33 | 34 | expect(classes).toMatchSnapshot(); 35 | }); 36 | 37 | it('should return class object', () => { 38 | const classes = useHandleContainerClasses({ 39 | drawerLocation: 'left', 40 | handlePosition: 'top', 41 | }); 42 | 43 | expect(classes).toMatchSnapshot(); 44 | }); 45 | }); 46 | 47 | describe('useHandleIconClasses', () => { 48 | it('should return class object with fa classes as false', () => { 49 | const classes = useHandleIconClasses({ 50 | drawerLocation: 'start', 51 | handlePosition: 'top', 52 | iconOptions: { 53 | defaultSet: 'mdi' 54 | }, 55 | isUserIcon: false, 56 | }); 57 | 58 | expect(classes).toMatchSnapshot(); 59 | }); 60 | 61 | it('should return class object with fa classes as true', () => { 62 | const classes = useHandleIconClasses({ 63 | drawerLocation: 'left', 64 | handlePosition: 'center', 65 | iconOptions: { 66 | defaultSet: 'fa' 67 | }, 68 | isUserIcon: false, 69 | }); 70 | 71 | expect(classes).toMatchSnapshot(); 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /src/plugin/composables/__tests__/colors.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | describe, 3 | it, 4 | expect, 5 | vi, 6 | } from 'vitest'; 7 | import { 8 | useGetColor, 9 | } from '../colors'; 10 | import vuetify from '../../../plugins/vuetify'; 11 | 12 | const theme = vuetify.theme; 13 | 14 | 15 | describe('Colors Composable', () => { 16 | describe('useGetColor', () => { 17 | it('should return color name as HSL', () => { 18 | const color = useGetColor('red', theme); 19 | expect(color).toMatchInlineSnapshot(`"hsl(0 100% 50%)"`); 20 | }); 21 | 22 | it('should return hex value as HSL', () => { 23 | const color = useGetColor('#f00', theme); 24 | expect(color).toMatchInlineSnapshot(`"hsl(0 100% 50%)"`); 25 | }); 26 | 27 | it('should return RGB value as HSL', () => { 28 | const color = useGetColor('rgb(255,0,0)', theme); 29 | expect(color).toMatchInlineSnapshot(`"hsl(0 100% 50%)"`); 30 | }); 31 | 32 | it('should return HSL value as HSL', () => { 33 | const color = useGetColor('hsl(0 100% 50%)', theme); 34 | expect(color).toMatchInlineSnapshot(`"hsl(0 100% 50%)"`); 35 | }); 36 | 37 | it('should return theme variable as an RGB', () => { 38 | const color = useGetColor('--v-theme-error', theme); 39 | expect(color).toMatchInlineSnapshot(`"rgb(var(--v-theme-error))"`); 40 | }); 41 | 42 | it('should return a non theme color option as default HSL color value', () => { 43 | const color = useGetColor('foobar', theme); 44 | 45 | expect(color).toMatchInlineSnapshot(`"hsl(0 0% 100% / 12%)"`); 46 | }); 47 | 48 | it('should return a non theme variable as default HSL color value', () => { 49 | const color = useGetColor('--v-foobar', theme); 50 | 51 | expect(color).toMatchInlineSnapshot(`"hsl(0 0% 100% / 12%)"`); 52 | }); 53 | 54 | // console.warn tests // 55 | const logSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); 56 | 57 | it('should console warn when color prop "foobar" doesn\'t exist in colors', () => { 58 | logSpy.mockReset(); 59 | 60 | useGetColor('foobar', theme); 61 | 62 | expect(logSpy).toHaveBeenCalled(); 63 | expect(logSpy).toHaveBeenCalledTimes(1); 64 | }); 65 | 66 | it('should console warn when color prop "--v-foobar" doesn\'t exist in colors', () => { 67 | logSpy.mockReset(); 68 | 69 | useGetColor('--v-foobar', theme); 70 | 71 | expect(logSpy).toHaveBeenCalled(); 72 | expect(logSpy).toHaveBeenCalledTimes(1); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /src/plugin/composables/__tests__/helpers.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { 3 | useConvertToUnit, 4 | } from '../helpers'; 5 | 6 | 7 | describe('Helpers Composable', () => { 8 | describe('useConvertToUnit', () => { 9 | it('should return string with a default px unit', () => { 10 | const unit = useConvertToUnit({ value: '10' }); 11 | expect(unit).toBe('10px'); 12 | }); 13 | 14 | it('should return number with a default px unit', () => { 15 | const unit = useConvertToUnit({ value: 10 }); 16 | expect(unit).toBe('10px'); 17 | }); 18 | 19 | it('should return string with a supplied unit', () => { 20 | const unit = useConvertToUnit({ unit: 'em', value: '10' }); 21 | expect(unit).toBe('10em'); 22 | }); 23 | 24 | it('should return number with a supplied unit', () => { 25 | const unit = useConvertToUnit({ unit: 'em', value: 10 }); 26 | expect(unit).toBe('10em'); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/plugin/composables/__tests__/icons.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { useGetIcon } from '../icons'; 3 | 4 | 5 | const iconOptions = { 6 | defaultSet: 'mdi' 7 | }; 8 | 9 | describe('Icon Composable', () => { 10 | describe('useGetIcon', () => { 11 | it('should return supplied icon value', () => { 12 | 13 | const unit = useGetIcon({ 14 | icon: 'mdi:mdi-cog', 15 | iconOptions, 16 | name: 'top', 17 | }); 18 | 19 | expect(unit).toMatchInlineSnapshot(`"mdi:mdi-cog"`); 20 | }); 21 | 22 | it('should return icon value using name', () => { 23 | const unit = useGetIcon({ 24 | icon: undefined, 25 | iconOptions, 26 | name: 'center', 27 | }); 28 | 29 | expect(unit).toMatchInlineSnapshot(`"mdi:mdi-chevron-double-right"`); 30 | }); 31 | 32 | it('throws error if vuetify defaultSet is not supplied', () => { 33 | expect(() => useGetIcon({ 34 | icon: undefined, 35 | iconOptions: {}, 36 | name: 'top', 37 | })).toThrowError('[VResizeDrawer]: No default undefined icon set found.'); 38 | }); 39 | 40 | it('throws error if supplied name not found', () => { 41 | expect(() => useGetIcon({ 42 | icon: undefined, 43 | iconOptions, 44 | name: 'foobar', 45 | })).toThrowError('[VResizeDrawer]: No foobar icon found.'); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /src/plugin/composables/__tests__/storage.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | beforeEach, 3 | describe, 4 | expect, 5 | test, 6 | vi, 7 | } from 'vitest'; 8 | import { 9 | useGetStorage, 10 | useSetStorage, 11 | } from '../storage'; 12 | 13 | 14 | const storageName = 'vuetify-resize-drawer'; 15 | 16 | vi.spyOn(Object.getPrototypeOf(window.localStorage), 'getItem'); 17 | vi.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem'); 18 | vi.spyOn(Object.getPrototypeOf(window.sessionStorage), 'getItem'); 19 | vi.spyOn(Object.getPrototypeOf(window.sessionStorage), 'setItem'); 20 | 21 | beforeEach(() => { 22 | window.localStorage.clear(); 23 | window.sessionStorage.clear(); 24 | }); 25 | 26 | describe('Storage Composable', () => { 27 | describe('localStorage', () => { 28 | test('useSetStorage - action update', () => { 29 | useSetStorage({ 30 | action: 'update', 31 | rail: false, 32 | resizedWidth: '100px', 33 | saveWidth: true, 34 | storageName, 35 | storageType: 'local', 36 | }); 37 | 38 | expect(window.localStorage.setItem).toHaveBeenCalled(); 39 | }); 40 | 41 | test('useSetStorage - action set', () => { 42 | useSetStorage({ 43 | action: 'set', 44 | rail: false, 45 | resizedWidth: '100px', 46 | saveWidth: true, 47 | storageName, 48 | storageType: 'local', 49 | }); 50 | 51 | expect(window.localStorage.setItem).toHaveBeenCalled(); 52 | }); 53 | 54 | test('useSetStorage & useGetStorage - action update', () => { 55 | useSetStorage({ 56 | action: 'update', 57 | rail: false, 58 | resizedWidth: '100px', 59 | saveWidth: true, 60 | storageName, 61 | storageType: 'local', 62 | }); 63 | 64 | expect(window.localStorage.setItem).toHaveBeenCalled(); 65 | 66 | useGetStorage('local', storageName); 67 | 68 | expect(window.localStorage.getItem).toHaveBeenCalled(); 69 | expect(window.localStorage.getItem(storageName)).toMatchInlineSnapshot(`"undefined"`); 70 | }); 71 | 72 | test('useSetStorage & useGetStorage - action set', () => { 73 | useSetStorage({ 74 | action: 'set', 75 | rail: false, 76 | resizedWidth: '100px', 77 | saveWidth: true, 78 | storageName, 79 | storageType: 'local', 80 | }); 81 | 82 | expect(window.localStorage.setItem).toHaveBeenCalled(); 83 | 84 | useGetStorage('local', storageName); 85 | 86 | expect(window.localStorage.getItem).toHaveBeenCalled(); 87 | expect(window.localStorage.getItem(storageName)).toMatchInlineSnapshot(`"undefined"`); 88 | }); 89 | 90 | test('useGetStorage that has not been set, and return null value', () => { 91 | useGetStorage('local', storageName); 92 | 93 | expect(window.localStorage.getItem).toHaveBeenCalled(); 94 | expect(window.localStorage.getItem(storageName)).toMatchInlineSnapshot(`null`); 95 | }); 96 | }); 97 | 98 | describe('sessionStorage', () => { 99 | test('useSetStorage - action update', () => { 100 | useSetStorage({ 101 | action: 'update', 102 | rail: false, 103 | resizedWidth: '100px', 104 | saveWidth: true, 105 | storageName, 106 | storageType: 'session', 107 | }); 108 | 109 | expect(window.sessionStorage.setItem).toHaveBeenCalled(); 110 | }); 111 | 112 | test('useSetStorage - action set', () => { 113 | useSetStorage({ 114 | action: 'set', 115 | rail: false, 116 | resizedWidth: '100px', 117 | saveWidth: true, 118 | storageName, 119 | storageType: 'session', 120 | }); 121 | 122 | expect(window.sessionStorage.setItem).toHaveBeenCalled(); 123 | }); 124 | 125 | test('useSetStorage & useGetStorage - action update', () => { 126 | useSetStorage({ 127 | action: 'update', 128 | rail: false, 129 | resizedWidth: '100px', 130 | saveWidth: true, 131 | storageName, 132 | storageType: 'session', 133 | }); 134 | 135 | expect(window.sessionStorage.setItem).toHaveBeenCalled(); 136 | 137 | useGetStorage('session', storageName); 138 | 139 | expect(window.sessionStorage.getItem).toHaveBeenCalled(); 140 | expect(window.sessionStorage.getItem(storageName)).toMatchInlineSnapshot(`"undefined"`); 141 | }); 142 | 143 | test('useSetStorage & useGetStorage - action set', () => { 144 | useSetStorage({ 145 | action: 'set', 146 | rail: false, 147 | resizedWidth: '100px', 148 | saveWidth: true, 149 | storageName, 150 | storageType: 'session', 151 | }); 152 | 153 | expect(window.sessionStorage.setItem).toHaveBeenCalled(); 154 | 155 | useGetStorage('session', storageName); 156 | 157 | expect(window.sessionStorage.getItem).toHaveBeenCalled(); 158 | expect(window.sessionStorage.getItem(storageName)).toMatchInlineSnapshot(`"undefined"`); 159 | }); 160 | 161 | test('useGetStorage that has not been set, and return null value', () => { 162 | useGetStorage('session', storageName); 163 | 164 | expect(window.sessionStorage.getItem).toHaveBeenCalled(); 165 | expect(window.sessionStorage.getItem(storageName)).toMatchInlineSnapshot(`null`); 166 | }); 167 | 168 | test('useSetStorage to set supplied value, and return supplied value', () => { 169 | useSetStorage({ 170 | action: 'set', 171 | rail: false, 172 | resizedWidth: '100px', 173 | saveWidth: true, 174 | storageName, 175 | storageType: 'session', 176 | }); 177 | 178 | useGetStorage('session', storageName); 179 | 180 | expect(window.sessionStorage.getItem).toHaveBeenCalled(); 181 | expect(window.sessionStorage.getItem(storageName)).toMatchInlineSnapshot(`"undefined"`); 182 | }); 183 | }); 184 | }); 185 | -------------------------------------------------------------------------------- /src/plugin/composables/__tests__/styles.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { 3 | useDrawerStyles, 4 | useHandleContainerStyles, 5 | useHandleIconStyles 6 | } from '../styles'; 7 | import vuetify from '../../../plugins/vuetify'; 8 | 9 | const theme = vuetify.theme; 10 | 11 | 12 | describe('Styles Composable', () => { 13 | describe('useDrawerStyles', () => { 14 | it('should return styles with transition if isMouseDown is false.', () => { 15 | const data = useDrawerStyles({ 16 | isMouseDown: false, 17 | maxWidth: '50%', 18 | minWidth: '56', 19 | rail: false, 20 | railWidth: '8', 21 | resizedWidth: '300', 22 | widthSnapBack: true, 23 | }); 24 | expect(data).toMatchInlineSnapshot(` 25 | { 26 | "transitionDuration": ".2s", 27 | "width": undefined, 28 | } 29 | `); 30 | }); 31 | 32 | it('should return styles with no transition if isMouseDown is true', () => { 33 | const data = useDrawerStyles({ 34 | isMouseDown: true, 35 | maxWidth: '500px', 36 | minWidth: '56px', 37 | rail: false, 38 | railWidth: '8px', 39 | resizedWidth: '256px', 40 | widthSnapBack: false, 41 | }); 42 | expect(data).toMatchInlineSnapshot(` 43 | { 44 | "transitionDuration": "0s", 45 | "width": undefined, 46 | } 47 | `); 48 | }); 49 | 50 | it('should return no styles if rail is true', () => { 51 | const data = useDrawerStyles({ 52 | isMouseDown: true, 53 | maxWidth: '500px', 54 | minWidth: '56px', 55 | rail: true, 56 | railWidth: '8px', 57 | resizedWidth: '300px', 58 | widthSnapBack: false, 59 | }); 60 | expect(data).toMatchInlineSnapshot(`{}`); 61 | }); 62 | }); 63 | 64 | 65 | describe('useHandleContainerStyles', () => { 66 | it('should return styles with a background color if position is border.', () => { 67 | const data = useHandleContainerStyles({ 68 | borderWidth: '8', 69 | handleColor: 'primary', 70 | iconSize: 'x-small', 71 | position: 'border', 72 | theme, 73 | }); 74 | expect(data).toMatchInlineSnapshot(` 75 | { 76 | "backgroundColor": "hsl(207 90% 54%)", 77 | "height": "100%", 78 | "width": "8px", 79 | } 80 | `); 81 | }); 82 | 83 | it('should return styles with a background color if position is border.', () => { 84 | const data = useHandleContainerStyles({ 85 | borderWidth: '8', 86 | handleColor: 'success', 87 | iconSize: 'x-small', 88 | position: 'border', 89 | theme, 90 | }); 91 | expect(data).toMatchInlineSnapshot(` 92 | { 93 | "backgroundColor": "hsl(122 39% 49%)", 94 | "height": "100%", 95 | "width": "8px", 96 | } 97 | `); 98 | }); 99 | 100 | it('should return styles with background color using theme variable handleColor', () => { 101 | const data = useHandleContainerStyles({ 102 | borderWidth: '8', 103 | handleColor: '--v-theme-primary', 104 | iconSize: 'x-small', 105 | position: 'border', 106 | theme, 107 | }); 108 | expect(data).toMatchInlineSnapshot(` 109 | { 110 | "backgroundColor": "rgb(var(--v-theme-primary))", 111 | "height": "100%", 112 | "width": "8px", 113 | } 114 | `); 115 | }); 116 | 117 | it('should return styles with background color using color name handleColor', () => { 118 | const data = useHandleContainerStyles({ 119 | borderWidth: '8', 120 | handleColor: 'blue', 121 | iconSize: 'x-small', 122 | position: 'border', 123 | theme, 124 | }); 125 | expect(data).toMatchInlineSnapshot(` 126 | { 127 | "backgroundColor": "hsl(240 100% 50%)", 128 | "height": "100%", 129 | "width": "8px", 130 | } 131 | `); 132 | }); 133 | 134 | it('should return styles with a transparent background color if position is not border.', () => { 135 | const data = useHandleContainerStyles({ 136 | borderWidth: '8', 137 | handleColor: 'primary', 138 | iconSize: 'x-small', 139 | position: 'center', 140 | theme, 141 | }); 142 | expect(data).toMatchInlineSnapshot(` 143 | { 144 | "backgroundColor": "transparent", 145 | "height": "undefinedpx", 146 | "transform": undefined, 147 | "width": "undefinedpx", 148 | } 149 | `); 150 | }); 151 | }); 152 | 153 | describe('useHandleIconStyles', () => { 154 | it('should return ...', () => { 155 | const data = useHandleIconStyles({ 156 | color: 'primary', 157 | theme, 158 | }); 159 | expect(data).toMatchInlineSnapshot(` 160 | { 161 | "color": "hsl(207 90% 54%)", 162 | } 163 | `); 164 | }); 165 | }); 166 | }); 167 | -------------------------------------------------------------------------------- /src/plugin/composables/classes.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UseDrawerClasses, 3 | UseHandleContainerClasses, 4 | UseHandleIconClasses, 5 | } from '@/plugin/types'; 6 | import { componentName } from '@utils/globals'; 7 | 8 | 9 | export const useDrawerClasses: UseDrawerClasses = (options) => { 10 | const { absolute = false, expandOnHover, floating, isMouseover, location, rail, railWidth, temporary } = options; 11 | 12 | let isLeft = location === 'left' || location === 'start' || typeof location === 'undefined'; 13 | 14 | if (location === 'top' || location === 'bottom') { 15 | isLeft = false; 16 | } 17 | 18 | return { 19 | [`${componentName}`]: true, 20 | [`${componentName}--${location}`]: location === 'bottom' || location === 'top', 21 | 'v-navigation-drawer--absolute': absolute ?? false, 22 | 'v-navigation-drawer--custom-rail': Number(railWidth) !== 56, 23 | 'v-navigation-drawer--fixed': !absolute, 24 | 'v-navigation-drawer--floating': floating, 25 | 'v-navigation-drawer--is-mouseover': unref(isMouseover), 26 | 'v-navigation-drawer--left': isLeft, 27 | 'v-navigation-drawer--open-on-hover': expandOnHover, 28 | 'v-navigation-drawer--rail': rail ?? false, 29 | 'v-navigation-drawer--right': location === 'right' || location === 'end', 30 | 'v-navigation-drawer--temporary': temporary || false, 31 | }; 32 | }; 33 | 34 | 35 | export const useHandleContainerClasses: UseHandleContainerClasses = (options) => { 36 | const { drawerLocation, handlePosition } = options; 37 | 38 | const classes = { 39 | [`${componentName}--handle-container`]: true, 40 | [`${componentName}--handle-container-position-${handlePosition}`]: true, 41 | [`${componentName}--handle-container-parent-${drawerLocation}`]: true, 42 | }; 43 | 44 | return classes; 45 | }; 46 | 47 | export const useHandleIconClasses: UseHandleIconClasses = (options) => { 48 | const { drawerLocation, handlePosition, iconOptions, isUserIcon } = options; 49 | 50 | const classes = { 51 | [`${componentName}--handle-container-icon`]: true, 52 | [`${componentName}--handle-container-icon-${handlePosition}-${drawerLocation}`]: true, 53 | [`${componentName}--handle-container-icon-user-icon`]: isUserIcon, 54 | [`${componentName}--handle-container-icon-fa`]: iconOptions?.defaultSet === 'fa', 55 | [`${componentName}--handle-container-icon-fa-${handlePosition}`]: iconOptions?.defaultSet === 'fa', 56 | }; 57 | 58 | return classes; 59 | }; 60 | -------------------------------------------------------------------------------- /src/plugin/composables/colors.ts: -------------------------------------------------------------------------------- 1 | import { ThemeInstance } from 'vuetify'; 2 | import { 3 | HEXColor, 4 | HSLColor, 5 | RGBColor, 6 | } from '@/plugin/types'; 7 | 8 | 9 | /** 10 | * Checks 11 | */ 12 | function checkDoNotConvert(color: string) { 13 | return color === 'transparent' || color === 'none' || color === 'inherit' || color === 'currentColor' || color === 'initial' || color === 'unset'; 14 | } 15 | 16 | function checkIfThemeVarColor(color: string) { 17 | return color.includes('--v-theme'); 18 | } 19 | 20 | function checkIfThemeColor(color: string, theme: ThemeInstance) { 21 | const themeColors = theme.global.current.value?.colors ?? {}; 22 | 23 | return Object.entries(themeColors).find(([key]) => { 24 | return key === color; 25 | }); 26 | } 27 | 28 | 29 | /** 30 | * Converts the color to HSL values 31 | */ 32 | function convertToHSL(color: string): string { 33 | let newColor: HEXColor | RGBColor | HSLColor = checkColorNames(color); 34 | let h = 0; 35 | let s = 0; 36 | let l = 0; 37 | let r = 0; 38 | let g = 0; 39 | let b = 0; 40 | 41 | // Convert hex color to RGB if necessary 42 | if (newColor.substring(0, 1) === '#') { 43 | newColor = hexToRGB(newColor); 44 | } 45 | // Convert RGB to array values if necessary 46 | else if (newColor.includes('rgb')) { 47 | newColor = [...newColor.matchAll(/\d+/g)].map(Number); 48 | } 49 | // If HSL is passed in, extract values 50 | else if (newColor.includes('hsl')) { 51 | newColor = [...newColor.matchAll(/\d+/g)].map(Number); 52 | 53 | h = newColor[0]; 54 | s = newColor[1]; 55 | l = newColor[2]; 56 | 57 | // Return HSL values 58 | return `${h} ${s}% ${l}%`; 59 | } 60 | 61 | // Extract RGB values 62 | [r, g, b] = newColor; 63 | 64 | // Convert RGB to HSL 65 | r /= 255; 66 | g /= 255; 67 | b /= 255; 68 | const max = Math.max(r, g, b); 69 | const min = Math.min(r, g, b); 70 | 71 | // Color doesn't exist, return --v-theme-surface // 72 | // eslint-disable-next-line no-constant-binary-expression 73 | if (max === null || !min === null || isNaN(max) || isNaN(min)) { 74 | const defaultColor = '0 0% 100% / 12%'; 75 | 76 | console.warn(`[VResizeDrawer]: The "color" prop value using "${newColor}" doesn't exist. Using the value "hsl(${defaultColor})" in it's place.`); 77 | return defaultColor; 78 | } 79 | 80 | h = (max + min) / 2; 81 | s = (max + min) / 2; 82 | l = (max + min) / 2; 83 | 84 | if (max == min) { 85 | h = s = 0; // achromatic 86 | } 87 | else { 88 | const d = max - min; 89 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 90 | switch (max) { 91 | case r: h = (g - b) / d + (g < b ? 6 : 0); break; 92 | case g: h = (b - r) / d + 2; break; 93 | case b: h = (r - g) / d + 4; break; 94 | default: break; 95 | } 96 | h /= 6; 97 | } 98 | 99 | 100 | h = Math.round(h * 360); 101 | s = Math.round(s * 100); 102 | l = Math.round(l * 100); 103 | 104 | // Return HSL values 105 | return `${h} ${s}% ${l}%`; 106 | } 107 | 108 | 109 | /** 110 | * Checks if the color is a color name and converts it to a hex value 111 | */ 112 | function checkColorNames(color: string): HEXColor { 113 | const colors = { 114 | AliceBlue: '#F0F8FF', 115 | AntiqueWhite: '#FAEBD7', 116 | Aqua: '#00FFFF', 117 | Aquamarine: '#7FFFD4', 118 | Azure: '#F0FFFF', 119 | Beige: '#F5F5DC', 120 | Bisque: '#FFE4C4', 121 | Black: '#000000', 122 | BlanchedAlmond: '#FFEBCD', 123 | Blue: '#0000FF', 124 | BlueViolet: '#8A2BE2', 125 | Brown: '#A52A2A', 126 | BurlyWood: '#DEB887', 127 | CadetBlue: '#5F9EA0', 128 | Chartreuse: '#7FFF00', 129 | Chocolate: '#D2691E', 130 | Coral: '#FF7F50', 131 | CornflowerBlue: '#6495ED', 132 | Cornsilk: '#FFF8DC', 133 | Crimson: '#DC143C', 134 | Cyan: '#00FFFF', 135 | DarkBlue: '#00008B', 136 | DarkCyan: '#008B8B', 137 | DarkGoldenRod: '#B8860B', 138 | DarkGray: '#A9A9A9', 139 | DarkGreen: '#006400', 140 | DarkGrey: '#A9A9A9', 141 | DarkKhaki: '#BDB76B', 142 | DarkMagenta: '#8B008B', 143 | DarkOliveGreen: '#556B2F', 144 | DarkOrange: '#FF8C00', 145 | DarkOrchid: '#9932CC', 146 | DarkRed: '#8B0000', 147 | DarkSalmon: '#E9967A', 148 | DarkSeaGreen: '#8FBC8F', 149 | DarkSlateBlue: '#483D8B', 150 | DarkSlateGray: '#2F4F4F', 151 | DarkSlateGrey: '#2F4F4F', 152 | DarkTurquoise: '#00CED1', 153 | DarkViolet: '#9400D3', 154 | DeepPink: '#FF1493', 155 | DeepSkyBlue: '#00BFFF', 156 | DimGray: '#696969', 157 | DimGrey: '#696969', 158 | DodgerBlue: '#1E90FF', 159 | FireBrick: '#B22222', 160 | FloralWhite: '#FFFAF0', 161 | ForestGreen: '#228B22', 162 | Fuchsia: '#FF00FF', 163 | Gainsboro: '#DCDCDC', 164 | GhostWhite: '#F8F8FF', 165 | Gold: '#FFD700', 166 | GoldenRod: '#DAA520', 167 | Gray: '#808080', 168 | Green: '#008000', 169 | GreenYellow: '#ADFF2F', 170 | Grey: '#808080', 171 | HoneyDew: '#F0FFF0', 172 | HotPink: '#FF69B4', 173 | IndianRed: '#CD5C5C', 174 | Indigo: '#4B0082', 175 | Ivory: '#FFFFF0', 176 | Khaki: '#F0E68C', 177 | Lavender: '#E6E6FA', 178 | LavenderBlush: '#FFF0F5', 179 | LawnGreen: '#7CFC00', 180 | LemonChiffon: '#FFFACD', 181 | LightBlue: '#ADD8E6', 182 | LightCoral: '#F08080', 183 | LightCyan: '#E0FFFF', 184 | LightGoldenRodYellow: '#FAFAD2', 185 | LightGray: '#D3D3D3', 186 | LightGreen: '#90EE90', 187 | LightGrey: '#D3D3D3', 188 | LightPink: '#FFB6C1', 189 | LightSalmon: '#FFA07A', 190 | LightSeaGreen: '#20B2AA', 191 | LightSkyBlue: '#87CEFA', 192 | LightSlateGray: '#778899', 193 | LightSlateGrey: '#778899', 194 | LightSteelBlue: '#B0C4DE', 195 | LightYellow: '#FFFFE0', 196 | Lime: '#00FF00', 197 | LimeGreen: '#32CD32', 198 | Linen: '#FAF0E6', 199 | Magenta: '#FF00FF', 200 | Maroon: '#800000', 201 | MediumAquaMarine: '#66CDAA', 202 | MediumBlue: '#0000CD', 203 | MediumOrchid: '#BA55D3', 204 | MediumPurple: '#9370DB', 205 | MediumSeaGreen: '#3CB371', 206 | MediumSlateBlue: '#7B68EE', 207 | MediumSpringGreen: '#00FA9A', 208 | MediumTurquoise: '#48D1CC', 209 | MediumVioletRed: '#C71585', 210 | MidnightBlue: '#191970', 211 | MintCream: '#F5FFFA', 212 | MistyRose: '#FFE4E1', 213 | Moccasin: '#FFE4B5', 214 | NavajoWhite: '#FFDEAD', 215 | Navy: '#000080', 216 | OldLace: '#FDF5E6', 217 | Olive: '#808000', 218 | OliveDrab: '#6B8E23', 219 | Orange: '#FFA500', 220 | OrangeRed: '#FF4500', 221 | Orchid: '#DA70D6', 222 | PaleGoldenRod: '#EEE8AA', 223 | PaleGreen: '#98FB98', 224 | PaleTurquoise: '#AFEEEE', 225 | PaleVioletRed: '#DB7093', 226 | PapayaWhip: '#FFEFD5', 227 | PeachPuff: '#FFDAB9', 228 | Peru: '#CD853F', 229 | Pink: '#FFC0CB', 230 | Plum: '#DDA0DD', 231 | PowderBlue: '#B0E0E6', 232 | Purple: '#800080', 233 | RebeccaPurple: '#663399', 234 | Red: '#FF0000', 235 | RosyBrown: '#BC8F8F', 236 | RoyalBlue: '#4169E1', 237 | SaddleBrown: '#8B4513', 238 | Salmon: '#FA8072', 239 | SandyBrown: '#F4A460', 240 | SeaGreen: '#2E8B57', 241 | SeaShell: '#FFF5EE', 242 | Sienna: '#A0522D', 243 | Silver: '#C0C0C0', 244 | SkyBlue: '#87CEEB', 245 | SlateBlue: '#6A5ACD', 246 | SlateGray: '#708090', 247 | SlateGrey: '#708090', 248 | Snow: '#FFFAFA', 249 | SpringGreen: '#00FF7F', 250 | SteelBlue: '#4682B4', 251 | Tan: '#D2B48C', 252 | Teal: '#008080', 253 | Thistle: '#D8BFD8', 254 | Tomato: '#FF6347', 255 | Turquoise: '#40E0D0', 256 | Violet: '#EE82EE', 257 | Wheat: '#F5DEB3', 258 | White: '#FFFFFF', 259 | WhiteSmoke: '#F5F5F5', 260 | Yellow: '#FFFF00', 261 | YellowGreen: '#9ACD32', 262 | }; 263 | let response = color; 264 | 265 | Object.entries(colors).forEach(([key, value]) => { 266 | if (color.toLowerCase() == key.toLowerCase()) { 267 | response = value; 268 | return; 269 | } 270 | }); 271 | 272 | return response; 273 | } 274 | 275 | 276 | /** 277 | * Converts the HEX color to RGB 278 | */ 279 | function hexToRGB(hex: string): RGBColor { 280 | let newHex = hex.replace('#', ''); 281 | 282 | // Convert 3-digit hex colors to 6-digit hex colors 283 | if (newHex.length === 3) { 284 | newHex = newHex.split('').map(char => char + char).join(''); 285 | } 286 | 287 | // Extract the red, green, and blue values from the hex string 288 | const r = parseInt(newHex.substring(0, 2), 16); 289 | const g = parseInt(newHex.substring(2, 4), 16); 290 | const b = parseInt(newHex.substring(4, 6), 16); 291 | 292 | // Return an array of the RGB values 293 | return [r, g, b]; 294 | } 295 | 296 | 297 | /** 298 | * Converts single color 299 | */ 300 | export const useGetColor = (color: string, theme: ThemeInstance): string => { 301 | if (checkDoNotConvert(color)) { 302 | return color; 303 | } 304 | 305 | if (checkIfThemeVarColor(color)) { 306 | return `rgb(var(${color}))`; 307 | } 308 | 309 | const isThemeColor = checkIfThemeColor(color, theme); 310 | 311 | if (isThemeColor) { 312 | return `hsl(${convertToHSL(isThemeColor[1])})`; 313 | } 314 | 315 | return `hsl(${convertToHSL(color)})`; 316 | }; 317 | -------------------------------------------------------------------------------- /src/plugin/composables/helpers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Props, 3 | UseConvertToUnit, 4 | } from '@/plugin/types'; 5 | 6 | 7 | /* 8 | * Converts a string to a number with a unit. 9 | */ 10 | export const useConvertToUnit: UseConvertToUnit = (options) => { 11 | const { unit = 'px', value } = options; 12 | 13 | if (value == null || value === '') { 14 | return undefined; 15 | } 16 | 17 | if (!+value) { 18 | return String(value); 19 | } 20 | 21 | return `${Number(value)}${unit}`; 22 | }; 23 | 24 | 25 | /* 26 | * Converts a string | number to a whole number founded. 27 | */ 28 | function roundToWhole(input: string | number): string | number { 29 | if (typeof input === 'string') { 30 | const regex = /(\d+(\.\d+)?)(\s*([a-zA-Z]+))?/; 31 | const match = input.match(regex); 32 | 33 | if (!match) { 34 | return input; 35 | } 36 | 37 | const numberPart = parseFloat(match[1] as string); 38 | const unitPart = match[4]; 39 | 40 | if (!isNaN(numberPart)) { 41 | const roundedNumber = Math.round(numberPart); 42 | const result = unitPart ? `${roundedNumber} ${unitPart}` : `${roundedNumber}`; 43 | return result; 44 | } 45 | 46 | return input; 47 | 48 | } 49 | else if (typeof input === 'number') { 50 | return Math.round(input); 51 | } 52 | 53 | return input; 54 | } 55 | 56 | 57 | /* 58 | * Converts a string to a number. 59 | */ 60 | export const useConvertToNumber = (val: string | number): number => { 61 | const windowsWidth = window.innerWidth; 62 | const drawerWidth = roundToWhole(val); 63 | 64 | if (typeof val === 'string' && val.includes('%')) { 65 | return Number(val.replace('%', '')) / 100 * windowsWidth; 66 | } 67 | 68 | if (typeof drawerWidth === 'string') { 69 | return Number(drawerWidth.replace(/\D/g, '')); 70 | } 71 | 72 | return drawerWidth; 73 | }; 74 | 75 | 76 | /* 77 | * Converts a unit to px. 78 | */ 79 | export const useUnitToPx = (valueWithUnit: Props['handleIconSize']): number => { 80 | if (!valueWithUnit) return 0; 81 | 82 | 83 | // check if string valueWithUnit contains px 84 | if (typeof valueWithUnit === 'number') { 85 | return valueWithUnit; 86 | } 87 | 88 | // Create a temporary element for calculating the value 89 | const tempElement = document.createElement('div'); 90 | tempElement.style.position = 'absolute'; 91 | tempElement.style.visibility = 'hidden'; 92 | tempElement.style.width = valueWithUnit as string; 93 | 94 | // Append the temporary element to the specified parent or body 95 | const targetParent = document.body; 96 | targetParent.appendChild(tempElement); 97 | 98 | // Get the computed width in px 99 | const computedValue = getComputedStyle(tempElement).width; 100 | 101 | // Remove the temporary element after calculation 102 | targetParent.removeChild(tempElement); 103 | 104 | // Return the numeric px value 105 | return parseFloat(computedValue); 106 | }; 107 | -------------------------------------------------------------------------------- /src/plugin/composables/icons.ts: -------------------------------------------------------------------------------- 1 | import { UseGetIcon } from '@/plugin/types'; 2 | 3 | const defaultIcons = { 4 | fa: { 5 | bottom: 'fas fa-grip', 6 | center: 'fas fa-angles-right', 7 | top: 'fas fa-grip', 8 | }, 9 | mdi: { 10 | bottom: 'mdi:mdi-dots-grid', 11 | center: 'mdi:mdi-chevron-double-right', 12 | top: 'mdi:mdi-dots-grid', 13 | }, 14 | }; 15 | 16 | export const useGetIcon: UseGetIcon = (options) => { 17 | const { icon, iconOptions, name } = options; 18 | 19 | if (name === 'border') { 20 | return; 21 | } 22 | 23 | if (icon) { 24 | return icon; 25 | } 26 | 27 | const defaultSet = iconOptions?.defaultSet as string ?? ''; 28 | let iconAbbv = defaultSet.toLowerCase(); 29 | 30 | iconAbbv = iconAbbv === 'fa' || iconAbbv === 'fasvg' ? 'fa' : iconAbbv; 31 | const iconSet = defaultIcons[iconAbbv]; 32 | 33 | if (!iconSet) { 34 | throw new Error(`[VResizeDrawer]: No default ${iconOptions?.defaultSet} icon set found.`); 35 | } 36 | 37 | const newIcon = iconSet[name as string]; 38 | 39 | if (!newIcon) { 40 | throw new Error(`[VResizeDrawer]: No ${name} icon found.`); 41 | } 42 | 43 | return newIcon; 44 | }; 45 | -------------------------------------------------------------------------------- /src/plugin/composables/storage.ts: -------------------------------------------------------------------------------- 1 | import { UseSetStorage } from '@/plugin/types'; 2 | 3 | 4 | export function useGetStorage(storageType: string, storageName: string): string | null { 5 | if (storageType === 'local') { 6 | return localStorage.getItem(storageName); 7 | } 8 | 9 | if (storageType === 'session') { 10 | return sessionStorage.getItem(storageName); 11 | } 12 | 13 | return ''; 14 | } 15 | 16 | 17 | export const useSetStorage: UseSetStorage = (options) => { 18 | const { action = 'update', rail, resizedAmount, saveAmount, storageName, storageType } = options; 19 | 20 | if (rail && !saveAmount) { 21 | return; 22 | } 23 | 24 | let amount = resizedAmount; 25 | amount = amount ?? undefined; 26 | 27 | if (action === 'set') { 28 | amount = useGetStorage(storageType as string, storageName as string) ?? ''; 29 | amount = amount || resizedAmount; 30 | } 31 | 32 | if (storageType === 'local') { 33 | localStorage.setItem(storageName as string, String(amount)); 34 | } 35 | 36 | if (storageType === 'session') { 37 | sessionStorage.setItem(storageName as string, String(amount)); 38 | } 39 | 40 | return; 41 | }; 42 | -------------------------------------------------------------------------------- /src/plugin/composables/styles.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UseDrawerStyles, 3 | UseHandleContainerStyles, 4 | UseHandleIconStyles, 5 | } from '@/plugin/types'; 6 | import { useGetColor } from '@composables/colors'; 7 | import { useConvertToUnit } from '@composables/helpers'; 8 | 9 | 10 | export const iconSizes = { 11 | default: '1.5em', 12 | large: '1.75em', 13 | small: '1.25em', 14 | 'x-large': '2em', 15 | 'x-small': '1em', 16 | }; 17 | 18 | 19 | // -------------------------------------------------- Drawer // 20 | export const useDrawerStyles: UseDrawerStyles = (options) => { 21 | const { isMouseDown, location, maxWidth, minWidth, rail, railWidth, resizedAmount, snapBack } = options; 22 | 23 | if (rail) { 24 | return {}; 25 | } 26 | 27 | let mountValue = rail ? railWidth : unref(resizedAmount); 28 | 29 | if (!snapBack) { 30 | if (parseInt(mountValue as string) >= parseInt(maxWidth as string)) { 31 | mountValue = parseInt(maxWidth as string); 32 | } 33 | 34 | if (parseInt(mountValue as string) <= parseInt(minWidth as string)) { 35 | mountValue = parseInt(minWidth as string); 36 | } 37 | } 38 | 39 | let response = {}; 40 | 41 | if (location === 'top' || location === 'bottom') { 42 | response = { 43 | maxHeight: `${useConvertToUnit({ value: mountValue as string }) as string} !important`, 44 | minHeight: `${useConvertToUnit({ value: mountValue as string }) as string} !important`, 45 | transitionDuration: unref(isMouseDown) ? '0s' : '.2s', 46 | width: '100%', 47 | }; 48 | } 49 | else { 50 | response = { 51 | transitionDuration: unref(isMouseDown) ? '0s' : '.2s', 52 | width: useConvertToUnit({ value: mountValue as string }) as string, 53 | }; 54 | } 55 | 56 | return response; 57 | }; 58 | 59 | 60 | // -------------------------------------------------- Handle // 61 | export const useHandleContainerStyles: UseHandleContainerStyles = (options) => { 62 | const { borderWidth, handleColor, iconSizeUnit, location, position, theme } = options; 63 | 64 | const transform = `translateX(-50%) ${location === 'top' ? 'rotate(90deg)' : 'rotate(-90deg)'}`; 65 | let height = `${iconSizeUnit}px`; 66 | let width = `${iconSizeUnit}px`; 67 | 68 | if (position === 'border') { 69 | if (location === 'bottom' || location === 'top') { 70 | height = useConvertToUnit({ value: borderWidth as string }) as string; 71 | } 72 | else { 73 | height = '100%'; 74 | width = useConvertToUnit({ value: borderWidth as string }) as string; 75 | } 76 | 77 | return { 78 | backgroundColor: useGetColor(handleColor as string, theme), 79 | height, 80 | width, 81 | }; 82 | } 83 | 84 | return { 85 | backgroundColor: 'transparent', 86 | height: height, 87 | transform: location === 'top' || location === 'bottom' ? transform : undefined, 88 | width, 89 | }; 90 | }; 91 | 92 | 93 | export const useHandleIconStyles: UseHandleIconStyles = (options) => { 94 | const { color, theme } = options; 95 | 96 | const handleColor = useGetColor(color as string, theme); 97 | 98 | const styles = { 99 | color: handleColor as string, 100 | }; 101 | 102 | return styles; 103 | }; 104 | -------------------------------------------------------------------------------- /src/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import type { GlobalOptions } from './types'; 2 | import type { App } from 'vue'; 3 | import './styles/main.scss'; 4 | import VResizeDrawer from './VResizeDrawer.vue'; 5 | 6 | 7 | export const globalOptions = Symbol(); 8 | 9 | export function createVResizeDrawer(options: GlobalOptions = {}) { 10 | const install = (app: App) => { 11 | app.provide(globalOptions, options); 12 | 13 | app.component('VResizeDrawer', defineAsyncComponent(() => import('./VResizeDrawer.vue'))); 14 | }; 15 | 16 | return { 17 | install, 18 | }; 19 | } 20 | 21 | export default VResizeDrawer; 22 | 23 | export { 24 | VResizeDrawer, 25 | }; 26 | -------------------------------------------------------------------------------- /src/plugin/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:map'; 2 | @use 'sass:math'; 3 | @use 'vuetify/tools'; 4 | @use 'vuetify/settings'; 5 | 6 | /* 7 | * @source vuetify/packages/vuetify/src/styles/tools/_functions.sass 8 | */ 9 | @function breakpoint-min($name, $breakpoints) { 10 | $min: map.get($breakpoints, $name); 11 | @return if($min !=0, $min, null); 12 | } 13 | 14 | /* 15 | * @source vuetify/packages/vuetify/src/styles/tools/_display.sass 16 | */ 17 | @mixin media-breakpoint-up($name, $breakpoints: settings.$grid-breakpoints) { 18 | $min: breakpoint-min($name, $breakpoints); 19 | 20 | @if $min { 21 | @container v-resize-drawer (min-width: #{$min}) { 22 | @content; 23 | } 24 | } 25 | } 26 | 27 | /* 28 | * @source vuetify/packages/vuetify/src/components/VGrid/_mixins.sass 29 | */ 30 | @mixin make-col($size, $columns: settings.$grid-columns) { 31 | flex: 0 0 math.percentage(math.div($size, $columns)); 32 | max-width: math.percentage(math.div($size, $columns)); 33 | } 34 | 35 | @mixin make-grid-columns($columns: settings.$grid-columns, $gutter: settings.$grid-gutter, $breakpoints: settings.$grid-breakpoints) { 36 | @each $breakpoint in map.keys($breakpoints) { 37 | $infix: tools.breakpoint-infix($breakpoint, $breakpoints); 38 | 39 | @include media-breakpoint-up($breakpoint, $breakpoints) { 40 | @for $i from 1 through $columns { 41 | .v-col#{$infix}-#{$i} { 42 | @include make-col($i, $columns); 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | /* 50 | * @source vuetify/packages/vuetify/src/styles/utilities/_display.sass 51 | */ 52 | @mixin make-grid-columns-hidden() { 53 | $display: map.get(settings.$utilities, 'display'); 54 | 55 | @each $size, 56 | $media_query in settings.$display-breakpoints { 57 | @container v-resize-drawer #{$media_query} { 58 | .hidden-#{$size} { 59 | display: none !important; 60 | } 61 | } 62 | 63 | @container v-resize-drawer #{$media_query} { 64 | @each $val in map.get($display, 'values') { 65 | .#{map.get($display, 'class')}-#{$size}-#{$val} { 66 | display: $val !important; 67 | } 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/plugin/styles/main.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:map'; 2 | @use './mixins' as *; 3 | @use 'vuetify/settings'; 4 | 5 | .v-resize-drawer { 6 | container-name: v-resize-drawer; 7 | container-type: inline-size; 8 | 9 | &--handle { 10 | &-container { 11 | align-items: center; 12 | cursor: grab; 13 | display: flex; 14 | justify-content: center; 15 | position: absolute; 16 | z-index: 1; 17 | 18 | &-icon { 19 | &-parent { 20 | &-end, 21 | &-right { 22 | left: initial; 23 | right: 0; 24 | } 25 | } 26 | 27 | &-center { 28 | &-end, 29 | &-right { 30 | transform: rotate(180deg); 31 | } 32 | } 33 | 34 | &-user-icon { 35 | transform: none; 36 | } 37 | 38 | &-fa { 39 | font-size: .7rem !important; 40 | } 41 | } 42 | 43 | &-parent { 44 | &-left, 45 | &-start, 46 | &-undefined { 47 | right: 0; 48 | } 49 | 50 | &-end, 51 | &-right { 52 | left: 0; 53 | } 54 | } 55 | 56 | &-position { 57 | &-top { 58 | top: 0; 59 | } 60 | 61 | &-center { 62 | top: 50%; 63 | transform: translateY(-50%); 64 | } 65 | 66 | &-bottom { 67 | bottom: 0; 68 | } 69 | 70 | &-border { 71 | cursor: col-resize; 72 | height: 100%; 73 | top: 0; 74 | width: 8px; 75 | } 76 | } 77 | } 78 | } 79 | 80 | &--bottom { 81 | transition: min-height 0.3s; 82 | 83 | .v-resize-drawer--handle-container { 84 | &-position { 85 | &-center { 86 | left: 50%; 87 | top: 0; 88 | transform: translateX(-50%); 89 | } 90 | 91 | &-border { 92 | cursor: row-resize; 93 | left: 0%; 94 | top: 0 !important; 95 | width: 100% !important; 96 | } 97 | } 98 | } 99 | } 100 | 101 | &--top { 102 | top: 0 !important; 103 | transition: min-height 0.3s; 104 | 105 | .v-resize-drawer--handle-container { 106 | &-position { 107 | &-center { 108 | bottom: 1px; 109 | left: 50%; 110 | top: unset; 111 | transform: translateX(-50%); 112 | } 113 | 114 | &-border { 115 | bottom: 0 !important; 116 | cursor: row-resize; 117 | left: 0%; 118 | top: unset; 119 | width: 100% !important; 120 | } 121 | } 122 | } 123 | } 124 | 125 | &--bottom, 126 | &--top { 127 | .v-navigation-drawer__content { 128 | flex: 1 1 auto; 129 | position: relative; 130 | } 131 | } 132 | } 133 | 134 | 135 | @container v-resize-drawer (width > #{map.get(settings.$grid-breakpoints, 'xs')}) and (max-width: #{map.get(settings.$grid-breakpoints, 'sm') - 0.02}) { 136 | .v-col { 137 | &-xs-12 { 138 | flex: 0 0 100% !important; 139 | flex-basis: 0; 140 | flex-grow: 1; 141 | max-width: 100% !important; 142 | } 143 | } 144 | } 145 | 146 | // Grid Columns // 147 | @include make-grid-columns; 148 | 149 | // Hidden & d-#{size}-#{display-value} Columns // 150 | @include make-grid-columns-hidden; 151 | -------------------------------------------------------------------------------- /src/plugin/types/auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-auto-import 5 | export {} 6 | declare global { 7 | const CSSProperties: typeof import('vue')['CSSProperties'] 8 | const EffectScope: typeof import('vue')['EffectScope'] 9 | const computed: typeof import('vue')['computed'] 10 | const createApp: typeof import('vue')['createApp'] 11 | const customRef: typeof import('vue')['customRef'] 12 | const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] 13 | const defineComponent: typeof import('vue')['defineComponent'] 14 | const effectScope: typeof import('vue')['effectScope'] 15 | const getCurrentInstance: typeof import('vue')['getCurrentInstance'] 16 | const getCurrentScope: typeof import('vue')['getCurrentScope'] 17 | const h: typeof import('vue')['h'] 18 | const inject: typeof import('vue')['inject'] 19 | const isProxy: typeof import('vue')['isProxy'] 20 | const isReactive: typeof import('vue')['isReactive'] 21 | const isReadonly: typeof import('vue')['isReadonly'] 22 | const isRef: typeof import('vue')['isRef'] 23 | const markRaw: typeof import('vue')['markRaw'] 24 | const nextTick: typeof import('vue')['nextTick'] 25 | const onActivated: typeof import('vue')['onActivated'] 26 | const onBeforeMount: typeof import('vue')['onBeforeMount'] 27 | const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] 28 | const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] 29 | const onDeactivated: typeof import('vue')['onDeactivated'] 30 | const onErrorCaptured: typeof import('vue')['onErrorCaptured'] 31 | const onMounted: typeof import('vue')['onMounted'] 32 | const onRenderTracked: typeof import('vue')['onRenderTracked'] 33 | const onRenderTriggered: typeof import('vue')['onRenderTriggered'] 34 | const onScopeDispose: typeof import('vue')['onScopeDispose'] 35 | const onServerPrefetch: typeof import('vue')['onServerPrefetch'] 36 | const onUnmounted: typeof import('vue')['onUnmounted'] 37 | const onUpdated: typeof import('vue')['onUpdated'] 38 | const provide: typeof import('vue')['provide'] 39 | const reactive: typeof import('vue')['reactive'] 40 | const readonly: typeof import('vue')['readonly'] 41 | const ref: typeof import('vue')['ref'] 42 | const resolveComponent: typeof import('vue')['resolveComponent'] 43 | const shallowReactive: typeof import('vue')['shallowReactive'] 44 | const shallowReadonly: typeof import('vue')['shallowReadonly'] 45 | const shallowRef: typeof import('vue')['shallowRef'] 46 | const toRaw: typeof import('vue')['toRaw'] 47 | const toRef: typeof import('vue')['toRef'] 48 | const toRefs: typeof import('vue')['toRefs'] 49 | const toValue: typeof import('vue')['toValue'] 50 | const triggerRef: typeof import('vue')['triggerRef'] 51 | const unref: typeof import('vue')['unref'] 52 | const useAttrs: typeof import('vue')['useAttrs'] 53 | const useCssModule: typeof import('vue')['useCssModule'] 54 | const useCssVars: typeof import('vue')['useCssVars'] 55 | const useSlots: typeof import('vue')['useSlots'] 56 | const useTheme: typeof import('vuetify')['useTheme'] 57 | const watch: typeof import('vue')['watch'] 58 | const watchEffect: typeof import('vue')['watchEffect'] 59 | const watchPostEffect: typeof import('vue')['watchPostEffect'] 60 | const watchSyncEffect: typeof import('vue')['watchSyncEffect'] 61 | } 62 | // for type re-export 63 | declare global { 64 | // @ts-ignore 65 | export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue' 66 | } 67 | // for vue template auto import 68 | import { UnwrapRef } from 'vue' 69 | declare module 'vue' { 70 | interface ComponentCustomProperties { 71 | readonly CSSProperties: UnwrapRef 72 | readonly EffectScope: UnwrapRef 73 | readonly computed: UnwrapRef 74 | readonly createApp: UnwrapRef 75 | readonly customRef: UnwrapRef 76 | readonly defineAsyncComponent: UnwrapRef 77 | readonly defineComponent: UnwrapRef 78 | readonly effectScope: UnwrapRef 79 | readonly getCurrentInstance: UnwrapRef 80 | readonly getCurrentScope: UnwrapRef 81 | readonly h: UnwrapRef 82 | readonly inject: UnwrapRef 83 | readonly isProxy: UnwrapRef 84 | readonly isReactive: UnwrapRef 85 | readonly isReadonly: UnwrapRef 86 | readonly isRef: UnwrapRef 87 | readonly markRaw: UnwrapRef 88 | readonly nextTick: UnwrapRef 89 | readonly onActivated: UnwrapRef 90 | readonly onBeforeMount: UnwrapRef 91 | readonly onBeforeUnmount: UnwrapRef 92 | readonly onBeforeUpdate: UnwrapRef 93 | readonly onDeactivated: UnwrapRef 94 | readonly onErrorCaptured: UnwrapRef 95 | readonly onMounted: UnwrapRef 96 | readonly onRenderTracked: UnwrapRef 97 | readonly onRenderTriggered: UnwrapRef 98 | readonly onScopeDispose: UnwrapRef 99 | readonly onServerPrefetch: UnwrapRef 100 | readonly onUnmounted: UnwrapRef 101 | readonly onUpdated: UnwrapRef 102 | readonly provide: UnwrapRef 103 | readonly reactive: UnwrapRef 104 | readonly readonly: UnwrapRef 105 | readonly ref: UnwrapRef 106 | readonly resolveComponent: UnwrapRef 107 | readonly shallowReactive: UnwrapRef 108 | readonly shallowReadonly: UnwrapRef 109 | readonly shallowRef: UnwrapRef 110 | readonly toRaw: UnwrapRef 111 | readonly toRef: UnwrapRef 112 | readonly toRefs: UnwrapRef 113 | readonly toValue: UnwrapRef 114 | readonly triggerRef: UnwrapRef 115 | readonly unref: UnwrapRef 116 | readonly useAttrs: UnwrapRef 117 | readonly useCssModule: UnwrapRef 118 | readonly useCssVars: UnwrapRef 119 | readonly useSlots: UnwrapRef 120 | readonly useTheme: UnwrapRef 121 | readonly watch: UnwrapRef 122 | readonly watchEffect: UnwrapRef 123 | readonly watchPostEffect: UnwrapRef 124 | readonly watchSyncEffect: UnwrapRef 125 | } 126 | } 127 | declare module '@vue/runtime-core' { 128 | interface ComponentCustomProperties { 129 | readonly CSSProperties: UnwrapRef 130 | readonly EffectScope: UnwrapRef 131 | readonly computed: UnwrapRef 132 | readonly createApp: UnwrapRef 133 | readonly customRef: UnwrapRef 134 | readonly defineAsyncComponent: UnwrapRef 135 | readonly defineComponent: UnwrapRef 136 | readonly effectScope: UnwrapRef 137 | readonly getCurrentInstance: UnwrapRef 138 | readonly getCurrentScope: UnwrapRef 139 | readonly h: UnwrapRef 140 | readonly inject: UnwrapRef 141 | readonly isProxy: UnwrapRef 142 | readonly isReactive: UnwrapRef 143 | readonly isReadonly: UnwrapRef 144 | readonly isRef: UnwrapRef 145 | readonly markRaw: UnwrapRef 146 | readonly nextTick: UnwrapRef 147 | readonly onActivated: UnwrapRef 148 | readonly onBeforeMount: UnwrapRef 149 | readonly onBeforeUnmount: UnwrapRef 150 | readonly onBeforeUpdate: UnwrapRef 151 | readonly onDeactivated: UnwrapRef 152 | readonly onErrorCaptured: UnwrapRef 153 | readonly onMounted: UnwrapRef 154 | readonly onRenderTracked: UnwrapRef 155 | readonly onRenderTriggered: UnwrapRef 156 | readonly onScopeDispose: UnwrapRef 157 | readonly onServerPrefetch: UnwrapRef 158 | readonly onUnmounted: UnwrapRef 159 | readonly onUpdated: UnwrapRef 160 | readonly provide: UnwrapRef 161 | readonly reactive: UnwrapRef 162 | readonly readonly: UnwrapRef 163 | readonly ref: UnwrapRef 164 | readonly resolveComponent: UnwrapRef 165 | readonly shallowReactive: UnwrapRef 166 | readonly shallowReadonly: UnwrapRef 167 | readonly shallowRef: UnwrapRef 168 | readonly toRaw: UnwrapRef 169 | readonly toRef: UnwrapRef 170 | readonly toRefs: UnwrapRef 171 | readonly toValue: UnwrapRef 172 | readonly triggerRef: UnwrapRef 173 | readonly unref: UnwrapRef 174 | readonly useAttrs: UnwrapRef 175 | readonly useCssModule: UnwrapRef 176 | readonly useCssVars: UnwrapRef 177 | readonly useSlots: UnwrapRef 178 | readonly useTheme: UnwrapRef 179 | readonly watch: UnwrapRef 180 | readonly watchEffect: UnwrapRef 181 | readonly watchPostEffect: UnwrapRef 182 | readonly watchSyncEffect: UnwrapRef 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/plugin/types/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CSSProperties, 3 | MaybeRef, 4 | } from 'vue'; 5 | import type { IconOptions, ThemeInstance } from 'vuetify'; 6 | import type { 7 | VIcon, 8 | VNavigationDrawer, 9 | } from 'vuetify/components'; 10 | import VResizeDrawer from '../VResizeDrawer.vue'; 11 | 12 | export * from '../index'; 13 | 14 | 15 | export type Classes = { 16 | [key: string]: boolean | undefined; 17 | }; 18 | 19 | export type EmitEventNames = 'handle:click' | 'handle:dblclick' | 'handle:drag' | 'handle:mousedown' | 'handle:mouseup' | 'handle:touchend' | 'handle:touchmove' | 'handle:touchstart'; 20 | export type StorageType = 'local' | 'session'; 21 | export type HandlePositions = 'bottom' | 'border' | 'center' | 'top'; 22 | export type DrawerLocations = 'bottom' | 'end' | 'start' | 'left' | 'right' | 'top' | undefined; 23 | 24 | type Height = number | string | undefined; 25 | 26 | // -------------------------------------------------- Colors // 27 | export type HEXColor = string; 28 | export type HSLColor = [number, number, number]; 29 | export type RGBColor = [number, number, number]; 30 | 31 | 32 | // -------------------------------------------------- Props // 33 | export interface Props { 34 | absolute?: VNavigationDrawer['absolute']; 35 | expandOnHover?: VNavigationDrawer['expandOnHover']; 36 | floating?: VNavigationDrawer['floating']; 37 | handleBorderWidth?: number | string; 38 | handleColor?: string | undefined; 39 | handleIcon?: string | undefined; 40 | handleIconSize?: VIcon['size']; 41 | handlePosition?: HandlePositions; 42 | height?: Height; 43 | image?: VNavigationDrawer['image']; 44 | location?: DrawerLocations; 45 | maxHeight?: Height; 46 | maxWidth?: VNavigationDrawer['width']; 47 | minHeight?: Height; 48 | minWidth?: VNavigationDrawer['width']; 49 | modelValue?: VNavigationDrawer['modelValue']; 50 | name?: VNavigationDrawer['name']; 51 | rail?: VNavigationDrawer['rail']; 52 | railWidth?: VNavigationDrawer['railWidth']; 53 | resizable?: boolean | undefined; 54 | saveHeight?: boolean | undefined; 55 | saveWidth?: boolean | undefined; 56 | snapBack?: boolean | undefined; 57 | storageName?: string | undefined; 58 | storageType?: StorageType; 59 | tag?: VNavigationDrawer['tag']; 60 | temporary?: VNavigationDrawer['temporary']; 61 | theme?: VNavigationDrawer['theme']; 62 | touchless?: boolean | undefined; 63 | width?: VNavigationDrawer['width']; 64 | widthSnapBack?: boolean | undefined; 65 | } 66 | 67 | export interface GlobalOptions extends Props { } 68 | 69 | 70 | // -------------------------------------------------- Composables // 71 | 72 | // ------------------------- Helpers // 73 | export interface UseConvertToUnit { 74 | ( 75 | options: { 76 | unit?: string, 77 | value: string | number, 78 | } 79 | ): string | void; 80 | } 81 | 82 | // ------------------------- Storage // 83 | export interface UseSetStorage { 84 | ( 85 | options: { 86 | action?: string; 87 | resizedAmount?: MaybeRef; 88 | resizedWidth?: MaybeRef; 89 | storageType?: StorageType; 90 | storageName?: Props['storageName']; 91 | saveAmount?: Props['saveWidth'] | Props['saveHeight']; 92 | rail?: Props['rail']; 93 | } 94 | ): void; 95 | } 96 | 97 | // ------------------------- Classes // 98 | export interface UseDrawerClasses { 99 | ( 100 | options: { 101 | absolute?: Props['absolute'], 102 | expandOnHover?: Props['expandOnHover'], 103 | floating?: Props['floating'], 104 | isMouseover?: MaybeRef, 105 | location?: Props['location'], 106 | rail?: Props['rail'], 107 | railWidth?: Props['railWidth'], 108 | temporary?: Props['temporary'], 109 | } 110 | ): object; 111 | } 112 | 113 | export interface UseHandleContainerClasses { 114 | ( 115 | options: { 116 | handlePosition?: Props['handlePosition'], 117 | drawerLocation?: Props['location'], 118 | } 119 | ): object; 120 | } 121 | 122 | export interface UseHandleIconClasses { 123 | ( 124 | options: { 125 | handlePosition?: Props['handlePosition'], 126 | iconOptions?: IconOptions, 127 | isUserIcon?: boolean, 128 | drawerLocation?: Props['location'], 129 | } 130 | ): object; 131 | } 132 | 133 | // ------------------------- Styles // 134 | export interface UseDrawerStyles { 135 | ( 136 | options: { 137 | isMouseDown?: MaybeRef, 138 | location?: Props['location'], 139 | maxHeight?: Props['maxHeight'], 140 | minHeight?: Props['minHeight'], 141 | maxWidth?: Props['maxWidth'], 142 | minWidth?: Props['minWidth'], 143 | rail?: Props['rail'], 144 | railWidth?: Props['railWidth'], 145 | resizedAmount: MaybeRef, 146 | snapBack?: Props['snapBack'], 147 | } 148 | ): CSSProperties; 149 | } 150 | 151 | export interface UseHandleContainerStyles { 152 | ( 153 | options: { 154 | borderWidth?: Props['handleBorderWidth'], 155 | handleColor?: Props['handleColor'], 156 | iconSize?: Props['handleIconSize'], 157 | iconSizeUnit?: number, 158 | location?: Props['location'], 159 | position?: Props['handlePosition'], 160 | theme: ThemeInstance, 161 | } 162 | ): CSSProperties; 163 | } 164 | 165 | export interface UseHandleIconStyles { 166 | ( 167 | options: { 168 | color?: Props['handleColor'], 169 | theme: ThemeInstance, 170 | } 171 | ): CSSProperties; 172 | } 173 | 174 | // ------------------------ Icons // 175 | export interface UseGetIcon { 176 | ( 177 | options: { 178 | icon: Props['handleIcon']; 179 | iconOptions: IconOptions | undefined; 180 | name: Props['handlePosition'], 181 | } 182 | ): Props['handleIcon']; 183 | } 184 | 185 | 186 | declare module 'vue' { 187 | interface ComponentCustomProperties { } 188 | 189 | interface GlobalComponents { 190 | VResizeDrawer: typeof VResizeDrawer; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/plugin/types/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/plugin/types/vue-shim.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue'; 4 | const component: DefineComponent<{}, {}, any>; 5 | export default component; 6 | } 7 | -------------------------------------------------------------------------------- /src/plugin/utils/globals.ts: -------------------------------------------------------------------------------- 1 | const defaultWidth = 256; 2 | const componentName = 'v-resize-drawer'; 3 | 4 | 5 | export { 6 | defaultWidth, 7 | componentName, 8 | }; 9 | -------------------------------------------------------------------------------- /src/plugin/utils/props.ts: -------------------------------------------------------------------------------- 1 | import { Props } from '@/plugin/types'; 2 | import { componentName } from './globals'; 3 | 4 | 5 | export const AllProps: Props = { 6 | handleBorderWidth: 8, 7 | handleColor: 'primary', 8 | handleIcon: undefined, 9 | handleIconSize: 'x-small' as const, 10 | handlePosition: 'center', 11 | height: 256, 12 | location: 'start', 13 | maxHeight: window.innerHeight, 14 | maxWidth: window.innerWidth, 15 | minHeight: 56, 16 | minWidth: 56, 17 | modelValue: true, 18 | name: undefined, 19 | rail: false, 20 | railWidth: 8, 21 | resizable: true, 22 | saveWidth: true, 23 | snapBack: true, 24 | storageName: `${componentName}-width`, 25 | storageType: 'local', 26 | tag: 'nav', 27 | theme: undefined, 28 | touchless: false, 29 | width: 256, 30 | }; 31 | -------------------------------------------------------------------------------- /src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import vuetify from '@/plugins/vuetify'; 3 | import { loadFonts } from '@/plugins/webfontloader'; 4 | 5 | 6 | export function registerPlugins(app: App) { 7 | loadFonts(); 8 | app.use(vuetify); 9 | } 10 | -------------------------------------------------------------------------------- /src/plugins/theme.ts: -------------------------------------------------------------------------------- 1 | import colors from 'vuetify/lib/util/colors.mjs'; 2 | 3 | export const dark = { 4 | colors: { 5 | accent: '#d00274', 6 | danger: colors.red.base, 7 | error: colors.red.base, 8 | info: colors.teal.base, 9 | primary: colors.blue.darken2, 10 | secondary: colors.purple.base, 11 | success: colors.green.base, 12 | warning: colors.orange.darken3, 13 | }, 14 | dark: true, 15 | }; 16 | 17 | export const light = { 18 | colors: { 19 | accent: '#905', 20 | danger: colors.red.base, 21 | error: colors.red.base, 22 | info: colors.teal.darken1, 23 | primary: colors.blue.base, 24 | secondary: colors.purple.lighten1, 25 | success: colors.green.base, 26 | warning: colors.orange.base, 27 | }, 28 | dark: false, 29 | }; 30 | 31 | 32 | export default { 33 | dark, 34 | light, 35 | }; 36 | -------------------------------------------------------------------------------- /src/plugins/vuetify.ts: -------------------------------------------------------------------------------- 1 | import 'vuetify/styles'; 2 | import '@mdi/font/css/materialdesignicons.css'; 3 | import { createVuetify } from 'vuetify'; 4 | import * as components from 'vuetify/components'; 5 | import * as directives from 'vuetify/directives'; 6 | import { fa } from 'vuetify/iconsets/fa-svg'; 7 | import { aliases, mdi } from 'vuetify/iconsets/mdi'; 8 | import defaultThemes from './theme'; 9 | 10 | 11 | export default createVuetify({ 12 | components, 13 | directives, 14 | icons: { 15 | aliases, 16 | defaultSet: 'mdi', 17 | sets: { 18 | fa, 19 | mdi, 20 | }, 21 | }, 22 | theme: { 23 | defaultTheme: 'light', 24 | themes: { 25 | ...defaultThemes, 26 | }, 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /src/plugins/webfontloader.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * plugins/webfontloader.ts 3 | * 4 | * webfontloader documentation: https://github.com/typekit/webfontloader 5 | */ 6 | 7 | export async function loadFonts() { 8 | const webFontLoader = await import(/* webpackChunkName: "webfontloader" */'webfontloader'); 9 | 10 | webFontLoader.load({ 11 | google: { 12 | families: ['Roboto:100,300,400,500,700,900&display=swap'], 13 | }, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import packageJson from '@root/package.json'; 2 | import { defineStore } from 'pinia'; 3 | 4 | 5 | export const useCoreStore = defineStore('core', () => { 6 | const scopedPackageName = packageJson.name; 7 | const packageName = scopedPackageName.split('/')[1]; 8 | 9 | // Links // 10 | const repoBaseUrl = `https://github.com/webdevnerdstuff/${packageName}`; 11 | const links = { 12 | changeLog: `${repoBaseUrl}/blob/main/CHANGELOG.md`, 13 | demo: 'https://stackblitz.com/edit/vuetify-resize-drawer?file=src%2Fcomponents%2FResizeDrawerExample.vue', 14 | demoGrid: 'https://stackblitz.com/edit/vuetify-resize-drawer?file=src%2Fcomponents%2FGridDrawerExample.vue', 15 | discord: 'https://discord.com/users/979453275369783346', 16 | github: repoBaseUrl, 17 | githubProfile: 'https://github.com/webdevnerdstuff', 18 | license: `${repoBaseUrl}/blob/main/LICENSE.md`, 19 | npm: `https://www.npmjs.com/package/${scopedPackageName}`, 20 | pnpm: 'https://pnpm.io/', 21 | vue: 'https://vuejs.org/', 22 | vueUse: 'https://vueuse.org/', 23 | vuetify: 'https://vuetifyjs.com/', 24 | vuetifyGithub: 'https://github.com/vuetifyjs/vuetify', 25 | }; 26 | 27 | const actions = { 28 | setLocalStorage(val: string): string { 29 | const oldValue = localStorage.getItem(packageName); 30 | const newValue = val ?? oldValue; 31 | 32 | localStorage.setItem(packageName, newValue); 33 | return newValue; 34 | }, 35 | setTheme(val: string): string { 36 | const themeName = val === 'dark' ? 'light' : 'dark'; 37 | const currentTheme = localStorage.getItem(`${packageName}-theme`); 38 | const newTheme = themeName ?? currentTheme; 39 | 40 | localStorage.setItem(`${packageName}-theme`, newTheme); 41 | return newTheme; 42 | }, 43 | }; 44 | 45 | const getters = { 46 | getLocalStorage: () => (): unknown => { 47 | const value = localStorage.getItem(packageName); 48 | return value; 49 | }, 50 | getTheme: () => { 51 | const value = localStorage.getItem(`${packageName}-theme`); 52 | return value; 53 | }, 54 | }; 55 | 56 | return { 57 | ...actions, 58 | ...getters, 59 | links, 60 | package: packageJson, 61 | packageName, 62 | pluginVersion: packageJson.version, 63 | }; 64 | }); 65 | -------------------------------------------------------------------------------- /src/stores/menu.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { useCoreStore } from './index'; 3 | 4 | 5 | export const useMenuStore = defineStore('menu', () => { 6 | const coreStore = useCoreStore(); 7 | const links = coreStore.links; 8 | 9 | const componentItems = [ 10 | { 11 | href: '#components-v-navigation-drawer', 12 | icon: 'mdi:mdi-menu', 13 | key: 'v-navigation-drawer', 14 | link: `${links.vuetify}en/api/v-navigation-drawer/`, 15 | title: 'VNavigationDrawer', 16 | topTitle: 'VNavigationDrawer', 17 | 18 | }, 19 | ]; 20 | 21 | const vuetifyLinks = [ 22 | { 23 | icon: 'mdi:mdi-github', 24 | key: 'vuetify-github', 25 | link: links.vuetifyGithub, 26 | title: 'Github', 27 | }, 28 | { 29 | key: 'vuetify', 30 | link: `${links.vuetify}en/components/all/`, 31 | title: 'Vuetify Components', 32 | }, 33 | ]; 34 | 35 | const menuItems = [ 36 | { 37 | href: '#home', 38 | icon: 'mdi:mdi-home', 39 | title: 'Home', 40 | }, 41 | { 42 | href: '#installation', 43 | icon: 'mdi:mdi-plus-thick', 44 | title: 'Installation', 45 | }, 46 | { 47 | href: '#description', 48 | icon: 'mdi:mdi-information-outline', 49 | title: 'Description', 50 | }, 51 | { 52 | href: '#usage', 53 | icon: 'mdi:mdi-power-plug-outline', 54 | title: 'Usage', 55 | }, 56 | { 57 | href: '#props', 58 | icon: 'mdi-cog', 59 | title: 'Props', 60 | }, 61 | { 62 | href: '#vuetify-grid-system', 63 | icon: 'mdi-view-grid', 64 | title: 'Grid System', 65 | }, 66 | { 67 | href: '#events', 68 | icon: 'mdi-calendar-star', 69 | title: 'Events', 70 | }, 71 | { 72 | href: '#slots', 73 | icon: 'mdi-slot-machine', 74 | title: 'Slots', 75 | }, 76 | { 77 | href: '#sass-variables', 78 | icon: 'mdi-sass', 79 | title: 'SASS Variables', 80 | }, 81 | { 82 | href: '#example', 83 | icon: 'mdi-code-json', 84 | title: 'Example', 85 | }, 86 | { 87 | href: '#playground', 88 | icon: 'mdi:mdi-seesaw', 89 | title: 'Playground', 90 | }, 91 | { 92 | href: '#dependencies', 93 | icon: 'mdi-asterisk-circle-outline', 94 | title: 'Dependencies', 95 | }, 96 | { 97 | href: '#license', 98 | icon: 'mdi-card-account-details-outline', 99 | title: 'License', 100 | }, 101 | { 102 | href: '#legal', 103 | icon: 'mdi-scale-balance', 104 | title: 'Legal', 105 | }, 106 | ]; 107 | 108 | return { 109 | componentItems, 110 | menuItems, 111 | vuetifyLinks, 112 | }; 113 | }); 114 | -------------------------------------------------------------------------------- /src/stores/props.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | // import { useCoreStore } from './'; 3 | 4 | 5 | export const usePropsStore = defineStore('props', () => { 6 | // const { links } = useCoreStore(); 7 | 8 | const propsHeaders = [ 9 | { 10 | align: 'start', 11 | filterable: true, 12 | key: 'name', 13 | sortable: true, 14 | title: 'Name', 15 | width: '20%', 16 | }, 17 | { 18 | align: 'start', 19 | filterable: false, 20 | key: 'type', 21 | sortable: false, 22 | title: 'Type', 23 | width: '20%', 24 | }, 25 | { 26 | align: 'start', 27 | filterable: false, 28 | key: 'default', 29 | sortable: false, 30 | title: 'Default', 31 | }, 32 | { 33 | align: 'start', 34 | filterable: false, 35 | key: 'desc', 36 | sortable: false, 37 | title: 'Description', 38 | }, 39 | ]; 40 | 41 | const componentProps = [ 42 | { 43 | default: '8', 44 | desc: 'Specifies the width of the handle if the handle position border is selected.', 45 | name: 'handle-border-width', 46 | type: 'string | number', 47 | }, 48 | { 49 | default: 'primary', 50 | desc: 'Determines the color of the handle', 51 | name: 'handle-color', 52 | type: 'string', 53 | }, 54 | { 55 | default: 'undefined', 56 | desc: 'Determines the icon of the handle', 57 | name: 'handle-icon', 58 | type: 'string', 59 | }, 60 | { 61 | default: 'x-small', 62 | desc: 'Sets the height and width of the icon. Default unit is px. Can also use the following predefined sizes: x-small, small, default, large, and x-large', 63 | name: 'handle-icon-size', 64 | type: 'string', 65 | }, 66 | { 67 | default: 'center', 68 | desc: 'Specifies the position of the handle. Valid values are border, center, top, bottom. If location prop is set to top or bottom, the handle position top and bottom are not allowed.', 69 | name: 'handle-position', 70 | type: "'bottom' | 'border' | 'center' | 'top'", 71 | }, 72 | { 73 | default: 'start', 74 | desc: 'Places the navigation drawer position on the the screen.', 75 | name: 'location', 76 | type: "'bottom' | 'end' | 'start' | 'left' | 'right' | 'top' | undefined", 77 | }, 78 | { 79 | default: '100%', 80 | desc: 'The maximum height of the navigation drawer. Accepts: number, px, or %', 81 | name: 'max-height', 82 | type: 'string', 83 | }, 84 | { 85 | default: '100%', 86 | desc: 'The maximum width of the navigation drawer. Accepts: number, px, or %', 87 | name: 'max-width', 88 | type: 'string', 89 | }, 90 | { 91 | default: '56', 92 | desc: 'The minimum height of the navigation drawer. Accepts: number, px, or %', 93 | name: 'min-height', 94 | type: 'string', 95 | }, 96 | { 97 | default: '56', 98 | desc: 'The minimum width of the navigation drawer. Accepts: number, px, or %', 99 | name: 'min-width', 100 | type: 'string', 101 | }, 102 | { 103 | default: 'true', 104 | desc: 'Enables resize functionality', 105 | name: 'resizable', 106 | type: 'boolean', 107 | }, 108 | { 109 | default: 'true', 110 | desc: 'Determines if the width of the component is saved in local/session storage', 111 | name: 'save-width', 112 | type: 'boolean', 113 | }, 114 | { 115 | default: 'true', 116 | desc: 'Determines if the width or height of the navigation drawer should snap back if the min-width, max-width, min-height or max-height prop values pass their respective thresholds. (previously width-snap-back. Backwards compatible)', 117 | name: 'snap-back', 118 | type: 'boolean', 119 | }, 120 | { 121 | default: 'v-resize-drawer-width', 122 | desc: 'Determines the name of the local/session storage item. The value is appended with the location prop value.', 123 | name: 'storage-name', 124 | type: 'string', 125 | }, 126 | { 127 | default: 'local', 128 | desc: 'Determines the type of storage to use when the save-width is true. Valid values are local and session', 129 | name: 'storage-type', 130 | type: "'local' | 'session'", 131 | }, 132 | { 133 | default: 'false', 134 | desc: 'Hides the resize handle on mobile devices.
* The touchless prop does not function the same as the v-navigation-drawer prop which is not supported', 135 | name: 'touchless', 136 | type: 'boolean', 137 | }, 138 | ]; 139 | 140 | const propsNotSupportedHeaders = [ 141 | { 142 | align: 'start', 143 | filterable: true, 144 | key: 'name', 145 | sortable: false, 146 | title: 'Name', 147 | width: '15%', 148 | }, 149 | { 150 | align: 'start', 151 | filterable: false, 152 | key: 'status', 153 | sortable: false, 154 | title: 'Status', 155 | width: '10%', 156 | }, 157 | { 158 | align: 'start', 159 | filterable: false, 160 | key: 'notes', 161 | sortable: false, 162 | title: 'Notes', 163 | }, 164 | ]; 165 | 166 | const propsNotSupported = [ 167 | { 168 | name: 'disable-route-watcher', 169 | notes: 'An environment that uses routes is needed to test', 170 | status: '', 171 | }, 172 | { 173 | name: 'expand-on-hover', 174 | notes: 'The expand-on-hover prop will work, but the resizable functionality is disabled', 175 | status: 'partial support', 176 | }, 177 | { 178 | name: 'rail', 179 | notes: 'The rail prop will work, but the resizable functionality is disabled', 180 | status: 'partial support', 181 | }, 182 | { 183 | name: 'rail-width', 184 | notes: 'The rail-width prop for use with the rail prop will work, but the resizable functionality is disabled', 185 | status: 'partial support', 186 | }, 187 | ]; 188 | 189 | 190 | return { 191 | componentProps, 192 | propsHeaders, 193 | propsNotSupported, 194 | propsNotSupportedHeaders, 195 | }; 196 | }); 197 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdevnerdstuff/vuetify-resize-drawer/0347ff6acf011e01e214915d351e7f89c39c9a86/src/style.css -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | { 3 | extends: ['@wdns/stylelint-config-wdns'], 4 | }; 5 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "playground", 4 | "src/**/*.spec.ts", 5 | "src/**/*.test.ts", 6 | "src/playground/**/*.ts", 7 | "src/playground/**/*.vue", 8 | "src/documentation/**/*.ts", 9 | "src/documentation/**/*.tsx", 10 | "src/documentation/**/*.vue", 11 | "src/libraries/**/*.ts", 12 | "src/plugins/**/*.ts", 13 | "src/stores/**/*.ts", 14 | "src/App.vue", 15 | "src/main.ts", 16 | "node_modules", 17 | ], 18 | "extends": "./tsconfig.json" 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "baseUrl": "./", 5 | "declaration": true, 6 | "declarationDir": "./dist", 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "importHelpers": true, 10 | "jsx": "preserve", 11 | "lib": [ 12 | "ESNext", 13 | "ES2017", 14 | "DOM" 15 | ], 16 | "module": "ESNext", 17 | "moduleResolution": "Node", 18 | "noEmit": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "noImplicitAny": false, 21 | "noUncheckedIndexedAccess": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "outDir": "./dist", 25 | "paths": { 26 | "@/*": [ 27 | "./src/*" 28 | ], 29 | "@types/*": [ 30 | "./src/plugin/types/*" 31 | ], 32 | "@components/*": [ 33 | "./src/plugin/components/*" 34 | ], 35 | "@composables/*": [ 36 | "./src/plugin/composables/*" 37 | ], 38 | "@slots/*": [ 39 | "./src/plugin/slots/*" 40 | ], 41 | "@utils/*": [ 42 | "./src/plugin/utils/*" 43 | ] 44 | }, 45 | "resolveJsonModule": true, 46 | "rootDir": "src", 47 | "skipLibCheck": true, 48 | "strict": true, 49 | "target": "ESNext", 50 | "typeRoots": [ 51 | "./src/plugin/types", 52 | "./src/documentation/types", 53 | "./node_modules/@types", 54 | "./node_modules/vuetify" 55 | ], 56 | "types": [ 57 | "jest", 58 | "node" 59 | ], 60 | "useDefineForClassFields": true 61 | }, 62 | "exclude": [ 63 | "cypress.config.ts", 64 | "cypress/**/*.ts", 65 | "node_modules", 66 | "playground", 67 | "src/**/*.spec.ts", 68 | "src/**/*.test.ts", 69 | "src/playground/configs/templates/PlaygroundPage.vue", 70 | "src/plugins/**/*.ts", 71 | "src/stores/**/*.ts", 72 | ], 73 | "include": [ 74 | "eslint.config.mjs", 75 | "src/App.vue", 76 | "src/main.ts", 77 | "src/playground/**/*.ts", 78 | "src/playground/**/*.vue", 79 | "src/documentation/**/*.ts", 80 | "src/documentation/**/*.tsx", 81 | "src/documentation/**/*.vue", 82 | "src/plugin/**/*.ts", 83 | "src/plugin/**/*.tsx", 84 | "src/plugin/**/*.vue" 85 | ], 86 | "references": [ 87 | { 88 | "path": "./tsconfig.node.json" 89 | } 90 | ] 91 | } 92 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": [ 10 | "vite.config.mts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /vite.build.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import * as path from 'path'; 3 | import AutoImport from 'unplugin-auto-import/vite'; 4 | import commonjs from '@rollup/plugin-commonjs'; 5 | import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'; 6 | import dts from 'vite-plugin-dts'; 7 | import pkg from './package.json'; 8 | import terser from '@rollup/plugin-terser'; 9 | import typescript from 'rollup-plugin-typescript2'; 10 | import vue from '@vitejs/plugin-vue'; 11 | import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'; 12 | import { viteStaticCopy } from 'vite-plugin-static-copy'; 13 | 14 | 15 | const scopedPackageName = pkg.name; 16 | const packageName = scopedPackageName.split('/')[1]; 17 | 18 | const banner = `/** 19 | * @name ${scopedPackageName} 20 | * @version ${pkg.version} 21 | * @description ${pkg.description} 22 | * @author ${pkg.author} 23 | * @copyright Copyright ${new Date().getFullYear()}, WebDevNerdStuff 24 | * @homepage ${pkg.homepage} 25 | * @repository ${pkg.repository} 26 | * @license ${pkg.license} License 27 | */ 28 | `; 29 | 30 | export default defineConfig({ 31 | publicDir: false, 32 | build: { 33 | lib: { 34 | entry: './src/plugin/index.ts', 35 | name: packageName, 36 | formats: ['es', 'cjs'], 37 | fileName: format => `${packageName}.${format}.js`, 38 | }, 39 | rollupOptions: { 40 | input: { 41 | main: path.resolve(__dirname, './src/plugin/index.ts') 42 | }, 43 | external: [ 44 | ...Object.keys(pkg.dependencies || {}), 45 | /^vuetify($|\/.+)/, 46 | ], 47 | output: { 48 | banner, 49 | exports: 'named', 50 | }, 51 | }, 52 | }, 53 | css: { 54 | preprocessorOptions: { 55 | scss: { 56 | api: 'modern-compiler', // or "modern", "legacy" 57 | importers: [], 58 | }, 59 | }, 60 | }, 61 | plugins: [ 62 | commonjs(), 63 | AutoImport({ 64 | dts: false, 65 | imports: [ 66 | 'vue', 67 | { 68 | vue: ['CSSProperties'], 69 | } 70 | ], 71 | vueTemplate: true, 72 | }), 73 | vue({ 74 | template: { transformAssetUrls }, 75 | }), 76 | dts({ 77 | insertTypesEntry: true, 78 | tsconfigPath: 'tsconfig.build.json', 79 | }), 80 | typescript({ 81 | check: true, 82 | include: ['./src/plugin/**/*.vue'], 83 | tsconfig: 'tsconfig.build.json', 84 | }), 85 | vuetify({ 86 | autoImport: true, 87 | styles: 'none', 88 | }), 89 | cssInjectedByJsPlugin({ topExecutionPriority: false }), 90 | viteStaticCopy({ 91 | targets: [ 92 | { 93 | src: 'src/plugin/styles/*', 94 | dest: 'scss', 95 | }, 96 | ] 97 | }), 98 | terser({ 99 | compress: { 100 | drop_console: ['log'], 101 | }, 102 | }), 103 | ], 104 | resolve: { 105 | alias: { 106 | '@': path.resolve(__dirname, './src'), 107 | '@components': path.resolve(__dirname, './src/plugin/components'), 108 | '@composables': path.resolve(__dirname, './src/plugin/composables'), 109 | '@plugin': path.resolve(__dirname, './src/plugin'), 110 | '@root': path.resolve(__dirname, './'), 111 | '@slots': path.resolve(__dirname, './src/plugin/slots'), 112 | '@types': path.resolve(__dirname, './src/plugin/types'), 113 | '@utils': path.resolve(__dirname, './src/plugin/utils'), 114 | }, 115 | extensions: [ 116 | '.js', 117 | '.json', 118 | '.jsx', 119 | '.mjs', 120 | '.mts', 121 | '.ts', 122 | '.tsx', 123 | '.vue', 124 | ], 125 | }, 126 | }); 127 | -------------------------------------------------------------------------------- /vite.config.mts: -------------------------------------------------------------------------------- 1 | import vue from '@vitejs/plugin-vue'; 2 | import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'; 3 | import eslint from 'vite-plugin-eslint2'; 4 | import stylelint from 'vite-plugin-stylelint'; 5 | import { defineConfig } from 'vite'; 6 | import { fileURLToPath, URL } from 'node:url'; 7 | import AutoImport from 'unplugin-auto-import/vite'; 8 | import vueDevTools from 'vite-plugin-vue-devtools'; 9 | 10 | const baseUrl = '/vuetify-resize-drawer/'; 11 | const playgroundUrl = baseUrl + 'playground/'; 12 | 13 | 14 | export default defineConfig({ 15 | base: baseUrl, 16 | build: { 17 | outDir: 'docs', 18 | }, 19 | css: { 20 | preprocessorOptions: { 21 | scss: { 22 | api: 'modern-compiler', 23 | importers: [], 24 | }, 25 | }, 26 | }, 27 | plugins: [ 28 | eslint({ 29 | fix: true, 30 | exclude: ['node_modules/**', 'vendor/**'], 31 | include: ['src/**/*.{ts,mts,tsx,vue}'], 32 | }), 33 | stylelint({ 34 | cache: false, 35 | fix: true, 36 | include: [ 37 | 'src/**/*.{css,scss,sass,vue}', 38 | './src/components/**/*.{css,scss,sass,vue}', 39 | './src/plugin/styles/*.{css,scss,sass}' 40 | ], 41 | }), 42 | AutoImport({ 43 | dts: false, 44 | imports: [ 45 | 'vue', 46 | { 47 | vue: ['CSSProperties'], 48 | vuetify: ['useTheme'] 49 | } 50 | ], 51 | vueTemplate: true, 52 | }), 53 | vue({ 54 | template: { transformAssetUrls } 55 | }), 56 | vuetify({ 57 | autoImport: true, 58 | }), 59 | ], 60 | resolve: { 61 | alias: { 62 | '@': fileURLToPath(new URL('./src', import.meta.url)), 63 | '@components': fileURLToPath(new URL('./src/plugin/components', import.meta.url)), 64 | '@composables': fileURLToPath(new URL('./src/plugin/composables', import.meta.url)), 65 | '@plugin': fileURLToPath(new URL('./src/plugin', import.meta.url)), 66 | '@root': fileURLToPath(new URL('.', import.meta.url)), 67 | '@slots': fileURLToPath(new URL('./src/plugin/slots', import.meta.url)), 68 | '@types': fileURLToPath(new URL('./src/plugin/types', import.meta.url)), 69 | '@utils': fileURLToPath(new URL('./src/plugin/utils', import.meta.url)), 70 | }, 71 | extensions: [ 72 | '.js', 73 | '.json', 74 | '.jsx', 75 | '.mjs', 76 | '.mts', 77 | '.ts', 78 | '.tsx', 79 | '.vue', 80 | ], 81 | }, 82 | server: { 83 | hmr: { 84 | protocol: 'ws', 85 | }, 86 | open: process?.env?.NODE_ENV === 'playground' ? playgroundUrl : false, 87 | }, 88 | }); 89 | 90 | export const assetAttrsConfig: Record = { 91 | link: ['href'], 92 | video: ['src', 'poster'], 93 | source: ['src', 'srcset'], 94 | img: ['src', 'srcset'], 95 | image: ['xlink:href', 'href'], 96 | use: ['xlink:href', 'href'] 97 | }; 98 | -------------------------------------------------------------------------------- /vitest.config.mts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url'; 2 | import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'; 3 | import viteConfig from './vite.config.mts'; 4 | 5 | export default mergeConfig( 6 | viteConfig, 7 | defineConfig({ 8 | test: { 9 | environment: 'jsdom', 10 | exclude: [ 11 | ...configDefaults.exclude, 12 | ], 13 | root: fileURLToPath(new URL('./', import.meta.url)), 14 | server: { 15 | deps: { 16 | inline: ['element-plus', 'vuetify-resize-drawer', 'vuetify'] 17 | } 18 | }, 19 | } 20 | }) 21 | ); 22 | --------------------------------------------------------------------------------