├── .cz.toml ├── .eslintignore ├── .eslintrc.cjs ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── checks.yml │ ├── commitlint.yml │ ├── deploy.yml │ ├── prlint.yml │ ├── publish.yml │ └── version-bump.yml ├── .gitignore ├── .husky └── commit-msg ├── .npmrc ├── .prettierrc ├── .vscode └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── _templates └── create │ ├── component │ ├── index.ejs.t │ ├── prompt.cjs │ └── types.ejs.t │ └── typings │ ├── prompt.cjs │ └── types.ejs.t ├── commitlint.config.cjs ├── package.json ├── playwright.config.js ├── pnpm-lock.yaml ├── src ├── app.d.ts ├── app.html ├── lib │ ├── components │ │ ├── anims │ │ │ ├── Loader.spec.ts │ │ │ ├── Loader.svelte │ │ │ ├── Loader.svelte.d.ts │ │ │ ├── Spinner.svelte │ │ │ ├── Spinner.svelte.d.ts │ │ │ └── index.ts │ │ ├── basic │ │ │ ├── Box.svelte │ │ │ ├── Box.svelte.d.ts │ │ │ ├── Container.svelte │ │ │ ├── Container.svelte.d.ts │ │ │ ├── Icon.svelte │ │ │ ├── Icon.svelte.d.ts │ │ │ ├── Link.svelte │ │ │ ├── Link.svelte.d.ts │ │ │ ├── Logo.svelte │ │ │ ├── Logo.svelte.d.ts │ │ │ ├── Text.svelte │ │ │ ├── Text.svelte.d.ts │ │ │ ├── VisuallyHidden.svelte │ │ │ ├── VisuallyHidden.svelte.d.ts │ │ │ └── index.ts │ │ ├── buttons │ │ │ ├── Button.svelte │ │ │ ├── Button.svelte.d.ts │ │ │ ├── ButtonGroup.svelte │ │ │ ├── ButtonGroup.svelte.d.ts │ │ │ ├── IconButton.svelte │ │ │ ├── IconButton.svelte.d.ts │ │ │ ├── RippleButton.svelte │ │ │ ├── RippleButton.svelte.d.ts │ │ │ └── index.ts │ │ ├── forms │ │ │ ├── Input.svelte │ │ │ ├── Input.svelte.d.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── providers │ │ │ ├── ChakraProvider.svelte │ │ │ ├── GlobalStyles.svelte │ │ │ └── index.ts │ │ └── stacks │ │ │ ├── Flex.svelte │ │ │ ├── Flex.svelte.d.ts │ │ │ ├── HStack.svelte │ │ │ ├── HStack.svelte.d.ts │ │ │ ├── Stack.svelte │ │ │ ├── Stack.svelte.d.ts │ │ │ ├── VStack.svelte │ │ │ ├── VStack.svelte.d.ts │ │ │ └── index.ts │ ├── core │ │ ├── attributes.ts │ │ ├── emotion.ts │ │ ├── events.ts │ │ ├── index.ts │ │ └── styled.ts │ ├── index.ts │ ├── stores │ │ ├── colormode.ts │ │ └── index.ts │ ├── theme │ │ ├── components │ │ │ ├── button.ts │ │ │ ├── icon.ts │ │ │ ├── index.ts │ │ │ ├── input.ts │ │ │ └── link.ts │ │ ├── core │ │ │ ├── blur.ts │ │ │ ├── borders.ts │ │ │ ├── breakpoints.ts │ │ │ ├── colors.ts │ │ │ ├── index.ts │ │ │ ├── radius.ts │ │ │ ├── shadows.ts │ │ │ ├── sizes.ts │ │ │ ├── spacing.ts │ │ │ ├── transition.ts │ │ │ ├── typography.ts │ │ │ └── z-index.ts │ │ ├── index.ts │ │ ├── styles.ts │ │ └── theme.types.ts │ ├── types.d.ts │ └── utils │ │ ├── index.ts │ │ ├── object.test.ts │ │ ├── object.ts │ │ ├── store.test.ts │ │ ├── store.ts │ │ ├── theme-tools.test.ts │ │ └── theme-tools.ts └── routes │ ├── +error.svelte │ ├── +layout.svelte │ ├── +page.svelte │ ├── components │ └── ActionButton.svelte │ ├── docs │ ├── [...page] │ │ ├── +layout.svelte │ │ └── +page.svelte │ └── pages │ │ └── installation.md │ ├── layout │ ├── Footer.svelte │ └── Navbar.svelte │ └── stores.ts ├── static ├── logo-dark.svg ├── logo-light.svg └── logo-square.svg ├── svelte.config.js ├── tests └── test.ts ├── tsconfig.json ├── vite.config.ts └── vitest.config.ts /.cz.toml: -------------------------------------------------------------------------------- 1 | [tool.commitizen] 2 | version = "0.2.7" 3 | version_files = [ 4 | "package.json:version" 5 | ] 6 | bump_message = "chore(release): $current_version → $new_version [skip-ci]" 7 | changelog_incremental = true 8 | changelog_file = "CHANGELOG.md" 9 | changelog_start_rev = "0.2.0" 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .github 2 | .vercel 3 | .husky 4 | .svelte-kit 5 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:unicorn/recommended', 8 | 'prettier' 9 | ], 10 | plugins: ['svelte3', '@typescript-eslint'], 11 | ignorePatterns: ['*.cjs', 'package/*'], 12 | overrides: [ 13 | { 14 | files: ['*.svelte'], 15 | processor: 'svelte3/svelte3' 16 | } 17 | ], 18 | rules: { 19 | 'linebreak-style': ['error', 'unix'], 20 | 'max-lines-per-function': ['error', 50], 21 | complexity: ['error', 10], 22 | 'max-depth': ['error', 3], 23 | 'max-nested-callbacks': ['error', 3], 24 | 'max-params': ['error', 3], 25 | 'unicorn/prefer-at': ['error', { checkAllIndexAccess: true }], 26 | 'unicorn/prevent-abbreviations': 'off', 27 | 'unicorn/filename-case': [ 28 | 'error', 29 | { 30 | case: 'kebabCase', 31 | ignore: ['.*\\.svelte\\.d\\.ts', '.*\\.spec\\.ts'] 32 | } 33 | ], 34 | 'no-undef': 'off' 35 | }, 36 | settings: { 37 | 'svelte3/typescript': () => require('typescript') 38 | }, 39 | parserOptions: { 40 | sourceType: 'module', 41 | ecmaVersion: 'latest' 42 | }, 43 | env: { 44 | browser: true, 45 | es2021: true, 46 | node: true 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" # See documentation for possible values 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: Run Svelte & Types Check 2 | 3 | on: 4 | workflow_dispatch: # on button click 5 | pull_request_target: 6 | types: 7 | - opened 8 | - edited 9 | - synchronize 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.head_ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | run-checks: 17 | if: github.event.pull_request.draft == false 18 | name: Run Svelte-Check & Lint 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v3 22 | - uses: actions/setup-node@v3 23 | with: 24 | node-version: 16 25 | - run: npm install -g pnpm 26 | - run: pnpm i 27 | - run: pnpm lint 28 | - run: pnpm svelte-check 29 | - run: pnpm test 30 | -------------------------------------------------------------------------------- /.github/workflows/commitlint.yml: -------------------------------------------------------------------------------- 1 | name: Lint Commits Messages 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.head_ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | lint_commits: 16 | if: github.event.pull_request.draft == false 17 | name: Lint Commit Messages 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Lint commits 21 | uses: actions-ecosystem/action-lint-commits@v1 22 | id: lint-commits 23 | with: 24 | github_token: ${{ secrets.GITHUB_TOKEN }} 25 | regex: '^(feat|chore|fix|refactor|docs)(\(([\w]+)\))*(!*): .+' # e.g.) "feat(api): Add /users/get" 26 | format: markdown 27 | 28 | - name: Post warning comment 29 | uses: actions-ecosystem/action-create-comment@v1 30 | if: ${{ steps.lint-commits.outputs.unmatched_commits != '' }} 31 | with: 32 | github_token: ${{ secrets.GITHUB_TOKEN }} 33 | body: | 34 | The following commits needs their message changes: 35 | ${{ steps.lint-commits.outputs.unmatched_commits }} 36 | The format `(): ` (`^\w+: .+`) is acceptable. e.g., `feat(api): Add /users/get` 37 | - name: Fail when commits don't pass lint 38 | if: ${{ steps.lint-commits.outputs.unmatched_commits != '' }} 39 | run: exit 1 40 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Vercel 2 | on: 3 | workflow_dispatch: # on button click 4 | push: 5 | branches: [main] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | with: 15 | ref: ${{ steps.script.outputs.ref }} 16 | repository: ${{ steps.script.outputs.repo }} 17 | - name: Deploy to Vercel Action 18 | uses: BetaHuhn/deploy-to-vercel-action@v1.9.10 19 | with: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} 22 | VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} 23 | VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} 24 | TRIM_COMMIT_MESSAGE: false 25 | CREATE_COMMENT: true 26 | PRODUCTION: true 27 | -------------------------------------------------------------------------------- /.github/workflows/prlint.yml: -------------------------------------------------------------------------------- 1 | name: Lint PR Title 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.head_ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | main: 16 | if: github.event.pull_request.draft == false 17 | name: Validate PR title 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: amannn/action-semantic-pull-request@v4 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-node@v3 13 | with: 14 | node-version: 16 15 | - run: npm install -g pnpm 16 | - run: pnpm i --no-optional 17 | - run: pnpm package 18 | - uses: JS-DevTools/npm-publish@v1 19 | with: 20 | token: ${{ secrets.NPM_TOKEN }} 21 | package: ./package/package.json 22 | -------------------------------------------------------------------------------- /.github/workflows/version-bump.yml: -------------------------------------------------------------------------------- 1 | name: Build Tag and Version 2 | 3 | on: 4 | workflow_dispatch: # on button click 5 | push: 6 | branches: 7 | - "main" 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | install-and-test: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/setup-node@v2 19 | with: 20 | node-version: 16 21 | - run: npm i -g husky pnpm 22 | - run: pnpm install --no-optional 23 | - run: pnpm test 24 | tag-and-bump: 25 | if: "!startsWith(github.event.head_commit.message, 'bump:')" 26 | needs: install-and-test 27 | runs-on: ubuntu-latest 28 | name: "Bump version and create changelog with commitizen" 29 | steps: 30 | - name: Check out 31 | uses: actions/checkout@v2 32 | with: 33 | fetch-depth: 0 34 | token: "${{ secrets.GITHUB_TOKEN }}" 35 | - id: cz 36 | name: Bump package 37 | uses: commitizen-tools/commitizen-action@master 38 | with: 39 | github_token: ${{ secrets.GITHUB_TOKEN }} 40 | - name: Print Version 41 | run: echo "Bumped to version ${{ steps.cz.outputs.version }}" 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # histoire 11 | .histoire 12 | .vercel 13 | .idea 14 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | legacy-peer-deps=true 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabCompletion": "on" 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.7 (2023-03-28) 2 | 3 | ### Fix 4 | 5 | - align icon button items to center (#82) 6 | 7 | ## 0.2.6 (2023-03-24) 8 | 9 | ### Fix 10 | 11 | - use csr to load docs pages (#64) 12 | 13 | ## 0.2.5 (2023-03-24) 14 | 15 | ### Fix 16 | 17 | - chakra action styling bug (#62) 18 | - minor cleanups and ci improvements (#61) 19 | - Use stricter component types definitions (#59) 20 | 21 | ## 0.2.4 (2023-03-19) 22 | 23 | ### Fix 24 | 25 | - disable route prerender (#56) 26 | 27 | ## 0.2.3 (2023-03-19) 28 | 29 | ### Fix 30 | 31 | - remove redundant component drilling (#48) 32 | - Improve events, attributes actions (#47) 33 | 34 | ## 0.2.2 (2023-02-25) 35 | 36 | ### Fix 37 | 38 | - change docs hero to match chakra-ui (#44) 39 | 40 | ## 0.2.1 (2023-02-25) 41 | 42 | ### Fix 43 | 44 | - **ops**: access denied to push changes (#38) 45 | - **ui**: pass no slots for input component (#36) 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | Thanks for showing interest to contribute to this project 😩. 4 | 5 | When it comes to open source, there are different ways you can contribute, all 6 | of which are valuable. 7 | 8 | ## Setup the Project 9 | 10 | The following steps will get you up and running to contribute to Chakra UI `svelte`: 11 | 12 | 1. Fork the repo (click the Fork button at the top right of 13 | [this page](https://github.com/elcharitas/chakra-ui-svelte)) 14 | 15 | 2. Clone your fork locally 16 | 17 | ```sh 18 | git clone https://github.com//chakra-ui-svelte.git 19 | cd chakra-ui-svelte 20 | ``` 21 | 22 | 3. Setup all the dependencies and packages by running `pnpm install`. This 23 | command will install dependencies. 24 | 25 | > If you run into any issues during this step, kindly reach out to me via twitter 26 | 27 | ### Tooling 28 | 29 | - [PNPM](https://pnpm.io/) to manage packages and dependencies 30 | - [Histoire](https://histoire.dev/) for rapid UI component development and 31 | testing 32 | 33 | ### Commands 34 | 35 | **`pnpm install`**: bootstraps the entire project, symlinks all dependencies for 36 | cross-component development and builds all components. 37 | 38 | **`pnpm test`**: run test for all component packages. 39 | 40 | ## Making a Pull Request? 41 | 42 | Pull requests need only the :+1: of two or more collaborators to be merged; when 43 | the PR author is a collaborator, that counts as one. 44 | 45 | ### Commit Convention 46 | 47 | Before you create a Pull Request, please check whether your commits comply with 48 | the commit conventions used in this repository. 49 | 50 | When you create a commit we kindly ask you to follow the convention 51 | `category(scope or module): message` in your commit message while using one of 52 | the following categories: 53 | 54 | - `feat / feature`: all changes that introduce completely new code or new 55 | features 56 | - `fix`: changes that fix a bug (ideally you will additionally reference an 57 | issue if present) 58 | - `refactor`: any code related change that is not a fix nor a feature 59 | - `docs`: changing existing or creating new documentation (i.e. README, docs for 60 | usage of a lib or cli usage) 61 | - `build`: all changes regarding the build of the software, changes to 62 | dependencies or the addition of new dependencies 63 | - `test`: all changes regarding tests (adding new tests or changing existing 64 | ones) 65 | - `ci`: all changes regarding the configuration of continuous integration (i.e. 66 | github actions, ci system) 67 | - `chore`: all changes to the repository that do not fit into any of the above 68 | categories 69 | 70 | If you are interested in the detailed specification you can visit 71 | https://www.conventionalcommits.org/ or check out the 72 | [Angular Commit Message Guidelines](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines). 73 | 74 | ### Steps to PR 75 | 76 | 1. Fork of the chakra-ui repository and clone your fork 77 | 78 | 2. Create a new branch out of the `main` branch. We follow the convention 79 | `[type/scope]`. For example `fix/accordion-hook` or `docs/menu-typo`. `type` 80 | can be either `docs`, `fix`, `feat`, `build`, or any other conventional 81 | commit type. `scope` is just a short id that describes the scope of work. 82 | 83 | 3. Make and commit your changes following the 84 | [commit convention](https://github.com/elcharitas/chakra-ui-svelte/blob/main/CONTRIBUTING.md#commit-convention). 85 | As you develop, you can run `pnpm pkg build` and 86 | `pnpm pkg test` to make sure everything works as expected. Please 87 | note that you might have to run `pnpm boot` first in order to build all 88 | dependencies. 89 | 90 | ## License 91 | 92 | By contributing your code to the chakra-ui GitHub repository, you agree to 93 | license your contribution under the MIT license. 94 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Till Date Jonathan Irhodia 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 | 3 |
4 | 5 | # Chakra UI `svelte` 👋 6 | 7 | ## Build Accessible Svelte Apps with Speed 8 | 9 | [![npm version](https://badge.fury.io/js/chakra-ui-svelte.svg)](https://www.npmjs.com/package/chakra-ui-svelte) 10 | [![Documentation](https://img.shields.io/badge/documentation-yes-brightgreen.svg)](#) 11 | [![iamelcharitas](https://img.shields.io/twitter/follow/iamelcharitas.svg?style=)](https://twitter.com/iamelcharitas) 12 | 13 | Chakra UI provides a set of accessible, reusable, and composable Svelte components that make it super easy to create websites and apps. 14 | 15 | ## Features 🚀 16 | 17 | - Ease of Styling: Chakra UI contains a set of layout components like Box and Stack that make it easy to style your components by passing props. 18 | - Flexible & composable: Chakra UI components are built to be adaptable and extended. 19 | - Accessible. Chakra UI components follow the WAI-ARIA guidelines specifications and have the right aria-\* attributes. 20 | - Out the box support for Dark Mode 😍: Most components in Chakra UI are dark mode compatible. 21 | 22 | ## Installation 23 | 24 | To use Chakra UI components, all you need to do is install the `chakra-ui-svelte` package and its peer dependencies: 25 | 26 | ```sh 27 | $ yarn add chakra-ui-svelte @emotion/css 28 | 29 | # or 30 | 31 | $ npm i chakra-ui-svelte @emotion/css 32 | 33 | # or 34 | 35 | $ pnpm install chakra-ui-svelte @emotion/css 36 | ``` 37 | 38 | ## Usage 39 | 40 | To start using the components, please follow these steps: 41 | 42 | - Wrap your application with the `ChakraProvider` 43 | 44 | ```html 45 | // page.svelte 46 | 50 | 51 | 52 | 53 | 54 | ``` 55 | 56 | - The provider is essential as it injects generated styles into your svelte app. 57 | 58 | ## Supported Components 59 | 60 | The latest release has the following components 61 | 62 | - ChakraProvider - Which should wrap all other components 63 | - Box - The Basic component upon which every other component is built on 64 | - Icon 65 | - Logo 66 | - Text 67 | - VisuallyHidden 68 | - Button 69 | - IconButton 70 | - RippleButton 71 | - Flex 72 | - Stack 73 | - HStack 74 | - VStack 75 | - Spinner 76 | - Loader 77 | 78 | Complete Documentation would be available soon 79 | 80 | ## Contributing 81 | 82 | Feel like contributing? That's awesome! There's a [contributing guide](./CONTRIBUTING.md) to help guide you. 83 | 84 | ## Author 85 | 86 | 👤 **elcharitas** 87 | 88 | - Website: https://elcharitas.dev 89 | - Twitter: [@iamelcharitas](https://twitter.com/iamelcharitas) 90 | - Github: [@elcharitas](https://github.com/elcharitas) 91 | - LinkedIn: [@elcharitas](https://linkedin.com/in/elcharitas) 92 | 93 | ## Show your support 94 | 95 | Give a ⭐️ if this project helped you! 96 | -------------------------------------------------------------------------------- /_templates/create/component/index.ejs.t: -------------------------------------------------------------------------------- 1 | --- 2 | to: src/lib/components/<%= name %>.svelte 3 | --- 4 | 26 | 27 | 32 | 33 | -------------------------------------------------------------------------------- /_templates/create/component/prompt.cjs: -------------------------------------------------------------------------------- 1 | // see types of prompts: 2 | // https://github.com/enquirer/enquirer/tree/master/examples 3 | // 4 | module.exports = [ 5 | { 6 | type: 'input', 7 | name: 'name', 8 | message: 'Component Name?' 9 | } 10 | ]; 11 | -------------------------------------------------------------------------------- /_templates/create/component/types.ejs.t: -------------------------------------------------------------------------------- 1 | --- 2 | to: src/lib/components/<%= name %>.svelte.d.ts 3 | --- 4 | import type { ChakraComponentProps } from '$lib/types'; 5 | import type { SvelteComponentTyped } from 'svelte'; 6 | 7 | export type <%= name %>Props = ChakraComponentProps; 8 | export default class <%= name %> extends SvelteComponentTyped<<%= name %>Props> {} 9 | -------------------------------------------------------------------------------- /_templates/create/typings/prompt.cjs: -------------------------------------------------------------------------------- 1 | // see types of prompts: 2 | // https://github.com/enquirer/enquirer/tree/master/examples 3 | // 4 | module.exports = [ 5 | { 6 | type: 'input', 7 | name: 'name', 8 | message: 'Component Name?' 9 | } 10 | ]; 11 | -------------------------------------------------------------------------------- /_templates/create/typings/types.ejs.t: -------------------------------------------------------------------------------- 1 | --- 2 | to: src/lib/components/<%= name %>.svelte.d.ts 3 | --- 4 | import type { ChakraComponentProps } from '$lib/types'; 5 | import type { SvelteComponentTyped } from 'svelte'; 6 | 7 | export type <%= name.split('/').reverse()[0] %>Props = ChakraComponentProps; 8 | export default class <%= name.split('/').reverse()[0] %> extends SvelteComponentTyped<<%= name.split('/').reverse()[0] %>Props> {} 9 | -------------------------------------------------------------------------------- /commitlint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'] 3 | }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chakra-ui-svelte", 3 | "description": "Create accessible svelte apps with speed", 4 | "version": "0.2.7", 5 | "type": "module", 6 | "main": "./index.js", 7 | "homepage": "https://github.com/elcharitas/chakra-ui-svelte", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/elcharitas/chakra-ui-svelte" 11 | }, 12 | "scripts": { 13 | "dev": "vite dev", 14 | "build": "svelte-kit sync && vite build", 15 | "package": "svelte-kit sync && svelte-package", 16 | "preview": "vite preview", 17 | "create:component": "hygen create component", 18 | "create:typings": "hygen create typings", 19 | "test:e2e": "playwright test", 20 | "test": "vitest run src", 21 | "test:watch": "vitest src", 22 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 23 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 24 | "lint": "eslint . --ext .svelte,.ts", 25 | "format": "prettier --write .", 26 | "prepare": "husky install" 27 | }, 28 | "dependencies": { 29 | "@chakra-ui/styled-system": "^2.3.1", 30 | "dash-get": "^1.0.2" 31 | }, 32 | "peerDependencies": { 33 | "@emotion/css": "^11.10.5", 34 | "svelte": "^3.50.0", 35 | "vite-plugin-css-injected-by-js": "^3.1.0" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "^7.16.7", 39 | "@commitlint/cli": "^17.4.4", 40 | "@commitlint/config-conventional": "^17.4.4", 41 | "@emotion/css": "^11.10.5", 42 | "@playwright/test": "^1.20.0", 43 | "@sveltejs/adapter-auto": "next", 44 | "@sveltejs/kit": "^1.8.0", 45 | "@sveltejs/package": "next", 46 | "@testing-library/jest-dom": "^5.16.5", 47 | "@testing-library/svelte": "^3.1.1", 48 | "@typescript-eslint/eslint-plugin": "^5.10.1", 49 | "@typescript-eslint/parser": "^5.10.1", 50 | "autoprefixer": "^10.4.2", 51 | "babel-loader": "^8.2.3", 52 | "commitizen": "^4.3.0", 53 | "eslint": "^8.35.0", 54 | "eslint-config-prettier": "^8.3.0", 55 | "eslint-plugin-svelte3": "^4.0.0", 56 | "eslint-plugin-unicorn": "^46.0.0", 57 | "html-webpack-plugin": "^5.5.0", 58 | "husky": "^8.0.0", 59 | "hygen": "^6.2.0", 60 | "jsdom": "^21.1.1", 61 | "prettier": "^2.5.1", 62 | "prettier-plugin-svelte": "^2.5.0", 63 | "svelte": "^3.50.1", 64 | "svelte-check": "^3.0.1", 65 | "svelte-loader": "^3.1.3", 66 | "svelte-preprocess": "^5.0.0", 67 | "svelte2tsx": "^0.6.0", 68 | "tslib": "^2.4.1", 69 | "typescript": "^4.7.4", 70 | "vite": "^4.0.3", 71 | "vite-plugin-css-injected-by-js": "^3.1.0", 72 | "vitest": "^0.29.7" 73 | }, 74 | "optionalDependencies": { 75 | "highlight.js": "^11.7.0", 76 | "lunr": "^2.3.9", 77 | "markdown-it-highlightjs": "^4.0.1", 78 | "svelte-icons": "^2.1.0", 79 | "vite-plugin-svelte-md": "^0.1.7" 80 | }, 81 | "resolutions": { 82 | "@types/jest": "npm:vitest" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /playwright.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@playwright/test').PlaywrightTestConfig} */ 2 | const config = { 3 | webServer: { 4 | command: 'npm run build && npm run preview', 5 | port: 3000 6 | } 7 | }; 8 | 9 | export default config; 10 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // See https://kit.svelte.dev/docs/types#the-app-namespace 4 | // for information about these interfaces 5 | declare namespace App { 6 | // interface Locals {} 7 | // interface Platform {} 8 | // interface Session {} 9 | // interface Stuff {} 10 | } 11 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Chakra UI Svelte 9 | 10 | 11 | 15 | %sveltekit.head% 16 | 21 | 22 | 23 |
%sveltekit.body%
24 | 25 | 26 | -------------------------------------------------------------------------------- /src/lib/components/anims/Loader.spec.ts: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/svelte'; 2 | import Loader from './Loader.svelte'; 3 | import '@testing-library/jest-dom'; 4 | 5 | describe('Loader', () => { 6 | it('finds the SVG element', async () => { 7 | const { container } = render(Loader); 8 | const svgElement = container.querySelector('svg'); 9 | expect(svgElement).toBeInTheDocument(); 10 | }); 11 | 12 | it('finds the circle element', async () => { 13 | const { container } = render(Loader); 14 | const circleElement = container.querySelector('circle'); 15 | expect(circleElement).toBeInTheDocument(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/lib/components/anims/Loader.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | 20 | 21 | 22 | 43 | -------------------------------------------------------------------------------- /src/lib/components/anims/Loader.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type LoaderProps = ChakraComponentProps & { 5 | size?: LoaderProps['inlineSize']; 6 | }; 7 | export default class Loader extends SvelteComponentTyped {} 8 | -------------------------------------------------------------------------------- /src/lib/components/anims/Spinner.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /src/lib/components/anims/Spinner.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type SpinnerProps = ChakraComponentProps & { 5 | size?: SpinnerProps['width']; 6 | }; 7 | export default class Spinner extends SvelteComponentTyped {} 8 | -------------------------------------------------------------------------------- /src/lib/components/anims/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Loader } from './Loader.svelte'; 2 | export { default as Spinner } from './Spinner.svelte'; 3 | -------------------------------------------------------------------------------- /src/lib/components/basic/Box.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | {#if typeof as === 'string'} 14 | {#if noSlot} 15 | 16 | {:else} 17 | 18 | 19 | 20 | {/if} 21 | {:else if typeof as !== 'string'} 22 | {#if wrap} 23 | 29 | 30 | 31 | 32 | 33 | {:else} 34 | 39 | 40 | 41 | 42 | {/if} 43 | {/if} 44 | -------------------------------------------------------------------------------- /src/lib/components/basic/Box.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type BoxProps = ChakraComponentProps; 5 | export default class Box extends SvelteComponentTyped {} 6 | -------------------------------------------------------------------------------- /src/lib/components/basic/Container.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/components/basic/Container.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type ContainerProps = ChakraComponentProps; 5 | export default class Container extends SvelteComponentTyped {} 6 | -------------------------------------------------------------------------------- /src/lib/components/basic/Icon.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/lib/components/basic/Icon.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type IconProps = ChakraComponentProps & { 5 | viewBox?: string; 6 | xmlns?: string; 7 | fill?: string; 8 | stroke?: string; 9 | strokeWidth?: string; 10 | strokeLinecap?: string; 11 | strokeLinejoin?: string; 12 | strokeDasharray?: string; 13 | strokeDashoffset?: string; 14 | cx?: string; 15 | cy?: string; 16 | r?: string; 17 | }; 18 | export default class Icon extends SvelteComponentTyped {} 19 | -------------------------------------------------------------------------------- /src/lib/components/basic/Link.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/components/basic/Link.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type LinkProps = ChakraComponentProps & { 5 | /** 6 | * The URL the link points to. 7 | * 8 | * Links are not restricted to HTTP-based URLs — they can use any URL scheme supported by browsers. 9 | */ 10 | href?: string; 11 | }; 12 | export default class Link extends SvelteComponentTyped {} 13 | -------------------------------------------------------------------------------- /src/lib/components/basic/Logo.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 17 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/lib/components/basic/Logo.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type LogoProps = ChakraComponentProps; 5 | export default class Logo extends SvelteComponentTyped {} 6 | -------------------------------------------------------------------------------- /src/lib/components/basic/Text.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/components/basic/Text.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type TextProps = ChakraComponentProps; 5 | export default class Text extends SvelteComponentTyped {} 6 | -------------------------------------------------------------------------------- /src/lib/components/basic/VisuallyHidden.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/lib/components/basic/VisuallyHidden.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type VisuallyHiddenProps = ChakraComponentProps; 5 | export default class VisuallyHidden extends SvelteComponentTyped {} 6 | -------------------------------------------------------------------------------- /src/lib/components/basic/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Box } from './Box.svelte'; 2 | export { default as Icon } from './Icon.svelte'; 3 | export { default as Link } from './Link.svelte'; 4 | export { default as Text } from './Text.svelte'; 5 | export { default as Logo } from './Logo.svelte'; 6 | export { default as VisuallyHidden } from './VisuallyHidden.svelte'; 7 | export { default as Container } from './Container.svelte'; 8 | -------------------------------------------------------------------------------- /src/lib/components/buttons/Button.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/components/buttons/Button.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type ButtonProps = ChakraComponentProps & { 5 | /** 6 | * The styled variant of the button. 7 | * 8 | * @default solid 9 | */ 10 | variant?: 'solid' | 'outline' | 'ghost' | 'link' | 'unstyled'; 11 | }; 12 | export default class Button extends SvelteComponentTyped {} 13 | -------------------------------------------------------------------------------- /src/lib/components/buttons/ButtonGroup.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/components/buttons/ButtonGroup.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type ButtonGroupProps = ChakraComponentProps & { 5 | /** 6 | * The space between each button 7 | */ 8 | spacing?: ButtonGroupProps['gap']; 9 | }; 10 | export default class ButtonGroup extends SvelteComponentTyped {} 11 | -------------------------------------------------------------------------------- /src/lib/components/buttons/IconButton.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 17 | -------------------------------------------------------------------------------- /src/lib/components/buttons/IconButton.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type IconButtonProps = ChakraComponentProps & { 5 | /** 6 | * The icon to display in the button. 7 | */ 8 | icon: IconButtonProps['as']; 9 | }; 10 | export default class IconButton extends SvelteComponentTyped {} 11 | -------------------------------------------------------------------------------- /src/lib/components/buttons/RippleButton.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 | 63 | 64 | 80 | -------------------------------------------------------------------------------- /src/lib/components/buttons/RippleButton.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type RippleButtonProps = ChakraComponentProps & { 5 | /** 6 | * Whether the button is currently rippling. 7 | */ 8 | isRippling: boolean; 9 | }; 10 | export default class RippleButton extends SvelteComponentTyped {} 11 | -------------------------------------------------------------------------------- /src/lib/components/buttons/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Button } from './Button.svelte'; 2 | export { default as IconButton } from './IconButton.svelte'; 3 | export { default as ButtonGroup } from './ButtonGroup.svelte'; 4 | export { default as RippleButton } from './RippleButton.svelte'; 5 | -------------------------------------------------------------------------------- /src/lib/components/forms/Input.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/components/forms/Input.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type InputProps = ChakraComponentProps; 5 | export default class Input extends SvelteComponentTyped {} 6 | -------------------------------------------------------------------------------- /src/lib/components/forms/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Input } from './Input.svelte'; 2 | -------------------------------------------------------------------------------- /src/lib/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './anims'; 2 | export * from './basic'; 3 | export * from './buttons'; 4 | export * from './forms'; 5 | export * from './providers'; 6 | export * from './stacks'; 7 | -------------------------------------------------------------------------------- /src/lib/components/providers/ChakraProvider.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 |
23 | -------------------------------------------------------------------------------- /src/lib/components/providers/GlobalStyles.svelte: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /src/lib/components/providers/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ChakraProvider } from './ChakraProvider.svelte'; 2 | export { default as GlobalStyles } from './GlobalStyles.svelte'; 3 | -------------------------------------------------------------------------------- /src/lib/components/stacks/Flex.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/lib/components/stacks/Flex.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type FlexProps = ChakraComponentProps & { 5 | direction?: FlexProps['flexDirection']; 6 | }; 7 | export default class Flex extends SvelteComponentTyped {} 8 | -------------------------------------------------------------------------------- /src/lib/components/stacks/HStack.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/components/stacks/HStack.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type StackProps = ChakraComponentProps & { 5 | spacing?: StackProps['gap']; 6 | orientation?: 'vertical' | 'horizontal'; 7 | }; 8 | export default class HStack extends SvelteComponentTyped {} 9 | -------------------------------------------------------------------------------- /src/lib/components/stacks/Stack.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/lib/components/stacks/Stack.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type StackProps = ChakraComponentProps & { 5 | spacing?: StackProps['gap']; 6 | orientation?: 'vertical' | 'horizontal'; 7 | }; 8 | export default class Stack extends SvelteComponentTyped {} 9 | -------------------------------------------------------------------------------- /src/lib/components/stacks/VStack.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/lib/components/stacks/VStack.svelte.d.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraComponentProps } from '$lib/types'; 2 | import type { SvelteComponentTyped } from 'svelte'; 3 | 4 | export type StackProps = ChakraComponentProps & { 5 | spacing?: StackProps['gap']; 6 | orientation?: 'vertical' | 'horizontal'; 7 | }; 8 | export default class VStack extends SvelteComponentTyped {} 9 | -------------------------------------------------------------------------------- /src/lib/components/stacks/index.ts: -------------------------------------------------------------------------------- 1 | export { default as HStack } from './HStack.svelte'; 2 | export { default as VStack } from './VStack.svelte'; 3 | export { default as Stack } from './Stack.svelte'; 4 | export { default as Flex } from './Flex.svelte'; 5 | -------------------------------------------------------------------------------- /src/lib/core/attributes.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraAction } from '$lib/types'; 2 | 3 | /** 4 | * Sets the specified attributes on the given element. 5 | * 6 | * @param node The node to set attributes on. 7 | * @param props The props of a component containing attributes. 8 | * @returns An object with an `update` function for updating the props. 9 | */ 10 | export const attributes: ChakraAction = (node, nodeProps) => { 11 | // Define the list of attributes to set. 12 | const validAttributes = ['id', 'title', 'viewBox', 'xmlns', 'fill', 'stroke']; 13 | 14 | const update = (newProps: typeof nodeProps) => { 15 | const props = { ...newProps, ...newProps?.props }; 16 | // If an `as` property is provided, add all prototype properties of the specified element. 17 | if (node.tagName && typeof document !== 'undefined') { 18 | const prototypeAttributes = Object.getOwnPropertyNames(Object.getPrototypeOf(node)); 19 | validAttributes.push(...prototypeAttributes); 20 | } 21 | 22 | // Set the valid attributes on the element. 23 | for (const attribute of validAttributes) { 24 | if (attribute in props && typeof props[attribute] !== 'function') { 25 | node.setAttribute(attribute, props[attribute] as string); 26 | } 27 | } 28 | }; 29 | 30 | update(nodeProps); 31 | 32 | // Return an object with an `update` function for updating the attributes. 33 | return { update }; 34 | }; 35 | -------------------------------------------------------------------------------- /src/lib/core/emotion.ts: -------------------------------------------------------------------------------- 1 | export { cx, css as system, injectGlobal } from '@emotion/css'; 2 | -------------------------------------------------------------------------------- /src/lib/core/events.ts: -------------------------------------------------------------------------------- 1 | import type { ChakraActionNoProps } from '$lib/types'; 2 | import type { SvelteComponent } from 'svelte'; 3 | import { bubble, get_current_component, listen } from 'svelte/internal'; 4 | 5 | const knownEvents = [ 6 | 'focus', 7 | 'blur', 8 | 'fullscreenchange', 9 | 'fullscreenerror', 10 | 'scroll', 11 | 'cut', 12 | 'copy', 13 | 'paste', 14 | 'keydown', 15 | 'keypress', 16 | 'keyup', 17 | 'auxclick', 18 | 'click', 19 | 'contextmenu', 20 | 'dblclick', 21 | 'mousedown', 22 | 'mouseenter', 23 | 'mouseleave', 24 | 'mousemove', 25 | 'mouseover', 26 | 'mouseout', 27 | 'mouseup', 28 | 'pointerlockchange', 29 | 'pointerlockerror', 30 | 'select', 31 | 'wheel', 32 | 'drag', 33 | 'dragend', 34 | 'dragenter', 35 | 'dragstart', 36 | 'dragleave', 37 | 'dragover', 38 | 'drop', 39 | 'touchcancel', 40 | 'touchend', 41 | 'touchmove', 42 | 'touchstart', 43 | 'pointerover', 44 | 'pointerenter', 45 | 'pointerdown', 46 | 'pointermove', 47 | 'pointerup', 48 | 'pointercancel', 49 | 'pointerout', 50 | 'pointerleave', 51 | 'gotpointercapture', 52 | 'lostpointercapture' 53 | ]; 54 | 55 | /** 56 | * Components in svelte are not event emitters, so we need to manually 57 | * forward events from the component to the underlying DOM element. 58 | * This is a builder. It builds a svelte action used to forward 59 | * events from the component to the underlying DOM element. 60 | * 61 | * @param additionalEvents The additional set of events to listen for 62 | */ 63 | export function forwardEvents(additionalEvents: string[] = []): ChakraActionNoProps { 64 | const component: SvelteComponent = get_current_component(); 65 | const events = [...knownEvents, ...additionalEvents]; 66 | 67 | function forward(e: Event) { 68 | bubble(component, e); 69 | } 70 | 71 | return (node) => { 72 | const destructors: (() => void)[] = []; 73 | 74 | for (const event of events) { 75 | if (node.addEventListener) { 76 | destructors.push( 77 | listen(node, event, forward, { 78 | passive: false 79 | }) 80 | ); 81 | } 82 | } 83 | 84 | return { 85 | destroy: () => { 86 | for (const d of destructors) d(); 87 | } 88 | }; 89 | }; 90 | } 91 | -------------------------------------------------------------------------------- /src/lib/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './styled'; 2 | export * from './emotion'; 3 | export * from './events'; 4 | export * from './attributes'; 5 | -------------------------------------------------------------------------------- /src/lib/core/styled.ts: -------------------------------------------------------------------------------- 1 | import get from 'dash-get'; 2 | import { css, toCSSVar, type StyleConfig } from '@chakra-ui/styled-system'; 3 | import { themeStore } from '$lib/theme'; 4 | import type { ChakraAction, ChakraComponentProps } from '$lib/types'; 5 | import { system, cx } from './emotion'; 6 | import { runIfFn, filter } from '$lib/utils'; 7 | 8 | /** 9 | * Creates and return a class based on a components props 10 | * 11 | * @param props 12 | */ 13 | export function createStyle({ sx, apply, ...props }: T) { 14 | const currentTheme = themeStore.get(); 15 | const themeVars = toCSSVar(currentTheme); 16 | 17 | /** TODO: handle responsive values as well */ 18 | const applyVal = apply?.toString() || ''; 19 | const applyAs = applyVal.includes('.') ? applyVal : `components.${applyVal}`; 20 | 21 | const { defaultProps, ...config }: StyleConfig = get(currentTheme, applyAs) || {}; 22 | const { size, variant, ...safeProps } = { ...defaultProps, ...props }; 23 | 24 | const style = css({ 25 | ...config.baseStyle, 26 | ...config.sizes?.[size], 27 | ...runIfFn(config.variants?.[variant], safeProps) 28 | })(themeVars); 29 | 30 | const customStyle = css({ 31 | ...safeProps, 32 | ...sx 33 | })(themeVars); 34 | 35 | return cx(system(style), system(customStyle), safeProps.class as string); 36 | } 37 | 38 | /** 39 | * Base action to style nodes 40 | * 41 | * @param node 42 | * @param props 43 | */ 44 | export const chakra: ChakraAction = (node, props) => { 45 | const nodeAttrs = Object.getOwnPropertyNames(Object.getPrototypeOf(node)); 46 | const update = (newProps: typeof props) => { 47 | const validProps = filter(newProps, (_, key) => !nodeAttrs.includes(String(key))); 48 | const className = createStyle(validProps); 49 | node.className = className; 50 | }; 51 | 52 | // onMount, set initial class 53 | update(props); 54 | 55 | return { update }; 56 | }; 57 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; 2 | export * from './core'; 3 | export * from './theme'; 4 | export * from './utils'; 5 | export * from './stores'; 6 | -------------------------------------------------------------------------------- /src/lib/stores/colormode.ts: -------------------------------------------------------------------------------- 1 | import { derived } from 'svelte/store'; 2 | import { createStore } from '$lib/utils'; 3 | 4 | type ColorMode = 'light' | 'dark'; 5 | 6 | export const colorMode = createStore( 7 | () => { 8 | const mode: ColorMode = 'light'; 9 | 10 | if (typeof window !== 'undefined') { 11 | /** When running in histoire, it seems `window` is defined but it's props aren't implemented 12 | * Thus we need to catch this error by assuming all window APIs are undefined. 13 | * This is a quick fix till we can get something better. 14 | */ 15 | const storage = window?.localStorage?.getItem('chakra-ui-color-mode') === 'dark'; 16 | 17 | return storage ? 'dark' : 'light'; 18 | } 19 | 20 | return mode; 21 | }, 22 | (mode) => { 23 | if (typeof window !== 'undefined') { 24 | window?.localStorage?.setItem('chakra-ui-color-mode', mode); 25 | } 26 | } 27 | ); 28 | 29 | /** Checks if current colorMode is 'dark' or 'light' */ 30 | export const isDarkMode = derived(colorMode, (colorMode) => colorMode === 'dark'); 31 | 32 | /** 33 | * Here is a function similar to useColorModeValue 34 | * 35 | * @param light 36 | * @param dark 37 | * @returns 38 | */ 39 | export const colorModeValue = (light: T, dark: T) => { 40 | const store = createStore(() => light); 41 | colorMode.subscribe((value) => { 42 | store.set(value === 'light' ? light : dark); 43 | }); 44 | return store; 45 | }; 46 | -------------------------------------------------------------------------------- /src/lib/stores/index.ts: -------------------------------------------------------------------------------- 1 | export * from './colormode'; 2 | -------------------------------------------------------------------------------- /src/lib/theme/components/button.ts: -------------------------------------------------------------------------------- 1 | import { defineStyleConfig } from '@chakra-ui/styled-system'; 2 | import { mode, transparentize } from '$lib/utils'; 3 | 4 | const baseStyle = { 5 | cursor: 'pointer', 6 | border: 'none', 7 | lineHeight: '1.2', 8 | borderRadius: 'md', 9 | fontWeight: 'semibold', 10 | transitionProperty: 'common', 11 | transitionDuration: 'normal', 12 | _focus: { 13 | boxShadow: 'outline' 14 | }, 15 | _disabled: { 16 | opacity: 0.4, 17 | cursor: 'not-allowed', 18 | boxShadow: 'none' 19 | }, 20 | _hover: { 21 | _disabled: { 22 | bg: 'initial' 23 | } 24 | } 25 | }; 26 | 27 | const variantGhost = (props) => { 28 | const { colorScheme: c, theme } = props; 29 | 30 | if (c === 'gray') { 31 | return { 32 | color: mode(`inherit`, `whiteAlpha.900`)(props), 33 | _hover: { 34 | bg: mode(`gray.100`, `whiteAlpha.200`)(props) 35 | }, 36 | _active: { bg: mode(`gray.200`, `whiteAlpha.300`)(props) } 37 | }; 38 | } 39 | 40 | const darkHoverBg = transparentize(`${c}.200`, 0.12)(theme); 41 | const darkActiveBg = transparentize(`${c}.200`, 0.24)(theme); 42 | 43 | return { 44 | color: mode(`${c}.600`, `${c}.200`)(props), 45 | bg: 'transparent', 46 | _hover: { 47 | bg: mode(`${c}.50`, darkHoverBg)(props) 48 | }, 49 | _active: { 50 | bg: mode(`${c}.100`, darkActiveBg)(props) 51 | } 52 | }; 53 | }; 54 | 55 | const variantOutline = (props) => { 56 | const { colorScheme: c } = props; 57 | const borderColor = mode(`gray.200`, `whiteAlpha.300`)(props); 58 | return { 59 | border: '1px solid', 60 | borderColor: c === 'gray' ? borderColor : 'currentColor', 61 | ...variantGhost(props) 62 | }; 63 | }; 64 | 65 | /** Accessible color overrides for less accessible colors. */ 66 | const accessibleColorMap = { 67 | yellow: { 68 | bg: 'yellow.400', 69 | color: 'black', 70 | hoverBg: 'yellow.500', 71 | activeBg: 'yellow.600' 72 | }, 73 | cyan: { 74 | bg: 'cyan.400', 75 | color: 'black', 76 | hoverBg: 'cyan.500', 77 | activeBg: 'cyan.600' 78 | } 79 | }; 80 | 81 | const variantSolid = (props) => { 82 | const { colorScheme: c } = props; 83 | 84 | if (c === 'gray') { 85 | const bg = mode(`gray.100`, `whiteAlpha.200`)(props); 86 | 87 | return { 88 | bg, 89 | _hover: { 90 | bg: mode(`gray.200`, `whiteAlpha.300`)(props), 91 | _disabled: { 92 | bg 93 | } 94 | }, 95 | _active: { bg: mode(`gray.300`, `whiteAlpha.400`)(props) } 96 | }; 97 | } 98 | 99 | const { bg = `${c}.500`, color = 'white', hoverBg = `${c}.600`, activeBg = `${c}.700` } = 100 | accessibleColorMap[c] ?? {}; 101 | 102 | const background = mode(bg, `${c}.200`)(props); 103 | 104 | return { 105 | bg: background, 106 | color: mode(color, `gray.800`)(props), 107 | _hover: { 108 | bg: mode(hoverBg, `${c}.300`)(props), 109 | _disabled: { 110 | bg: background 111 | } 112 | }, 113 | _active: { bg: mode(activeBg, `${c}.400`)(props) } 114 | }; 115 | }; 116 | 117 | const variantLink = (props) => { 118 | const { colorScheme: c } = props; 119 | return { 120 | padding: 0, 121 | height: 'auto', 122 | lineHeight: 'normal', 123 | verticalAlign: 'baseline', 124 | color: mode(`${c}.500`, `${c}.200`)(props), 125 | _hover: { 126 | textDecoration: 'underline', 127 | _disabled: { 128 | textDecoration: 'none' 129 | } 130 | }, 131 | _active: { 132 | color: mode(`${c}.700`, `${c}.500`)(props) 133 | } 134 | }; 135 | }; 136 | 137 | const variantUnstyled = { 138 | bg: 'none', 139 | color: 'inherit', 140 | display: 'inline', 141 | lineHeight: 'inherit', 142 | m: 0, 143 | p: 0 144 | }; 145 | 146 | const variants = { 147 | ghost: variantGhost, 148 | outline: variantOutline, 149 | solid: variantSolid, 150 | link: variantLink, 151 | unstyled: variantUnstyled 152 | }; 153 | 154 | const sizes = { 155 | lg: { 156 | h: 12, 157 | minW: 12, 158 | fontSize: 'lg', 159 | px: 6 160 | }, 161 | md: { 162 | h: 10, 163 | minW: 10, 164 | fontSize: 'md', 165 | px: 4 166 | }, 167 | sm: { 168 | h: 8, 169 | minW: 8, 170 | fontSize: 'sm', 171 | px: 3 172 | }, 173 | xs: { 174 | h: 6, 175 | minW: 6, 176 | fontSize: 'xs', 177 | px: 2 178 | } 179 | }; 180 | 181 | export default defineStyleConfig({ 182 | baseStyle, 183 | variants, 184 | sizes, 185 | defaultProps: { 186 | variant: 'solid', 187 | size: 'md', 188 | colorScheme: 'gray' 189 | } 190 | }); 191 | -------------------------------------------------------------------------------- /src/lib/theme/components/icon.ts: -------------------------------------------------------------------------------- 1 | import { defineStyleConfig } from '@chakra-ui/styled-system'; 2 | 3 | const sizes = { 4 | lg: { 5 | h: 12, 6 | w: 12 7 | }, 8 | md: { 9 | h: 10, 10 | w: 10 11 | }, 12 | sm: { 13 | h: 8, 14 | w: 8 15 | }, 16 | xs: { 17 | h: 6, 18 | w: 6 19 | } 20 | }; 21 | 22 | export default defineStyleConfig({ 23 | sizes, 24 | defaultProps: { 25 | size: 'xs', 26 | colorScheme: 'gray' 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /src/lib/theme/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Button } from './button'; 2 | export { default as Link } from './link'; 3 | export { default as Icon } from './icon'; 4 | export { default as Input } from './input'; 5 | -------------------------------------------------------------------------------- /src/lib/theme/components/input.ts: -------------------------------------------------------------------------------- 1 | import { defineStyleConfig, type StyleFunctionProps } from '@chakra-ui/styled-system'; 2 | import { mode, getColor } from '$lib/utils'; 3 | 4 | const baseStyle = { 5 | width: '100%', 6 | minWidth: 0, 7 | outline: 0, 8 | position: 'relative', 9 | appearance: 'none', 10 | transitionProperty: 'common', 11 | transitionDuration: 'normal', 12 | _disabled: { 13 | opacity: 0.4, 14 | cursor: 'not-allowed' 15 | } 16 | }; 17 | 18 | const sizes = { 19 | lg: { 20 | fontSize: 'lg', 21 | px: 4, 22 | h: 12, 23 | borderRadius: 'md' 24 | }, 25 | md: { 26 | fontSize: 'md', 27 | px: 4, 28 | h: 10, 29 | borderRadius: 'md' 30 | }, 31 | sm: { 32 | fontSize: 'sm', 33 | px: 3, 34 | h: 8, 35 | borderRadius: 'sm' 36 | }, 37 | xs: { 38 | fontSize: 'xs', 39 | px: 2, 40 | h: 6, 41 | borderRadius: 'sm' 42 | } 43 | }; 44 | 45 | function getDefaults(properties: StyleFunctionProps) { 46 | const { focusBorderColor: fc, errorBorderColor: ec } = properties; 47 | return { 48 | focusBorderColor: fc || mode('blue.500', 'blue.300')(properties), 49 | errorBorderColor: ec || mode('red.500', 'red.300')(properties) 50 | }; 51 | } 52 | 53 | const variantOutline = (properties: StyleFunctionProps) => { 54 | const { theme } = properties; 55 | const { focusBorderColor: fc, errorBorderColor: ec } = getDefaults(properties); 56 | 57 | return { 58 | border: '1px solid', 59 | borderColor: mode('whiteAlpha.50', 'gray.200')(properties), 60 | bg: 'inherit', 61 | _hover: { 62 | borderColor: mode('gray.300', 'whiteAlpha.400')(properties) 63 | }, 64 | _readOnly: { 65 | boxShadow: 'none !important', 66 | userSelect: 'all' 67 | }, 68 | _invalid: { 69 | borderColor: getColor(theme, ec), 70 | boxShadow: `0 0 0 1px ${getColor(theme, ec)}` 71 | }, 72 | _focusVisible: { 73 | zIndex: 1, 74 | borderColor: getColor(theme, fc), 75 | boxShadow: `0 0 0 1px ${getColor(theme, fc)}` 76 | } 77 | }; 78 | }; 79 | 80 | const variantFilled = (properties: StyleFunctionProps) => { 81 | const { theme } = properties; 82 | const { focusBorderColor: fc, errorBorderColor: ec } = getDefaults(properties); 83 | 84 | return { 85 | border: '2px solid', 86 | borderColor: 'transparent', 87 | bg: mode('gray.100', 'whiteAlpha.50')(properties), 88 | _hover: { 89 | bg: mode('gray.200', 'whiteAlpha.100')(properties) 90 | }, 91 | _readOnly: { 92 | boxShadow: 'none !important', 93 | userSelect: 'all' 94 | }, 95 | _invalid: { 96 | borderColor: getColor(theme, ec) 97 | }, 98 | _focusVisible: { 99 | bg: 'transparent', 100 | borderColor: getColor(theme, fc) 101 | } 102 | }; 103 | }; 104 | 105 | const variantFlushed = (properties: StyleFunctionProps) => { 106 | const { theme } = properties; 107 | const { focusBorderColor: fc, errorBorderColor: ec } = getDefaults(properties); 108 | 109 | return { 110 | borderBottom: '1px solid', 111 | borderColor: 'inherit', 112 | borderRadius: '0', 113 | px: '0', 114 | bg: 'transparent', 115 | _readOnly: { 116 | boxShadow: 'none !important', 117 | userSelect: 'all' 118 | }, 119 | _invalid: { 120 | borderColor: getColor(theme, ec), 121 | boxShadow: `0px 1px 0px 0px ${getColor(theme, ec)}` 122 | }, 123 | _focusVisible: { 124 | borderColor: getColor(theme, fc), 125 | boxShadow: `0px 1px 0px 0px ${getColor(theme, fc)}` 126 | } 127 | }; 128 | }; 129 | 130 | const variantUnstyled = { 131 | bg: 'transparent', 132 | px: '0', 133 | height: 'auto' 134 | }; 135 | 136 | const variants = { 137 | outline: variantOutline, 138 | filled: variantFilled, 139 | flushed: variantFlushed, 140 | unstyled: variantUnstyled 141 | }; 142 | 143 | export default defineStyleConfig({ 144 | baseStyle, 145 | sizes, 146 | variants, 147 | defaultProps: { 148 | size: 'md', 149 | variant: 'outline', 150 | colorScheme: 'gray' 151 | } 152 | }); 153 | -------------------------------------------------------------------------------- /src/lib/theme/components/link.ts: -------------------------------------------------------------------------------- 1 | import { defineStyleConfig } from '@chakra-ui/styled-system'; 2 | 3 | const baseStyle = { 4 | padding: 0, 5 | height: 'auto', 6 | lineHeight: 'normal', 7 | verticalAlign: 'baseline', 8 | color: 'green.500', 9 | cursor: 'pointer', 10 | border: 'none', 11 | transitionProperty: 'common', 12 | transitionDuration: 'normal', 13 | textDecoration: 'none', 14 | _hover: { 15 | textDecoration: 'underline', 16 | _disabled: { 17 | textDecoration: 'none' 18 | } 19 | }, 20 | _active: { 21 | color: 'green.800', 22 | textDecoration: 'none' 23 | }, 24 | _focus: { 25 | boxShadow: 'outline', 26 | textDecoration: 'none' 27 | }, 28 | _disabled: { 29 | opacity: 0.4, 30 | cursor: 'not-allowed', 31 | boxShadow: 'none', 32 | textDecoration: 'none' 33 | } 34 | }; 35 | 36 | export default defineStyleConfig({ 37 | baseStyle, 38 | defaultProps: { 39 | size: 'md', 40 | colorScheme: 'primary' 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /src/lib/theme/core/blur.ts: -------------------------------------------------------------------------------- 1 | const blur = { 2 | none: 0, 3 | sm: "4px", 4 | base: "8px", 5 | md: "12px", 6 | lg: "16px", 7 | xl: "24px", 8 | "2xl": "40px", 9 | "3xl": "64px", 10 | } 11 | 12 | export default blur 13 | -------------------------------------------------------------------------------- /src/lib/theme/core/borders.ts: -------------------------------------------------------------------------------- 1 | const borders = { 2 | none: 0, 3 | "1px": "1px solid", 4 | "2px": "2px solid", 5 | "4px": "4px solid", 6 | "8px": "8px solid", 7 | } 8 | 9 | export default borders 10 | -------------------------------------------------------------------------------- /src/lib/theme/core/breakpoints.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Breakpoints for responsive design 3 | */ 4 | const breakpoints = { 5 | sm: '30em', 6 | md: '48em', 7 | lg: '62em', 8 | xl: '80em', 9 | '2xl': '96em' 10 | }; 11 | 12 | export default breakpoints; 13 | -------------------------------------------------------------------------------- /src/lib/theme/core/colors.ts: -------------------------------------------------------------------------------- 1 | const colors = { 2 | transparent: 'transparent', 3 | current: 'currentColor', 4 | black: '#000000', 5 | white: '#FFFFFF', 6 | 7 | whiteAlpha: { 8 | 50: 'rgba(255, 255, 255, 0.04)', 9 | 100: 'rgba(255, 255, 255, 0.06)', 10 | 200: 'rgba(255, 255, 255, 0.08)', 11 | 300: 'rgba(255, 255, 255, 0.16)', 12 | 400: 'rgba(255, 255, 255, 0.24)', 13 | 500: 'rgba(255, 255, 255, 0.36)', 14 | 600: 'rgba(255, 255, 255, 0.48)', 15 | 700: 'rgba(255, 255, 255, 0.64)', 16 | 800: 'rgba(255, 255, 255, 0.80)', 17 | 900: 'rgba(255, 255, 255, 0.92)' 18 | }, 19 | 20 | blackAlpha: { 21 | 50: 'rgba(0, 0, 0, 0.04)', 22 | 100: 'rgba(0, 0, 0, 0.06)', 23 | 200: 'rgba(0, 0, 0, 0.08)', 24 | 300: 'rgba(0, 0, 0, 0.16)', 25 | 400: 'rgba(0, 0, 0, 0.24)', 26 | 500: 'rgba(0, 0, 0, 0.36)', 27 | 600: 'rgba(0, 0, 0, 0.48)', 28 | 700: 'rgba(0, 0, 0, 0.64)', 29 | 800: 'rgba(0, 0, 0, 0.80)', 30 | 900: 'rgba(0, 0, 0, 0.92)' 31 | }, 32 | 33 | gray: { 34 | 50: '#F7FAFC', 35 | 100: '#EDF2F7', 36 | 200: '#E2E8F0', 37 | 300: '#CBD5E0', 38 | 400: '#A0AEC0', 39 | 500: '#718096', 40 | 600: '#4A5568', 41 | 700: '#2D3748', 42 | 800: '#1A202C', 43 | 900: '#171923' 44 | }, 45 | 46 | red: { 47 | 50: '#FFF5F5', 48 | 100: '#FED7D7', 49 | 200: '#FEB2B2', 50 | 300: '#FC8181', 51 | 400: '#F56565', 52 | 500: '#E53E3E', 53 | 600: '#C53030', 54 | 700: '#9B2C2C', 55 | 800: '#822727', 56 | 900: '#63171B' 57 | }, 58 | 59 | orange: { 60 | 50: '#FFFAF0', 61 | 100: '#FEEBC8', 62 | 200: '#FBD38D', 63 | 300: '#F6AD55', 64 | 400: '#ED8936', 65 | 500: '#DD6B20', 66 | 600: '#C05621', 67 | 700: '#9C4221', 68 | 800: '#7B341E', 69 | 900: '#652B19' 70 | }, 71 | 72 | yellow: { 73 | 50: '#FFFFF0', 74 | 100: '#FEFCBF', 75 | 200: '#FAF089', 76 | 300: '#F6E05E', 77 | 400: '#ECC94B', 78 | 500: '#D69E2E', 79 | 600: '#B7791F', 80 | 700: '#975A16', 81 | 800: '#744210', 82 | 900: '#5F370E' 83 | }, 84 | 85 | green: { 86 | 50: '#F0FFF4', 87 | 100: '#C6F6D5', 88 | 200: '#9AE6B4', 89 | 300: '#68D391', 90 | 400: '#48BB78', 91 | 500: '#38A169', 92 | 600: '#2F855A', 93 | 700: '#276749', 94 | 800: '#22543D', 95 | 900: '#1C4532' 96 | }, 97 | 98 | teal: { 99 | 50: '#E6FFFA', 100 | 100: '#B2F5EA', 101 | 200: '#81E6D9', 102 | 300: '#4FD1C5', 103 | 400: '#38B2AC', 104 | 500: '#319795', 105 | 600: '#2C7A7B', 106 | 700: '#285E61', 107 | 800: '#234E52', 108 | 900: '#1D4044' 109 | }, 110 | 111 | blue: { 112 | 50: '#ebf8ff', 113 | 100: '#bee3f8', 114 | 200: '#90cdf4', 115 | 300: '#63b3ed', 116 | 400: '#4299e1', 117 | 500: '#3182ce', 118 | 600: '#2b6cb0', 119 | 700: '#2c5282', 120 | 800: '#2a4365', 121 | 900: '#1A365D' 122 | }, 123 | 124 | cyan: { 125 | 50: '#EDFDFD', 126 | 100: '#C4F1F9', 127 | 200: '#9DECF9', 128 | 300: '#76E4F7', 129 | 400: '#0BC5EA', 130 | 500: '#00B5D8', 131 | 600: '#00A3C4', 132 | 700: '#0987A0', 133 | 800: '#086F83', 134 | 900: '#065666' 135 | }, 136 | 137 | purple: { 138 | 50: '#FAF5FF', 139 | 100: '#E9D8FD', 140 | 200: '#D6BCFA', 141 | 300: '#B794F4', 142 | 400: '#9F7AEA', 143 | 500: '#805AD5', 144 | 600: '#6B46C1', 145 | 700: '#553C9A', 146 | 800: '#44337A', 147 | 900: '#322659' 148 | }, 149 | 150 | pink: { 151 | 50: '#FFF5F7', 152 | 100: '#FED7E2', 153 | 200: '#FBB6CE', 154 | 300: '#F687B3', 155 | 400: '#ED64A6', 156 | 500: '#D53F8C', 157 | 600: '#B83280', 158 | 700: '#97266D', 159 | 800: '#702459', 160 | 900: '#521B41' 161 | }, 162 | 163 | linkedin: { 164 | 50: '#E8F4F9', 165 | 100: '#CFEDFB', 166 | 200: '#9BDAF3', 167 | 300: '#68C7EC', 168 | 400: '#34B3E4', 169 | 500: '#00A0DC', 170 | 600: '#008CC9', 171 | 700: '#0077B5', 172 | 800: '#005E93', 173 | 900: '#004471' 174 | }, 175 | 176 | facebook: { 177 | 50: '#E8F4F9', 178 | 100: '#D9DEE9', 179 | 200: '#B7C2DA', 180 | 300: '#6482C0', 181 | 400: '#4267B2', 182 | 500: '#385898', 183 | 600: '#314E89', 184 | 700: '#29487D', 185 | 800: '#223B67', 186 | 900: '#1E355B' 187 | }, 188 | 189 | messenger: { 190 | 50: '#D0E6FF', 191 | 100: '#B9DAFF', 192 | 200: '#A2CDFF', 193 | 300: '#7AB8FF', 194 | 400: '#2E90FF', 195 | 500: '#0078FF', 196 | 600: '#0063D1', 197 | 700: '#0052AC', 198 | 800: '#003C7E', 199 | 900: '#002C5C' 200 | }, 201 | 202 | whatsapp: { 203 | 50: '#dffeec', 204 | 100: '#b9f5d0', 205 | 200: '#90edb3', 206 | 300: '#65e495', 207 | 400: '#3cdd78', 208 | 500: '#22c35e', 209 | 600: '#179848', 210 | 700: '#0c6c33', 211 | 800: '#01421c', 212 | 900: '#001803' 213 | }, 214 | 215 | twitter: { 216 | 50: '#E5F4FD', 217 | 100: '#C8E9FB', 218 | 200: '#A8DCFA', 219 | 300: '#83CDF7', 220 | 400: '#57BBF5', 221 | 500: '#1DA1F2', 222 | 600: '#1A94DA', 223 | 700: '#1681BF', 224 | 800: '#136B9E', 225 | 900: '#0D4D71' 226 | }, 227 | 228 | telegram: { 229 | 50: '#E3F2F9', 230 | 100: '#C5E4F3', 231 | 200: '#A2D4EC', 232 | 300: '#7AC1E4', 233 | 400: '#47A9DA', 234 | 500: '#0088CC', 235 | 600: '#007AB8', 236 | 700: '#006BA1', 237 | 800: '#005885', 238 | 900: '#003F5E' 239 | } 240 | }; 241 | 242 | export default colors; 243 | -------------------------------------------------------------------------------- /src/lib/theme/core/index.ts: -------------------------------------------------------------------------------- 1 | import borders from './borders'; 2 | import breakpoints from './breakpoints'; 3 | import colors from './colors'; 4 | import radii from './radius'; 5 | import shadows from './shadows'; 6 | import sizes from './sizes'; 7 | import { spacing } from './spacing'; 8 | import transition from './transition'; 9 | import typography from './typography'; 10 | import zIndices from './z-index'; 11 | import blur from './blur'; 12 | 13 | const core = { 14 | breakpoints, 15 | zIndices, 16 | radii, 17 | blur, 18 | colors, 19 | ...typography, 20 | sizes, 21 | shadows, 22 | space: spacing, 23 | borders, 24 | transition 25 | }; 26 | 27 | export default core; 28 | -------------------------------------------------------------------------------- /src/lib/theme/core/radius.ts: -------------------------------------------------------------------------------- 1 | const radii = { 2 | none: '0', 3 | sm: '0.125rem', 4 | base: '0.25rem', 5 | md: '0.375rem', 6 | lg: '0.5rem', 7 | xl: '0.75rem', 8 | '2xl': '1rem', 9 | '3xl': '1.5rem', 10 | full: '9999px' 11 | }; 12 | 13 | export default radii; 14 | -------------------------------------------------------------------------------- /src/lib/theme/core/shadows.ts: -------------------------------------------------------------------------------- 1 | const shadows = { 2 | xs: '0 0 0 1px rgba(0, 0, 0, 0.05)', 3 | sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', 4 | base: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)', 5 | md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', 6 | lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', 7 | xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', 8 | '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)', 9 | outline: '0 0 0 3px rgba(66, 153, 225, 0.6)', 10 | inner: 'inset 0 2px 4px 0 rgba(0,0,0,0.06)', 11 | none: 'none', 12 | 'dark-lg': 13 | 'rgba(0, 0, 0, 0.1) 0px 0px 0px 1px, rgba(0, 0, 0, 0.2) 0px 5px 10px, rgba(0, 0, 0, 0.4) 0px 15px 40px' 14 | }; 15 | 16 | export default shadows; 17 | -------------------------------------------------------------------------------- /src/lib/theme/core/sizes.ts: -------------------------------------------------------------------------------- 1 | import { spacing } from './spacing'; 2 | 3 | const largeSizes = { 4 | max: 'max-content', 5 | min: 'min-content', 6 | full: '100%', 7 | '3xs': '14rem', 8 | '2xs': '16rem', 9 | xs: '20rem', 10 | sm: '24rem', 11 | md: '28rem', 12 | lg: '32rem', 13 | xl: '36rem', 14 | '2xl': '42rem', 15 | '3xl': '48rem', 16 | '4xl': '56rem', 17 | '5xl': '64rem', 18 | '6xl': '72rem', 19 | '7xl': '80rem', 20 | '8xl': '90rem' 21 | }; 22 | 23 | const container = { 24 | sm: '640px', 25 | md: '768px', 26 | lg: '1024px', 27 | xl: '1280px' 28 | }; 29 | 30 | const sizes = { 31 | ...spacing, 32 | ...largeSizes, 33 | container 34 | }; 35 | 36 | export default sizes; 37 | -------------------------------------------------------------------------------- /src/lib/theme/core/spacing.ts: -------------------------------------------------------------------------------- 1 | export const spacing = { 2 | px: '1px', 3 | 0.5: '0.125rem', 4 | 1: '0.25rem', 5 | 1.5: '0.375rem', 6 | 2: '0.5rem', 7 | 2.5: '0.625rem', 8 | 3: '0.75rem', 9 | 3.5: '0.875rem', 10 | 4: '1rem', 11 | 5: '1.25rem', 12 | 6: '1.5rem', 13 | 7: '1.75rem', 14 | 8: '2rem', 15 | 9: '2.25rem', 16 | 10: '2.5rem', 17 | 12: '3rem', 18 | 14: '3.5rem', 19 | 16: '4rem', 20 | 20: '5rem', 21 | 24: '6rem', 22 | 28: '7rem', 23 | 32: '8rem', 24 | 36: '9rem', 25 | 40: '10rem', 26 | 44: '11rem', 27 | 48: '12rem', 28 | 52: '13rem', 29 | 56: '14rem', 30 | 60: '15rem', 31 | 64: '16rem', 32 | 72: '18rem', 33 | 80: '20rem', 34 | 96: '24rem' 35 | }; 36 | -------------------------------------------------------------------------------- /src/lib/theme/core/transition.ts: -------------------------------------------------------------------------------- 1 | const transitionProperty = { 2 | common: 3 | "background-color, border-color, color, fill, stroke, opacity, box-shadow, transform", 4 | colors: "background-color, border-color, color, fill, stroke", 5 | dimensions: "width, height", 6 | position: "left, right, top, bottom", 7 | background: "background-color, background-image, background-position", 8 | } 9 | 10 | const transitionTimingFunction = { 11 | "ease-in": "cubic-bezier(0.4, 0, 1, 1)", 12 | "ease-out": "cubic-bezier(0, 0, 0.2, 1)", 13 | "ease-in-out": "cubic-bezier(0.4, 0, 0.2, 1)", 14 | } 15 | 16 | const transitionDuration = { 17 | "ultra-fast": "50ms", 18 | faster: "100ms", 19 | fast: "150ms", 20 | normal: "200ms", 21 | slow: "300ms", 22 | slower: "400ms", 23 | "ultra-slow": "500ms", 24 | } 25 | 26 | const transition = { 27 | property: transitionProperty, 28 | easing: transitionTimingFunction, 29 | duration: transitionDuration, 30 | } 31 | 32 | export default transition 33 | -------------------------------------------------------------------------------- /src/lib/theme/core/typography.ts: -------------------------------------------------------------------------------- 1 | const typography = { 2 | letterSpacings: { 3 | tighter: '-0.05em', 4 | tight: '-0.025em', 5 | normal: '0', 6 | wide: '0.025em', 7 | wider: '0.05em', 8 | widest: '0.1em' 9 | }, 10 | 11 | lineHeights: { 12 | normal: 'normal', 13 | none: 1, 14 | shorter: 1.25, 15 | short: 1.375, 16 | base: 1.5, 17 | tall: 1.625, 18 | taller: '2', 19 | '3': '.75rem', 20 | '4': '1rem', 21 | '5': '1.25rem', 22 | '6': '1.5rem', 23 | '7': '1.75rem', 24 | '8': '2rem', 25 | '9': '2.25rem', 26 | '10': '2.5rem' 27 | }, 28 | 29 | fontWeights: { 30 | hairline: 100, 31 | thin: 200, 32 | light: 300, 33 | normal: 400, 34 | medium: 500, 35 | semibold: 600, 36 | bold: 700, 37 | extrabold: 800, 38 | black: 900 39 | }, 40 | 41 | fonts: { 42 | heading: `-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`, 43 | body: `-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`, 44 | mono: `SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace` 45 | }, 46 | 47 | fontSizes: { 48 | xs: '0.75rem', 49 | sm: '0.875rem', 50 | md: '1rem', 51 | lg: '1.125rem', 52 | xl: '1.25rem', 53 | '2xl': '1.5rem', 54 | '3xl': '1.875rem', 55 | '4xl': '2.25rem', 56 | '5xl': '3rem', 57 | '6xl': '3.75rem', 58 | '7xl': '4.5rem', 59 | '8xl': '6rem', 60 | '9xl': '8rem' 61 | } 62 | }; 63 | 64 | export default typography; 65 | -------------------------------------------------------------------------------- /src/lib/theme/core/z-index.ts: -------------------------------------------------------------------------------- 1 | const zIndices = { 2 | hide: -1, 3 | auto: 'auto', 4 | base: 0, 5 | docked: 10, 6 | dropdown: 1000, 7 | sticky: 1100, 8 | banner: 1200, 9 | overlay: 1300, 10 | modal: 1400, 11 | popover: 1500, 12 | skipLink: 1600, 13 | toast: 1700, 14 | tooltip: 1800 15 | }; 16 | 17 | export default zIndices; 18 | -------------------------------------------------------------------------------- /src/lib/theme/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from '$lib/utils'; 2 | 3 | import core from './core'; 4 | import { styles } from './styles'; 5 | import * as components from './components'; 6 | import type { ThemeConfig } from './theme.types'; 7 | 8 | const config: ThemeConfig = { 9 | useSystemColorMode: false, 10 | initialColorMode: 'light', 11 | cssVarPrefix: 'chakra' 12 | }; 13 | 14 | export type Theme = typeof theme; 15 | 16 | export const theme = { 17 | ...core, 18 | ...config, 19 | components, 20 | styles 21 | }; 22 | 23 | export const themeStore = createStore(() => theme); 24 | themeStore.subscribe((newTheme) => { 25 | Object.assign(theme, newTheme); 26 | }); 27 | 28 | export * from './theme.types'; 29 | export default theme; 30 | -------------------------------------------------------------------------------- /src/lib/theme/styles.ts: -------------------------------------------------------------------------------- 1 | import { mode } from '$lib/utils'; 2 | import { defineStyle } from '@chakra-ui/styled-system'; 3 | 4 | export const styles = { 5 | global: defineStyle((props) => ({ 6 | body: { 7 | bg: mode('gray.50', 'gray.800')(props), 8 | color: mode('gray.600', 'gray.300')(props) 9 | } 10 | })) 11 | }; 12 | -------------------------------------------------------------------------------- /src/lib/theme/theme.types.ts: -------------------------------------------------------------------------------- 1 | import type { SemanticValue, Pseudos } from '@chakra-ui/styled-system'; 2 | 3 | export type RecursiveProperty = RecursiveObject | T; 4 | 5 | export interface RecursiveObject { 6 | [property: string]: RecursiveProperty; 7 | } 8 | 9 | export interface ThemeConfig { 10 | cssVarPrefix?: string; 11 | useSystemColorMode: boolean; 12 | initialColorMode: 'light' | 'dark'; 13 | } 14 | 15 | export type ThemeTransitions = RecursiveObject & { 16 | property: RecursiveObject; 17 | easing: RecursiveObject; 18 | duration: RecursiveObject; 19 | }; 20 | 21 | export interface ColorHues { 22 | 50: string; 23 | 100: string; 24 | 200: string; 25 | 300: string; 26 | 400: string; 27 | 500: string; 28 | 600: string; 29 | 700: string; 30 | 800: string; 31 | 900: string; 32 | } 33 | 34 | export type Colors = RecursiveObject> | string>; 35 | 36 | export type ThemeDirection = 'ltr' | 'rtl'; 37 | 38 | interface Typography { 39 | fonts: RecursiveObject; 40 | fontSizes: RecursiveObject; 41 | fontWeights: RecursiveObject; 42 | letterSpacings: RecursiveObject; 43 | lineHeights: RecursiveObject; 44 | } 45 | 46 | interface Foundations extends Typography { 47 | borders: RecursiveObject; 48 | breakpoints: RecursiveObject; 49 | colors: Colors; 50 | radii: RecursiveObject; 51 | shadows: RecursiveObject; 52 | sizes: RecursiveObject; 53 | space: RecursiveObject; 54 | transition: ThemeTransitions; 55 | zIndices: RecursiveObject; 56 | } 57 | 58 | export interface ChakraTheme extends Foundations { 59 | semanticTokens?: Partial>>>; 60 | components: RecursiveObject; 61 | config: ThemeConfig; 62 | direction: ThemeDirection; 63 | styles: RecursiveObject; 64 | layerStyles?: RecursiveObject; 65 | textStyles?: RecursiveObject; 66 | } 67 | -------------------------------------------------------------------------------- /src/lib/types.d.ts: -------------------------------------------------------------------------------- 1 | import type { StyleProps } from '@chakra-ui/styled-system'; 2 | 3 | declare global { 4 | // eslint-disable-next-line @typescript-eslint/no-namespace 5 | namespace jest { 6 | interface Matchers { 7 | toBeInTheDocument(): R; 8 | toHaveAttribute(name: string, value: string): R; 9 | } 10 | } 11 | } 12 | 13 | export type ChakraActionCycle = { 14 | destroy?: () => void; 15 | update?: (newProps: T) => void; 16 | }; 17 | 18 | export type ChakraAction = ( 19 | node: Element, 20 | props: T 21 | ) => ChakraActionCycle; 22 | 23 | export type ChakraActionNoProps = (node: Element) => ChakraActionCycle; 24 | 25 | export type ChakraElementAs = keyof HTMLElementTagNameMap | keyof SVGElementTagNameMap; 26 | 27 | export interface ChakraComponentProps extends StyleProps { 28 | [key: string]: unknown; 29 | 30 | /** 31 | * The class name to apply to the component. 32 | */ 33 | class?: string; 34 | /** 35 | * Optionally render the component as a different element or component. 36 | * 37 | * Defaults to `div`. 38 | */ 39 | as?: ChakraElementAs | ConstructorOfATypedSvelteComponent; 40 | /** 41 | * Wrap the component in with a HTML element or don't wrap it at all. 42 | * This is useful when you want to render a component wrapped with a different element 43 | * 44 | * Defaults to `false`. 45 | */ 46 | wrap?: boolean | ChakraElementAs; 47 | /** Styles applied to a component */ 48 | sx?: StyleProps & { 49 | [rule: string]: string | number | StyleProps; 50 | }; 51 | /** Props applied to underlying element */ 52 | props?: Record; 53 | /** A drilled action which would bubble events to the next available element */ 54 | events?: ChakraActionNoProps; 55 | /** 56 | * Render the component without slots. 57 | * This is useful for elements like `input` which don't support slots. 58 | * 59 | * Defaults to `false`. 60 | */ 61 | noSlot?: boolean; 62 | } 63 | -------------------------------------------------------------------------------- /src/lib/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './object'; 2 | export * from './store'; 3 | export * from './theme-tools'; 4 | -------------------------------------------------------------------------------- /src/lib/utils/object.test.ts: -------------------------------------------------------------------------------- 1 | import { omit, pick, filter } from './object'; 2 | 3 | const testObject = { 4 | name: 'Jonathan', 5 | age: 30, 6 | email: 'jonathan@example.com', 7 | address: { 8 | street: '123 Main St', 9 | city: 'Anytown', 10 | state: 'CA', 11 | zip: '12345' 12 | } 13 | }; 14 | 15 | describe('omit', () => { 16 | it('should omit a single key', () => { 17 | const result = omit(testObject, ['name']); 18 | expect(result).toEqual({ 19 | age: 30, 20 | email: 'jonathan@example.com', 21 | address: { 22 | street: '123 Main St', 23 | city: 'Anytown', 24 | state: 'CA', 25 | zip: '12345' 26 | } 27 | }); 28 | }); 29 | 30 | it('should omit multiple keys', () => { 31 | const result = omit(testObject, ['name', 'email']); 32 | expect(result).toEqual({ 33 | age: 30, 34 | address: { 35 | street: '123 Main St', 36 | city: 'Anytown', 37 | state: 'CA', 38 | zip: '12345' 39 | } 40 | }); 41 | }); 42 | 43 | it('should return the original object if no keys are specified', () => { 44 | const result = omit(testObject, []); 45 | expect(result).toEqual(testObject); 46 | }); 47 | }); 48 | 49 | describe('filter', () => { 50 | it('should filter by a single key', () => { 51 | const result = filter(testObject, (_, key) => key === 'name'); 52 | expect(result).toEqual({ 53 | name: 'Jonathan' 54 | }); 55 | }); 56 | 57 | it('should filter by multiple keys', () => { 58 | const result = filter(testObject, (_, key) => ['name', 'age'].includes(key)); 59 | expect(result).toEqual({ 60 | name: 'Jonathan', 61 | age: 30 62 | }); 63 | }); 64 | 65 | it('should filter by a nested key', () => { 66 | const result = filter(testObject.address, (_, key) => key === 'street'); 67 | expect(result).toEqual({ 68 | street: '123 Main St' 69 | }); 70 | }); 71 | 72 | it('should return an empty object if no keys match', () => { 73 | const result = filter(testObject, (_, key) => key === ('foo' as typeof key)); // this hack is needed to make TS happy 74 | expect(result).toEqual({}); 75 | }); 76 | }); 77 | 78 | describe('pick', () => { 79 | it('should pick a single key', () => { 80 | const result = pick(testObject, ['name']); 81 | expect(result).toEqual({ 82 | name: 'Jonathan' 83 | }); 84 | }); 85 | 86 | it('should pick multiple keys', () => { 87 | const result = pick(testObject, ['name', 'age']); 88 | expect(result).toEqual({ 89 | name: 'Jonathan', 90 | age: 30 91 | }); 92 | }); 93 | 94 | it('should return an empty object if no keys are specified', () => { 95 | const result = pick(testObject, []); 96 | expect(result).toEqual({}); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /src/lib/utils/object.ts: -------------------------------------------------------------------------------- 1 | export type Dict = Record; 2 | 3 | /** 4 | * Omit keys from an object 5 | * 6 | * @param object 7 | * @param keys 8 | */ 9 | export function omit(object: T, keys: (keyof T)[]) { 10 | return filter(object, (_, key) => !keys.includes(key)); 11 | } 12 | 13 | /** 14 | * Filter an object by a predicate function 15 | * 16 | * @param object 17 | * @param predicate 18 | */ 19 | export function filter( 20 | object: T, 21 | predicate: (value: T[keyof T], key: keyof T) => boolean 22 | ) { 23 | const result = {} as T; 24 | const filtered = Object.keys(object).filter((key: keyof T) => { 25 | return predicate(object[key], key); 26 | }); 27 | for (const key of filtered) { 28 | result[key as keyof T] = object[key] as T[keyof T]; 29 | } 30 | return result; 31 | } 32 | 33 | /** 34 | * Pick a subset of keys from an object 35 | * 36 | * @param object 37 | * @param keys 38 | */ 39 | export function pick(object: T, keys: (keyof T)[]) { 40 | return filter(object, (_, key) => keys.includes(key)); 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/utils/store.test.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from './store'; 2 | 3 | describe('createStore', () => { 4 | it('should create a store with the initial value', () => { 5 | const store = createStore(() => 42); 6 | expect(store.get()).toBe(42); 7 | }); 8 | 9 | it('should call the subscription function when the store is updated', () => { 10 | const subscription = vi.fn(); 11 | const store = createStore(() => 42, subscription); 12 | store.set(43); 13 | expect(subscription).toHaveBeenCalledWith(43); 14 | }); 15 | 16 | it('should return the initial value', () => { 17 | const store = createStore(() => 42); 18 | expect(store.initial).toBe(42); 19 | }); 20 | 21 | it('should return the current value of the store', () => { 22 | const store = createStore(() => 42); 23 | expect(store.get()).toBe(42); 24 | store.set(43); 25 | expect(store.get()).toBe(43); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/lib/utils/store.ts: -------------------------------------------------------------------------------- 1 | import { writable, get } from 'svelte/store'; 2 | 3 | export type StoreCallback = () => T; 4 | 5 | /** 6 | * Creates a custom store for use by Chakra UI Svelte 7 | * 8 | * @param initialValue - The initial value of the store 9 | * @param subscription - A function that will be called when the store is updated 10 | * @returns 11 | */ 12 | export function createStore( 13 | initialValue: StoreCallback, 14 | subscription: (value: T) => void = () => { 15 | // do nothing 16 | } 17 | ) { 18 | const initial = initialValue(); 19 | const store = writable(initial); 20 | store.subscribe(subscription); 21 | 22 | return { 23 | initial, 24 | get: get.bind({}, store) as StoreCallback, 25 | ...store 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/utils/theme-tools.test.ts: -------------------------------------------------------------------------------- 1 | import { runIfFn, mode, transparentize, getColor } from './theme-tools'; 2 | 3 | const testTheme = { 4 | colors: { 5 | red: '#ff0000', 6 | blue: '#0000ff' 7 | } 8 | }; 9 | 10 | describe('mode', () => { 11 | it('should return the light value in light mode', () => { 12 | const result = mode('light', 'dark')({ colorMode: 'light' }); 13 | expect(result).toBe('light'); 14 | }); 15 | 16 | it('should return the dark value in dark mode', () => { 17 | const result = mode('light', 'dark')({ colorMode: 'dark' }); 18 | expect(result).toBe('dark'); 19 | }); 20 | }); 21 | 22 | describe('getColor', () => { 23 | it('should return the color value from the theme', () => { 24 | const result = getColor(testTheme, 'red'); 25 | expect(result).toBe('#ff0000'); 26 | }); 27 | 28 | it('should return the fallback value if the color is not in the theme', () => { 29 | const result = getColor(testTheme, 'green', '#00ff00'); 30 | expect(result).toBe('#00ff00'); 31 | }); 32 | 33 | it('should return undefined if the color is not in the theme and no fallback is provided', () => { 34 | const result = getColor(testTheme, 'green'); 35 | expect(result).toBeUndefined(); 36 | }); 37 | }); 38 | 39 | describe('transparentize', () => { 40 | it('should return the transparentized color', () => { 41 | const result = transparentize('red', 0.5)(testTheme as unknown); 42 | expect(result).toBe('ff 00 00 0.5'); 43 | }); 44 | }); 45 | 46 | describe('runIfFn', () => { 47 | it('should return the value if it is not a function', () => { 48 | const result = runIfFn('foo'); 49 | expect(result).toBe('foo'); 50 | }); 51 | 52 | it('should call the function with the arguments if the value is a function', () => { 53 | const fn = vi.fn((a, b) => a + b); 54 | const result = runIfFn(fn, 2, 3); 55 | expect(fn).toHaveBeenCalledWith(2, 3); 56 | expect(result).toBe(5); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/lib/utils/theme-tools.ts: -------------------------------------------------------------------------------- 1 | import type { Theme } from '$lib/theme'; 2 | import type { StyleFunctionProps } from '@chakra-ui/styled-system'; 3 | type Dict = Record; 4 | 5 | /** 6 | * Returns the value based on current theme color mode. 7 | * 8 | * @param light 9 | * @param dark 10 | * @returns 11 | */ 12 | export const mode = (light: T, dark: T): ((properties: Partial) => T) => ( 13 | properties 14 | ) => (properties.colorMode == 'dark' ? dark : light); 15 | 16 | export const getColor = (theme: Dict, color: string, fallback?: string) => { 17 | return theme?.colors[color] || fallback; 18 | }; 19 | 20 | /** 21 | * Makes colors transparent 22 | * 23 | * @param color color to transform 24 | * @param opacity opacity to apply from 0 to 1 25 | * @returns 26 | */ 27 | export const transparentize = (color: string, opacity: number) => (theme: Partial) => { 28 | return ( 29 | theme?.colors[color] 30 | ?.replace(/^#/, '') 31 | .replace(/([\da-f]{2})/g, '$1 ') 32 | .trim() + 33 | ' ' + 34 | opacity 35 | ); 36 | }; 37 | 38 | /** 39 | * Runs a function or return value 40 | * 41 | * @param value - value to check 42 | * @param args - args to pass 43 | */ 44 | export const runIfFn = (value: T, ...args: unknown[]) => { 45 | if (typeof value === 'function') { 46 | return value(...args); 47 | } 48 | return value; 49 | }; 50 | -------------------------------------------------------------------------------- /src/routes/+error.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 |

404

7 |

Page not founded

8 |
9 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 19 | Create accessible Svelte apps 20 | 21 | with speed 22 | 23 | 24 | 25 | 26 | Chakra UI is a simple, modular and accessible component library that gives you the building 27 | blocks you need to build your Svelte apps. 28 | 29 | 30 | 31 | 34 | 35 | 36 | 39 | 40 | 41 | 42 | Looking for other implementations? 43 | ReactJs 44 | || 45 | VueJs 46 | 47 | 48 |