├── src ├── helpers │ ├── index.ts │ └── treeAdapter.ts ├── assets │ └── scss │ │ ├── tailwind.scss │ │ ├── styles.scss │ │ └── themes.scss ├── index.ts ├── components │ ├── index.ts │ ├── button │ │ ├── index.ts │ │ ├── Secondary.vue │ │ └── Primary.vue │ ├── toast │ │ ├── index.ts │ │ ├── Toast.vue │ │ └── legacy-toast.ts │ ├── modal │ │ ├── index.ts │ │ ├── examples │ │ │ ├── index.ts │ │ │ ├── Greetings.vue │ │ │ ├── InputDialog.vue │ │ │ └── NestedDialog.vue │ │ ├── Heading.vue │ │ └── index.vue │ └── smart │ │ ├── Spinner.vue │ │ ├── FileChip.vue │ │ ├── SelectWrapper.vue │ │ ├── Radio.vue │ │ ├── RadioGroup.vue │ │ ├── Intersection.vue │ │ ├── Picture.vue │ │ ├── Placeholder.vue │ │ ├── Expand.vue │ │ ├── Anchor.vue │ │ ├── ProgressRing.vue │ │ ├── index.ts │ │ ├── Window.vue │ │ ├── SlideOver.vue │ │ ├── ConfirmModal.vue │ │ ├── Tab.vue │ │ ├── Input.vue │ │ ├── Link.vue │ │ ├── Toggle.vue │ │ ├── Tree.vue │ │ ├── Checkbox.vue │ │ ├── Item.vue │ │ ├── TreeBranch.vue │ │ ├── AutoComplete.vue │ │ ├── Modal.vue │ │ ├── Tabs.vue │ │ ├── Table.vue │ │ └── Windows.vue ├── stories │ ├── Spinner.story.vue │ ├── Item.story.vue │ ├── Checkbox.story.vue │ ├── ProgressRing.story.vue │ ├── Toggle.story.vue │ ├── Button.story.vue │ ├── SlideOver.story.vue │ ├── ConfirmModal.story.vue │ ├── Link.story.vue │ ├── Radio.story.vue │ ├── Tab.story.vue │ ├── Anchor.story.vue │ ├── Modal.story.vue │ ├── AutoComplete.story.vue │ ├── Expand.story.vue │ ├── Toast.story.vue │ ├── LegacyToast.story.vue │ ├── Window.story.vue │ ├── NewModal.story.vue │ └── Table.story.vue ├── env.d.ts ├── GlobalWrapper.vue └── plugin.ts ├── public ├── favicon.ico └── logo.svg ├── ui-preset.d.ts ├── helpers.d.ts ├── .prettierrc.cjs ├── postcss.config.cjs ├── tailwind.config.ts ├── .prettierignore ├── .gitignore ├── histoire.setup.ts ├── histoire.config.ts ├── .github └── workflows │ ├── publish-npm.yml │ └── ui.yml ├── tsconfig.json ├── vite.config.ts ├── .eslintrc.js ├── ui-preset.ts ├── README.md └── package.json /src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./treeAdapter" 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoppscotch/ui/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/scss/tailwind.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /ui-preset.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./dist/ui-preset" 2 | 3 | export { default } from "./dist/ui-preset" 4 | -------------------------------------------------------------------------------- /helpers.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./dist/src/helpers" 2 | 3 | export { default } from "./dist/src/helpers" 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./components" 2 | export * from "./plugin" 3 | export * from "./helpers" 4 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | plugins: ["prettier-plugin-tailwindcss"], 4 | } 5 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./button" 2 | export * from "./smart" 3 | export * from "./modal" 4 | export * from "./toast" 5 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import preset from "./ui-preset" 2 | 3 | export default { 4 | content: ["src/**/*.{vue,html}"], 5 | presets: [preset], 6 | } 7 | -------------------------------------------------------------------------------- /src/components/button/index.ts: -------------------------------------------------------------------------------- 1 | export { default as HoppButtonPrimary } from "./Primary.vue" 2 | export { default as HoppButtonSecondary } from "./Secondary.vue" 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .dependabot 2 | .github 3 | .hoppscotch 4 | .vscode 5 | package-lock.json 6 | node_modules 7 | dist 8 | static 9 | components.d.ts 10 | src/types 11 | -------------------------------------------------------------------------------- /src/components/toast/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./legacy-toast" 2 | export { 3 | default as HoppToastPlugin, 4 | toast, 5 | Toaster, 6 | } from "@hoppscotch/vue-sonner" 7 | -------------------------------------------------------------------------------- /src/components/modal/index.ts: -------------------------------------------------------------------------------- 1 | export { default as HoppModal } from "./index.vue" 2 | export { 3 | useModals, 4 | DialogHost, 5 | plugin as VuePromiseModalsPlugin, 6 | } from "vue-promise-modals" 7 | -------------------------------------------------------------------------------- /src/components/smart/Spinner.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /src/components/modal/examples/index.ts: -------------------------------------------------------------------------------- 1 | export { default as InputDialog } from "./InputDialog.vue" 2 | export { default as NestedDialog } from "./NestedDialog.vue" 3 | export { default as GreetingsModal } from "./Greetings.vue" 4 | -------------------------------------------------------------------------------- /src/stories/Spinner.story.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module "*.vue" { 4 | import { DefineComponent } from "vue" 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 6 | const component: DefineComponent<{}, {}, any> 7 | export default component 8 | } 9 | -------------------------------------------------------------------------------- /src/stories/Item.story.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /src/stories/Checkbox.story.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | -------------------------------------------------------------------------------- /src/GlobalWrapper.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /src/components/smart/FileChip.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /.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 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | # Environment Variables 27 | .env 28 | -------------------------------------------------------------------------------- /src/stories/ProgressRing.story.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /src/components/modal/examples/Greetings.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | -------------------------------------------------------------------------------- /src/stories/Toggle.story.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /src/stories/Button.story.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /src/stories/SlideOver.story.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | -------------------------------------------------------------------------------- /src/stories/ConfirmModal.story.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /histoire.setup.ts: -------------------------------------------------------------------------------- 1 | import { defineSetupVue3 } from "@histoire/plugin-vue" 2 | import { plugin } from "vue-promise-modals" 3 | 4 | import GlobalWrapper from "./src/GlobalWrapper.vue" 5 | 6 | import "./src/assets/scss/themes.scss" 7 | import "./src/assets/scss/styles.scss" 8 | 9 | import "@fontsource-variable/inter" 10 | import "@fontsource-variable/material-symbols-rounded" 11 | import "@fontsource-variable/roboto-mono" 12 | 13 | export const setupVue3 = defineSetupVue3(({ app, addWrapper }) => { 14 | app.use(plugin, {}) 15 | addWrapper(GlobalWrapper) 16 | }) 17 | -------------------------------------------------------------------------------- /histoire.config.ts: -------------------------------------------------------------------------------- 1 | import { HstVue } from "@histoire/plugin-vue" 2 | import { defineConfig } from "histoire" 3 | 4 | export default defineConfig({ 5 | theme: { 6 | title: "Hoppscotch Design • Hoppscotch", 7 | logo: { 8 | square: "/logo.svg", 9 | light: "/logo.svg", 10 | dark: "/logo.svg", 11 | }, 12 | logoHref: "/", 13 | favicon: "favicon.ico", 14 | }, 15 | setupFile: "histoire.setup.ts", 16 | plugins: [HstVue()], 17 | viteIgnorePlugins: ["vite:dts"], 18 | viteNodeInlineDeps: [ 19 | /vue-promise-modals/, 20 | /@boringer-avatars\/vue3/ 21 | ] 22 | }) 23 | -------------------------------------------------------------------------------- /src/stories/Link.story.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 21 | -------------------------------------------------------------------------------- /src/stories/Radio.story.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 23 | -------------------------------------------------------------------------------- /src/stories/Tab.story.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 22 | -------------------------------------------------------------------------------- /src/components/smart/SelectWrapper.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | 31 | -------------------------------------------------------------------------------- /src/stories/Anchor.story.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 25 | -------------------------------------------------------------------------------- /src/components/smart/Radio.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 30 | -------------------------------------------------------------------------------- /src/components/smart/RadioGroup.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | -------------------------------------------------------------------------------- /.github/workflows/publish-npm.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to NPM 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish: 9 | name: Publish 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | 15 | - name: Setup pnpm 16 | uses: pnpm/action-setup@v4 17 | with: 18 | version: 9 19 | run_install: true 20 | 21 | - name: Setup node 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node }} 25 | registry-url: https://registry.npmjs.org/ 26 | cache: pnpm 27 | 28 | - name: Publish package 29 | run: pnpm publish --access public --no-git-checks 30 | env: 31 | NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_ACCESS_TOKEN }} 32 | -------------------------------------------------------------------------------- /src/stories/Modal.story.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | 30 | 41 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "allowJs": true, 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "jsx": "preserve", 10 | "sourceMap": true, 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "esModuleInterop": true, 14 | "lib": ["ESNext", "DOM"], 15 | "skipLibCheck": true, 16 | "noUnusedLocals": true, 17 | "paths": { 18 | "~/*": ["./src/*"], 19 | "@composables/*": ["./src/composables/*"], 20 | "@components/*": ["./src/components/*"], 21 | "@helpers/*": ["./src/helpers/*"], 22 | "@modules/*": ["./src/modules/*"], 23 | "@workers/*": ["./src/workers/*"], 24 | "@functional/*": ["./src/helpers/functional/*"] 25 | }, 26 | "types": ["vite/client", "unplugin-icons/types/vue"] 27 | }, 28 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 29 | } 30 | -------------------------------------------------------------------------------- /src/plugin.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin, App } from "vue" 2 | 3 | import "./assets/scss/styles.scss" 4 | import "./assets/scss/tailwind.scss" 5 | 6 | /** 7 | @constant HOPP_UI_OPTIONS 8 | A constant representing the key for storing HoppUI plugin options in the global context. 9 | */ 10 | 11 | export const HOPP_UI_OPTIONS = "HOPP_UI_OPTIONS" 12 | 13 | /** 14 | @typedef {Object} HoppUIPluginOptions 15 | @property [t] - A function for handling translations for the plugin. 16 | @property [onModalOpen] - A callback function that is called when a modal is opened. 17 | @property [onModalClose] - A callback function that is called when a modal is closed. 18 | */ 19 | 20 | export type HoppUIPluginOptions = { 21 | t?: (key: string) => string 22 | onModalOpen?: () => void 23 | onModalClose?: () => void 24 | } 25 | 26 | export const plugin: Plugin = { 27 | install(app: App, options: HoppUIPluginOptions = {}) { 28 | app.provide(HOPP_UI_OPTIONS, options) 29 | }, 30 | } 31 | 32 | export default plugin 33 | -------------------------------------------------------------------------------- /src/components/smart/Intersection.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 39 | -------------------------------------------------------------------------------- /src/components/smart/Picture.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 39 | -------------------------------------------------------------------------------- /.github/workflows/ui.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Netlify (ui) 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | # run this workflow only if an update is made to the ui package 7 | workflow_dispatch: 8 | 9 | jobs: 10 | deploy: 11 | name: Deploy 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup pnpm 18 | uses: pnpm/action-setup@v4 19 | with: 20 | version: 9 21 | run_install: true 22 | 23 | - name: Setup node 24 | uses: actions/setup-node@v3 25 | with: 26 | node-version: ${{ matrix.node }} 27 | cache: pnpm 28 | 29 | - name: Build site 30 | run: pnpm run story:build 31 | 32 | # Deploy the ui site with netlify-cli 33 | - name: Deploy to Netlify (ui) 34 | run: npx netlify-cli@15.11.0 deploy --dir=.histoire/dist --prod 35 | env: 36 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_UI_SITE_ID }} 37 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 38 | -------------------------------------------------------------------------------- /src/helpers/treeAdapter.ts: -------------------------------------------------------------------------------- 1 | import { Ref } from "vue" 2 | 3 | /** 4 | * Representation of a tree node in the SmartTreeAdapter. 5 | */ 6 | export type TreeNode = { 7 | id: string 8 | data: T 9 | } 10 | 11 | /** 12 | * Representation of children result from a tree node when there will be a loading state. 13 | */ 14 | export type ChildrenResult = 15 | | { 16 | status: "loading" 17 | } 18 | | { 19 | status: "loaded" 20 | data: Array> 21 | } 22 | 23 | /** 24 | * A tree adapter that can be used with the SmartTree component. 25 | * @template T The type of data that is stored in the tree. 26 | */ 27 | export interface SmartTreeAdapter { 28 | /** 29 | * 30 | * @param nodeID - id of the node to get children for 31 | * @param nodeType - Type of the node (`collection` | `request`) 32 | * @returns - Ref that contains the children of the node. It is reactive and will be updated when the children are changed. 33 | */ 34 | getChildren: (nodeID: string | null, nodeType?: string) => Ref> 35 | } 36 | -------------------------------------------------------------------------------- /src/components/modal/examples/InputDialog.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 48 | -------------------------------------------------------------------------------- /src/components/smart/Placeholder.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 50 | -------------------------------------------------------------------------------- /src/components/toast/Toast.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 54 | -------------------------------------------------------------------------------- /src/stories/AutoComplete.story.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 49 | -------------------------------------------------------------------------------- /src/stories/Expand.story.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 21 | -------------------------------------------------------------------------------- /src/components/smart/Expand.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 47 | -------------------------------------------------------------------------------- /src/components/modal/Heading.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 48 | -------------------------------------------------------------------------------- /src/components/smart/Anchor.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 56 | -------------------------------------------------------------------------------- /src/components/smart/ProgressRing.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 58 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import vue from "@vitejs/plugin-vue" 2 | import Icons from "unplugin-icons/vite" 3 | import { defineConfig } from "vite" 4 | import dts from "vite-plugin-dts" 5 | import Unfonts from "unplugin-fonts/vite" 6 | 7 | export default defineConfig({ 8 | plugins: [ 9 | vue(), 10 | dts({ 11 | insertTypesEntry: true, 12 | skipDiagnostics: true, 13 | outputDir: ["dist"], 14 | }), 15 | Icons({ 16 | compiler: "vue3", 17 | }), 18 | Unfonts({ 19 | fontsource: { 20 | families: [ 21 | { 22 | name: "Inter Variable", 23 | variables: ["variable-full"], 24 | }, 25 | { 26 | name: "Material Symbols Rounded Variable", 27 | variables: ["variable-full"], 28 | }, 29 | { 30 | name: "Roboto Mono Variable", 31 | variables: ["variable-full"], 32 | }, 33 | ], 34 | }, 35 | }), 36 | ], // to process SFC 37 | build: { 38 | sourcemap: true, 39 | minify: false, 40 | lib: { 41 | entry: { 42 | index: "./src/index.ts", 43 | "ui-preset": "./ui-preset.ts", 44 | "postcss.config": "./postcss.config.cjs", 45 | }, 46 | formats: ["es"], 47 | }, 48 | rollupOptions: { 49 | external: ["vue"], 50 | output: { 51 | exports: "named", 52 | }, 53 | }, 54 | emptyOutDir: true, 55 | }, 56 | }) 57 | -------------------------------------------------------------------------------- /src/stories/Toast.story.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 51 | 52 | 63 | -------------------------------------------------------------------------------- /src/assets/scss/styles.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Write hoppscotch-ui related custom global styles in this file. 3 | */ 4 | 5 | .legacy-toast [data-content] { 6 | width: 100%; 7 | } 8 | 9 | // legacy toast styles 10 | .l-toast { 11 | display: flex; 12 | width: auto; 13 | clear: both; 14 | position: relative; 15 | max-width: 100%; 16 | height: auto; 17 | word-break: normal; 18 | display: flex; 19 | align-items: center; 20 | justify-content: space-between; 21 | box-sizing: inherit; 22 | overflow: hidden; 23 | } 24 | 25 | .l-toast-actions { 26 | display: flex; 27 | 28 | button { 29 | margin-left: 1rem; 30 | cursor: pointer; 31 | } 32 | } 33 | 34 | @mixin actionButton { 35 | position: relative; 36 | margin-top: 0.25rem; 37 | margin-bottom: 0.25rem; 38 | margin-left: auto; 39 | padding: 0.5rem; 40 | padding-left: 1rem; 41 | padding-right: 1rem; 42 | border-radius: 0.25rem; 43 | font-weight: 600; 44 | text-transform: none; 45 | line-height: 1.5; 46 | letter-spacing: normal; 47 | text-decoration: inherit; 48 | margin-left: 1rem; 49 | @apply hover:no-underline sm:ml-8; 50 | 51 | &:before { 52 | position: absolute; 53 | inset: 0; 54 | background-color: currentColor; 55 | opacity: 0.1; 56 | transition: all 0.15s ease-in-out; 57 | content: ""; 58 | } 59 | &:hover::before { 60 | opacity: 0.2; 61 | } 62 | } 63 | 64 | .action { 65 | @include actionButton; 66 | } 67 | 68 | [data-sonner-toaster] [data-button] { 69 | @include actionButton; 70 | } 71 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require("@rushstack/eslint-patch/modern-module-resolution") 3 | module.exports = { 4 | root: true, 5 | env: { 6 | browser: true, 7 | node: true, 8 | jest: true, 9 | }, 10 | parserOptions: { 11 | sourceType: "module", 12 | requireConfigFile: false, 13 | }, 14 | extends: [ 15 | "@vue/typescript/recommended", 16 | "plugin:vue/vue3-recommended", 17 | "plugin:prettier/recommended", 18 | "plugin:storybook/recommended", 19 | ], 20 | ignorePatterns: [ 21 | "static/**/*", 22 | "./helpers/backend/graphql.ts", 23 | "**/*.d.ts", 24 | "types/**/*", 25 | ], 26 | plugins: ["vue", "prettier"], 27 | // add your custom rules here 28 | rules: { 29 | semi: [2, "never"], 30 | "import/named": "off", 31 | // because, named import issue with typescript see: https://github.com/typescript-eslint/typescript-eslint/issues/154 32 | "no-console": "off", 33 | "no-debugger": process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn", 34 | "prettier/prettier": 35 | process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn", 36 | "vue/multi-word-component-names": "off", 37 | "vue/no-side-effects-in-computed-properties": "off", 38 | "import/no-named-as-default": "off", 39 | "import/no-named-as-default-member": "off", 40 | "@typescript-eslint/no-unused-vars": 41 | process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn", 42 | "@typescript-eslint/no-non-null-assertion": "off", 43 | "@typescript-eslint/no-explicit-any": "off", 44 | "import/default": "off", 45 | "no-undef": "off", 46 | }, 47 | } 48 | -------------------------------------------------------------------------------- /src/components/smart/index.ts: -------------------------------------------------------------------------------- 1 | export { default as HoppSmartAnchor } from "./Anchor.vue" 2 | export { default as HoppSmartAutoComplete } from "./AutoComplete.vue" 3 | export { default as HoppSmartCheckbox } from "./Checkbox.vue" 4 | export { default as HoppSmartConfirmModal } from "./ConfirmModal.vue" 5 | export { default as HoppSmartExpand } from "./Expand.vue" 6 | export { default as HoppSmartFileChip } from "./FileChip.vue" 7 | export { default as HoppSmartInput } from "./Input.vue" 8 | export { default as HoppSmartIntersection } from "./Intersection.vue" 9 | export { default as HoppSmartItem } from "./Item.vue" 10 | export { default as HoppSmartLink } from "./Link.vue" 11 | export { default as HoppSmartModal } from "./Modal.vue" 12 | export { default as HoppSmartProgressRing } from "./ProgressRing.vue" 13 | export { default as HoppSmartRadio } from "./Radio.vue" 14 | export { default as HoppSmartRadioGroup } from "./RadioGroup.vue" 15 | export { default as HoppSmartSlideOver } from "./SlideOver.vue" 16 | export { default as HoppSmartSpinner } from "./Spinner.vue" 17 | export { default as HoppSmartTab } from "./Tab.vue" 18 | export { default as HoppSmartTabs } from "./Tabs.vue" 19 | export { default as HoppSmartTable } from "./Table.vue" 20 | export { default as HoppSmartToggle } from "./Toggle.vue" 21 | export { default as HoppSmartWindow } from "./Window.vue" 22 | export { default as HoppSmartWindows } from "./Windows.vue" 23 | export { default as HoppSmartPicture } from "./Picture.vue" 24 | export { default as HoppSmartPlaceholder } from "./Placeholder.vue" 25 | export { default as HoppSmartTree } from "./Tree.vue" 26 | export { default as HoppSmartTreeBranch } from "./TreeBranch.vue" 27 | export { default as HoppSmartSelectWrapper } from "./SelectWrapper.vue" 28 | -------------------------------------------------------------------------------- /src/stories/LegacyToast.story.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 53 | 54 | 65 | -------------------------------------------------------------------------------- /src/components/modal/examples/NestedDialog.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 63 | -------------------------------------------------------------------------------- /src/components/smart/Window.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 80 | -------------------------------------------------------------------------------- /src/components/smart/SlideOver.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 67 | -------------------------------------------------------------------------------- /src/components/smart/ConfirmModal.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 75 | -------------------------------------------------------------------------------- /src/components/smart/Tab.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 84 | -------------------------------------------------------------------------------- /src/components/smart/Input.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 31 | 32 | 90 | -------------------------------------------------------------------------------- /src/components/smart/Link.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 28 | 29 | 73 | -------------------------------------------------------------------------------- /src/components/smart/Toggle.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | 30 | 90 | -------------------------------------------------------------------------------- /src/components/toast/legacy-toast.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, h, markRaw } from "vue" 2 | import { toast as sonner } from "@hoppscotch/vue-sonner" 3 | import Toast, { LegacyToastAction } from "./Toast.vue" 4 | 5 | export type ToastOptions = { 6 | duration?: number 7 | closeOnSwipe?: boolean 8 | action?: LegacyToastAction | LegacyToastAction[] 9 | } 10 | 11 | /* 12 | * Legacy support for toast.success, toast.error, and toast.warning 13 | */ 14 | 15 | const generateLegacyToastWithActions = ( 16 | message: string, 17 | action: LegacyToastAction | LegacyToastAction[], 18 | toastId: number, 19 | ) => { 20 | const actions = Array.isArray(action) ? action : [action] 21 | 22 | return defineComponent({ 23 | render() { 24 | return h(Toast, { 25 | message, 26 | actions, 27 | onCloseToast: (delay?: number) => { 28 | if (delay !== undefined) { 29 | setTimeout(() => { 30 | sonner.dismiss(toastId) 31 | }, delay) 32 | } else { 33 | sonner.dismiss(toastId) 34 | } 35 | }, 36 | }) 37 | }, 38 | }) 39 | } 40 | 41 | let toastIDTicker = 0 42 | 43 | const addLegacyToast = 44 | (type?: string) => (message: string, option?: ToastOptions) => { 45 | // if action is an array or object with property text then it is a legacy toast 46 | const isLegacyToast = 47 | Array.isArray(option?.action) || 48 | Object.prototype.hasOwnProperty.call(option?.action ?? {}, "text") 49 | 50 | toastIDTicker++ 51 | const toastID = toastIDTicker 52 | 53 | const raw = isLegacyToast 54 | ? markRaw( 55 | generateLegacyToastWithActions( 56 | message, 57 | option?.action as LegacyToastAction, // type assertion is safe here because we checked if it is a legacy toast 58 | toastID, 59 | ), 60 | ) 61 | : message 62 | 63 | const duration = option?.duration === 0 ? Infinity : option?.duration 64 | 65 | sonner(raw, { 66 | ...option, 67 | duration, 68 | action: undefined, 69 | id: toastID, 70 | classes: { 71 | toast: "legacy-toast", 72 | }, 73 | }) 74 | } 75 | 76 | /** 77 | * @deprecated LegacyToast is deprecated and will be removed in a future version. Not recommended for use. Instead import toast from '@hoppscotch/ui' and use 'toast.show()' 78 | */ 79 | const legacyToast = { 80 | success: addLegacyToast("success"), 81 | error: addLegacyToast("error"), 82 | warning: addLegacyToast("warning"), 83 | show: addLegacyToast(), 84 | } 85 | 86 | export { legacyToast } 87 | -------------------------------------------------------------------------------- /src/stories/Window.story.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 96 | -------------------------------------------------------------------------------- /src/components/smart/Tree.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 78 | -------------------------------------------------------------------------------- /src/components/smart/Checkbox.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 35 | 36 | 54 | 55 | 108 | -------------------------------------------------------------------------------- /src/components/button/Secondary.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 100 | -------------------------------------------------------------------------------- /ui-preset.ts: -------------------------------------------------------------------------------- 1 | import { Config } from "tailwindcss" 2 | 3 | export default { 4 | content: [], 5 | theme: { 6 | container: { 7 | center: true, 8 | }, 9 | extend: { 10 | colors: { 11 | primary: "var(--primary-color)", 12 | primaryLight: "var(--primary-light-color)", 13 | primaryDark: "var(--primary-dark-color)", 14 | primaryContrast: "var(--primary-contrast-color)", 15 | secondary: "var(--secondary-color)", 16 | secondaryLight: "var(--secondary-light-color)", 17 | secondaryDark: "var(--secondary-dark-color)", 18 | accent: "var(--accent-color)", 19 | accentLight: "var(--accent-light-color)", 20 | accentDark: "var(--accent-dark-color)", 21 | accentContrast: "var(--accent-contrast-color)", 22 | divider: "var(--divider-color)", 23 | dividerLight: "var(--divider-light-color)", 24 | dividerDark: "var(--divider-dark-color)", 25 | bannerInfo: "var(--banner-info-color)", 26 | bannerWarning: "var(--banner-warning-color)", 27 | bannerError: "var(--banner-error-color)", 28 | tooltip: "var(--tooltip-color)", 29 | popover: "var(--popover-color)", 30 | gradientFrom: "var(--gradient-from-color)", 31 | gradientVia: "var(--gradient-via-color)", 32 | gradientTo: "var(--gradient-to-color)", 33 | dark: { 34 | 50: "#4a4a4a", 35 | 100: "#3c3c3c", 36 | 200: "#323232", 37 | 300: "#2d2d2d", 38 | 400: "#222222", 39 | 500: "#1f1f1f", 40 | 600: "#1c1c1e", 41 | 700: "#1b1b1b", 42 | 800: "#181818", 43 | 900: "#0f0f0f", 44 | }, 45 | light: { 46 | 50: "#fdfdfd", 47 | }, 48 | }, 49 | fontFamily: { 50 | sans: "var(--font-sans)", 51 | mono: "var(--font-mono)", 52 | }, 53 | fontSize: { 54 | tiny: "var(--font-size-tiny)", 55 | body: "var(--font-size-body)", 56 | }, 57 | lineHeight: { 58 | body: "var(--line-height-body)", 59 | }, 60 | cursor: { 61 | nsResize: "ns-resize", 62 | grab: "grab", 63 | grabbing: "grabbing", 64 | }, 65 | spacing: { 66 | 0.25: "0.0625rem", 67 | 0.75: "0.1875rem", 68 | 20: "5rem", 69 | 26: "6.5rem", 70 | 46: "11.5rem", 71 | }, 72 | minWidth: { 73 | 4: "1rem", 74 | 5: "1.25rem", 75 | 20: "5rem", 76 | 46: "11.5rem", 77 | }, 78 | minHeight: { 79 | 5: "1.25rem", 80 | 46: "11.5rem", 81 | }, 82 | maxWidth: { 83 | "1/2": "50%", 84 | "1/3": "33%", 85 | "3/4": "75%", 86 | 46: "11.5rem", 87 | }, 88 | maxHeight: { 89 | 46: "11.5rem", 90 | sm: "24rem", 91 | md: "28rem", 92 | lg: "32rem", 93 | }, 94 | backgroundOpacity: { 95 | 15: "0.15", 96 | }, 97 | screens: { 98 | " 2 | 30 | 34 | 43 |
44 | {{ label }} 45 |
46 |
47 | 52 | {{ key }} 53 | 54 |
55 |
56 | 60 | 61 | 62 |
63 | 64 | 65 | 107 | -------------------------------------------------------------------------------- /src/stories/NewModal.story.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 104 | -------------------------------------------------------------------------------- /src/components/smart/Item.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | Hoppscotch Logo 8 | 9 |
10 |
11 | 12 | # Hoppscotch UI ALPHA 13 | 14 |
15 | 16 | Welcome to hoppscotch-ui, a collection of presentational components for our web applications. This library is built using [Vue 3](https://v3.vuejs.org/) and [Tailwind CSS](https://tailwindcss.com/). Preview the components in [Histoire](https://ui.hoppscotch.io/). 17 | 18 | ## Installation 19 | 20 | To install the library in your project, run the following command: 21 | 22 | ```bash 23 | pnpm add @hoppscotch/ui 24 | ``` 25 | 26 | ## Setup 27 | 28 | In your `main.ts` file, import the library and register it as a plugin: 29 | 30 | ```ts 31 | import { createApp } from "vue" 32 | import App from "./App.vue" 33 | // Import the library 34 | import { plugin as HoppUI } from "@hoppscotch/ui" 35 | 36 | // Import theme styles for default styling (optional) 37 | import "@hoppscotch/ui/themes.css" 38 | 39 | // Import the styles 40 | import "@hoppscotch/ui/style.css" 41 | 42 | const app = createApp(App) 43 | 44 | // Register the library as a plugin 45 | app.use(HoppUI) 46 | 47 | app.mount("#app") 48 | ``` 49 | 50 | The Library uses Tailwind CSS under the hood, so you have to import the preset in your `tailwind.config.ts` file: 51 | 52 | ```ts 53 | import preset from "@hoppscotch/ui/ui-preset" 54 | 55 | export default { 56 | content: ["src/**/*.{vue,html}"], 57 | presets: [preset], 58 | } 59 | ``` 60 | 61 | ## Usage 62 | 63 | You can use the components in your Vue templates like this: 64 | 65 | ```ts 66 | 69 | 70 | 73 | ``` 74 | 75 | If you're using `unplugin-vue-components` in your project, you can import the components like this without having to import them in the script section: 76 | 77 | ```ts 78 | 81 | 82 | 84 | ``` 85 | 86 | To configure resolve options for `unplugin-vue-components`, add the following to your `vite.config.ts` file: 87 | 88 | ```ts 89 | import { defineConfig } from "vite" 90 | import vue from "@vitejs/plugin-vue" 91 | import Components from "unplugin-vue-components/vite" 92 | 93 | export default defineConfig({ 94 | plugins: [ 95 | vue(), 96 | Components({ 97 | resolvers: [ 98 | // auto import components 99 | (name) => { 100 | if (name.startsWith("Hopp")) { 101 | return { 102 | importName: name, 103 | path: "@hoppscotch/ui", 104 | } 105 | } 106 | }, 107 | ], 108 | }), 109 | ], 110 | }) 111 | ``` 112 | 113 | ## Histoire 114 | 115 | We've included Histoire in this library which is similar to Storybook, to make it easy to play with the components in the browser. You can run Histoire in the browser with command 116 | 117 | `pnpm run story:dev` 118 | 119 | You can also use [Histoire](https://histoire.dev/) to create stories for your components and test them in different scenarios. 120 | 121 | ## Versioning 122 | 123 | This project follows [Semantic Versioning](https://semver.org/) but as the project is still pre-1.0. The code and the public exposed API should not be considered to be fixed and stable. Things can change at any time! 124 | 125 | ## License 126 | 127 | This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) - see [`LICENSE`](https://github.com/hoppscotch/hoppscotch/blob/main/LICENSE) for more details. 128 | 129 |
130 | 131 |
132 | 133 |
134 | 135 | ###### built with ❤︎ by the [Hoppscotch Team](https://github.com/hoppscotch) and [contributors](https://github.com/hoppscotch/hoppscotch/graphs/contributors). 136 | 137 |
138 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hoppscotch/ui", 3 | "version": "0.2.5", 4 | "license": "MIT", 5 | "description": "Hoppscotch UI", 6 | "author": "Hoppscotch (support@hoppscotch.io)", 7 | "homepage": "https://github.com/hoppscotch/ui", 8 | "bugs": { 9 | "url": "https://github.com/hoppscotch/ui/issues" 10 | }, 11 | "keywords": [ 12 | "ui", 13 | "vue ui", 14 | "vue", 15 | "hoppscotch ui", 16 | "hoppscotch", 17 | "hoppscotch.io", 18 | "hoppscotch api" 19 | ], 20 | "scripts": { 21 | "dev": "histoire dev --port 6006", 22 | "watch": "vite build --watch", 23 | "build": "vite build && npm run build:css", 24 | "build:css": "sass src/assets/scss/themes.scss dist/themes.css --no-source-map", 25 | "story:build": "histoire build", 26 | "story:preview": "histoire preview", 27 | "prepublish": "pnpm run build" 28 | }, 29 | "peerDependencies": { 30 | "vue": "^3.2.25" 31 | }, 32 | "dependencies": { 33 | "@boringer-avatars/vue3": "^0.2.1", 34 | "@fontsource-variable/inter": "^5.0.5", 35 | "@fontsource-variable/material-symbols-rounded": "^5.0.5", 36 | "@fontsource-variable/roboto-mono": "^5.0.6", 37 | "@hoppscotch/vue-sonner": "^1.2.3", 38 | "@hoppscotch/vue-toasted": "^0.1.0", 39 | "@vitejs/plugin-legacy": "^2.3.0", 40 | "@vueuse/core": "^8.7.5", 41 | "fp-ts": "^2.12.1", 42 | "lodash-es": "^4.17.21", 43 | "path": "^0.12.7", 44 | "vite-plugin-eslint": "^1.8.1", 45 | "vue-promise-modals": "^0.1.0", 46 | "vuedraggable-es": "^4.1.1" 47 | }, 48 | "devDependencies": { 49 | "@esbuild-plugins/node-globals-polyfill": "^0.1.1", 50 | "@esbuild-plugins/node-modules-polyfill": "^0.1.4", 51 | "@histoire/plugin-vue": "^0.17.14", 52 | "@iconify-json/lucide": "^1.1.109", 53 | "@intlify/vite-plugin-vue-i18n": "^6.0.1", 54 | "@rushstack/eslint-patch": "^1.1.4", 55 | "@types/lodash-es": "^4.17.6", 56 | "@types/splitpanes": "^2.2.1", 57 | "@typescript-eslint/eslint-plugin": "^5.19.0", 58 | "@typescript-eslint/parser": "^5.19.0", 59 | "@vitejs/plugin-vue": "^3.1.0", 60 | "@vue/compiler-sfc": "^3.2.39", 61 | "@vue/eslint-config-typescript": "^11.0.1", 62 | "@vue/runtime-core": "^3.2.39", 63 | "autoprefixer": "^10.4.14", 64 | "cross-env": "^7.0.3", 65 | "eslint": "^8.24.0", 66 | "eslint-plugin-prettier": "^4.2.1", 67 | "eslint-plugin-vue": "^9.5.1", 68 | "histoire": "^0.17.14", 69 | "npm-run-all": "^4.1.5", 70 | "postcss": "^8.4.23", 71 | "prettier": "^3.1.0", 72 | "prettier-plugin-tailwindcss": "^0.5.7", 73 | "rollup-plugin-polyfill-node": "^0.10.1", 74 | "sass": "^1.77.0", 75 | "tailwindcss": "^3.3.2", 76 | "typescript": "^4.5.4", 77 | "unplugin-fonts": "^1.0.3", 78 | "unplugin-icons": "^0.16.1", 79 | "unplugin-vue-components": "^0.21.0", 80 | "vite": "^3.2.3", 81 | "vite-plugin-checker": "^0.5.1", 82 | "vite-plugin-dts": "3.2.0", 83 | "vite-plugin-fonts": "^0.6.0", 84 | "vite-plugin-html-config": "^1.0.10", 85 | "vite-plugin-inspect": "^0.7.4", 86 | "vite-plugin-pages": "^0.26.0", 87 | "vite-plugin-pages-sitemap": "^1.4.5", 88 | "vite-plugin-pwa": "^0.13.1", 89 | "vite-plugin-vue-layouts": "^0.7.0", 90 | "vue": "^3.2.25", 91 | "vue-loader": "^16.8.3", 92 | "vue-router": "^4.0.16", 93 | "vue-tsc": "^0.38.2" 94 | }, 95 | "engines": { 96 | "node": ">=16" 97 | }, 98 | "type": "module", 99 | "files": [ 100 | "dist", 101 | "ui-preset.d.ts", 102 | "helpers.d.ts" 103 | ], 104 | "module": "./dist/index.js", 105 | "main": "./dist/index.js", 106 | "exports": { 107 | ".": "./dist/index.js", 108 | "./style.css": "./dist/style.css", 109 | "./themes.css": "./dist/themes.css", 110 | "./ui-preset": { 111 | "import": "./dist/ui-preset.js", 112 | "require": "./dist/ui-preset.js", 113 | "types": "./dist/ui-preset.d.ts" 114 | }, 115 | "./helpers": { 116 | "import": "./dist/src/helpers", 117 | "require": "./dist/src/helpers", 118 | "types": "./dist/src/helpers/index.d.ts" 119 | } 120 | }, 121 | "types": "./dist/index.d.ts" 122 | } 123 | -------------------------------------------------------------------------------- /src/components/modal/index.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 147 | 148 | 164 | -------------------------------------------------------------------------------- /src/components/smart/TreeBranch.vue: -------------------------------------------------------------------------------- 1 | 75 | 76 | 157 | -------------------------------------------------------------------------------- /src/components/smart/AutoComplete.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 191 | 192 | 237 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/components/smart/Modal.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 96 | 97 | 199 | 200 | 216 | -------------------------------------------------------------------------------- /src/stories/Table.story.vue: -------------------------------------------------------------------------------- 1 | 135 | 136 | 222 | -------------------------------------------------------------------------------- /src/components/smart/Tabs.vue: -------------------------------------------------------------------------------- 1 | 87 | 88 | 210 | 211 | 283 | -------------------------------------------------------------------------------- /src/components/smart/Table.vue: -------------------------------------------------------------------------------- 1 | 96 | 97 | 272 | -------------------------------------------------------------------------------- /src/assets/scss/themes.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Write hoppscotch-common related custom styles in this file. 3 | * If styles are sharable across all package then write into hoppscotch-ui/assets/scss/styles.scss file. 4 | */ 5 | 6 | @mixin base-theme { 7 | --font-sans: "Inter Variable", sans-serif; 8 | --font-mono: "Roboto Mono Variable", monospace; 9 | --font-size-body: 0.75rem; 10 | --font-size-tiny: 0.625rem; 11 | --line-height-body: 1rem; 12 | --upper-primary-sticky-fold: 4rem; 13 | --upper-secondary-sticky-fold: 6.188rem; 14 | --upper-tertiary-sticky-fold: 8.25rem; 15 | --upper-fourth-sticky-fold: 10.2rem; 16 | --upper-mobile-primary-sticky-fold: 6.75rem; 17 | --upper-mobile-secondary-sticky-fold: 8.813rem; 18 | --upper-mobile-sticky-fold: 10.875rem; 19 | --upper-mobile-tertiary-sticky-fold: 8.25rem; 20 | --lower-primary-sticky-fold: 3rem; 21 | --lower-secondary-sticky-fold: 5.063rem; 22 | --lower-tertiary-sticky-fold: 7.125rem; 23 | --lower-fourth-sticky-fold: 9.188rem; 24 | --sidebar-primary-sticky-fold: 2rem; 25 | --properties-primary-sticky-fold: 2.063rem; 26 | } 27 | 28 | @mixin dark-theme { 29 | --primary-color: #181818; 30 | --primary-light-color: #1c1c1e; 31 | --primary-dark-color: #262626; 32 | --primary-contrast-color: #171717; 33 | 34 | --secondary-color: #a3a3a3; 35 | --secondary-light-color: #737373; 36 | --secondary-dark-color: #fafafa; 37 | 38 | --divider-color: #262626; 39 | --divider-light-color: #1f1f1f; 40 | --divider-dark-color: #2d2d2d; 41 | 42 | --error-color: #292524; 43 | --tooltip-color: #f5f5f5; 44 | --popover-color: #1b1b1b; 45 | --editor-theme: "merbivore_soft"; 46 | } 47 | 48 | @mixin green-theme { 49 | --accent-color: #10b981; 50 | --accent-light-color: #34d399; 51 | --accent-dark-color: #059669; 52 | --accent-contrast-color: #fff; 53 | --gradient-from-color: #a7f3d0; 54 | --gradient-via-color: #34d399; 55 | --gradient-to-color: #059669; 56 | } 57 | 58 | * { 59 | backface-visibility: hidden; 60 | -moz-backface-visibility: hidden; 61 | -webkit-backface-visibility: hidden; 62 | 63 | &::before { 64 | backface-visibility: hidden; 65 | -moz-backface-visibility: hidden; 66 | -webkit-backface-visibility: hidden; 67 | } 68 | 69 | &::after { 70 | backface-visibility: hidden; 71 | -moz-backface-visibility: hidden; 72 | -webkit-backface-visibility: hidden; 73 | } 74 | 75 | @apply selection:bg-accentDark; 76 | @apply selection:text-accentContrast; 77 | @apply overscroll-none; 78 | } 79 | 80 | :root { 81 | @include base-theme; 82 | @include dark-theme; 83 | @include green-theme; 84 | 85 | @apply antialiased; 86 | accent-color: var(--accent-color); 87 | font-variant-ligatures: common-ligatures; 88 | } 89 | 90 | ::-webkit-scrollbar-track { 91 | @apply bg-transparent; 92 | @apply border-b-0 border-l border-r-0 border-t-0 border-solid border-dividerLight; 93 | } 94 | 95 | ::-webkit-scrollbar-thumb { 96 | @apply bg-divider bg-clip-content; 97 | @apply rounded-full; 98 | @apply border-4 border-solid border-transparent; 99 | @apply hover:bg-dividerDark; 100 | @apply hover:bg-clip-content; 101 | } 102 | 103 | ::-webkit-scrollbar { 104 | @apply w-4; 105 | @apply h-0; 106 | } 107 | 108 | .no-scrollbar { 109 | scrollbar-width: none; 110 | } 111 | 112 | input::placeholder, 113 | textarea::placeholder, 114 | .cm-placeholder { 115 | @apply text-secondary; 116 | @apply opacity-50 #{!important}; 117 | } 118 | 119 | input, 120 | textarea { 121 | @apply text-secondaryDark; 122 | @apply font-medium; 123 | } 124 | 125 | html { 126 | scroll-behavior: smooth; 127 | } 128 | 129 | body { 130 | @apply bg-primary; 131 | @apply text-body text-secondary; 132 | @apply font-medium; 133 | @apply select-none; 134 | @apply overflow-x-hidden; 135 | @apply leading-body #{!important}; 136 | animation: fade 300ms forwards; 137 | -webkit-tap-highlight-color: transparent; 138 | -webkit-touch-callout: none; 139 | } 140 | 141 | @keyframes fade { 142 | 0% { 143 | @apply opacity-0; 144 | } 145 | 146 | 100% { 147 | @apply opacity-100; 148 | } 149 | } 150 | 151 | .fade-enter-active, 152 | .fade-leave-active { 153 | @apply transition-opacity; 154 | } 155 | 156 | .fade-enter-from, 157 | .fade-leave-to { 158 | @apply opacity-0; 159 | } 160 | 161 | .slide-enter-active, 162 | .slide-leave-active { 163 | @apply transition; 164 | @apply duration-300; 165 | } 166 | 167 | .slide-enter-from, 168 | .slide-leave-to { 169 | @apply transform; 170 | @apply translate-x-full; 171 | } 172 | 173 | .bounce-enter-active, 174 | .bounce-leave-active { 175 | @apply transition; 176 | } 177 | 178 | .bounce-enter-from, 179 | .bounce-leave-to { 180 | @apply transform; 181 | @apply scale-95; 182 | } 183 | 184 | .svg-icons { 185 | @apply flex-shrink-0; 186 | @apply overflow-hidden; 187 | height: var(--line-height-body); 188 | width: var(--line-height-body); 189 | } 190 | 191 | a { 192 | @apply inline-flex; 193 | @apply text-current; 194 | @apply no-underline; 195 | @apply transition; 196 | @apply leading-body; 197 | @apply focus:outline-none; 198 | 199 | &.link { 200 | @apply items-center; 201 | @apply px-1 py-0.5; 202 | @apply -mx-1 -my-0.5; 203 | @apply text-accent; 204 | @apply rounded; 205 | @apply hover:text-accentDark; 206 | @apply focus-visible:ring; 207 | @apply focus-visible:ring-accent; 208 | @apply focus-visible:text-accentDark; 209 | } 210 | } 211 | 212 | .cm-tooltip { 213 | .tippy-box { 214 | @apply shadow-none #{!important}; 215 | @apply fixed; 216 | @apply inline-flex; 217 | @apply -mt-7; 218 | } 219 | } 220 | 221 | .tippy-box[data-theme~="tooltip"] { 222 | @apply bg-tooltip; 223 | @apply border-solid border-tooltip; 224 | @apply rounded; 225 | @apply shadow; 226 | 227 | .tippy-content { 228 | @apply flex; 229 | @apply text-tiny text-primary; 230 | @apply font-semibold; 231 | @apply px-2 py-1; 232 | @apply truncate; 233 | @apply leading-body; 234 | @apply items-center; 235 | 236 | kbd { 237 | @apply hidden; 238 | @apply font-sans; 239 | background-color: rgba(107, 114, 128, 0.45); 240 | @apply text-primaryLight; 241 | @apply rounded-sm; 242 | @apply px-1; 243 | @apply my-0 ml-1; 244 | @apply truncate; 245 | @apply sm:inline-flex; 246 | } 247 | 248 | .env-icon { 249 | @apply transition; 250 | @apply inline-flex; 251 | @apply items-center; 252 | } 253 | } 254 | 255 | .tippy-svg-arrow { 256 | svg:first-child { 257 | @apply fill-tooltip; 258 | } 259 | 260 | svg:last-child { 261 | @apply fill-tooltip; 262 | } 263 | } 264 | } 265 | 266 | .tippy-box[data-theme~="popover"] { 267 | @apply bg-popover; 268 | @apply border-solid border-dividerDark; 269 | @apply rounded; 270 | @apply shadow-lg; 271 | @apply max-w-[45vw] #{!important}; 272 | 273 | .tippy-content { 274 | @apply flex flex-col; 275 | @apply max-h-[45vh]; 276 | @apply items-stretch; 277 | @apply overflow-y-auto; 278 | @apply text-body text-secondary; 279 | @apply p-2; 280 | @apply leading-body; 281 | @apply focus:outline-none; 282 | scroll-behavior: smooth; 283 | 284 | & > span { 285 | @apply block #{!important}; 286 | } 287 | } 288 | 289 | .tippy-svg-arrow { 290 | svg:first-child { 291 | @apply fill-dividerDark; 292 | } 293 | 294 | svg:last-child { 295 | @apply fill-popover; 296 | } 297 | } 298 | } 299 | 300 | [data-v-tippy] { 301 | @apply flex flex-1; 302 | @apply truncate; 303 | } 304 | 305 | [interactive] > div { 306 | @apply flex flex-1; 307 | @apply h-full; 308 | } 309 | 310 | hr { 311 | @apply border-b border-dividerLight; 312 | @apply my-2 #{!important}; 313 | } 314 | 315 | .heading { 316 | @apply font-bold; 317 | @apply text-lg text-secondaryDark; 318 | @apply tracking-tight; 319 | } 320 | 321 | .input, 322 | .select, 323 | .textarea { 324 | @apply flex; 325 | @apply w-full; 326 | @apply px-4 py-2; 327 | @apply bg-transparent; 328 | @apply rounded; 329 | @apply text-secondaryDark; 330 | @apply border border-divider; 331 | @apply focus-visible:border-dividerDark; 332 | } 333 | 334 | input, 335 | select, 336 | textarea, 337 | button { 338 | @apply truncate; 339 | @apply transition; 340 | @apply text-body; 341 | @apply leading-body; 342 | @apply focus:outline-none; 343 | @apply disabled:cursor-not-allowed; 344 | } 345 | 346 | .input[type="file"], 347 | .input[type="radio"], 348 | #installPWA { 349 | @apply hidden; 350 | } 351 | 352 | .floating-input ~ label { 353 | @apply absolute; 354 | @apply px-2 py-0.5; 355 | @apply m-2; 356 | @apply rounded; 357 | @apply transition; 358 | @apply origin-top-left; 359 | } 360 | 361 | .floating-input:focus-within ~ label, 362 | .floating-input:not(:placeholder-shown) ~ label { 363 | @apply bg-primary; 364 | @apply transform; 365 | @apply origin-top-left; 366 | @apply scale-75; 367 | @apply -translate-y-4 translate-x-1; 368 | } 369 | 370 | .floating-input:focus-within ~ label { 371 | @apply text-secondaryDark; 372 | } 373 | 374 | .floating-input ~ .end-actions { 375 | @apply absolute; 376 | @apply right-[.05rem]; 377 | @apply inset-y-0; 378 | @apply flex; 379 | @apply items-center; 380 | } 381 | 382 | .floating-input:has(~ .end-actions) { 383 | @apply pr-12; 384 | } 385 | 386 | pre.ace_editor { 387 | @apply font-mono; 388 | @apply resize-none; 389 | @apply z-0; 390 | } 391 | 392 | .select { 393 | @apply appearance-none; 394 | @apply cursor-pointer; 395 | 396 | &::-ms-expand { 397 | @apply hidden; 398 | } 399 | } 400 | 401 | .info-response { 402 | color: var(--status-info-color); 403 | } 404 | 405 | .success-response { 406 | color: var(--status-success-color); 407 | } 408 | 409 | .redirect-response { 410 | color: var(--status-redirect-color); 411 | } 412 | 413 | .critical-error-response { 414 | color: var(--status-critical-error-color); 415 | } 416 | 417 | .server-error-response { 418 | color: var(--status-server-error-color); 419 | } 420 | 421 | .missing-data-response { 422 | color: var(--status-missing-data-color); 423 | } 424 | 425 | .toasted-container { 426 | @apply max-w-md; 427 | @apply z-[10000]; 428 | 429 | .toasted { 430 | &.toasted-primary { 431 | @apply px-4 py-2; 432 | @apply bg-tooltip; 433 | @apply border-secondaryDark; 434 | @apply text-body text-primary; 435 | @apply justify-between; 436 | @apply shadow-lg; 437 | @apply font-semibold; 438 | @apply transition; 439 | @apply leading-body; 440 | @apply sm:rounded; 441 | @apply sm:border; 442 | 443 | .action { 444 | @apply relative; 445 | @apply flex flex-shrink-0; 446 | @apply text-body; 447 | @apply px-4; 448 | @apply my-1; 449 | @apply ml-auto; 450 | @apply normal-case; 451 | @apply font-semibold; 452 | @apply leading-body; 453 | @apply tracking-normal; 454 | @apply rounded; 455 | @apply last:ml-4; 456 | @apply sm:ml-8; 457 | @apply before:absolute; 458 | @apply before:bg-current; 459 | @apply before:opacity-10; 460 | @apply before:inset-0; 461 | @apply before:transition; 462 | @apply before:content-['']; 463 | @apply hover:no-underline; 464 | @apply hover:before:opacity-20; 465 | } 466 | } 467 | 468 | &.info { 469 | @apply bg-accent; 470 | @apply text-accentContrast; 471 | @apply border-accentDark; 472 | } 473 | 474 | &.error { 475 | @apply bg-red-200; 476 | @apply text-red-800; 477 | @apply border-red-400; 478 | } 479 | 480 | &.success { 481 | @apply bg-green-200; 482 | @apply text-green-800; 483 | @apply border-green-400; 484 | } 485 | } 486 | } 487 | 488 | .smart-splitter .splitpanes__splitter { 489 | @apply relative; 490 | @apply before:absolute; 491 | @apply before:inset-0; 492 | @apply before:bg-accentLight; 493 | @apply before:opacity-0; 494 | @apply before:z-20; 495 | @apply before:transition; 496 | @apply before:content-['']; 497 | @apply hover:before:opacity-100; 498 | } 499 | 500 | .no-splitter .splitpanes__splitter { 501 | @apply relative; 502 | } 503 | 504 | .smart-splitter.splitpanes--vertical > .splitpanes__splitter { 505 | @apply w-0; 506 | @apply before:-left-0.5; 507 | @apply before:-right-0.5; 508 | @apply before:h-full; 509 | @apply bg-divider; 510 | } 511 | 512 | .smart-splitter.splitpanes--horizontal > .splitpanes__splitter { 513 | @apply h-0; 514 | @apply before:-top-0.5; 515 | @apply before:-bottom-0.5; 516 | @apply before:w-full; 517 | @apply bg-divider; 518 | } 519 | 520 | .no-splitter.splitpanes--vertical > .splitpanes__splitter { 521 | @apply w-0; 522 | @apply pointer-events-none; 523 | @apply bg-dividerLight; 524 | } 525 | 526 | .no-splitter.splitpanes--horizontal > .splitpanes__splitter { 527 | @apply h-0; 528 | @apply pointer-events-none; 529 | @apply bg-dividerLight; 530 | } 531 | 532 | .splitpanes--horizontal .splitpanes__pane { 533 | @apply transition-none; 534 | } 535 | 536 | .splitpanes--vertical .splitpanes__pane { 537 | @apply transition-none; 538 | } 539 | 540 | .cm-focused { 541 | @apply select-auto; 542 | @apply outline-none #{!important}; 543 | 544 | .cm-activeLine { 545 | @apply bg-primaryLight; 546 | } 547 | 548 | .cm-activeLineGutter { 549 | @apply bg-primaryDark; 550 | } 551 | } 552 | 553 | .cm-scroller { 554 | @apply overscroll-y-auto; 555 | } 556 | 557 | .cm-editor { 558 | .cm-line::selection { 559 | @apply bg-accentDark #{!important}; 560 | @apply text-accentContrast #{!important}; 561 | } 562 | 563 | .cm-line ::selection { 564 | @apply bg-accentDark #{!important}; 565 | @apply text-accentContrast #{!important}; 566 | } 567 | } 568 | 569 | .shortcut-key { 570 | @apply inline-flex; 571 | @apply font-sans; 572 | @apply text-tiny; 573 | @apply bg-dividerLight; 574 | @apply rounded; 575 | @apply ml-2; 576 | @apply px-0.5; 577 | @apply min-w-[1rem]; 578 | @apply min-h-[1rem]; 579 | @apply leading-none; 580 | @apply items-center; 581 | @apply justify-center; 582 | @apply border border-dividerDark; 583 | @apply shadow-sm; 584 | @apply 2 |
3 |
6 |
10 |
14 |
15 | 23 | 81 | 82 |
83 |
86 | 90 | 98 | 99 |
100 |
101 |
102 | 103 |
104 | 105 |
106 | 107 | 124 |
125 |
126 | 127 |
128 |
129 | 130 | 131 | 376 | 377 | 490 | --------------------------------------------------------------------------------