merge with next
This commit is contained in:
commit
b876e91ae6
|
@ -0,0 +1 @@
|
||||||
|
packages/server/builder/**/*.js
|
|
@ -14,6 +14,13 @@ Budibase is a monorepo managed by [lerna](https://github.com/lerna/lerna). Lerna
|
||||||
|
|
||||||
- **packages/server** - The budibase server. This [Koa](https://koajs.com/) app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system.
|
- **packages/server** - The budibase server. This [Koa](https://koajs.com/) app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system.
|
||||||
|
|
||||||
|
## Contributor License Agreement (CLA)
|
||||||
|
|
||||||
|
In order to accept your pull request, we need you to submit a CLA. You only need to do this once. If you are submitting a pull request for the first time, just submit a Pull Request and our CLA Bot will give you instructions on how to sign the CLA before merging your Pull Request.
|
||||||
|
|
||||||
|
All contributors must sign an [Individual Contributor License Agreement](https://github.com/budibase/budibase/blob/next/.github/cla/individual-cla.md).
|
||||||
|
|
||||||
|
If contributing on behalf of your company, your company must sign a [Corporate Contributor License Agreement](https://github.com/budibase/budibase/blob/next/.github/cla/corporate-cla.md). If so, please contact us via community@budibase.com.
|
||||||
|
|
||||||
## Glossary of Terms
|
## Glossary of Terms
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Software Grant and Corporate Contributor License Agreement ("Agreement"), v1.0
|
||||||
|
|
||||||
|
You accept and agree to the following terms and conditions for Your present and future Contributions submitted to Budibase, Inc. ("Budibase"). Except for the license granted herein to Budibase and recipients of software distributed by Budibase, You reserve all right, title, and interest in and to Your Contributions.
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with Budibase. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"Contribution" shall mean the code, documentation or other original works of authorship expressly identified in Schedule B, as well as any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to Budibase for inclusion in, or documentation of, any of the products owned or managed by Budibase (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to Budibase or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Budibase for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to Budibase and to recipients of software distributed by Budibase a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to Budibase and to recipients of software distributed by Budibase a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. You represent that You are legally entitled to grant the above license. You represent further that each employee of the Corporation designated on Schedule A below (or in a subsequent written modification to that Schedule) is authorized to submit Contributions on behalf of the Corporation.
|
||||||
|
|
||||||
|
5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others).
|
||||||
|
|
||||||
|
6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
|
||||||
|
7. Should You wish to submit work that is not Your original creation, You may submit it to Budibase separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]".
|
||||||
|
|
||||||
|
8. It is your responsibility to notify Budibase when any change is required to the list of designated employees authorized to submit Contributions on behalf of the Corporation, or to the Corporation's Point of Contact with Budibase.
|
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
|
||||||
|
# Individual Contributor License Agreement ("Agreement"), v1.0
|
||||||
|
|
||||||
|
Thank you for your interest in contributing to open-source software projects (“Projects”) made available by Budibase Inc, or its affiliates (“Budibase”). This Individual Contributor License Agreement (“Agreement”) sets out the terms governing any source code, object code, bug fixes, configuration changes, tools, specifications, documentation, data, materials, feedback, information or other works of authorship that you submit or have submitted, in any form and in any manner, to Budibase in respect of any of the Projects (collectively “Contributions”). If you have any questions respecting this Agreement, please contact community@budibase.com.
|
||||||
|
|
||||||
|
You accept and agree to the following terms and conditions for Your past, present, and future Contributions submitted to Budibase, Inc. ("Budibase"). Except for the license granted herein to Budibase and recipients of software distributed by Budibase, You reserve all right, title, and interest in and to Your Contributions.
|
||||||
|
|
||||||
|
1. Definitions. "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with Budibase. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to Budibase for inclusion in, or documentation of, any of the products owned or managed by Budibase (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to Budibase or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Budibase for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to Budibase and to recipients of software distributed by Budibase a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to Budibase and to recipients of software distributed by Budibase a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes a direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to Budibase, or that your employer has executed a separate Corporate CLA with Budibase.
|
||||||
|
|
||||||
|
5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions.
|
||||||
|
|
||||||
|
6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
|
||||||
|
7. Should You wish to submit work that is not Your original creation, You may submit it to Budibase separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]".
|
||||||
|
|
||||||
|
8. You agree to notify Budibase of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect.
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"signedContributors": [
|
||||||
|
{
|
||||||
|
"name": "shogunpurple",
|
||||||
|
"id": 11256663,
|
||||||
|
"comment_id": 819645107,
|
||||||
|
"created_at": "2021-04-14T16:20:01Z",
|
||||||
|
"repoId": 190729906,
|
||||||
|
"pullRequestNo": 1383
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mike12345567",
|
||||||
|
"id": 4407001,
|
||||||
|
"comment_id": 819645152,
|
||||||
|
"created_at": "2021-04-14T16:20:04Z",
|
||||||
|
"repoId": 190729906,
|
||||||
|
"pullRequestNo": 1383
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
name: "CLA Assistant"
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened,closed,synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
CLAssistant:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: "CLA Assistant"
|
||||||
|
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
||||||
|
# Beta Release
|
||||||
|
uses: cla-assistant/github-action@v2.1.2-beta
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
# the below token should have repo scope and must be manually added by you in the repository's secret
|
||||||
|
PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||||
|
with:
|
||||||
|
path-to-signatures: '.github/cla/signatures.json'
|
||||||
|
path-to-document: 'https://github.com/budibase/budibase/blob/next/.github/cla/individual-cla.md' # e.g. a CLA or a DCO document
|
||||||
|
# branch should not be protected
|
||||||
|
branch: 'next'
|
||||||
|
allowlist: user1,bot*
|
||||||
|
|
||||||
|
#below are the optional inputs - If the optional inputs are not given, then default values will be taken
|
||||||
|
#remote-organization-name: enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository)
|
||||||
|
#remote-repository-name: enter the remote repository name where the signatures should be stored (Default is storing the signatures in the same repository)
|
||||||
|
#create-file-commit-message: 'For example: Creating file for storing CLA Signatures'
|
||||||
|
#signed-commit-message: 'For example: $contributorName has signed the CLA in #$pullRequestNo'
|
||||||
|
custom-notsigned-prcomment: '<br/><br/>Thank you for your submission - we really appreciate it ❤️. Like many open-source projects, we ask that $you sign a [Contributor License Agreement](https://github.com/budibase/budibase/blob/next/.github/cla/individual-cla.md) before we can accept your contribution.<br/><br/>You can sign the CLA by just posting a Pull Request Comment, the same as the text below.<br/><br/>If you are contributing on behalf of a company, your company should contact us to sign a [Corporate Contributor License Agreement](https://github.com/budibase/budibase/blob/next/.github/cla/corporate-cla.md), via community@budibase.com.'
|
||||||
|
#custom-pr-sign-comment: 'The signature to be committed in order to sign the CLA'
|
||||||
|
#custom-allsigned-prcomment: 'pull request comment when all contributors has signed, defaults to **CLA Assistant Lite bot** All Contributors have signed the CLA.'
|
||||||
|
#lock-pullrequest-aftermerge: false - if you don't want this bot to automatically lock the pull request after merging (default - true)
|
||||||
|
#use-dco-flag: true - If you are using DCO instead of CLA
|
|
@ -1,6 +1,13 @@
|
||||||
name: Budibase Release
|
name: Budibase Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
name:
|
||||||
|
description: 'Version'
|
||||||
|
required: false
|
||||||
|
default: '0.8'
|
||||||
|
|
||||||
# Trigger the workflow on push with tags,
|
# Trigger the workflow on push with tags,
|
||||||
# but only for the master branch
|
# but only for the master branch
|
||||||
push:
|
push:
|
||||||
|
|
|
@ -63,6 +63,7 @@ typings/
|
||||||
# dotenv environment variables file
|
# dotenv environment variables file
|
||||||
.env
|
.env
|
||||||
!hosting/.env
|
!hosting/.env
|
||||||
|
hosting/.generated-envoy.dev.yaml
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
.cache
|
.cache
|
||||||
|
|
24
README.md
24
README.md
|
@ -31,14 +31,14 @@
|
||||||
<img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/Budibase/budibase">
|
<img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/Budibase/budibase">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.gg/rCYayfe">
|
<a href="https://discord.gg/rCYayfe">
|
||||||
<img alt="Discord" src="https://img.shields.io/discord/733030666647765003">
|
<img alt="Discord" src="https://img.shields.io/discord/733030666647765003">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://twitter.com/intent/follow?screen_name=budibase">
|
<a href="https://twitter.com/intent/follow?screen_name=budibase">
|
||||||
<img src="https://img.shields.io/twitter/follow/budibase?style=social" alt="Follow @budibase" />
|
<img src="https://img.shields.io/twitter/follow/budibase?style=social" alt="Follow @budibase" />
|
||||||
</a>
|
</a>
|
||||||
<img src="https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg" alt="Code of conduct" />
|
<img src="https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg" alt="Code of conduct" />
|
||||||
<a href="https://codecov.io/gh/Budibase/budibase">
|
<a href="https://codecov.io/gh/Budibase/budibase">
|
||||||
<img src="https://codecov.io/gh/Budibase/budibase/branch/next/graph/badge.svg?token=E8W2ZFXQOH"/>
|
<img src="https://codecov.io/gh/Budibase/budibase/graph/badge.svg?token=E8W2ZFXQOH"/>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@ -58,9 +58,9 @@
|
||||||
|
|
||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
When other platforms chose the closed source route, we decided to go open source. When other platforms chose cloud builders, we decided a local builder offered the better developer experience. We like to do things differently at Budibase.
|
When other platforms chose the closed source route, we decided to go open source. When other platforms chose cloud builders, we decided a local builder offered the better developer experience. We like to do things differently at Budibase.
|
||||||
|
|
||||||
- **Build and ship real software.** Unlike other platforms, with Budibase you build and ship single page applications. Budibase applications have performance baked in and can be designed responsively, providing your users with a great experience.
|
- **Build and ship real software.** Unlike other platforms, with Budibase you build and ship single page applications. Budibase applications have performance baked in and can be designed responsively, providing your users with a great experience.
|
||||||
|
|
||||||
- **Open source and extensable.** Budibase is open-source. The builder is licensed AGPL v3, the server is GPL v3, and the client is MPL. This should fill you with confidence that Budibase will always be around. You can also code against Budibase or fork it and make changes as you please, providing a developer-friendly experience.
|
- **Open source and extensable.** Budibase is open-source. The builder is licensed AGPL v3, the server is GPL v3, and the client is MPL. This should fill you with confidence that Budibase will always be around. You can also code against Budibase or fork it and make changes as you please, providing a developer-friendly experience.
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ When other platforms chose the closed source route, we decided to go open source
|
||||||
- [x] Public Beta: Anyone can [sign-up and use Budibase](https://portal.budi.live/signup).
|
- [x] Public Beta: Anyone can [sign-up and use Budibase](https://portal.budi.live/signup).
|
||||||
- [ ] Official Launch
|
- [ ] Official Launch
|
||||||
|
|
||||||
Watch "releases" of this repo to get notified of major updates, and give the star button a click whilst you're there.
|
Watch "releases" of this repo to get notified of major updates, and give the star button a click whilst you're there.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://i.imgur.com/cJpgqm8.png">
|
<img src="https://i.imgur.com/cJpgqm8.png">
|
||||||
|
@ -93,7 +93,7 @@ Watch "releases" of this repo to get notified of major updates, and give the sta
|
||||||
|
|
||||||
[![Stargazers over time](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase)
|
[![Stargazers over time](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase)
|
||||||
|
|
||||||
If you are having issues between updates of the builder, please use the guide [here](https://github.com/Budibase/budibase/blob/master/CONTRIBUTING.md#troubleshooting) to clear down your environment.
|
If you are having issues between updates of the builder, please use the guide [here](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md#troubleshooting) to clear down your environment.
|
||||||
|
|
||||||
|
|
||||||
## 🏁 Getting Started with Budibase
|
## 🏁 Getting Started with Budibase
|
||||||
|
@ -131,25 +131,25 @@ Checkout our [Public Roadmap](https://github.com/Budibase/budibase/projects/10).
|
||||||
|
|
||||||
## ❗ Code of Conduct
|
## ❗ Code of Conduct
|
||||||
|
|
||||||
Budibase is dedicated to providing a welcoming, diverse, and harrassment-free experience for everyone. We expect everyone in the Budibase community to abide by our [**Code of Conduct**](https://github.com/Budibase/budibase/blob/master/.github/CODE_OF_CONDUCT.md). Please read it.
|
Budibase is dedicated to providing a welcoming, diverse, and harrassment-free experience for everyone. We expect everyone in the Budibase community to abide by our [**Code of Conduct**](https://github.com/Budibase/budibase/blob/HEAD/.github/CODE_OF_CONDUCT.md). Please read it.
|
||||||
|
|
||||||
## 🙌 Contributing to Budibase
|
## 🙌 Contributing to Budibase
|
||||||
|
|
||||||
From opening a bug report to creating a pull request: every contribution is appreciated and welcomed. If you're planning to implement a new feature or change the API please create an issue first. This way we can ensure your work is not in vain.
|
From opening a bug report to creating a pull request: every contribution is appreciated and welcomed. If you're planning to implement a new feature or change the API please create an issue first. This way we can ensure your work is not in vain.
|
||||||
|
|
||||||
### Not Sure Where to Start?
|
### Not Sure Where to Start?
|
||||||
A good place to start contributing, is the [First time issues project](https://github.com/Budibase/budibase/projects/22).
|
A good place to start contributing, is the [First time issues project](https://github.com/Budibase/budibase/projects/22).
|
||||||
|
|
||||||
### How the repository is organized
|
### How the repository is organized
|
||||||
Budibase is a monorepo managed by lerna. Lerna manages the building and publishing of the budibase packages. At a high level, here are the packages that make up Budibase.
|
Budibase is a monorepo managed by lerna. Lerna manages the building and publishing of the budibase packages. At a high level, here are the packages that make up Budibase.
|
||||||
|
|
||||||
- [packages/builder](https://github.com/Budibase/budibase/tree/master/packages/builder) - contains code for the budibase builder client side svelte application.
|
- [packages/builder](https://github.com/Budibase/budibase/tree/HEAD/packages/builder) - contains code for the budibase builder client side svelte application.
|
||||||
|
|
||||||
- [packages/client](https://github.com/Budibase/budibase/tree/master/packages/client) - A module that runs in the browser responsible for reading JSON definition and creating living, breathing web apps from it.
|
- [packages/client](https://github.com/Budibase/budibase/tree/HEAD/packages/client) - A module that runs in the browser responsible for reading JSON definition and creating living, breathing web apps from it.
|
||||||
|
|
||||||
- [packages/server](https://github.com/Budibase/budibase/tree/master/packages/server) - The budibase server. This Koa app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system.
|
- [packages/server](https://github.com/Budibase/budibase/tree/HEAD/packages/server) - The budibase server. This Koa app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system.
|
||||||
|
|
||||||
For more information, see [CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/master/.github/CONTRIBUTING.md)
|
For more information, see [CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md)
|
||||||
|
|
||||||
## 📝 License
|
## 📝 License
|
||||||
|
|
||||||
|
|
|
@ -27,10 +27,9 @@ services:
|
||||||
restart: always
|
restart: always
|
||||||
image: envoyproxy/envoy:v1.16-latest
|
image: envoyproxy/envoy:v1.16-latest
|
||||||
volumes:
|
volumes:
|
||||||
- ./envoy.dev.yaml:/etc/envoy/envoy.yaml
|
- ./.generated-envoy.dev.yaml:/etc/envoy/envoy.yaml
|
||||||
ports:
|
ports:
|
||||||
- "${MAIN_PORT}:10000"
|
- "${MAIN_PORT}:10000"
|
||||||
#- "9901:9901"
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- minio-service
|
- minio-service
|
||||||
- couchdb-service
|
- couchdb-service
|
||||||
|
@ -38,18 +37,17 @@ services:
|
||||||
couchdb-service:
|
couchdb-service:
|
||||||
container_name: budi-couchdb-dev
|
container_name: budi-couchdb-dev
|
||||||
restart: always
|
restart: always
|
||||||
image: apache/couchdb:3.0
|
image: ibmcom/couchdb3
|
||||||
environment:
|
environment:
|
||||||
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
|
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
|
||||||
- COUCHDB_USER=${COUCH_DB_USER}
|
- COUCHDB_USER=${COUCH_DB_USER}
|
||||||
ports:
|
ports:
|
||||||
- "${COUCH_DB_PORT}:5984"
|
- "${COUCH_DB_PORT}:5984"
|
||||||
#- "4369:4369"
|
|
||||||
#- "9100:9100"
|
|
||||||
volumes:
|
volumes:
|
||||||
- couchdb_data:/opt/couchdb/data
|
- couchdb3_data:/opt/couchdb/data
|
||||||
|
|
||||||
couch-init:
|
couch-init:
|
||||||
|
container_name: budi-couchdb-init-dev
|
||||||
image: curlimages/curl
|
image: curlimages/curl
|
||||||
environment:
|
environment:
|
||||||
PUT_CALL: "curl -u ${COUCH_DB_USER}:${COUCH_DB_PASSWORD} -X PUT couchdb-service:5984"
|
PUT_CALL: "curl -u ${COUCH_DB_USER}:${COUCH_DB_PASSWORD} -X PUT couchdb-service:5984"
|
||||||
|
@ -66,9 +64,8 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- redis_data:/data
|
- redis_data:/data
|
||||||
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
couchdb_data:
|
couchdb3_data:
|
||||||
driver: local
|
driver: local
|
||||||
minio_data:
|
minio_data:
|
||||||
driver: local
|
driver: local
|
||||||
|
|
|
@ -22,7 +22,7 @@ services:
|
||||||
JWT_SECRET: ${JWT_SECRET}
|
JWT_SECRET: ${JWT_SECRET}
|
||||||
LOG_LEVEL: info
|
LOG_LEVEL: info
|
||||||
SENTRY_DSN: https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131
|
SENTRY_DSN: https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131
|
||||||
ENABLE_ANALYTICS: true
|
ENABLE_ANALYTICS: "true"
|
||||||
depends_on:
|
depends_on:
|
||||||
- worker-service
|
- worker-service
|
||||||
|
|
||||||
|
@ -35,9 +35,10 @@ services:
|
||||||
environment:
|
environment:
|
||||||
SELF_HOSTED: 1
|
SELF_HOSTED: 1
|
||||||
PORT: 4003
|
PORT: 4003
|
||||||
|
JWT_SECRET: ${JWT_SECRET}
|
||||||
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
|
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
|
||||||
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
|
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
|
||||||
RAW_MINIO_URL: http://minio-service:9000
|
MINIO_URL: http://minio-service:9000
|
||||||
COUCH_DB_USERNAME: ${COUCH_DB_USER}
|
COUCH_DB_USERNAME: ${COUCH_DB_USER}
|
||||||
COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD}
|
COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD}
|
||||||
COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984
|
COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984
|
||||||
|
@ -71,7 +72,6 @@ services:
|
||||||
- ./envoy.yaml:/etc/envoy/envoy.yaml
|
- ./envoy.yaml:/etc/envoy/envoy.yaml
|
||||||
ports:
|
ports:
|
||||||
- "${MAIN_PORT}:10000"
|
- "${MAIN_PORT}:10000"
|
||||||
#- "9901:9901"
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- minio-service
|
- minio-service
|
||||||
- worker-service
|
- worker-service
|
||||||
|
@ -80,16 +80,14 @@ services:
|
||||||
|
|
||||||
couchdb-service:
|
couchdb-service:
|
||||||
restart: always
|
restart: always
|
||||||
image: apache/couchdb:3.0
|
image: ibmcom/couchdb3
|
||||||
environment:
|
environment:
|
||||||
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
|
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
|
||||||
- COUCHDB_USER=${COUCH_DB_USER}
|
- COUCHDB_USER=${COUCH_DB_USER}
|
||||||
ports:
|
ports:
|
||||||
- "${COUCH_DB_PORT}:5984"
|
- "${COUCH_DB_PORT}:5984"
|
||||||
#- "4369:4369"
|
|
||||||
#- "9100:9100"
|
|
||||||
volumes:
|
volumes:
|
||||||
- couchdb_data:/opt/couchdb/data
|
- couchdb3_data:/opt/couchdb/data
|
||||||
|
|
||||||
couch-init:
|
couch-init:
|
||||||
image: curlimages/curl
|
image: curlimages/curl
|
||||||
|
@ -108,7 +106,7 @@ services:
|
||||||
- redis_data:/data
|
- redis_data:/data
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
couchdb_data:
|
couchdb3_data:
|
||||||
driver: local
|
driver: local
|
||||||
minio_data:
|
minio_data:
|
||||||
driver: local
|
driver: local
|
||||||
|
|
|
@ -26,6 +26,31 @@ static_resources:
|
||||||
cluster: redis-service
|
cluster: redis-service
|
||||||
prefix_rewrite: "/"
|
prefix_rewrite: "/"
|
||||||
|
|
||||||
|
- match: { prefix: "/api/admin/" }
|
||||||
|
route:
|
||||||
|
cluster: worker-dev
|
||||||
|
|
||||||
|
- match: { prefix: "/api/" }
|
||||||
|
route:
|
||||||
|
cluster: server-dev
|
||||||
|
|
||||||
|
- match: { prefix: "/app_" }
|
||||||
|
route:
|
||||||
|
cluster: server-dev
|
||||||
|
|
||||||
|
- match: { prefix: "/builder/" }
|
||||||
|
route:
|
||||||
|
cluster: builder-dev
|
||||||
|
|
||||||
|
- match: { prefix: "/builder" }
|
||||||
|
route:
|
||||||
|
cluster: builder-dev
|
||||||
|
prefix_rewrite: "/builder/"
|
||||||
|
|
||||||
|
# special case in dev to redirect no path to builder
|
||||||
|
- match: { path: "/" }
|
||||||
|
redirect: { path_redirect: "/builder/" }
|
||||||
|
|
||||||
# minio is on the default route because this works
|
# minio is on the default route because this works
|
||||||
# best, minio + AWS SDK doesn't handle path proxy
|
# best, minio + AWS SDK doesn't handle path proxy
|
||||||
- match: { prefix: "/" }
|
- match: { prefix: "/" }
|
||||||
|
@ -77,3 +102,46 @@ static_resources:
|
||||||
socket_address:
|
socket_address:
|
||||||
address: redis-service
|
address: redis-service
|
||||||
port_value: 6379
|
port_value: 6379
|
||||||
|
|
||||||
|
- name: server-dev
|
||||||
|
connect_timeout: 0.25s
|
||||||
|
type: strict_dns
|
||||||
|
lb_policy: round_robin
|
||||||
|
load_assignment:
|
||||||
|
cluster_name: server-dev
|
||||||
|
endpoints:
|
||||||
|
- lb_endpoints:
|
||||||
|
- endpoint:
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: {{ address }}
|
||||||
|
port_value: 4001
|
||||||
|
|
||||||
|
- name: builder-dev
|
||||||
|
connect_timeout: 15s
|
||||||
|
type: strict_dns
|
||||||
|
lb_policy: round_robin
|
||||||
|
load_assignment:
|
||||||
|
cluster_name: builder-dev
|
||||||
|
endpoints:
|
||||||
|
- lb_endpoints:
|
||||||
|
- endpoint:
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: {{ address }}
|
||||||
|
port_value: 3000
|
||||||
|
|
||||||
|
- name: worker-dev
|
||||||
|
connect_timeout: 0.25s
|
||||||
|
type: strict_dns
|
||||||
|
lb_policy: round_robin
|
||||||
|
load_assignment:
|
||||||
|
cluster_name: worker-dev
|
||||||
|
endpoints:
|
||||||
|
- lb_endpoints:
|
||||||
|
- endpoint:
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: {{ address }}
|
||||||
|
port_value: 4002
|
||||||
|
|
|
@ -25,6 +25,11 @@ static_resources:
|
||||||
- match: { path: "/" }
|
- match: { path: "/" }
|
||||||
route:
|
route:
|
||||||
cluster: app-service
|
cluster: app-service
|
||||||
|
|
||||||
|
# special case for worker admin API
|
||||||
|
- match: { path: "/api/admin" }
|
||||||
|
route:
|
||||||
|
cluster: worker-service
|
||||||
|
|
||||||
# special case for when API requests are made, can just forward, not to minio
|
# special case for when API requests are made, can just forward, not to minio
|
||||||
- match: { prefix: "/api/" }
|
- match: { prefix: "/api/" }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "0.8.9",
|
"version": "0.8.16",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
11
package.json
11
package.json
|
@ -17,16 +17,19 @@
|
||||||
"svelte": "^3.30.0"
|
"svelte": "^3.30.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna link && lerna bootstrap",
|
||||||
"build": "lerna run build",
|
"build": "lerna run build",
|
||||||
"initialise": "lerna run initialise",
|
"initialise": "lerna run initialise",
|
||||||
"publishdev": "lerna run publishdev",
|
"publishdev": "lerna run publishdev",
|
||||||
"publishnpm": "yarn build && lerna publish --force-publish",
|
"publishnpm": "yarn build && lerna publish --force-publish",
|
||||||
"restore": "npm run clean && npm run bootstrap && npm run build",
|
"restore": "yarn run clean && yarn run bootstrap && yarn run build",
|
||||||
"nuke": "rimraf ~/.budibase && npm run restore",
|
"nuke": "yarn run nuke:packages && yarn run nuke:docker",
|
||||||
|
"nuke:packages": "yarn run restore",
|
||||||
|
"nuke:docker": "lerna run --parallel dev:stack:nuke",
|
||||||
"clean": "lerna clean",
|
"clean": "lerna clean",
|
||||||
"kill-port": "kill-port 4001",
|
"kill-port": "kill-port 4001",
|
||||||
"dev": "yarn run kill-port && node ./scripts/symlinkDev.js && lerna run --parallel dev:builder --concurrency 1",
|
"dev": "yarn run kill-port && lerna link && lerna run --parallel dev:builder --concurrency 1",
|
||||||
|
"dev:noserver": "lerna link && lerna run dev:stack:up && lerna run --parallel dev:builder --concurrency 1 --ignore @budibase/server --ignore @budibase/worker",
|
||||||
"test": "lerna run test",
|
"test": "lerna run test",
|
||||||
"lint": "eslint packages",
|
"lint": "eslint packages",
|
||||||
"lint:fix": "eslint --fix packages",
|
"lint:fix": "eslint --fix packages",
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
# Budibase Authentication Library
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "@budibase/auth",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Authentication middlewares for budibase builder and apps",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"author": "Budibase",
|
||||||
|
"license": "AGPL-3.0",
|
||||||
|
"dependencies": {
|
||||||
|
"bcryptjs": "^2.4.3",
|
||||||
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"koa-passport": "^4.1.4",
|
||||||
|
"passport-google-auth": "^1.0.2",
|
||||||
|
"passport-google-oauth": "^2.0.0",
|
||||||
|
"passport-jwt": "^4.0.0",
|
||||||
|
"passport-local": "^1.0.0",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
exports.UserStatus = {
|
||||||
|
ACTIVE: "active",
|
||||||
|
INACTIVE: "inactive",
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.Cookies = {
|
||||||
|
CurrentApp: "budibase:currentapp",
|
||||||
|
Auth: "budibase:auth",
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.GlobalRoles = {
|
||||||
|
OWNER: "owner",
|
||||||
|
ADMIN: "admin",
|
||||||
|
BUILDER: "builder",
|
||||||
|
GROUP_MANAGER: "group_manager",
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
let Pouch
|
||||||
|
|
||||||
|
module.exports.setDB = pouch => {
|
||||||
|
Pouch = pouch
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.getDB = dbName => {
|
||||||
|
return new Pouch(dbName)
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
const { newid } = require("../hashing")
|
||||||
|
|
||||||
|
exports.ViewNames = {
|
||||||
|
USER_BY_EMAIL: "by_email",
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.StaticDatabases = {
|
||||||
|
GLOBAL: {
|
||||||
|
name: "global-db",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const DocumentTypes = {
|
||||||
|
USER: "us",
|
||||||
|
APP: "app",
|
||||||
|
GROUP: "group",
|
||||||
|
CONFIG: "config",
|
||||||
|
TEMPLATE: "template",
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.DocumentTypes = DocumentTypes
|
||||||
|
|
||||||
|
const UNICODE_MAX = "\ufff0"
|
||||||
|
const SEPARATOR = "_"
|
||||||
|
|
||||||
|
exports.SEPARATOR = SEPARATOR
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new group ID.
|
||||||
|
* @returns {string} The new group ID which the group doc can be stored under.
|
||||||
|
*/
|
||||||
|
exports.generateGroupID = () => {
|
||||||
|
return `${DocumentTypes.GROUP}${SEPARATOR}${newid()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets parameters for retrieving groups.
|
||||||
|
*/
|
||||||
|
exports.getGroupParams = (id = "", otherProps = {}) => {
|
||||||
|
return {
|
||||||
|
...otherProps,
|
||||||
|
startkey: `${DocumentTypes.GROUP}${SEPARATOR}${id}`,
|
||||||
|
endkey: `${DocumentTypes.GROUP}${SEPARATOR}${id}${UNICODE_MAX}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new global user ID.
|
||||||
|
* @returns {string} The new user ID which the user doc can be stored under.
|
||||||
|
*/
|
||||||
|
exports.generateGlobalUserID = id => {
|
||||||
|
return `${DocumentTypes.USER}${SEPARATOR}${id || newid()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets parameters for retrieving users.
|
||||||
|
*/
|
||||||
|
exports.getGlobalUserParams = (globalId, otherProps = {}) => {
|
||||||
|
if (!globalId) {
|
||||||
|
globalId = ""
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...otherProps,
|
||||||
|
startkey: `${DocumentTypes.USER}${SEPARATOR}${globalId}`,
|
||||||
|
endkey: `${DocumentTypes.USER}${SEPARATOR}${globalId}${UNICODE_MAX}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a template ID.
|
||||||
|
* @param ownerId The owner/user of the template, this could be global or a group level.
|
||||||
|
*/
|
||||||
|
exports.generateTemplateID = ownerId => {
|
||||||
|
return `${DocumentTypes.TEMPLATE}${SEPARATOR}${ownerId}${newid()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets parameters for retrieving templates. Owner ID must be specified, either global or a group level.
|
||||||
|
*/
|
||||||
|
exports.getTemplateParams = (ownerId, templateId, otherProps = {}) => {
|
||||||
|
if (!templateId) {
|
||||||
|
templateId = ""
|
||||||
|
}
|
||||||
|
let final
|
||||||
|
if (templateId) {
|
||||||
|
final = templateId
|
||||||
|
} else {
|
||||||
|
final = `${DocumentTypes.TEMPLATE}${SEPARATOR}${ownerId}${SEPARATOR}`
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...otherProps,
|
||||||
|
startkey: final,
|
||||||
|
endkey: `${final}${UNICODE_MAX}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new configuration ID.
|
||||||
|
* @returns {string} The new configuration ID which the config doc can be stored under.
|
||||||
|
*/
|
||||||
|
const generateConfigID = ({ type, group, user }) => {
|
||||||
|
const scope = [type, group, user].filter(Boolean).join(SEPARATOR)
|
||||||
|
|
||||||
|
return `${DocumentTypes.CONFIG}${SEPARATOR}${scope}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets parameters for retrieving configurations.
|
||||||
|
*/
|
||||||
|
const getConfigParams = ({ type, group, user }, otherProps = {}) => {
|
||||||
|
const scope = [type, group, user].filter(Boolean).join(SEPARATOR)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...otherProps,
|
||||||
|
startkey: `${DocumentTypes.CONFIG}${SEPARATOR}${scope}`,
|
||||||
|
endkey: `${DocumentTypes.CONFIG}${SEPARATOR}${scope}${UNICODE_MAX}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the most granular configuration document from the DB based on the type, group and userID passed.
|
||||||
|
* @param {Object} db - db instance to query
|
||||||
|
* @param {Object} scopes - the type, group and userID scopes of the configuration.
|
||||||
|
* @returns The most granular configuration document based on the scope.
|
||||||
|
*/
|
||||||
|
const determineScopedConfig = async function(db, { type, user, group }) {
|
||||||
|
const response = await db.allDocs(
|
||||||
|
getConfigParams(
|
||||||
|
{ type, user, group },
|
||||||
|
{
|
||||||
|
include_docs: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const configs = response.rows.map(row => {
|
||||||
|
const config = row.doc
|
||||||
|
|
||||||
|
// Config is specific to a user and a group
|
||||||
|
if (config._id.includes(generateConfigID({ type, user, group }))) {
|
||||||
|
config.score = 4
|
||||||
|
} else if (config._id.includes(generateConfigID({ type, user }))) {
|
||||||
|
// Config is specific to a user only
|
||||||
|
config.score = 3
|
||||||
|
} else if (config._id.includes(generateConfigID({ type, group }))) {
|
||||||
|
// Config is specific to a group only
|
||||||
|
config.score = 2
|
||||||
|
} else if (config._id.includes(generateConfigID({ type }))) {
|
||||||
|
// Config is specific to a type only
|
||||||
|
config.score = 1
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
|
||||||
|
// Find the config with the most granular scope based on context
|
||||||
|
const scopedConfig = configs.sort((a, b) => b.score - a.score)[0]
|
||||||
|
|
||||||
|
return scopedConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.generateConfigID = generateConfigID
|
||||||
|
exports.getConfigParams = getConfigParams
|
||||||
|
exports.determineScopedConfig = determineScopedConfig
|
|
@ -0,0 +1,35 @@
|
||||||
|
const { DocumentTypes, ViewNames, StaticDatabases } = require("./utils")
|
||||||
|
const { getDB } = require("./index")
|
||||||
|
|
||||||
|
function DesignDoc() {
|
||||||
|
return {
|
||||||
|
_id: "_design/database",
|
||||||
|
// view collation information, read before writing any complex views:
|
||||||
|
// https://docs.couchdb.org/en/master/ddocs/views/collation.html#collation-specification
|
||||||
|
views: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.createUserEmailView = async () => {
|
||||||
|
const db = getDB(StaticDatabases.GLOBAL.name)
|
||||||
|
let designDoc
|
||||||
|
try {
|
||||||
|
designDoc = await db.get("_design/database")
|
||||||
|
} catch (err) {
|
||||||
|
// no design doc, make one
|
||||||
|
designDoc = DesignDoc()
|
||||||
|
}
|
||||||
|
const view = {
|
||||||
|
// if using variables in a map function need to inject them before use
|
||||||
|
map: `function(doc) {
|
||||||
|
if (doc._id.startsWith("${DocumentTypes.USER}")) {
|
||||||
|
emit(doc.email, doc._id)
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
}
|
||||||
|
designDoc.views = {
|
||||||
|
...designDoc.views,
|
||||||
|
[ViewNames.USER_BY_EMAIL]: view,
|
||||||
|
}
|
||||||
|
await db.put(designDoc)
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = {
|
||||||
|
JWT_SECRET: process.env.JWT_SECRET,
|
||||||
|
COUCH_DB_URL: process.env.COUCH_DB_URL,
|
||||||
|
SALT_ROUNDS: process.env.SALT_ROUNDS,
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
const bcrypt = require("bcryptjs")
|
||||||
|
const env = require("./environment")
|
||||||
|
const { v4 } = require("uuid")
|
||||||
|
|
||||||
|
const SALT_ROUNDS = env.SALT_ROUNDS || 10
|
||||||
|
|
||||||
|
exports.hash = async data => {
|
||||||
|
const salt = await bcrypt.genSalt(SALT_ROUNDS)
|
||||||
|
return bcrypt.hash(data, salt)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.compare = async (data, encrypted) => {
|
||||||
|
return bcrypt.compare(data, encrypted)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.newid = function() {
|
||||||
|
return v4().replace(/-/g, "")
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
const passport = require("koa-passport")
|
||||||
|
const LocalStrategy = require("passport-local").Strategy
|
||||||
|
const JwtStrategy = require("passport-jwt").Strategy
|
||||||
|
const { StaticDatabases } = require("./db/utils")
|
||||||
|
const { jwt, local, authenticated, google } = require("./middleware")
|
||||||
|
const { setDB, getDB } = require("./db")
|
||||||
|
|
||||||
|
// Strategies
|
||||||
|
passport.use(new LocalStrategy(local.options, local.authenticate))
|
||||||
|
passport.use(new JwtStrategy(jwt.options, jwt.authenticate))
|
||||||
|
|
||||||
|
passport.serializeUser((user, done) => done(null, user))
|
||||||
|
|
||||||
|
passport.deserializeUser(async (user, done) => {
|
||||||
|
const db = getDB(StaticDatabases.GLOBAL.name)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await db.get(user._id)
|
||||||
|
return done(null, user)
|
||||||
|
} catch (err) {
|
||||||
|
console.error("User not found", err)
|
||||||
|
return done(null, false, { message: "User not found" })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init(pouch) {
|
||||||
|
setDB(pouch)
|
||||||
|
},
|
||||||
|
db: require("./db/utils"),
|
||||||
|
utils: {
|
||||||
|
...require("./utils"),
|
||||||
|
...require("./hashing"),
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
buildAuthMiddleware: authenticated,
|
||||||
|
passport,
|
||||||
|
google,
|
||||||
|
jwt: require("jsonwebtoken"),
|
||||||
|
},
|
||||||
|
StaticDatabases,
|
||||||
|
constants: require("./constants"),
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
const { Cookies } = require("../constants")
|
||||||
|
const database = require("../db")
|
||||||
|
const { getCookie, clearCookie } = require("../utils")
|
||||||
|
const { StaticDatabases } = require("../db/utils")
|
||||||
|
|
||||||
|
module.exports = (noAuthPatterns = []) => {
|
||||||
|
const regex = new RegExp(noAuthPatterns.join("|"))
|
||||||
|
return async (ctx, next) => {
|
||||||
|
// the path is not authenticated
|
||||||
|
if (regex.test(ctx.request.url)) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// check the actual user is authenticated first
|
||||||
|
const authCookie = getCookie(ctx, Cookies.Auth)
|
||||||
|
|
||||||
|
if (authCookie) {
|
||||||
|
try {
|
||||||
|
const db = database.getDB(StaticDatabases.GLOBAL.name)
|
||||||
|
const user = await db.get(authCookie.userId)
|
||||||
|
delete user.password
|
||||||
|
ctx.isAuthenticated = true
|
||||||
|
ctx.user = user
|
||||||
|
} catch (err) {
|
||||||
|
// remove the cookie as the use does not exist anymore
|
||||||
|
clearCookie(ctx, Cookies.Auth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// be explicit
|
||||||
|
if (ctx.isAuthenticated !== true) {
|
||||||
|
ctx.isAuthenticated = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return next()
|
||||||
|
} catch (err) {
|
||||||
|
ctx.throw(err.status || 403, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
const jwt = require("./passport/jwt")
|
||||||
|
const local = require("./passport/local")
|
||||||
|
const google = require("./passport/google")
|
||||||
|
const authenticated = require("./authenticated")
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
google,
|
||||||
|
jwt,
|
||||||
|
local,
|
||||||
|
authenticated,
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
const env = require("../../environment")
|
||||||
|
const jwt = require("jsonwebtoken")
|
||||||
|
const database = require("../../db")
|
||||||
|
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
||||||
|
const { StaticDatabases, generateGlobalUserID } = require("../../db/utils")
|
||||||
|
|
||||||
|
async function authenticate(token, tokenSecret, profile, done) {
|
||||||
|
// Check the user exists in the instance DB by email
|
||||||
|
const db = database.getDB(StaticDatabases.GLOBAL.name)
|
||||||
|
|
||||||
|
let dbUser
|
||||||
|
const userId = generateGlobalUserID(profile.id)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// use the google profile id
|
||||||
|
dbUser = await db.get(userId)
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Google user not found. Creating..")
|
||||||
|
// create the user
|
||||||
|
const user = {
|
||||||
|
_id: userId,
|
||||||
|
provider: profile.provider,
|
||||||
|
roles: {},
|
||||||
|
builder: {
|
||||||
|
global: true,
|
||||||
|
},
|
||||||
|
...profile._json,
|
||||||
|
}
|
||||||
|
const response = await db.post(user)
|
||||||
|
|
||||||
|
dbUser = user
|
||||||
|
dbUser._rev = response.rev
|
||||||
|
}
|
||||||
|
|
||||||
|
// authenticate
|
||||||
|
const payload = {
|
||||||
|
userId: dbUser._id,
|
||||||
|
builder: dbUser.builder,
|
||||||
|
email: dbUser.email,
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUser.token = jwt.sign(payload, env.JWT_SECRET, {
|
||||||
|
expiresIn: "1 day",
|
||||||
|
})
|
||||||
|
|
||||||
|
return done(null, dbUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance of the google passport strategy. This wrapper fetches the configuration
|
||||||
|
* from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport.
|
||||||
|
* @returns Dynamically configured Passport Google Strategy
|
||||||
|
*/
|
||||||
|
exports.strategyFactory = async function(config) {
|
||||||
|
try {
|
||||||
|
const { clientID, clientSecret, callbackURL } = config
|
||||||
|
|
||||||
|
if (!clientID || !clientSecret || !callbackURL) {
|
||||||
|
throw new Error(
|
||||||
|
"Configuration invalid. Must contain google clientID, clientSecret and callbackURL"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GoogleStrategy(
|
||||||
|
{
|
||||||
|
clientID: config.clientID,
|
||||||
|
clientSecret: config.clientSecret,
|
||||||
|
callbackURL: config.callbackURL,
|
||||||
|
},
|
||||||
|
authenticate
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
throw new Error("Error constructing google authentication strategy", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
const { Cookies } = require("../../constants")
|
||||||
|
const env = require("../../environment")
|
||||||
|
|
||||||
|
exports.options = {
|
||||||
|
secretOrKey: env.JWT_SECRET,
|
||||||
|
jwtFromRequest: function(ctx) {
|
||||||
|
return ctx.cookies.get(Cookies.Auth)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.authenticate = async function(jwt, done) {
|
||||||
|
try {
|
||||||
|
return done(null, jwt)
|
||||||
|
} catch (err) {
|
||||||
|
return done(new Error("JWT invalid."), false)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
const jwt = require("jsonwebtoken")
|
||||||
|
const { UserStatus } = require("../../constants")
|
||||||
|
const { compare } = require("../../hashing")
|
||||||
|
const env = require("../../environment")
|
||||||
|
const { getGlobalUserByEmail } = require("../../utils")
|
||||||
|
|
||||||
|
const INVALID_ERR = "Invalid Credentials"
|
||||||
|
|
||||||
|
exports.options = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passport Local Authentication Middleware.
|
||||||
|
* @param {*} email - username to login with
|
||||||
|
* @param {*} password - plain text password to log in with
|
||||||
|
* @param {*} done - callback from passport to return user information and errors
|
||||||
|
* @returns The authenticated user, or errors if they occur
|
||||||
|
*/
|
||||||
|
exports.authenticate = async function(email, password, done) {
|
||||||
|
if (!email) return done(null, false, "Email Required.")
|
||||||
|
if (!password) return done(null, false, "Password Required.")
|
||||||
|
|
||||||
|
const dbUser = await getGlobalUserByEmail(email)
|
||||||
|
if (dbUser == null) {
|
||||||
|
return done(null, false, { message: "User not found" })
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that the user is currently inactive, if this is the case throw invalid
|
||||||
|
if (dbUser.status === UserStatus.INACTIVE) {
|
||||||
|
return done(null, false, { message: INVALID_ERR })
|
||||||
|
}
|
||||||
|
|
||||||
|
// authenticate
|
||||||
|
if (await compare(password, dbUser.password)) {
|
||||||
|
const payload = {
|
||||||
|
userId: dbUser._id,
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUser.token = jwt.sign(payload, env.JWT_SECRET, {
|
||||||
|
expiresIn: "1 day",
|
||||||
|
})
|
||||||
|
// Remove users password in payload
|
||||||
|
delete dbUser.password
|
||||||
|
|
||||||
|
return done(null, dbUser)
|
||||||
|
} else {
|
||||||
|
done(new Error(INVALID_ERR), false)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
const {
|
||||||
|
DocumentTypes,
|
||||||
|
SEPARATOR,
|
||||||
|
ViewNames,
|
||||||
|
StaticDatabases,
|
||||||
|
} = require("./db/utils")
|
||||||
|
const jwt = require("jsonwebtoken")
|
||||||
|
const { options } = require("./middleware/passport/jwt")
|
||||||
|
const { createUserEmailView } = require("./db/views")
|
||||||
|
const { getDB } = require("./db")
|
||||||
|
|
||||||
|
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||||
|
|
||||||
|
function confirmAppId(possibleAppId) {
|
||||||
|
return possibleAppId && possibleAppId.startsWith(APP_PREFIX)
|
||||||
|
? possibleAppId
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a request tries to find the appId, which can be located in various places
|
||||||
|
* @param {object} ctx The main request body to look through.
|
||||||
|
* @returns {string|undefined} If an appId was found it will be returned.
|
||||||
|
*/
|
||||||
|
exports.getAppId = ctx => {
|
||||||
|
const options = [ctx.headers["x-budibase-app-id"], ctx.params.appId]
|
||||||
|
if (ctx.subdomains) {
|
||||||
|
options.push(ctx.subdomains[1])
|
||||||
|
}
|
||||||
|
let appId
|
||||||
|
for (let option of options) {
|
||||||
|
appId = confirmAppId(option)
|
||||||
|
if (appId) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// look in body if can't find it in subdomain
|
||||||
|
if (!appId && ctx.request.body && ctx.request.body.appId) {
|
||||||
|
appId = confirmAppId(ctx.request.body.appId)
|
||||||
|
}
|
||||||
|
let appPath =
|
||||||
|
ctx.request.headers.referrer ||
|
||||||
|
ctx.path.split("/").filter(subPath => subPath.startsWith(APP_PREFIX))
|
||||||
|
if (!appId && appPath.length !== 0) {
|
||||||
|
appId = confirmAppId(appPath[0])
|
||||||
|
}
|
||||||
|
return appId
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a cookie from context, and decrypt if necessary.
|
||||||
|
* @param {object} ctx The request which is to be manipulated.
|
||||||
|
* @param {string} name The name of the cookie to get.
|
||||||
|
*/
|
||||||
|
exports.getCookie = (ctx, name) => {
|
||||||
|
const cookie = ctx.cookies.get(name)
|
||||||
|
|
||||||
|
if (!cookie) {
|
||||||
|
return cookie
|
||||||
|
}
|
||||||
|
|
||||||
|
return jwt.verify(cookie, options.secretOrKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a cookie for the request, has a hardcoded expiry.
|
||||||
|
* @param {object} ctx The request which is to be manipulated.
|
||||||
|
* @param {string} name The name of the cookie to set.
|
||||||
|
* @param {string|object} value The value of cookie which will be set.
|
||||||
|
*/
|
||||||
|
exports.setCookie = (ctx, value, name = "builder") => {
|
||||||
|
const expires = new Date()
|
||||||
|
expires.setDate(expires.getDate() + 1)
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
ctx.cookies.set(name)
|
||||||
|
} else {
|
||||||
|
value = jwt.sign(value, options.secretOrKey, {
|
||||||
|
expiresIn: "1 day",
|
||||||
|
})
|
||||||
|
ctx.cookies.set(name, value, {
|
||||||
|
expires,
|
||||||
|
path: "/",
|
||||||
|
httpOnly: false,
|
||||||
|
overwrite: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function, simply calls setCookie with an empty string for value
|
||||||
|
*/
|
||||||
|
exports.clearCookie = (ctx, name) => {
|
||||||
|
exports.setCookie(ctx, null, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the API call being made (based on the provided ctx object) is from the client. If
|
||||||
|
* the call is not from a client app then it is from the builder.
|
||||||
|
* @param {object} ctx The koa context object to be tested.
|
||||||
|
* @return {boolean} returns true if the call is from the client lib (a built app rather than the builder).
|
||||||
|
*/
|
||||||
|
exports.isClient = ctx => {
|
||||||
|
return ctx.headers["x-budibase-type"] === "client"
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getGlobalUserByEmail = async email => {
|
||||||
|
const db = getDB(StaticDatabases.GLOBAL.name)
|
||||||
|
try {
|
||||||
|
let users = (
|
||||||
|
await db.query(`database/${ViewNames.USER_BY_EMAIL}`, {
|
||||||
|
key: email,
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
).rows
|
||||||
|
users = users.map(user => user.doc)
|
||||||
|
return users.length <= 1 ? users[0] : users
|
||||||
|
} catch (err) {
|
||||||
|
if (err != null && err.name === "not_found") {
|
||||||
|
await createUserEmailView()
|
||||||
|
return exports.getGlobalUserByEmail(email)
|
||||||
|
} else {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,599 @@
|
||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
ajv@^6.12.3:
|
||||||
|
version "6.12.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||||
|
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||||
|
dependencies:
|
||||||
|
fast-deep-equal "^3.1.1"
|
||||||
|
fast-json-stable-stringify "^2.0.0"
|
||||||
|
json-schema-traverse "^0.4.1"
|
||||||
|
uri-js "^4.2.2"
|
||||||
|
|
||||||
|
asn1@~0.2.3:
|
||||||
|
version "0.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||||
|
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
|
||||||
|
dependencies:
|
||||||
|
safer-buffer "~2.1.0"
|
||||||
|
|
||||||
|
assert-plus@1.0.0, assert-plus@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||||
|
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
|
||||||
|
|
||||||
|
async@~2.1.4:
|
||||||
|
version "2.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc"
|
||||||
|
integrity sha1-5YfGhYCZSsZ/xW/4bTrFa9voELw=
|
||||||
|
dependencies:
|
||||||
|
lodash "^4.14.0"
|
||||||
|
|
||||||
|
asynckit@^0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
|
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
|
||||||
|
|
||||||
|
aws-sign2@~0.7.0:
|
||||||
|
version "0.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
||||||
|
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
|
||||||
|
|
||||||
|
aws4@^1.8.0:
|
||||||
|
version "1.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
|
||||||
|
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
||||||
|
|
||||||
|
base64url@3.x.x:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
|
||||||
|
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
|
||||||
|
|
||||||
|
bcrypt-pbkdf@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||||
|
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
|
||||||
|
dependencies:
|
||||||
|
tweetnacl "^0.14.3"
|
||||||
|
|
||||||
|
bcryptjs@^2.4.3:
|
||||||
|
version "2.4.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
|
||||||
|
integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=
|
||||||
|
|
||||||
|
buffer-equal-constant-time@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||||
|
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
|
||||||
|
|
||||||
|
caseless@~0.12.0:
|
||||||
|
version "0.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||||
|
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
||||||
|
|
||||||
|
combined-stream@^1.0.6, combined-stream@~1.0.6:
|
||||||
|
version "1.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||||
|
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||||
|
dependencies:
|
||||||
|
delayed-stream "~1.0.0"
|
||||||
|
|
||||||
|
core-util-is@1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
|
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||||
|
|
||||||
|
dashdash@^1.12.0:
|
||||||
|
version "1.14.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||||
|
integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
|
||||||
|
dependencies:
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
|
delayed-stream@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||||
|
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
||||||
|
|
||||||
|
ecc-jsbn@~0.1.1:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
||||||
|
integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
|
||||||
|
dependencies:
|
||||||
|
jsbn "~0.1.0"
|
||||||
|
safer-buffer "^2.1.0"
|
||||||
|
|
||||||
|
ecdsa-sig-formatter@1.0.11:
|
||||||
|
version "1.0.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
|
||||||
|
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
extend@~3.0.2:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||||
|
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
||||||
|
|
||||||
|
extsprintf@1.3.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||||
|
integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
|
||||||
|
|
||||||
|
extsprintf@^1.2.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||||
|
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
||||||
|
|
||||||
|
fast-deep-equal@^3.1.1:
|
||||||
|
version "3.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||||
|
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||||
|
|
||||||
|
fast-json-stable-stringify@^2.0.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||||
|
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
||||||
|
|
||||||
|
forever-agent@~0.6.1:
|
||||||
|
version "0.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||||
|
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
|
||||||
|
|
||||||
|
form-data@~2.3.2:
|
||||||
|
version "2.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
||||||
|
integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
|
||||||
|
dependencies:
|
||||||
|
asynckit "^0.4.0"
|
||||||
|
combined-stream "^1.0.6"
|
||||||
|
mime-types "^2.1.12"
|
||||||
|
|
||||||
|
getpass@^0.1.1:
|
||||||
|
version "0.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
|
||||||
|
integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
|
||||||
|
dependencies:
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
|
google-auth-library@~0.10.0:
|
||||||
|
version "0.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-0.10.0.tgz#6e15babee85fd1dd14d8d128a295b6838d52136e"
|
||||||
|
integrity sha1-bhW6vuhf0d0U2NEoopW2g41SE24=
|
||||||
|
dependencies:
|
||||||
|
gtoken "^1.2.1"
|
||||||
|
jws "^3.1.4"
|
||||||
|
lodash.noop "^3.0.1"
|
||||||
|
request "^2.74.0"
|
||||||
|
|
||||||
|
google-p12-pem@^0.1.0:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-0.1.2.tgz#33c46ab021aa734fa0332b3960a9a3ffcb2f3177"
|
||||||
|
integrity sha1-M8RqsCGqc0+gMys5YKmj/8svMXc=
|
||||||
|
dependencies:
|
||||||
|
node-forge "^0.7.1"
|
||||||
|
|
||||||
|
googleapis@^16.0.0:
|
||||||
|
version "16.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-16.1.0.tgz#0f19f2d70572d918881a0f626e3b1a2fa8629576"
|
||||||
|
integrity sha1-Dxny1wVy2RiIGg9ibjsaL6hilXY=
|
||||||
|
dependencies:
|
||||||
|
async "~2.1.4"
|
||||||
|
google-auth-library "~0.10.0"
|
||||||
|
string-template "~1.0.0"
|
||||||
|
|
||||||
|
gtoken@^1.2.1:
|
||||||
|
version "1.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-1.2.3.tgz#5509571b8afd4322e124cf66cf68115284c476d8"
|
||||||
|
integrity sha512-wQAJflfoqSgMWrSBk9Fg86q+sd6s7y6uJhIvvIPz++RElGlMtEqsdAR2oWwZ/WTEtp7P9xFbJRrT976oRgzJ/w==
|
||||||
|
dependencies:
|
||||||
|
google-p12-pem "^0.1.0"
|
||||||
|
jws "^3.0.0"
|
||||||
|
mime "^1.4.1"
|
||||||
|
request "^2.72.0"
|
||||||
|
|
||||||
|
har-schema@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
|
||||||
|
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
|
||||||
|
|
||||||
|
har-validator@~5.1.3:
|
||||||
|
version "5.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
|
||||||
|
integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
|
||||||
|
dependencies:
|
||||||
|
ajv "^6.12.3"
|
||||||
|
har-schema "^2.0.0"
|
||||||
|
|
||||||
|
http-signature@~1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
||||||
|
integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
|
||||||
|
dependencies:
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
jsprim "^1.2.2"
|
||||||
|
sshpk "^1.7.0"
|
||||||
|
|
||||||
|
is-typedarray@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||||
|
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
|
||||||
|
|
||||||
|
isstream@~0.1.2:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||||
|
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
|
||||||
|
|
||||||
|
jsbn@~0.1.0:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
||||||
|
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
|
||||||
|
|
||||||
|
json-schema-traverse@^0.4.1:
|
||||||
|
version "0.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||||
|
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||||
|
|
||||||
|
json-schema@0.2.3:
|
||||||
|
version "0.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
|
||||||
|
integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
|
||||||
|
|
||||||
|
json-stringify-safe@~5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||||
|
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
|
||||||
|
|
||||||
|
jsonwebtoken@^8.2.0, jsonwebtoken@^8.5.1:
|
||||||
|
version "8.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
|
||||||
|
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
|
||||||
|
dependencies:
|
||||||
|
jws "^3.2.2"
|
||||||
|
lodash.includes "^4.3.0"
|
||||||
|
lodash.isboolean "^3.0.3"
|
||||||
|
lodash.isinteger "^4.0.4"
|
||||||
|
lodash.isnumber "^3.0.3"
|
||||||
|
lodash.isplainobject "^4.0.6"
|
||||||
|
lodash.isstring "^4.0.1"
|
||||||
|
lodash.once "^4.0.0"
|
||||||
|
ms "^2.1.1"
|
||||||
|
semver "^5.6.0"
|
||||||
|
|
||||||
|
jsprim@^1.2.2:
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
|
||||||
|
integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
|
||||||
|
dependencies:
|
||||||
|
assert-plus "1.0.0"
|
||||||
|
extsprintf "1.3.0"
|
||||||
|
json-schema "0.2.3"
|
||||||
|
verror "1.10.0"
|
||||||
|
|
||||||
|
jwa@^1.4.1:
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
|
||||||
|
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
|
||||||
|
dependencies:
|
||||||
|
buffer-equal-constant-time "1.0.1"
|
||||||
|
ecdsa-sig-formatter "1.0.11"
|
||||||
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
jws@^3.0.0, jws@^3.1.4, jws@^3.2.2:
|
||||||
|
version "3.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||||
|
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
|
||||||
|
dependencies:
|
||||||
|
jwa "^1.4.1"
|
||||||
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
koa-passport@^4.1.4:
|
||||||
|
version "4.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/koa-passport/-/koa-passport-4.1.4.tgz#5f1665c1c2a37ace79af9f970b770885ca30ccfa"
|
||||||
|
integrity sha512-dJBCkl4X+zdYxbI2V2OtoGy0PUenpvp2ZLLWObc8UJhsId0iQpTFT8RVcuA0709AL2txGwRHnSPoT1bYNGa6Kg==
|
||||||
|
dependencies:
|
||||||
|
passport "^0.4.0"
|
||||||
|
|
||||||
|
lodash.includes@^4.3.0:
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
|
||||||
|
integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
|
||||||
|
|
||||||
|
lodash.isboolean@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
|
||||||
|
integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
|
||||||
|
|
||||||
|
lodash.isinteger@^4.0.4:
|
||||||
|
version "4.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
|
||||||
|
integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
|
||||||
|
|
||||||
|
lodash.isnumber@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
|
||||||
|
integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
|
||||||
|
|
||||||
|
lodash.isplainobject@^4.0.6:
|
||||||
|
version "4.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||||
|
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
|
||||||
|
|
||||||
|
lodash.isstring@^4.0.1:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||||
|
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
|
||||||
|
|
||||||
|
lodash.noop@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c"
|
||||||
|
integrity sha1-OBiPTWUKOkdCWEObluxFsyYXEzw=
|
||||||
|
|
||||||
|
lodash.once@^4.0.0:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||||
|
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
|
||||||
|
|
||||||
|
lodash@^4.14.0:
|
||||||
|
version "4.17.21"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
|
|
||||||
|
mime-db@1.47.0:
|
||||||
|
version "1.47.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c"
|
||||||
|
integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==
|
||||||
|
|
||||||
|
mime-types@^2.1.12, mime-types@~2.1.19:
|
||||||
|
version "2.1.30"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d"
|
||||||
|
integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==
|
||||||
|
dependencies:
|
||||||
|
mime-db "1.47.0"
|
||||||
|
|
||||||
|
mime@^1.4.1:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||||
|
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||||
|
|
||||||
|
ms@^2.1.1:
|
||||||
|
version "2.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||||
|
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||||
|
|
||||||
|
node-forge@^0.7.1:
|
||||||
|
version "0.7.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"
|
||||||
|
integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==
|
||||||
|
|
||||||
|
oauth-sign@~0.9.0:
|
||||||
|
version "0.9.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
||||||
|
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
|
||||||
|
|
||||||
|
oauth@0.9.x:
|
||||||
|
version "0.9.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
|
||||||
|
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
|
||||||
|
|
||||||
|
passport-google-auth@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-google-auth/-/passport-google-auth-1.0.2.tgz#8b300b5aa442ef433de1d832ed3112877d0b2938"
|
||||||
|
integrity sha1-izALWqRC70M94dgy7TESh30LKTg=
|
||||||
|
dependencies:
|
||||||
|
googleapis "^16.0.0"
|
||||||
|
passport-strategy "1.x"
|
||||||
|
|
||||||
|
passport-google-oauth1@1.x.x:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-google-oauth1/-/passport-google-oauth1-1.0.0.tgz#af74a803df51ec646f66a44d82282be6f108e0cc"
|
||||||
|
integrity sha1-r3SoA99R7GRvZqRNgigr5vEI4Mw=
|
||||||
|
dependencies:
|
||||||
|
passport-oauth1 "1.x.x"
|
||||||
|
|
||||||
|
passport-google-oauth20@2.x.x:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz#0d241b2d21ebd3dc7f2b60669ec4d587e3a674ef"
|
||||||
|
integrity sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==
|
||||||
|
dependencies:
|
||||||
|
passport-oauth2 "1.x.x"
|
||||||
|
|
||||||
|
passport-google-oauth@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-google-oauth/-/passport-google-oauth-2.0.0.tgz#f6eb4bc96dd6c16ec0ecfdf4e05ec48ca54d4dae"
|
||||||
|
integrity sha512-JKxZpBx6wBQXX1/a1s7VmdBgwOugohH+IxCy84aPTZNq/iIPX6u7Mqov1zY7MKRz3niFPol0KJz8zPLBoHKtYA==
|
||||||
|
dependencies:
|
||||||
|
passport-google-oauth1 "1.x.x"
|
||||||
|
passport-google-oauth20 "2.x.x"
|
||||||
|
|
||||||
|
passport-jwt@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.0.tgz#7f0be7ba942e28b9f5d22c2ebbb8ce96ef7cf065"
|
||||||
|
integrity sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==
|
||||||
|
dependencies:
|
||||||
|
jsonwebtoken "^8.2.0"
|
||||||
|
passport-strategy "^1.0.0"
|
||||||
|
|
||||||
|
passport-local@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee"
|
||||||
|
integrity sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=
|
||||||
|
dependencies:
|
||||||
|
passport-strategy "1.x.x"
|
||||||
|
|
||||||
|
passport-oauth1@1.x.x:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-oauth1/-/passport-oauth1-1.1.0.tgz#a7de988a211f9cf4687377130ea74df32730c918"
|
||||||
|
integrity sha1-p96YiiEfnPRoc3cTDqdN8ycwyRg=
|
||||||
|
dependencies:
|
||||||
|
oauth "0.9.x"
|
||||||
|
passport-strategy "1.x.x"
|
||||||
|
utils-merge "1.x.x"
|
||||||
|
|
||||||
|
passport-oauth2@1.x.x:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.5.0.tgz#64babbb54ac46a4dcab35e7f266ed5294e3c4108"
|
||||||
|
integrity sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ==
|
||||||
|
dependencies:
|
||||||
|
base64url "3.x.x"
|
||||||
|
oauth "0.9.x"
|
||||||
|
passport-strategy "1.x.x"
|
||||||
|
uid2 "0.0.x"
|
||||||
|
utils-merge "1.x.x"
|
||||||
|
|
||||||
|
passport-strategy@1.x, passport-strategy@1.x.x, passport-strategy@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
|
||||||
|
integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=
|
||||||
|
|
||||||
|
passport@^0.4.0:
|
||||||
|
version "0.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270"
|
||||||
|
integrity sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==
|
||||||
|
dependencies:
|
||||||
|
passport-strategy "1.x.x"
|
||||||
|
pause "0.0.1"
|
||||||
|
|
||||||
|
pause@0.0.1:
|
||||||
|
version "0.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d"
|
||||||
|
integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=
|
||||||
|
|
||||||
|
performance-now@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||||
|
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
||||||
|
|
||||||
|
psl@^1.1.28:
|
||||||
|
version "1.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
|
||||||
|
integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
|
||||||
|
|
||||||
|
punycode@^2.1.0, punycode@^2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||||
|
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||||
|
|
||||||
|
qs@~6.5.2:
|
||||||
|
version "6.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||||
|
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
||||||
|
|
||||||
|
request@^2.72.0, request@^2.74.0:
|
||||||
|
version "2.88.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
|
||||||
|
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
|
||||||
|
dependencies:
|
||||||
|
aws-sign2 "~0.7.0"
|
||||||
|
aws4 "^1.8.0"
|
||||||
|
caseless "~0.12.0"
|
||||||
|
combined-stream "~1.0.6"
|
||||||
|
extend "~3.0.2"
|
||||||
|
forever-agent "~0.6.1"
|
||||||
|
form-data "~2.3.2"
|
||||||
|
har-validator "~5.1.3"
|
||||||
|
http-signature "~1.2.0"
|
||||||
|
is-typedarray "~1.0.0"
|
||||||
|
isstream "~0.1.2"
|
||||||
|
json-stringify-safe "~5.0.1"
|
||||||
|
mime-types "~2.1.19"
|
||||||
|
oauth-sign "~0.9.0"
|
||||||
|
performance-now "^2.1.0"
|
||||||
|
qs "~6.5.2"
|
||||||
|
safe-buffer "^5.1.2"
|
||||||
|
tough-cookie "~2.5.0"
|
||||||
|
tunnel-agent "^0.6.0"
|
||||||
|
uuid "^3.3.2"
|
||||||
|
|
||||||
|
safe-buffer@^5.0.1, safe-buffer@^5.1.2:
|
||||||
|
version "5.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||||
|
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||||
|
|
||||||
|
safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
||||||
|
semver@^5.6.0:
|
||||||
|
version "5.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||||
|
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||||
|
|
||||||
|
sshpk@^1.7.0:
|
||||||
|
version "1.16.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
|
||||||
|
integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==
|
||||||
|
dependencies:
|
||||||
|
asn1 "~0.2.3"
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
bcrypt-pbkdf "^1.0.0"
|
||||||
|
dashdash "^1.12.0"
|
||||||
|
ecc-jsbn "~0.1.1"
|
||||||
|
getpass "^0.1.1"
|
||||||
|
jsbn "~0.1.0"
|
||||||
|
safer-buffer "^2.0.2"
|
||||||
|
tweetnacl "~0.14.0"
|
||||||
|
|
||||||
|
string-template@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
|
||||||
|
integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=
|
||||||
|
|
||||||
|
tough-cookie@~2.5.0:
|
||||||
|
version "2.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
|
||||||
|
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
|
||||||
|
dependencies:
|
||||||
|
psl "^1.1.28"
|
||||||
|
punycode "^2.1.1"
|
||||||
|
|
||||||
|
tunnel-agent@^0.6.0:
|
||||||
|
version "0.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
||||||
|
integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||||
|
version "0.14.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||||
|
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
|
||||||
|
|
||||||
|
uid2@0.0.x:
|
||||||
|
version "0.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82"
|
||||||
|
integrity sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=
|
||||||
|
|
||||||
|
uri-js@^4.2.2:
|
||||||
|
version "4.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
|
||||||
|
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
|
||||||
|
dependencies:
|
||||||
|
punycode "^2.1.0"
|
||||||
|
|
||||||
|
utils-merge@1.x.x:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||||
|
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||||
|
|
||||||
|
uuid@^3.3.2:
|
||||||
|
version "3.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||||
|
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||||
|
|
||||||
|
uuid@^8.3.2:
|
||||||
|
version "8.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||||
|
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||||
|
|
||||||
|
verror@1.10.0:
|
||||||
|
version "1.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
||||||
|
integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
|
||||||
|
dependencies:
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
core-util-is "1.0.2"
|
||||||
|
extsprintf "^1.2.0"
|
|
@ -0,0 +1,5 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist/
|
||||||
|
/public/svench/
|
||||||
|
.idea
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Budibase bbui
|
||||||
|
|
||||||
|
A package that handles all common components across the Budibase organisation. You can find the current live version [Here](http://bbui.budibase.com).
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
1. Clone
|
||||||
|
2. `npm install`
|
||||||
|
3. `npm run svench`
|
||||||
|
|
||||||
|
(Note: yarn won't work!)
|
||||||
|
|
||||||
|
## Example workflow to create a component
|
||||||
|
|
||||||
|
1. Create a file: `Headline.svelte`
|
||||||
|
2. Create a Svench file: `Headline.svench`
|
||||||
|
3. Build component and add variants to the Svench file.
|
||||||
|
4. Once done, re-export the file in `src/index.js`.
|
||||||
|
5. Publish, update the package in the main project and profit.
|
||||||
|
|
||||||
|
## Guidelines
|
||||||
|
### Making components
|
||||||
|
|
||||||
|
1. Think about re-usability
|
||||||
|
2. Use the css custom properties (variables) that are in the css stylesheet. This makes it easy to tweak things later down the line.
|
||||||
|
3. Opt to forward events (`<button on:click>` for example) rather than using callbacks.
|
||||||
|
4. Avoid adding margins to the outermost container of the component.
|
||||||
|
|
||||||
|
### Using components and the styleguide
|
||||||
|
|
||||||
|
1. Get familiar with the different props that exist on the component. If something vital is missing, make a PR and add it.
|
||||||
|
2. Take advantage of the css custom properties in the stylesheet and avoid writing hard-coded values.
|
||||||
|
4. Since there is no margin on the components, think about the structure of the DOM and how to achieve correct spacing, etc. This can be done using `css grid` + `grid gap` or with a container div where you specify a padding or margin. The best solution depends on the circumstance.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
* [ ] Figure out a good documentation situation
|
||||||
|
* [ ] Add testing suite (E2E using Playwright?)
|
||||||
|
|
||||||
|
## Other
|
||||||
|
|
||||||
|
The project uses [Svench](https://github.com/rixo/svench). It is somewhat akin to Storybook but a lot less bloated and much easier to setup. It also supports HMR for quick development.
|
|
@ -0,0 +1,53 @@
|
||||||
|
{
|
||||||
|
"name": "@budibase/bbui",
|
||||||
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
|
"version": "1.58.13",
|
||||||
|
"svelte": "src/index.js",
|
||||||
|
"module": "dist/bbui.es.js",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/bbui.es.js"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json",
|
||||||
|
"./dist/style.css": "./dist/style.css"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev:builder": "vite build",
|
||||||
|
"build": "vite build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^16.0.0",
|
||||||
|
"@rollup/plugin-json": "^4.1.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^11.0.0",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.5",
|
||||||
|
"cross-env": "^7.0.2",
|
||||||
|
"nollup": "^0.14.1",
|
||||||
|
"postcss": "^8.2.9",
|
||||||
|
"rollup": "^2.34.0",
|
||||||
|
"rollup-plugin-copy": "^3.3.0",
|
||||||
|
"rollup-plugin-delete": "^1.2.0",
|
||||||
|
"rollup-plugin-hot": "^0.1.1",
|
||||||
|
"rollup-plugin-node-builtins": "^2.1.2",
|
||||||
|
"rollup-plugin-postcss": "^4.0.0",
|
||||||
|
"rollup-plugin-svelte-hot": "^0.11.0",
|
||||||
|
"semantic-release": "^17.0.8",
|
||||||
|
"svelte": "^3.29.0",
|
||||||
|
"svench": "^0.0.10-7",
|
||||||
|
"vite": "^2.1.5"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"svelte"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"src",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"markdown-it": "^12.0.4",
|
||||||
|
"quill": "^1.3.7",
|
||||||
|
"sirv-cli": "^0.4.6",
|
||||||
|
"svelte-flatpickr": "^2.4.0",
|
||||||
|
"svelte-portal": "^1.0.0",
|
||||||
|
"turndown": "^7.0.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
import * as path from "path"
|
||||||
|
import svelte from "rollup-plugin-svelte-hot"
|
||||||
|
import resolve from "@rollup/plugin-node-resolve"
|
||||||
|
import commonjs from "@rollup/plugin-commonjs"
|
||||||
|
import json from "@rollup/plugin-json"
|
||||||
|
import copy from "rollup-plugin-copy"
|
||||||
|
import hmr from "rollup-plugin-hot"
|
||||||
|
import del from "rollup-plugin-delete"
|
||||||
|
import postcss from "rollup-plugin-postcss"
|
||||||
|
import { plugin as Svench } from "svench/rollup"
|
||||||
|
import builtins from "rollup-plugin-node-builtins"
|
||||||
|
|
||||||
|
const WATCH = !!process.env.ROLLUP_WATCH
|
||||||
|
const SVENCH = !!process.env.SVENCH
|
||||||
|
const HOT = WATCH
|
||||||
|
const PRODUCTION = !WATCH
|
||||||
|
|
||||||
|
const svench = Svench({
|
||||||
|
// The root dir that Svench will parse and watch.
|
||||||
|
//
|
||||||
|
// NOTE Watching the root of the project, to let Svench render *.md for us.
|
||||||
|
//
|
||||||
|
// NOTE By default, `node_modules` and `.git` dirs are ignored. This can be
|
||||||
|
// customized by passing a function to `ignore` option. Default ignore is:
|
||||||
|
//
|
||||||
|
// ignore: path => /(?:^|\/)(?:node_modules|\.git)\//.test(path),
|
||||||
|
//
|
||||||
|
dir: ".",
|
||||||
|
|
||||||
|
// Make `src` dir a section (that is, it will always be "expanded" in the
|
||||||
|
// menu).
|
||||||
|
autoSections: ["src"],
|
||||||
|
|
||||||
|
// Use custom index.html
|
||||||
|
index: {
|
||||||
|
source: "public/index.html",
|
||||||
|
},
|
||||||
|
|
||||||
|
extensions: [".svench", ".svench.svelte", ".svench.svx", ".md"],
|
||||||
|
|
||||||
|
serve: WATCH && {
|
||||||
|
host: "0.0.0.0",
|
||||||
|
port: 4242,
|
||||||
|
public: "public",
|
||||||
|
nollup: "0.0.0.0:42421",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// NOTE configs are in function form to avoid instantiating plugins of the
|
||||||
|
// config that is not used for nothing (in particular, the HMR plugin launches
|
||||||
|
// a dev server on startup, this is not desired when just building for prod)
|
||||||
|
const configs = {
|
||||||
|
svench: () => ({
|
||||||
|
input: ".svench/svench.js",
|
||||||
|
output: {
|
||||||
|
format: "es",
|
||||||
|
dir: "public/svench",
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
builtins(),
|
||||||
|
|
||||||
|
// NOTE cleaning old builds is required to avoid serving stale static
|
||||||
|
// files from a previous build instead of in-memory files from the dev/hmr
|
||||||
|
// server
|
||||||
|
del({
|
||||||
|
targets: "public/svench/*",
|
||||||
|
runOnce: true,
|
||||||
|
}),
|
||||||
|
|
||||||
|
postcss({
|
||||||
|
hot: HOT,
|
||||||
|
extract: path.resolve("public/svench/theme.css"),
|
||||||
|
sourceMap: true,
|
||||||
|
}),
|
||||||
|
|
||||||
|
svench,
|
||||||
|
|
||||||
|
svelte({
|
||||||
|
dev: !PRODUCTION,
|
||||||
|
extensions: [".svelte", ".svench", ".svx", ".md"],
|
||||||
|
// Svench's "combined" preprocessor wraps both Mdsvex preprocessors
|
||||||
|
// (configured for Svench), and its own preprocessor (for static
|
||||||
|
// analysis -- eg extract source from views)
|
||||||
|
preprocess: svench.$.preprocess,
|
||||||
|
hot: HOT && {
|
||||||
|
optimistic: true,
|
||||||
|
noPreserveState: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
resolve({ browser: true }),
|
||||||
|
|
||||||
|
commonjs(),
|
||||||
|
json(),
|
||||||
|
|
||||||
|
HOT &&
|
||||||
|
hmr({
|
||||||
|
host: "0.0.0.0",
|
||||||
|
public: "public",
|
||||||
|
inMemory: true,
|
||||||
|
compatModuleHot: !HOT, // for terser
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
clearScreen: false,
|
||||||
|
// buildDelay is needed to ensure Svench's code (routes) generator will
|
||||||
|
// pick file changes before Rollup and prevent a double build (if Rollup
|
||||||
|
// first sees a change to src/Foo.svench, then to Svench's routes.js)
|
||||||
|
buildDelay: 100,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
lib: () => ({
|
||||||
|
input: "src/index.js",
|
||||||
|
output: [{ file: "dist/bundle.mjs", format: "es" }],
|
||||||
|
plugins: [
|
||||||
|
svelte({
|
||||||
|
dev: !PRODUCTION,
|
||||||
|
extensions: [".svelte"],
|
||||||
|
emitCss: true,
|
||||||
|
}),
|
||||||
|
postcss(),
|
||||||
|
copy({
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
src: ".svench/svench.css",
|
||||||
|
dest: "public",
|
||||||
|
rename: "global.css",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
resolve(),
|
||||||
|
commonjs(),
|
||||||
|
json(),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
export default configs[SVENCH ? "svench" : "lib"]()
|
|
@ -0,0 +1,30 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Multiselect from "../Form/Multiselect.svelte";
|
||||||
|
import DropdownMenu from "../DropdownMenu/DropdownMenu.svelte";
|
||||||
|
import Button from "../Button/Button.svelte";
|
||||||
|
import Icon from "../Icons/Icon.svelte";
|
||||||
|
|
||||||
|
const options = ["Red", "Blue", "Yellow", "Green", "Pink", "Very long color name to show text wrapping"];
|
||||||
|
|
||||||
|
let anchorLeft;
|
||||||
|
let dropdownLeft;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
### Click Outside Action
|
||||||
|
|
||||||
|
This action can be used to listen for when you want to call a function whenever a user clicks outside of the element it is applied toString.
|
||||||
|
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
import { clickOutside } from '@budibase/bbui'
|
||||||
|
const someFunction = () => {
|
||||||
|
// Some logic to close a dropdown or stop the user from doing something without saving?
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button use:clickOutside={someFunction} class="multiselect" bind:this={anchor}>
|
||||||
|
Clicking this opens the
|
||||||
|
</button>
|
||||||
|
```
|
|
@ -0,0 +1,81 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Multiselect from "../Form/Multiselect.svelte";
|
||||||
|
import DropdownMenu from "../DropdownMenu/DropdownMenu.svelte";
|
||||||
|
import Button from "../Button/Button.svelte";
|
||||||
|
import Icon from "../Icons/Icon.svelte";
|
||||||
|
|
||||||
|
const options = ["Red", "Blue", "Yellow", "Green", "Pink", "Very long color name to show text wrapping"];
|
||||||
|
|
||||||
|
let anchorLeft;
|
||||||
|
let dropdownLeft;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
### Position Dropdown Action
|
||||||
|
|
||||||
|
This action positions an element close to it's anchor, either above or below it, depending on the amount of space that exists. There's also an option to align the dropdown to the right instead of the left of the anchor. An example of how to use it follows:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
let visible = false;
|
||||||
|
let anchor;
|
||||||
|
let align = 'right';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button on:click={() => visible = !visible} class="multiselect" bind:this={anchor}>
|
||||||
|
Clicking this opens the
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if visible}
|
||||||
|
<Portal>
|
||||||
|
<div use:positionDropdown={{anchor, align}}>
|
||||||
|
Some content here.
|
||||||
|
</div>
|
||||||
|
</Portal>
|
||||||
|
{/if}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here are some components that currently use this action:
|
||||||
|
|
||||||
|
<View name="Multiselect Example">
|
||||||
|
<Multiselect name="Test" label="Colours" placeholder="Choose some colours">
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</Multiselect>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Dropdown on close event example">
|
||||||
|
<div bind:this={anchorLeft}>
|
||||||
|
<Button primary on:click={dropdownLeft.show}>
|
||||||
|
Field Name
|
||||||
|
<Icon name="arrowdown" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<DropdownMenu
|
||||||
|
on:close={() => alert('Closed!')}
|
||||||
|
bind:this={dropdownLeft}
|
||||||
|
width="175px"
|
||||||
|
borderColor="#d1d1d1ff"
|
||||||
|
anchor={anchorLeft}
|
||||||
|
align="left">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<Icon name="edit" />
|
||||||
|
Edit
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Icon name="delete" />
|
||||||
|
Delete
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Icon name="sortascending" />
|
||||||
|
Sort A - Z
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Icon name="sortdescending" />
|
||||||
|
Sort Z - A
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</DropdownMenu>
|
||||||
|
</View>
|
|
@ -0,0 +1,14 @@
|
||||||
|
function resize({ target }) {
|
||||||
|
target.style.height = "1px"
|
||||||
|
target.style.height = +target.scrollHeight + "px"
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function text_area_resize(el) {
|
||||||
|
resize({ target: el })
|
||||||
|
el.style.overflow = "hidden"
|
||||||
|
el.addEventListener("input", resize)
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy: () => el.removeEventListener("input", resize),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
export default function clickOutside(element, callbackFunction) {
|
||||||
|
function onClick(event) {
|
||||||
|
if (!element.contains(event.target)) {
|
||||||
|
callbackFunction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.addEventListener("click", onClick, true)
|
||||||
|
|
||||||
|
return {
|
||||||
|
update(newCallbackFunction) {
|
||||||
|
callbackFunction = newCallbackFunction
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
document.body.removeEventListener("click", onClick, true)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
export default function positionDropdown(element, { anchor, align }) {
|
||||||
|
let positionSide = "top"
|
||||||
|
let maxHeight = 0
|
||||||
|
let dimensions = getDimensions(anchor)
|
||||||
|
|
||||||
|
function getDimensions() {
|
||||||
|
const {
|
||||||
|
bottom,
|
||||||
|
top: spaceAbove,
|
||||||
|
left,
|
||||||
|
width,
|
||||||
|
} = anchor.getBoundingClientRect()
|
||||||
|
const spaceBelow = window.innerHeight - bottom
|
||||||
|
const containerRect = element.getBoundingClientRect()
|
||||||
|
|
||||||
|
let y
|
||||||
|
|
||||||
|
if (spaceAbove > spaceBelow) {
|
||||||
|
positionSide = "bottom"
|
||||||
|
maxHeight = spaceAbove - 20
|
||||||
|
y = window.innerHeight - spaceAbove
|
||||||
|
} else {
|
||||||
|
positionSide = "top"
|
||||||
|
y = bottom
|
||||||
|
maxHeight = spaceBelow - 20
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
[positionSide]: y,
|
||||||
|
left,
|
||||||
|
width,
|
||||||
|
containerWidth: containerRect.width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcLeftPosition() {
|
||||||
|
return align === "right"
|
||||||
|
? dimensions.left + dimensions.width - dimensions.containerWidth
|
||||||
|
: dimensions.left
|
||||||
|
}
|
||||||
|
|
||||||
|
element.style.position = "absolute"
|
||||||
|
element.style.zIndex = "9999"
|
||||||
|
element.style.minWidth = `${dimensions.width}px`
|
||||||
|
element.style.maxHeight = `${maxHeight.toFixed(0)}px`
|
||||||
|
element.style.transformOrigin = `center ${positionSide}`
|
||||||
|
element.style[positionSide] = `${dimensions[positionSide]}px`
|
||||||
|
element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px`
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(entries => {
|
||||||
|
entries.forEach(() => {
|
||||||
|
dimensions = getDimensions()
|
||||||
|
element.style[positionSide] = `${dimensions[positionSide]}px`
|
||||||
|
element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
resizeObserver.observe(anchor)
|
||||||
|
resizeObserver.observe(element)
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
resizeObserver.disconnect()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,212 @@
|
||||||
|
<script>
|
||||||
|
export let primary = false,
|
||||||
|
secondary = false,
|
||||||
|
blue = false,
|
||||||
|
disabled = false,
|
||||||
|
translucent = false,
|
||||||
|
text = false,
|
||||||
|
red = false,
|
||||||
|
yellow = false,
|
||||||
|
orange = false,
|
||||||
|
green = false,
|
||||||
|
purple = false,
|
||||||
|
small = false,
|
||||||
|
medium = false,
|
||||||
|
wide = false,
|
||||||
|
large = false,
|
||||||
|
href = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if href}
|
||||||
|
<a
|
||||||
|
{href}
|
||||||
|
class:primary
|
||||||
|
class:secondary
|
||||||
|
class:translucent
|
||||||
|
class:blue
|
||||||
|
class:red
|
||||||
|
class:yellow
|
||||||
|
class:orange
|
||||||
|
class:green
|
||||||
|
class:purple
|
||||||
|
class:small
|
||||||
|
class:medium
|
||||||
|
class:wide
|
||||||
|
class:large
|
||||||
|
class:text
|
||||||
|
{disabled}>
|
||||||
|
<slot />
|
||||||
|
</a>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
class:primary
|
||||||
|
class:secondary
|
||||||
|
class:translucent
|
||||||
|
class:blue
|
||||||
|
class:red
|
||||||
|
class:yellow
|
||||||
|
class:orange
|
||||||
|
class:green
|
||||||
|
class:purple
|
||||||
|
class:small
|
||||||
|
class:medium
|
||||||
|
class:wide
|
||||||
|
class:large
|
||||||
|
class:text
|
||||||
|
{disabled}
|
||||||
|
on:click|preventDefault>
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
button,
|
||||||
|
a {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
color: white;
|
||||||
|
padding: var(--spacing-s) var(--spacing-l);
|
||||||
|
transition: all 0.2s ease 0s;
|
||||||
|
display: inline-flex;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
text-decoration: none;
|
||||||
|
min-width: auto;
|
||||||
|
outline: none;
|
||||||
|
font-feature-settings: "case" 1, "rlig" 1, "calt" 0;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
user-select: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0;
|
||||||
|
border-width: 2px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary {
|
||||||
|
color: var(--background);
|
||||||
|
border-color: var(--ink);
|
||||||
|
background-color: var(--ink);
|
||||||
|
}
|
||||||
|
button.primary:hover:not([disabled]) {
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
.secondary {
|
||||||
|
border-color: var(--grey-4);
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--grey-8);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
button.secondary:hover:not([disabled]) {
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
.translucent {
|
||||||
|
border-color: rgba(0, 0, 0, 0.1);
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
color: var(--grey-6);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
button.translucent:hover:not([disabled]) {
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
.blue {
|
||||||
|
background-color: var(--blue);
|
||||||
|
border-color: var(--blue);
|
||||||
|
}
|
||||||
|
button.blue:hover:not([disabled]) {
|
||||||
|
background-color: var(--blue-light);
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
.red {
|
||||||
|
border-color: var(--red);
|
||||||
|
background-color: var(--red);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
button.red:hover:not([disabled]) {
|
||||||
|
background-color: var(--red-light);
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
.yellow {
|
||||||
|
border-color: var(--yellow);
|
||||||
|
background-color: var(--yellow);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
button.yellow:hover:not([disabled]) {
|
||||||
|
background-color: var(--yellow-light);
|
||||||
|
color: var(--yellow);
|
||||||
|
}
|
||||||
|
.orange {
|
||||||
|
border-color: var(--orange);
|
||||||
|
background-color: var(--orange);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
button.orange:hover:not([disabled]) {
|
||||||
|
background-color: var(--orange-light);
|
||||||
|
color: var(--orange);
|
||||||
|
}
|
||||||
|
.green {
|
||||||
|
border-color: var(--green);
|
||||||
|
background-color: var(--green);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
button.green:hover:not([disabled]) {
|
||||||
|
background-color: var(--green-light);
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
.purple {
|
||||||
|
border-color: var(--purple);
|
||||||
|
background-color: var(--purple);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
button.purple:hover:not([disabled]) {
|
||||||
|
background-color: var(--purple-light);
|
||||||
|
color: var(--purple);
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--grey-7);
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
button.text:hover:not([disabled]) {
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
button.text:active:not([disabled]) {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
padding: var(--spacing-xs) var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.medium {
|
||||||
|
font-size: var(--font-size-m);
|
||||||
|
padding: var(--spacing-s) var(--spacing-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
.large {
|
||||||
|
font-size: var(--font-size-l);
|
||||||
|
padding: var(--spacing-m) var(--layout-l);
|
||||||
|
}
|
||||||
|
.wide {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
background-color: var(--grey-4);
|
||||||
|
cursor: not-allowed;
|
||||||
|
border-color: var(--grey-4);
|
||||||
|
color: var(--grey-5);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,138 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Button from "./Button.svelte";
|
||||||
|
import Icon from "../Icons/Icon.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<View name="Primary">
|
||||||
|
<Button primary small on:click={() => alert('Clicked!')}>Small</Button>
|
||||||
|
<Button primary on:click={() => alert('Clicked!')}>Default</Button>
|
||||||
|
<Button primary medium on:click={() => alert('Clicked!')}>Medium</Button>
|
||||||
|
<Button primary large on:click={() => alert('Clicked!')}>Large</Button>
|
||||||
|
<Button primary wide on:click={() => alert('Clicked!')}>Wide</Button>
|
||||||
|
</View>
|
||||||
|
<View name="Secondary">
|
||||||
|
<Button secondary small on:click={() => alert('Clicked!')}>Small</Button>
|
||||||
|
<Button secondary on:click={() => alert('Clicked!')}>Default</Button>
|
||||||
|
<Button secondary medium on:click={() => alert('Clicked!')}>Medium</Button>
|
||||||
|
<Button secondary large on:click={() => alert('Clicked!')}>Large</Button>
|
||||||
|
<Button secondary wide on:click={() => alert('Clicked!')}>Wide</Button>
|
||||||
|
</View>
|
||||||
|
<View name="Translucent">
|
||||||
|
<div class="translucent">
|
||||||
|
<div class="light">
|
||||||
|
<Button translucent small on:click={() => alert('Clicked!')}>Small</Button>
|
||||||
|
<Button translucent on:click={() => alert('Clicked!')}>Default</Button>
|
||||||
|
<Button translucent medium on:click={() => alert('Clicked!')}>Medium</Button>
|
||||||
|
<Button translucent large on:click={() => alert('Clicked!')}>Large</Button>
|
||||||
|
<Button translucent wide on:click={() => alert('Clicked!')}>Wide</Button>
|
||||||
|
</div>
|
||||||
|
<div class="dark">
|
||||||
|
<Button translucent small on:click={() => alert('Clicked!')}>Small</Button>
|
||||||
|
<Button translucent on:click={() => alert('Clicked!')}>Default</Button>
|
||||||
|
<Button translucent medium on:click={() => alert('Clicked!')}>Medium</Button>
|
||||||
|
<Button translucent large on:click={() => alert('Clicked!')}>Large</Button>
|
||||||
|
<Button translucent wide on:click={() => alert('Clicked!')}>Wide</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</View>
|
||||||
|
<View name="Blue">
|
||||||
|
<Button blue small on:click={() => alert('Clicked!')}>Small</Button>
|
||||||
|
<Button blue on:click={() => alert('Clicked!')}>Default</Button>
|
||||||
|
<Button blue medium on:click={() => alert('Clicked!')}>Medium</Button>
|
||||||
|
<Button blue large on:click={() => alert('Clicked!')}>Large</Button>
|
||||||
|
<Button blue wide on:click={() => alert('Clicked!')}>Wide</Button>
|
||||||
|
</View>
|
||||||
|
<View name="Red">
|
||||||
|
<Button red small on:click={() => alert('Clicked!')}>Small</Button>
|
||||||
|
<Button red on:click={() => alert('Clicked!')}>Default</Button>
|
||||||
|
<Button red medium on:click={() => alert('Clicked!')}>Medium</Button>
|
||||||
|
<Button red large on:click={() => alert('Clicked!')}>Large</Button>
|
||||||
|
<Button red wide on:click={() => alert('Clicked!')}>Wide</Button>
|
||||||
|
</View>
|
||||||
|
<View name="Yellow">
|
||||||
|
<Button yellow small on:click={() => alert('Clicked!')}>Small</Button>
|
||||||
|
<Button yellow on:click={() => alert('Clicked!')}>Default</Button>
|
||||||
|
<Button yellow medium on:click={() => alert('Clicked!')}>Medium</Button>
|
||||||
|
<Button yellow large on:click={() => alert('Clicked!')}>Large</Button>
|
||||||
|
<Button yellow wide on:click={() => alert('Clicked!')}>Wide</Button>
|
||||||
|
</View>
|
||||||
|
<View name="Orange">
|
||||||
|
<Button orange small on:click={() => alert('Clicked!')}>Small</Button>
|
||||||
|
<Button orange on:click={() => alert('Clicked!')}>Default</Button>
|
||||||
|
<Button orange medium on:click={() => alert('Clicked!')}>Medium</Button>
|
||||||
|
<Button orange large on:click={() => alert('Clicked!')}>Large</Button>
|
||||||
|
<Button orange wide on:click={() => alert('Clicked!')}>Wide</Button>
|
||||||
|
</View>
|
||||||
|
<View name="Green">
|
||||||
|
<Button green small on:click={() => alert('Clicked!')}>Small</Button>
|
||||||
|
<Button green on:click={() => alert('Clicked!')}>Default</Button>
|
||||||
|
<Button green medium on:click={() => alert('Clicked!')}>Medium</Button>
|
||||||
|
<Button green large on:click={() => alert('Clicked!')}>Large</Button>
|
||||||
|
<Button green wide on:click={() => alert('Clicked!')}>Wide</Button>
|
||||||
|
</View>
|
||||||
|
<View name="Purple">
|
||||||
|
<Button purple small on:click={() => alert('Clicked!')}>Small</Button>
|
||||||
|
<Button purple on:click={() => alert('Clicked!')}>Default</Button>
|
||||||
|
<Button purple medium on:click={() => alert('Clicked!')}>Medium</Button>
|
||||||
|
<Button purple large on:click={() => alert('Clicked!')}>Large</Button>
|
||||||
|
<Button purple wide on:click={() => alert('Clicked!')}>Wide</Button>
|
||||||
|
</View>
|
||||||
|
<View name="Disabled">
|
||||||
|
<Button primary disabled on:click={() => alert('Clicked!')}>Disabled</Button>
|
||||||
|
</View>
|
||||||
|
<View name="Button With Icon">
|
||||||
|
<Button primary small on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="addrow" direction="n" />
|
||||||
|
Small
|
||||||
|
</Button>
|
||||||
|
<Button primary on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="add" direction="n" />
|
||||||
|
Default
|
||||||
|
</Button>
|
||||||
|
<Button primary medium on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="addcolumn" direction="n" />
|
||||||
|
Medium
|
||||||
|
</Button>
|
||||||
|
<Button primary large on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="copy" direction="n" />
|
||||||
|
Large
|
||||||
|
</Button>
|
||||||
|
<Button primary wide on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="view" direction="n" />
|
||||||
|
Wide
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
<View name="Button with symbols">
|
||||||
|
<Button primary small on:click={() => alert('Clicked!')}>⊗ Small</Button>
|
||||||
|
<Button primary on:click={() => alert('Clicked!')}>↓ Default</Button>
|
||||||
|
<Button primary medium on:click={() => alert('Clicked!')}>+ Medium</Button>
|
||||||
|
<Button primary large on:click={() => alert('Clicked!')}>⋯ Large</Button>
|
||||||
|
<Button primary wide on:click={() => alert('Clicked!')}>⊕ Wide</Button>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
name="use Knobs"
|
||||||
|
knobs={{ primary: true, secondary: false, blue: false, red: false, yellow: false, orange: false, green: false, purple: false, disabled: false, small: false, medium: false, large: false, wide: false, text: false }}
|
||||||
|
let:knobs>
|
||||||
|
<Button {...knobs} on:click={() => alert('Clicked!')}>Knooby</Button>
|
||||||
|
</View>
|
||||||
|
<View name="a link that looks like a button">
|
||||||
|
<Button primary href="https://google.com">Linkbutton</Button>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.translucent {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.translucent > div {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.light {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
.dark {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,59 @@
|
||||||
|
<script>
|
||||||
|
export let onClose
|
||||||
|
export let dark = false
|
||||||
|
export let small = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button class:small class:dark on:click={onClose}>×</button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
button {
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
font-size: var(--font-size-l);
|
||||||
|
z-index: 1000;
|
||||||
|
top: var(--spacing-l);
|
||||||
|
right: var(--spacing-l);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
border: 0;
|
||||||
|
color: black;
|
||||||
|
border-radius: var(--border-radius-xl);
|
||||||
|
background: white;
|
||||||
|
transition: transform 0.2s cubic-bezier(0.25, 0.1, 0.25, 1),
|
||||||
|
background 0.2s cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||||
|
-webkit-appearance: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
background-color: var(--grey-4);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.small {
|
||||||
|
font-size: var(--font-size-m);
|
||||||
|
line-height: 110%;
|
||||||
|
width: 1.3rem;
|
||||||
|
height: 1.3rem;
|
||||||
|
}
|
||||||
|
.dark {
|
||||||
|
color: white;
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
.dark:hover {
|
||||||
|
background-color: var(--grey-8);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.dark:active {
|
||||||
|
background-color: var(--grey-9);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Close from "./Close.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
min-width: 400px;
|
||||||
|
height: 200px;
|
||||||
|
border: var(--border-dark);
|
||||||
|
border-radius: var(--border-radius-l);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<View name="default">
|
||||||
|
<div>
|
||||||
|
<Close />
|
||||||
|
</div>
|
||||||
|
</View>
|
||||||
|
<View name="dark color">
|
||||||
|
<div>
|
||||||
|
<Close dark />
|
||||||
|
</div>
|
||||||
|
</View>
|
||||||
|
<View name="small">
|
||||||
|
<div>
|
||||||
|
<Close small />
|
||||||
|
</div>
|
||||||
|
</View>
|
||||||
|
<View name="small dark">
|
||||||
|
<div>
|
||||||
|
<Close small dark />
|
||||||
|
</div>
|
||||||
|
</View>
|
|
@ -0,0 +1,128 @@
|
||||||
|
<script>
|
||||||
|
export let active = false,
|
||||||
|
text = false,
|
||||||
|
small = false,
|
||||||
|
medium = false,
|
||||||
|
large = false,
|
||||||
|
blue = false,
|
||||||
|
green = false,
|
||||||
|
yellow = false,
|
||||||
|
purple = false,
|
||||||
|
red = false,
|
||||||
|
orange = false,
|
||||||
|
disabled = false,
|
||||||
|
href = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if href}
|
||||||
|
<a
|
||||||
|
{href}
|
||||||
|
class:active
|
||||||
|
class:small
|
||||||
|
class:medium
|
||||||
|
class:large
|
||||||
|
class:text
|
||||||
|
class:blue
|
||||||
|
class:green
|
||||||
|
class:yellow
|
||||||
|
class:purple
|
||||||
|
class:red
|
||||||
|
class:orange><slot /></a>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
class:active
|
||||||
|
class:small
|
||||||
|
class:medium
|
||||||
|
class:large
|
||||||
|
class:text
|
||||||
|
class:blue
|
||||||
|
class:green
|
||||||
|
class:yellow
|
||||||
|
class:purple
|
||||||
|
class:red
|
||||||
|
class:orange
|
||||||
|
{disabled}
|
||||||
|
on:click|preventDefault>
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
button,
|
||||||
|
a {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.08s ease 0s;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
text-decoration: none;
|
||||||
|
outline: none;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
user-select: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--grey-7);
|
||||||
|
border: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
button.text:hover:not([disabled]) {
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
button.text:active:not([disabled]) {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
button.text.active:not([disabled]) {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
button.text:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: var(--grey-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple {
|
||||||
|
color: var(--purple);
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellow {
|
||||||
|
color: var(--yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.orange {
|
||||||
|
color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.medium {
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.large {
|
||||||
|
font-size: var(--font-size-m);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,69 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import TextButton from "./TextButton.svelte";
|
||||||
|
import Icon from "../Icons/Icon.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<View name="Text">
|
||||||
|
<div>
|
||||||
|
<TextButton text small on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="view" />
|
||||||
|
Add View
|
||||||
|
</TextButton>
|
||||||
|
<TextButton text medium on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="addcolumn" />
|
||||||
|
Add Column
|
||||||
|
</TextButton>
|
||||||
|
<TextButton text large on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="addrow" />
|
||||||
|
Add Row
|
||||||
|
</TextButton>
|
||||||
|
<TextButton text disabled on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="arrow" direction="w" />
|
||||||
|
Disabled Text Button
|
||||||
|
</TextButton>
|
||||||
|
<TextButton active text on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="calculate" />
|
||||||
|
Active Calculation
|
||||||
|
</TextButton>
|
||||||
|
</div>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Colours">
|
||||||
|
<div>
|
||||||
|
<TextButton text medium yellow on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="view" />
|
||||||
|
Add View
|
||||||
|
</TextButton>
|
||||||
|
<TextButton text medium blue on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="addcolumn" />
|
||||||
|
Add Column
|
||||||
|
</TextButton>
|
||||||
|
<TextButton text medium purple on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="addrow" />
|
||||||
|
Add Row
|
||||||
|
</TextButton>
|
||||||
|
<TextButton text medium red on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="arrow" />
|
||||||
|
Delete
|
||||||
|
</TextButton>
|
||||||
|
<TextButton text medium green on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="calculate" />
|
||||||
|
Calculate
|
||||||
|
</TextButton>
|
||||||
|
</div>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Usage as a link">
|
||||||
|
<div>
|
||||||
|
<TextButton green text href="https://google.com">This is a link</TextButton>
|
||||||
|
</div>
|
||||||
|
</View>
|
|
@ -0,0 +1,45 @@
|
||||||
|
<script>
|
||||||
|
import Flatpickr from "svelte-flatpickr"
|
||||||
|
import { Label, Input } from "../"
|
||||||
|
import "flatpickr/dist/flatpickr.css"
|
||||||
|
|
||||||
|
const PICKER_OPTIONS = {
|
||||||
|
enableTime: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
export let label
|
||||||
|
export let placeholder
|
||||||
|
export let value
|
||||||
|
export let thin = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class:thin>
|
||||||
|
{#if label}
|
||||||
|
<Label extraSmall grey>{label}</Label>
|
||||||
|
{/if}
|
||||||
|
<Flatpickr {placeholder} options={PICKER_OPTIONS} on:change bind:value />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(.flatpickr-input) {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: var(--ink);
|
||||||
|
border-radius: 5px;
|
||||||
|
border: none;
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
margin: 0;
|
||||||
|
outline: none;
|
||||||
|
border: var(--border-transparent);
|
||||||
|
}
|
||||||
|
:global(.flatpickr-input:focus) {
|
||||||
|
border: var(--border-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.thin :global(.flatpickr-input) {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import DatePicker from "./DatePicker.svelte";
|
||||||
|
|
||||||
|
function handleChange(event) {
|
||||||
|
const [fullDate, shortDate, instance] = event.detail
|
||||||
|
alert("Date is " + fullDate)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<View name="default">
|
||||||
|
<DatePicker on:change={handleChange} label="Start Date" placeholder="Pick a date" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="thin">
|
||||||
|
<DatePicker on:change={handleChange} label="Start Date" thin placeholder="Pick a date" />
|
||||||
|
</View>
|
|
@ -0,0 +1,88 @@
|
||||||
|
<script>
|
||||||
|
import { slide } from "svelte/transition"
|
||||||
|
import Portal from "svelte-portal"
|
||||||
|
import clickOutside from "../Actions/click_outside"
|
||||||
|
|
||||||
|
export let title
|
||||||
|
|
||||||
|
let visible = false
|
||||||
|
|
||||||
|
export function show() {
|
||||||
|
if (visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hide() {
|
||||||
|
if (!visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKey(e) {
|
||||||
|
if (visible && e.key === "Escape") {
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={handleKey} />
|
||||||
|
|
||||||
|
{#if visible}
|
||||||
|
<Portal>
|
||||||
|
<section class="drawer" transition:slide>
|
||||||
|
<header>
|
||||||
|
<div class="text">
|
||||||
|
<div class="title">{title}</div>
|
||||||
|
<slot name="description" />
|
||||||
|
</div>
|
||||||
|
<div class="controls">
|
||||||
|
<slot name="buttons" />
|
||||||
|
<i class="ri-close-fill close" on:click={hide} />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<slot name="body" />
|
||||||
|
</section>
|
||||||
|
</Portal>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.drawer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 260px;
|
||||||
|
width: calc(100% - 520px);
|
||||||
|
background: var(--background);
|
||||||
|
border: var(--border-light);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: var(--border-light);
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: grid;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
grid-gap: var(--spacing-m);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,42 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Button from "../Button/Button.svelte";
|
||||||
|
import Drawer from "./Drawer.svelte";
|
||||||
|
|
||||||
|
let drawer1;
|
||||||
|
let drawer2;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.drawer {
|
||||||
|
height: 40vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<View name="Basic Bottom Drawer">
|
||||||
|
<Button primary on:click={drawer1.show}>Open Drawer</Button>
|
||||||
|
<Drawer
|
||||||
|
bind:this={drawer1}
|
||||||
|
title="A basic Drawer with some text"
|
||||||
|
on:close={() => alert('You closed the drawer!')}>
|
||||||
|
<p slot="body">
|
||||||
|
Lorem ipsum dolor sit amet consectetur adipisicing elit. Placeat,
|
||||||
|
architecto assumenda! Quia harum hic numquam, soluta maiores facere
|
||||||
|
explicabo vero obcaecati voluptas, qui placeat ad, dolorem recusandae
|
||||||
|
labore quos? Nisi!
|
||||||
|
</p>
|
||||||
|
</Drawer>
|
||||||
|
</View>
|
||||||
|
<View name="Basic Bottom Drawer">
|
||||||
|
<Button blue on:click={drawer2.show}>Open Drawer</Button>
|
||||||
|
<Drawer
|
||||||
|
bind:this={drawer2}
|
||||||
|
title={'Actions'}
|
||||||
|
on:close={() => alert('You closed the drawer!')}>
|
||||||
|
<heading slot="buttons">
|
||||||
|
<Button thin blue on:click={drawer2.hide}>Save</Button>
|
||||||
|
</heading>
|
||||||
|
<div slot="description">This describes the drawer!</div>
|
||||||
|
<div class="drawer" slot="body">Some content here</div>
|
||||||
|
</Drawer>
|
||||||
|
</View>
|
|
@ -0,0 +1,77 @@
|
||||||
|
<script>
|
||||||
|
import Portal from "svelte-portal"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import buildStyle from "../utils/buildStyle"
|
||||||
|
import positionDropdown from "../Actions/position_dropdown"
|
||||||
|
import clickOutside from "../Actions/click_outside"
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export let anchor
|
||||||
|
export let align = "right"
|
||||||
|
export let borderColor = ""
|
||||||
|
|
||||||
|
export const show = () => {
|
||||||
|
dispatch("open")
|
||||||
|
open = true
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hide = () => {
|
||||||
|
dispatch("close")
|
||||||
|
open = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let open = null
|
||||||
|
|
||||||
|
function handleEscape(e) {
|
||||||
|
if (open && e.key === "Escape") {
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: menuStyle = buildStyle({
|
||||||
|
borderColor,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if open}
|
||||||
|
<Portal>
|
||||||
|
<div
|
||||||
|
tabindex="0"
|
||||||
|
class:open
|
||||||
|
use:positionDropdown={{ anchor, align }}
|
||||||
|
use:clickOutside={hide}
|
||||||
|
style={menuStyle}
|
||||||
|
on:keydown={handleEscape}
|
||||||
|
class="menu-container">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</Portal>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.menu-container {
|
||||||
|
position: fixed;
|
||||||
|
margin-top: var(--spacing-xs);
|
||||||
|
outline: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
opacity: 0;
|
||||||
|
min-width: 200px;
|
||||||
|
z-index: 2;
|
||||||
|
color: var(--ink);
|
||||||
|
font-weight: 400;
|
||||||
|
height: fit-content !important;
|
||||||
|
border: var(--border-dark);
|
||||||
|
border-radius: var(--border-radius-m);
|
||||||
|
transform: scale(0);
|
||||||
|
transition: opacity 0.13s linear, transform 0.12s cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
overflow-y: auto;
|
||||||
|
background: var(--background);
|
||||||
|
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.open {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,161 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import DropdownMenu from "./DropdownMenu.svelte";
|
||||||
|
import Button from "../Button/Button.svelte";
|
||||||
|
import Icon from "../Icons/Icon.svelte";
|
||||||
|
|
||||||
|
let anchorRight;
|
||||||
|
let anchorLeft;
|
||||||
|
let dropdownRight;
|
||||||
|
let dropdownLeft;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: var(--spacing-s) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
color: var(--ink);
|
||||||
|
padding: var(--spacing-s) var(--spacing-m);
|
||||||
|
margin: auto 0px;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:hover {
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
li:active {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<View name="Right Align (default)">
|
||||||
|
<div bind:this={anchorRight}>
|
||||||
|
<Button primary on:click={dropdownRight.show}>Right Align</Button>
|
||||||
|
</div>
|
||||||
|
<DropdownMenu bind:this={dropdownRight} anchor={anchorRight}>
|
||||||
|
<ul>
|
||||||
|
<li>Item 1</li>
|
||||||
|
<li>Item 2</li>
|
||||||
|
<li>Item 3</li>
|
||||||
|
</ul>
|
||||||
|
</DropdownMenu>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Left Align">
|
||||||
|
<div bind:this={anchorLeft}>
|
||||||
|
<Button primary on:click={dropdownLeft.show}>Left Align</Button>
|
||||||
|
</div>
|
||||||
|
<DropdownMenu bind:this={dropdownLeft} anchor={anchorLeft} align="left">
|
||||||
|
<ul>
|
||||||
|
<li>Item 1</li>
|
||||||
|
<li>Item 2</li>
|
||||||
|
<li>Item 3</li>
|
||||||
|
</ul>
|
||||||
|
</DropdownMenu>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Left Align, TextButton, Small, Icons">
|
||||||
|
<div bind:this={anchorLeft}>
|
||||||
|
<Button text on:click={dropdownLeft.show}>
|
||||||
|
Field Name
|
||||||
|
<Icon name="arrowdown" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<DropdownMenu bind:this={dropdownLeft} anchor={anchorLeft} align="left">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<Icon name="edit" />
|
||||||
|
Edit
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Icon name="delete" />
|
||||||
|
Delete
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Icon name="sortascending" />
|
||||||
|
Sort A - Z
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Icon name="sortdescending" />
|
||||||
|
Sort Z - A
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</DropdownMenu>
|
||||||
|
</View>
|
||||||
|
<View name="Dropdown menu with slim menu and border color">
|
||||||
|
<div bind:this={anchorLeft}>
|
||||||
|
<Button primary on:click={dropdownLeft.show}>
|
||||||
|
Field Name
|
||||||
|
<Icon name="arrowdown" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<DropdownMenu
|
||||||
|
bind:this={dropdownLeft}
|
||||||
|
width="175px"
|
||||||
|
borderColor="#d1d1d1ff"
|
||||||
|
anchor={anchorLeft}
|
||||||
|
align="left">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<Icon name="edit" />
|
||||||
|
Edit
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Icon name="delete" />
|
||||||
|
Delete
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Icon name="sortascending" />
|
||||||
|
Sort A - Z
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Icon name="sortdescending" />
|
||||||
|
Sort Z - A
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</DropdownMenu>
|
||||||
|
</View>
|
||||||
|
<View name="Dropdown on close event example">
|
||||||
|
<div bind:this={anchorLeft}>
|
||||||
|
<Button primary on:click={dropdownLeft.show}>
|
||||||
|
Field Name
|
||||||
|
<Icon name="arrowdown" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<DropdownMenu
|
||||||
|
on:close={() => alert('Closed!')}
|
||||||
|
bind:this={dropdownLeft}
|
||||||
|
width="175px"
|
||||||
|
borderColor="#d1d1d1ff"
|
||||||
|
anchor={anchorLeft}
|
||||||
|
align="left">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<Icon name="edit" />
|
||||||
|
Edit
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Icon name="delete" />
|
||||||
|
Delete
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Icon name="sortascending" />
|
||||||
|
Sort A - Z
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Icon name="sortdescending" />
|
||||||
|
Sort Z - A
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</DropdownMenu>
|
||||||
|
</View>
|
|
@ -0,0 +1,295 @@
|
||||||
|
<script>
|
||||||
|
import { Heading, Body, Button } from "../"
|
||||||
|
import { FILE_TYPES } from "./fileTypes"
|
||||||
|
|
||||||
|
const BYTES_IN_KB = 1000
|
||||||
|
const BYTES_IN_MB = 1000000
|
||||||
|
|
||||||
|
export let icons = {
|
||||||
|
image: "fas fa-file-image",
|
||||||
|
code: "fas fa-file-code",
|
||||||
|
file: "fas fa-file",
|
||||||
|
fileUpload: "fas fa-upload",
|
||||||
|
}
|
||||||
|
|
||||||
|
export let files = []
|
||||||
|
export let fileSizeLimit = BYTES_IN_MB * 20
|
||||||
|
export let processFiles
|
||||||
|
export let handleFileTooLarge
|
||||||
|
|
||||||
|
let selectedImageIdx = 0
|
||||||
|
let fileDragged = false
|
||||||
|
// Generate a random ID so that multiple dropzones on the page don't conflict
|
||||||
|
let id = Math.random()
|
||||||
|
.toString(36)
|
||||||
|
.substring(7)
|
||||||
|
|
||||||
|
$: selectedImage = files ? files[selectedImageIdx] : null
|
||||||
|
|
||||||
|
function determineFileIcon(extension) {
|
||||||
|
const ext = extension.toLowerCase()
|
||||||
|
|
||||||
|
if (FILE_TYPES.IMAGE.includes(ext)) return icons.image
|
||||||
|
if (FILE_TYPES.CODE.includes(ext)) return icons.code
|
||||||
|
|
||||||
|
return icons.file
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processFileList(fileList) {
|
||||||
|
if (Array.from(fileList).some(file => file.size >= fileSizeLimit)) {
|
||||||
|
handleFileTooLarge(fileSizeLimit, file)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const processedFiles = await processFiles(fileList)
|
||||||
|
|
||||||
|
files = [...processedFiles, ...files]
|
||||||
|
selectedImageIdx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeFile() {
|
||||||
|
files.splice(selectedImageIdx, 1)
|
||||||
|
files = files
|
||||||
|
selectedImageIdx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateLeft() {
|
||||||
|
selectedImageIdx -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateRight() {
|
||||||
|
selectedImageIdx += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFile(evt) {
|
||||||
|
processFileList(evt.target.files)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragOver(evt) {
|
||||||
|
evt.preventDefault()
|
||||||
|
fileDragged = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragLeave(evt) {
|
||||||
|
evt.preventDefault()
|
||||||
|
fileDragged = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDrop(evt) {
|
||||||
|
evt.preventDefault()
|
||||||
|
processFileList(evt.dataTransfer.files)
|
||||||
|
fileDragged = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="dropzone"
|
||||||
|
on:dragover={handleDragOver}
|
||||||
|
on:dragleave={handleDragLeave}
|
||||||
|
on:dragenter={handleDragOver}
|
||||||
|
on:drop={handleDrop}
|
||||||
|
class:fileDragged>
|
||||||
|
{#if selectedImage}
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<header>
|
||||||
|
<div>
|
||||||
|
<i class={determineFileIcon(selectedImage.extension)} />
|
||||||
|
<span class="filename">{selectedImage.name}</span>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{#if selectedImage.size <= BYTES_IN_MB}
|
||||||
|
{selectedImage.size / BYTES_IN_KB}KB
|
||||||
|
{:else}{selectedImage.size / BYTES_IN_MB}MB{/if}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div class="delete-button" on:click={removeFile}>
|
||||||
|
<i class="ri-close-circle-fill" />
|
||||||
|
</div>
|
||||||
|
{#if selectedImageIdx !== 0}
|
||||||
|
<div class="nav left" on:click={navigateLeft}>
|
||||||
|
<i class="ri-arrow-left-circle-fill" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<img alt="preview" src={selectedImage.url} />
|
||||||
|
{#if selectedImageIdx !== files.length - 1}
|
||||||
|
<div class="nav right" on:click={navigateRight}>
|
||||||
|
<i class="ri-arrow-right-circle-fill" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
<i class={icons.fileUpload} />
|
||||||
|
<input {id} type="file" multiple on:change={handleFile} {...$$restProps} />
|
||||||
|
<i class="ri-upload-cloud-line" />
|
||||||
|
<p class="drop">Drop your files here</p>
|
||||||
|
<label for={id}>Select a file from your computer</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dropzone {
|
||||||
|
padding: var(--spacing-l);
|
||||||
|
border: 2px dashed var(--grey-4);
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileDragged {
|
||||||
|
border: 2px dashed var(--grey-7);
|
||||||
|
background: var(--blue-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--grey-7);
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
min-width: auto;
|
||||||
|
outline: none;
|
||||||
|
font-feature-settings: "case" 1, "rlig" 1, "calt" 0;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.nav {
|
||||||
|
padding: var(--spacing-xs);
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
bottom: var(--spacing-s);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: 0.2s transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
left: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
right: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
position: relative;
|
||||||
|
height: 300px;
|
||||||
|
background: var(--grey-7);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: 0 var(--spacing-s) 12px rgba(0, 0, 0, 0.15);
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
i:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon {
|
||||||
|
color: var(--background);
|
||||||
|
font-size: 2em;
|
||||||
|
margin-right: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--spacing-s);
|
||||||
|
list-style-type: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
position: absolute;
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgb(255, 255, 255),
|
||||||
|
rgba(255, 255, 255, 0)
|
||||||
|
);
|
||||||
|
width: 100%;
|
||||||
|
border-top-left-radius: 10px;
|
||||||
|
border-top-right-radius: 10px;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header > div {
|
||||||
|
color: var(--ink);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: var(--spacing-m);
|
||||||
|
width: 60%;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filename {
|
||||||
|
overflow: hidden;
|
||||||
|
margin-left: 5px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
header > p {
|
||||||
|
color: var(--grey-5);
|
||||||
|
margin-right: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-button {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--spacing-s);
|
||||||
|
right: var(--spacing-s);
|
||||||
|
padding: var(--spacing-s);
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-button i {
|
||||||
|
font-size: 2em;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-button:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Dropzone from "./Dropzone.svelte";
|
||||||
|
|
||||||
|
async function processFiles(files) {
|
||||||
|
console.log("Processing", files);
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFileTooLarge() {
|
||||||
|
alert("File too large.");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<View name="dropzone">
|
||||||
|
<Dropzone {processFiles} {handleFileTooLarge} />
|
||||||
|
</View>
|
|
@ -0,0 +1,5 @@
|
||||||
|
export const FILE_TYPES = {
|
||||||
|
IMAGE: ["png", "tiff", "gif", "raw", "jpg", "jpeg", "svg"],
|
||||||
|
CODE: ["js", "rs", "py", "java", "rb", "hs", "yml"],
|
||||||
|
DOCUMENT: ["odf", "docx", "doc", "pdf", "csv"],
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export let checked = false
|
||||||
|
export let value
|
||||||
|
export let name
|
||||||
|
export let disabled
|
||||||
|
|
||||||
|
function handleChange() {
|
||||||
|
if (disabled) return
|
||||||
|
checked = !checked
|
||||||
|
dispatch("change", checked)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<input
|
||||||
|
{disabled}
|
||||||
|
on:change={handleChange}
|
||||||
|
{value}
|
||||||
|
bind:checked
|
||||||
|
type="checkbox"
|
||||||
|
{name}
|
||||||
|
class="checkbox"
|
||||||
|
id={value} />
|
||||||
|
<div class="checkbox-container" on:click={handleChange}>
|
||||||
|
<div class:disabled class="check-div" class:checked>
|
||||||
|
<div class="tick_mark" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.checkbox-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-div {
|
||||||
|
position: relative;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.2s ease transform, 0.2s ease background-color,
|
||||||
|
0.2s ease box-shadow;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-div:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: var(--background);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
transition: 0.2s ease width, 0.2s ease height;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-div:active {
|
||||||
|
transform: translateY(-50%) scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tick_mark {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 6px;
|
||||||
|
width: 5px;
|
||||||
|
height: 4px;
|
||||||
|
margin: 0 auto;
|
||||||
|
transform: rotateZ(-40deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tick_mark:before,
|
||||||
|
.tick_mark:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--ink);
|
||||||
|
border-radius: 2px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: 0.2s ease transform, 0.2s ease opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tick_mark:before {
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 2px;
|
||||||
|
height: 6px;
|
||||||
|
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.23);
|
||||||
|
transform: translateY(-68px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tick_mark:after {
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.23);
|
||||||
|
transform: translateX(78px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-div.disabled:active {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checked {
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
}
|
||||||
|
.checked.disabled {
|
||||||
|
background-color: var(--grey-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checked:before {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checked .tick_mark:before,
|
||||||
|
.checked .tick_mark:after {
|
||||||
|
transform: translate(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,67 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Checkbox from "./Checkbox.svelte";
|
||||||
|
|
||||||
|
let checked = false
|
||||||
|
|
||||||
|
let menu = [
|
||||||
|
{text: 'Cookies and cream', checked: false},
|
||||||
|
{text: 'Mint choc chip', checked: false},
|
||||||
|
{text: 'Raspberry ripple', checked: true}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<View name="Single checkbox">
|
||||||
|
<Checkbox bind:checked value="value">
|
||||||
|
<label for="value">One single checkbox with text</label>
|
||||||
|
</Checkbox>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Single disabled checkbox">
|
||||||
|
<Checkbox disabled checked value="value">
|
||||||
|
<label for="someOtherValue">A disabled checkbox</label>
|
||||||
|
</Checkbox>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="No text">
|
||||||
|
<Checkbox bind:checked value="somevalue" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
## Multiple checkboxes
|
||||||
|
Use an array and an each block to use multiple checkboxes
|
||||||
|
```svelte
|
||||||
|
<script>
|
||||||
|
let menu = [
|
||||||
|
{text: 'Cookies and cream', checked: false},
|
||||||
|
{text: 'Mint choc chip', checked: false},
|
||||||
|
{text: 'Raspberry ripple', checked: true}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each menu as {text, checked}}
|
||||||
|
<Checkbox value={text} bind:checked>
|
||||||
|
<label for={text}>{text}</label>
|
||||||
|
</Checkbox>
|
||||||
|
{/each}
|
||||||
|
```
|
||||||
|
|
||||||
|
<View name="Multiple checkboxes">
|
||||||
|
<div class="container">
|
||||||
|
{#each menu as {text, checked}}
|
||||||
|
<Checkbox value={text} bind:checked>
|
||||||
|
<label for={text}>{text}</label>
|
||||||
|
</Checkbox>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
label {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,158 @@
|
||||||
|
<script>
|
||||||
|
import Icon from "../Icons/Icon.svelte"
|
||||||
|
import Label from "../Styleguide/Label.svelte"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
export let label = undefined
|
||||||
|
export let value = ""
|
||||||
|
export let name = undefined
|
||||||
|
export let thin = false
|
||||||
|
export let extraThin = false
|
||||||
|
export let secondary = false
|
||||||
|
export let outline = false
|
||||||
|
export let disabled = false
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
let focus = false
|
||||||
|
|
||||||
|
const updateValue = e => {
|
||||||
|
value = e.target.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFocus(e) {
|
||||||
|
focus = true
|
||||||
|
dispatch("focus", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBlur(e) {
|
||||||
|
focus = false
|
||||||
|
dispatch("blur", e)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if label}
|
||||||
|
<Label extraSmall grey forAttr={name}>{label}</Label>
|
||||||
|
{/if}
|
||||||
|
<div class="container" class:disabled class:secondary class:outline class:focus>
|
||||||
|
<select
|
||||||
|
{name}
|
||||||
|
class:thin
|
||||||
|
class:extraThin
|
||||||
|
class:secondary
|
||||||
|
{disabled}
|
||||||
|
on:change
|
||||||
|
on:focus={handleFocus}
|
||||||
|
on:blur={handleBlur}
|
||||||
|
bind:value>
|
||||||
|
<slot />
|
||||||
|
</select>
|
||||||
|
<slot name="custom-input" />
|
||||||
|
<input
|
||||||
|
class:thin
|
||||||
|
class:extraThin
|
||||||
|
class:secondary
|
||||||
|
class:disabled
|
||||||
|
{disabled}
|
||||||
|
on:change={updateValue}
|
||||||
|
on:input={updateValue}
|
||||||
|
on:focus={handleFocus}
|
||||||
|
on:blur={e => {
|
||||||
|
updateValue(e)
|
||||||
|
handleBlur(e)
|
||||||
|
}}
|
||||||
|
value={value || ''}
|
||||||
|
type="text" />
|
||||||
|
<div class="pointer editable-pointer">
|
||||||
|
<Icon name="arrowdown" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
position: relative !important;
|
||||||
|
display: block;
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
border: var(--border-transparent);
|
||||||
|
background-color: var(--background);
|
||||||
|
}
|
||||||
|
.container.outline {
|
||||||
|
border: var(--border-dark);
|
||||||
|
}
|
||||||
|
.container.focus {
|
||||||
|
border: var(--border-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select {
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
font-size: var(--font-size-m);
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--ink);
|
||||||
|
text-align: left;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
display: block !important;
|
||||||
|
width: 100% !important;
|
||||||
|
padding: var(--spacing-m) 2rem var(--spacing-m) var(--spacing-m);
|
||||||
|
appearance: none !important;
|
||||||
|
-webkit-appearance: none !important;
|
||||||
|
-moz-appearance: none !important;
|
||||||
|
align-items: center;
|
||||||
|
white-space: pre;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: calc(100% - 30px);
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: var(--spacing-m) 0 var(--spacing-m) var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
select.thin,
|
||||||
|
input.thin {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
}
|
||||||
|
select.extraThin,
|
||||||
|
input.extraThin {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
padding: var(--spacing-s) 0 var(--spacing-s) var(--spacing-m);
|
||||||
|
}
|
||||||
|
.secondary {
|
||||||
|
background: var(--grey-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
select:disabled,
|
||||||
|
input:disabled,
|
||||||
|
.disabled {
|
||||||
|
background: var(--grey-4);
|
||||||
|
color: var(--grey-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pointer {
|
||||||
|
right: 0 !important;
|
||||||
|
top: 0 !important;
|
||||||
|
bottom: 0 !important;
|
||||||
|
position: absolute !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
align-items: center !important;
|
||||||
|
display: flex !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editable-pointer {
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0 0 0 1px;
|
||||||
|
border-color: var(--grey-4);
|
||||||
|
padding-left: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
.editable-pointer :global(svg) {
|
||||||
|
margin-right: var(--spacing-xs);
|
||||||
|
fill: var(--ink);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,57 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Select from "./Select.svelte";
|
||||||
|
import DataList from "./DataList.svelte";
|
||||||
|
import Spacer from "../Spacer/Spacer.svelte"
|
||||||
|
|
||||||
|
const options = ["Chocolate", "Vanilla", "Strawberry Cheesecake"];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<View name="default">
|
||||||
|
<DataList name="Test" label="Flavour">
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</DataList>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="secondary">
|
||||||
|
<DataList secondary name="Test" label="Flavour">
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</DataList>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="outline">
|
||||||
|
<DataList outline name="Test" label="Flavour">
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</DataList>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="disabled">
|
||||||
|
<DataList disabled name="Test" label="Flavour">
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</DataList>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="thin">
|
||||||
|
<DataList thin name="Test" label="Flavour">
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</DataList>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="extraThin">
|
||||||
|
<DataList extraThin name="Test" label="Flavour">
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</DataList>
|
||||||
|
</View>
|
||||||
|
|
|
@ -0,0 +1,190 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import Button from "../Button/Button.svelte"
|
||||||
|
import Label from "../Styleguide/Label.svelte"
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export let name = undefined
|
||||||
|
export let label = undefined
|
||||||
|
export let outline = false
|
||||||
|
export let presentation = false
|
||||||
|
export let thin = false
|
||||||
|
export let extraThin = false
|
||||||
|
export let large = false
|
||||||
|
export let border = false
|
||||||
|
export let edit = false
|
||||||
|
export let disabled = false
|
||||||
|
export let type = undefined
|
||||||
|
export let placeholder = ""
|
||||||
|
export let value = ""
|
||||||
|
export let error = false
|
||||||
|
export let validator = () => {}
|
||||||
|
|
||||||
|
// This section handles the edit mode and dispatching of things to the parent when saved
|
||||||
|
let editMode = false
|
||||||
|
|
||||||
|
const updateValue = e => {
|
||||||
|
if (type === "number") {
|
||||||
|
const num = parseFloat(e.target.value)
|
||||||
|
value = isNaN(num) ? "" : num
|
||||||
|
} else {
|
||||||
|
value = e.target.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = () => {
|
||||||
|
editMode = false
|
||||||
|
dispatch("save", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const enableEdit = () => {
|
||||||
|
editMode = true
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{#if label || edit}
|
||||||
|
<div class="label-container">
|
||||||
|
{#if label}
|
||||||
|
<Label extraSmall grey forAttr={name}>{label}</Label>
|
||||||
|
{/if}
|
||||||
|
{#if edit}
|
||||||
|
<div class="controls">
|
||||||
|
<Button small secondary disabled={editMode} on:click={enableEdit}>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button small blue disabled={!editMode} on:click={save}>Save</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<input
|
||||||
|
class:outline
|
||||||
|
class:presentation
|
||||||
|
class:thin
|
||||||
|
class:extraThin
|
||||||
|
class:large
|
||||||
|
class:border
|
||||||
|
on:change
|
||||||
|
on:input
|
||||||
|
on:change={updateValue}
|
||||||
|
on:input={updateValue}
|
||||||
|
on:blur={updateValue}
|
||||||
|
use:validator
|
||||||
|
disabled={disabled || (edit && !editMode)}
|
||||||
|
value={value == null ? '' : value}
|
||||||
|
{type}
|
||||||
|
{name}
|
||||||
|
{placeholder} />
|
||||||
|
{#if error}
|
||||||
|
<div class="error">{error}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-bottom: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.label-container :global(label) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
align-items: center;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
grid-gap: 12px;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
.controls :global(button) {
|
||||||
|
min-width: 100px;
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
border-radius: var(--rounded-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
min-width: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
border: none;
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
margin: 0;
|
||||||
|
outline: none;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
border: var(--border-transparent);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
input.presentation {
|
||||||
|
background-color: var(--background);
|
||||||
|
border: var(--background) 2px solid;
|
||||||
|
}
|
||||||
|
input.presentation:hover {
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
border: var(--grey-4) 2px solid;
|
||||||
|
}
|
||||||
|
input.thin {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
}
|
||||||
|
input.extraThin {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
padding: var(--spacing-s) var(--spacing-m);
|
||||||
|
}
|
||||||
|
input.large {
|
||||||
|
font-size: var(--font-size-m);
|
||||||
|
padding: var(--spacing-l);
|
||||||
|
}
|
||||||
|
input.border {
|
||||||
|
border: var(--border-grey-2);
|
||||||
|
}
|
||||||
|
input.border:active {
|
||||||
|
border: var(--border-blue);
|
||||||
|
}
|
||||||
|
input.border:focus {
|
||||||
|
border: var(--border-blue);
|
||||||
|
}
|
||||||
|
input.outline {
|
||||||
|
border: var(--border-light-2);
|
||||||
|
background: var(--background);
|
||||||
|
}
|
||||||
|
input.outline:active {
|
||||||
|
border: var(--border-blue);
|
||||||
|
}
|
||||||
|
input.outline:focus {
|
||||||
|
border: var(--border-blue);
|
||||||
|
}
|
||||||
|
input:hover {
|
||||||
|
border: var(--grey-4) 2px solid;
|
||||||
|
}
|
||||||
|
input::placeholder {
|
||||||
|
color: var(--grey-6);
|
||||||
|
}
|
||||||
|
input:focus {
|
||||||
|
border: var(--border-blue);
|
||||||
|
}
|
||||||
|
input:disabled {
|
||||||
|
background: var(--grey-4);
|
||||||
|
color: var(--grey-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
line-height: 1.17;
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,62 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Input from "./Input.svelte";
|
||||||
|
import Button from "../Button/Button.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<View name="default">
|
||||||
|
<Input placeholder="Enter your name" label="Name" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="presentation">
|
||||||
|
<Input presentation placeholder="Enter your name" label="Name" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="outline">
|
||||||
|
<Input outline placeholder="Enter your name" label="Name" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="disabled">
|
||||||
|
<Input disabled placeholder="Enter your name" label="Name" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="disabled with value">
|
||||||
|
<Input value="Some text" disabled placeholder="Enter your name" label="Name" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="thin">
|
||||||
|
<Input thin placeholder="Enter your name" label="Name" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="extraThin">
|
||||||
|
<Input extraThin placeholder="Enter your name" label="Name" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="large">
|
||||||
|
<Input large placeholder="Enter your name" label="Name" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="border">
|
||||||
|
<Input border presentation placeholder="Enter your name" label="Name" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
|
||||||
|
<View name="number">
|
||||||
|
<Input type="number" placeholder="Enter your age" label="Age" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="with edit buttons">
|
||||||
|
<Input
|
||||||
|
thin
|
||||||
|
edit
|
||||||
|
placeholder="Enter your name"
|
||||||
|
label="Name"
|
||||||
|
on:save={console.log} />
|
||||||
|
</View>
|
||||||
|
<View name="with error message">
|
||||||
|
<Input
|
||||||
|
placeholder="Enter your name"
|
||||||
|
label="Name"
|
||||||
|
error="This is an error message!"
|
||||||
|
on:save={console.log} />
|
||||||
|
</View>
|
|
@ -0,0 +1,324 @@
|
||||||
|
<script>
|
||||||
|
import Portal from "svelte-portal"
|
||||||
|
import { afterUpdate } from "svelte"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { fly } from "svelte/transition"
|
||||||
|
import Label from "../Styleguide/Label.svelte"
|
||||||
|
const xPath =
|
||||||
|
"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
|
||||||
|
|
||||||
|
import positionDropdown from "../Actions/position_dropdown"
|
||||||
|
import clickOutside from "../Actions/click_outside"
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export let value = []
|
||||||
|
export let label = undefined
|
||||||
|
export let align = "left"
|
||||||
|
export let secondary = false
|
||||||
|
export let outline = false
|
||||||
|
export let disabled = false
|
||||||
|
export let placeholder = undefined
|
||||||
|
export let extraThin = false
|
||||||
|
|
||||||
|
let options = []
|
||||||
|
let optionsVisible = false
|
||||||
|
let slot
|
||||||
|
let anchor
|
||||||
|
$: lookupMap = mapValues(value)
|
||||||
|
$: selectedOptions = options.filter(option => lookupMap[option.value])
|
||||||
|
|
||||||
|
afterUpdate(() => {
|
||||||
|
// Update available options
|
||||||
|
const domOptions = Array.from(slot.querySelectorAll("option"))
|
||||||
|
options = domOptions.map(option => ({
|
||||||
|
value: option.value,
|
||||||
|
name: option.textContent,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
function mapValues(value) {
|
||||||
|
let map = {}
|
||||||
|
if (value) {
|
||||||
|
value.forEach(option => {
|
||||||
|
map[option] = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
function add(val) {
|
||||||
|
value = [...value, val]
|
||||||
|
dispatch("change", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(val) {
|
||||||
|
value = value.filter(option => option !== val)
|
||||||
|
dispatch("change", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function showOptions(show) {
|
||||||
|
optionsVisible = show
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
showOptions(!optionsVisible)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOptionMousedown(e) {
|
||||||
|
const value = e.target.dataset.value
|
||||||
|
if (value == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (lookupMap[value]) {
|
||||||
|
remove(value)
|
||||||
|
} else {
|
||||||
|
add(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if label}
|
||||||
|
<Label extraSmall grey>{label}</Label>
|
||||||
|
{/if}
|
||||||
|
<div class="multiselect" bind:this={anchor}>
|
||||||
|
<div class="tokens-wrapper">
|
||||||
|
<div
|
||||||
|
class="tokens"
|
||||||
|
class:outline
|
||||||
|
class:disabled
|
||||||
|
class:secondary
|
||||||
|
class:extraThin
|
||||||
|
class:optionsVisible
|
||||||
|
on:click|self={handleClick}
|
||||||
|
class:empty={!value || !value.length}>
|
||||||
|
{#each selectedOptions as option}
|
||||||
|
<div
|
||||||
|
class="token"
|
||||||
|
class:extraThin
|
||||||
|
data-id={option.value}
|
||||||
|
on:click|self={handleClick}>
|
||||||
|
<span>{option.name}</span>
|
||||||
|
<div
|
||||||
|
class="token-remove"
|
||||||
|
title="Remove {option.name}"
|
||||||
|
on:click={() => remove(option.value)}>
|
||||||
|
<svg
|
||||||
|
class="icon-clear"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path d={xPath} />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{#if !value || !value.length}
|
||||||
|
{#if placeholder && placeholder.length}
|
||||||
|
<div class:disabled class="placeholder">{placeholder}</div>
|
||||||
|
{:else}
|
||||||
|
<div class="placeholder"> </div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<select bind:this={slot} type="multiple" class="hidden">
|
||||||
|
<slot />
|
||||||
|
</select>
|
||||||
|
|
||||||
|
{#if optionsVisible}
|
||||||
|
<Portal>
|
||||||
|
<ul
|
||||||
|
class="options"
|
||||||
|
use:positionDropdown={{ anchor, align }}
|
||||||
|
use:clickOutside={() => showOptions(false)}
|
||||||
|
transition:fly={{ duration: 200, y: 5 }}
|
||||||
|
on:mousedown|preventDefault={handleOptionMousedown}>
|
||||||
|
{#each options as option}
|
||||||
|
<li
|
||||||
|
class:selected={lookupMap[option.value]}
|
||||||
|
data-value={option.value}>
|
||||||
|
{option.name}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
{#if !options.length}
|
||||||
|
<li class="no-results">No results</li>
|
||||||
|
{/if}
|
||||||
|
</ul>
|
||||||
|
</Portal>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.multiselect {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.multiselect:hover {
|
||||||
|
border-bottom-color: hsl(0, 0%, 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tokens-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tokens {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
position: relative;
|
||||||
|
width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
background-color: var(--background);
|
||||||
|
border-radius: var(--border-radius-m);
|
||||||
|
padding: 0 var(--spacing-m) calc(var(--spacing-m) - var(--spacing-xs))
|
||||||
|
calc(var(--spacing-m) / 2);
|
||||||
|
border: var(--border-transparent);
|
||||||
|
}
|
||||||
|
.tokens.disabled {
|
||||||
|
background-color: var(--grey-4);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.tokens.outline {
|
||||||
|
border: var(--border-dark);
|
||||||
|
}
|
||||||
|
.tokens.secondary {
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
}
|
||||||
|
.tokens.extraThin {
|
||||||
|
padding: 0 var(--spacing-m) calc(var(--spacing-s) - var(--spacing-xs))
|
||||||
|
calc(var(--spacing-m) / 2);
|
||||||
|
}
|
||||||
|
.tokens:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.tokens.optionsVisible {
|
||||||
|
border: var(--border-blue);
|
||||||
|
}
|
||||||
|
.tokens.empty {
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.tokens.empty.extraThin {
|
||||||
|
padding: var(--spacing-s) var(--spacing-m);
|
||||||
|
}
|
||||||
|
.tokens::after {
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
background-color: var(--ink);
|
||||||
|
color: var(--background);
|
||||||
|
border-radius: var(--border-radius-l);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin: calc(var(--spacing-m) - var(--spacing-xs)) 0 0
|
||||||
|
calc(var(--spacing-m) / 2);
|
||||||
|
max-height: 1.3rem;
|
||||||
|
padding: var(--spacing-xs) var(--spacing-s);
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.token.extraThin {
|
||||||
|
margin: calc(var(--spacing-s) - var(--spacing-xs)) 0 0
|
||||||
|
calc(var(--spacing-m) / 2);
|
||||||
|
}
|
||||||
|
.token span {
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.token .token-remove {
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--grey-7);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
height: 1rem;
|
||||||
|
width: 1rem;
|
||||||
|
margin: calc(-1 * var(--spacing-xs)) 0 calc(-1 * var(--spacing-xs))
|
||||||
|
var(--spacing-xs);
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.token path {
|
||||||
|
fill: var(--background);
|
||||||
|
}
|
||||||
|
.token .token-remove:hover {
|
||||||
|
background-color: var(--grey-6);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
pointer-events: none;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
.placeholder.disabled {
|
||||||
|
color: var(--grey-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-clear path {
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
.options {
|
||||||
|
left: 0;
|
||||||
|
list-style: none;
|
||||||
|
margin-block-end: 0;
|
||||||
|
margin-block-start: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-inline-start: 0;
|
||||||
|
position: absolute;
|
||||||
|
border: var(--border-dark);
|
||||||
|
border-radius: var(--border-radius-m);
|
||||||
|
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
margin: var(--spacing-xs) 0;
|
||||||
|
padding: var(--spacing-s) 0;
|
||||||
|
background-color: var(--background);
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: var(--spacing-s) var(--spacing-m);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
li.selected {
|
||||||
|
background-color: var(--blue);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
li:not(.selected):hover {
|
||||||
|
background-color: var(--grey-1);
|
||||||
|
}
|
||||||
|
li.no-results:hover {
|
||||||
|
background-color: white;
|
||||||
|
cursor: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,63 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Multiselect from "./Multiselect.svelte";
|
||||||
|
|
||||||
|
const options = ["Red", "Blue", "Yellow", "Green", "Pink", "Very long color name to show text wrapping"];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<View name="default">
|
||||||
|
<Multiselect name="Test" label="Colours" placeholder="Choose some colours">
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</Multiselect>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="right aligned">
|
||||||
|
<div class="max-width">
|
||||||
|
<Multiselect align="right" name="Test" label="Colours" placeholder="Choose some colours">
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</Multiselect>
|
||||||
|
</div>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="secondary">
|
||||||
|
<Multiselect name="Test" label="Colours" secondary placeholder="Choose some colours">
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</Multiselect>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="outline">
|
||||||
|
<Multiselect name="Test" label="Colours" outline placeholder="Choose some colours">
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</Multiselect>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="disabled">
|
||||||
|
<Multiselect name="Test" label="Colours" disabled placeholder="Choose some colours">
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</Multiselect>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="extraThin">
|
||||||
|
<Multiselect name="Test" label="Colours" extraThin placeholder="Choose some colours">
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</Multiselect>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.max-width {
|
||||||
|
align-self: flex-end;
|
||||||
|
max-width: 150px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,140 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export let value
|
||||||
|
export let group
|
||||||
|
export let name
|
||||||
|
export let disabled = false
|
||||||
|
|
||||||
|
function handleChange() {
|
||||||
|
if (disabled) return
|
||||||
|
group = value
|
||||||
|
dispatch("change", group)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<input
|
||||||
|
{disabled}
|
||||||
|
on:change={handleChange}
|
||||||
|
{value}
|
||||||
|
bind:group
|
||||||
|
type="radio"
|
||||||
|
{name}
|
||||||
|
class="checkbox"
|
||||||
|
id={value} />
|
||||||
|
<div class="checkbox-container" on:click={handleChange}>
|
||||||
|
<div class:disabled class="check-div" class:checked={group === value}>
|
||||||
|
<div class="tick_mark" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.checkbox-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-div {
|
||||||
|
position: relative;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.2s ease transform, 0.2s ease background-color,
|
||||||
|
0.2s ease box-shadow;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-div:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: var(--background);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
transition: 0.2s ease width, 0.2s ease height;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-div:active {
|
||||||
|
transform: translateY(-50%) scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tick_mark {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 6px;
|
||||||
|
width: 5px;
|
||||||
|
height: 4px;
|
||||||
|
margin: 0 auto;
|
||||||
|
transform: rotateZ(-40deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tick_mark:before,
|
||||||
|
.tick_mark:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--ink);
|
||||||
|
border-radius: 2px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: 0.2s ease transform, 0.2s ease opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tick_mark:before {
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 2px;
|
||||||
|
height: 6px;
|
||||||
|
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.23);
|
||||||
|
transform: translateY(-68px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tick_mark:after {
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.23);
|
||||||
|
transform: translateX(78px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-div.disabled:active {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checked {
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
}
|
||||||
|
.checked.disabled {
|
||||||
|
background-color: var(--grey-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checked:before {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checked .tick_mark:before,
|
||||||
|
.checked .tick_mark:after {
|
||||||
|
transform: translate(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,64 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Radio from "./Radio.svelte";
|
||||||
|
|
||||||
|
let selected = 'Cookies and cream'
|
||||||
|
let selected2 = 'Mint choc chip'
|
||||||
|
|
||||||
|
let menu = [
|
||||||
|
'Cookies and cream',
|
||||||
|
'Mint choc chip',
|
||||||
|
'Raspberry ripple'
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
## Multiple checkboxes
|
||||||
|
Use an array and an each block to use the radio button.
|
||||||
|
```svelte
|
||||||
|
<script>
|
||||||
|
let selected = 'Cookies and cream'
|
||||||
|
let selected2 = 'Cookies and cream'
|
||||||
|
|
||||||
|
let menu = [
|
||||||
|
'Cookies and cream',
|
||||||
|
'Mint choc chip',
|
||||||
|
'Raspberry ripple'
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each menu as flavour}
|
||||||
|
<Radio name="Ice Cream Flavour" value={flavour} bind:group={selected} label={flavour} showLabel/>
|
||||||
|
{/each}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
<View name="Multiple radio buttons">
|
||||||
|
<div class="container">
|
||||||
|
{#each menu as flavour}
|
||||||
|
<Radio name="Ice Cream Flavour" value={flavour} bind:group={selected}>
|
||||||
|
<label for={flavour}>{flavour}</label>
|
||||||
|
</Radio>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Disabled Radio inputs">
|
||||||
|
<div class="container">
|
||||||
|
{#each menu as flavour}
|
||||||
|
<Radio disabled name="Ice Cream Flavour" value={flavour} bind:group={selected2}>
|
||||||
|
<label for={flavour}>{flavour}</label>
|
||||||
|
</Radio>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
label {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,59 @@
|
||||||
|
<script>
|
||||||
|
import * as Quill from "quill"
|
||||||
|
import * as MarkdownIt from "markdown-it"
|
||||||
|
import TurndownService from "turndown"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import "quill/dist/quill.snow.css"
|
||||||
|
|
||||||
|
const convertMarkdown = new MarkdownIt()
|
||||||
|
convertMarkdown.set({
|
||||||
|
html: true,
|
||||||
|
})
|
||||||
|
const turndownService = new TurndownService()
|
||||||
|
|
||||||
|
export let value = ""
|
||||||
|
export let options = null
|
||||||
|
export let width = 400
|
||||||
|
|
||||||
|
let quill
|
||||||
|
let container
|
||||||
|
let defaultOptions = {
|
||||||
|
modules: {
|
||||||
|
toolbar: [
|
||||||
|
[{ header: [1, 2, 3, false] }],
|
||||||
|
["bold", "italic", "underline", "strike"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
placeholder: "Type something...",
|
||||||
|
theme: "snow", // or 'bubble'
|
||||||
|
}
|
||||||
|
|
||||||
|
let mergedOptions = { ...defaultOptions, ...options }
|
||||||
|
|
||||||
|
const updateContent = () => {
|
||||||
|
value = turndownService.turndown(quill.container.firstChild.innerHTML)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
quill = new Quill(container, mergedOptions)
|
||||||
|
if (value)
|
||||||
|
quill.clipboard.dangerouslyPasteHTML(convertMarkdown.render(value + "\n"))
|
||||||
|
|
||||||
|
quill.on("text-change", updateContent)
|
||||||
|
return () => {
|
||||||
|
quill.off("text-change", updateContent)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
{#if mergedOptions.theme !== 'snow'}
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="//cdn.quilljs.com/1.3.6/quill.{mergedOptions.theme}.css" />
|
||||||
|
{/if}
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div style="width: {width}px">
|
||||||
|
<div bind:this={container} />
|
||||||
|
</div>
|
|
@ -0,0 +1,40 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import RichText from "./RichText.svelte";
|
||||||
|
|
||||||
|
const options = { placeholder: "this is not the default value!" };
|
||||||
|
let value;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
### Rich Text Component
|
||||||
|
|
||||||
|
This component uses the QuillJS library to add Rich Text editing functionality.
|
||||||
|
|
||||||
|
It exposes a <code>content</code> variable that you can bind to in order to get Markdown out of the component.
|
||||||
|
|
||||||
|
As well as the content you can also pass in an option object that looks like so:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let options = {
|
||||||
|
modules: {
|
||||||
|
toolbar: [
|
||||||
|
[{ header: [1, 2, 3, false] }],
|
||||||
|
['bold', 'italic', 'underline', 'strike']
|
||||||
|
]
|
||||||
|
},
|
||||||
|
placeholder: 'Type something...',
|
||||||
|
theme: 'snow'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<View name="default">
|
||||||
|
<RichText bind:value />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="passing in Markdown">
|
||||||
|
<RichText value="# This is an h1 heading!" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="passing in custom options">
|
||||||
|
<RichText {options} />
|
||||||
|
</View>
|
|
@ -0,0 +1,95 @@
|
||||||
|
<script>
|
||||||
|
import Icon from "../Icons/Icon.svelte"
|
||||||
|
import Label from "../Styleguide/Label.svelte"
|
||||||
|
|
||||||
|
export let value = ""
|
||||||
|
export let name = undefined
|
||||||
|
export let label = undefined
|
||||||
|
export let thin = false
|
||||||
|
export let extraThin = false
|
||||||
|
export let secondary = false
|
||||||
|
export let outline = false
|
||||||
|
export let disabled = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{#if label}
|
||||||
|
<Label extraSmall grey forAttr={name}>{label}</Label>
|
||||||
|
{/if}
|
||||||
|
<div class="relative">
|
||||||
|
<select
|
||||||
|
{name}
|
||||||
|
class:thin
|
||||||
|
class:extraThin
|
||||||
|
class:secondary
|
||||||
|
class:outline
|
||||||
|
{disabled}
|
||||||
|
on:change
|
||||||
|
bind:value>
|
||||||
|
<slot />
|
||||||
|
</select>
|
||||||
|
<div class="pointer">
|
||||||
|
<Icon name="arrowdown" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
select {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
display: block !important;
|
||||||
|
width: 100% !important;
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
border: none;
|
||||||
|
text-align: left;
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
padding: var(--spacing-m) 2rem var(--spacing-m) var(--spacing-m) !important;
|
||||||
|
appearance: none !important;
|
||||||
|
-webkit-appearance: none !important;
|
||||||
|
-moz-appearance: none !important;
|
||||||
|
align-items: center;
|
||||||
|
white-space: pre;
|
||||||
|
outline: none;
|
||||||
|
border: var(--border-transparent);
|
||||||
|
background-color: var(--background);
|
||||||
|
}
|
||||||
|
select.thin {
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
}
|
||||||
|
select.extraThin {
|
||||||
|
padding: var(--spacing-s) 2rem var(--spacing-s) var(--spacing-m) !important;
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
}
|
||||||
|
select.secondary {
|
||||||
|
background: var(--grey-2);
|
||||||
|
}
|
||||||
|
select.outline {
|
||||||
|
border: var(--border-light-2);
|
||||||
|
}
|
||||||
|
select:focus {
|
||||||
|
border: var(--border-blue);
|
||||||
|
}
|
||||||
|
select:disabled {
|
||||||
|
background: var(--grey-4);
|
||||||
|
color: var(--grey-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.relative {
|
||||||
|
position: relative !important;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pointer {
|
||||||
|
right: 0 !important;
|
||||||
|
top: 0 !important;
|
||||||
|
bottom: 0 !important;
|
||||||
|
position: absolute !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
padding-left: 0.5rem !important;
|
||||||
|
align-items: center !important;
|
||||||
|
display: flex !important;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,62 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Select from "./Select.svelte";
|
||||||
|
import Spacer from "../Spacer/Spacer.svelte"
|
||||||
|
|
||||||
|
const options = ["Chocolate", "Vanilla", "Strawberry Cheesecake"];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<View name="default">
|
||||||
|
<Select name="Test" label="Flavour">
|
||||||
|
<option value="">Choose an option</option>
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="secondary">
|
||||||
|
<Select secondary name="Test" label="Flavour">
|
||||||
|
<option value="">Choose an option</option>
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="outline">
|
||||||
|
<Select outline name="Test" label="Flavour">
|
||||||
|
<option value="">Choose an option</option>
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="disabled">
|
||||||
|
<Select disabled name="Test" label="Flavour">
|
||||||
|
<option value="">Choose an option</option>
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="thin">
|
||||||
|
<Select thin name="Test" label="Flavour">
|
||||||
|
<option value="">Choose an option</option>
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="extraThin">
|
||||||
|
<Select extraThin name="Test" label="Flavour">
|
||||||
|
<option value="">Choose an option</option>
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
</View>
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
<script>
|
||||||
|
import Label from "../Styleguide/Label.svelte"
|
||||||
|
|
||||||
|
export let label
|
||||||
|
export let min = 0
|
||||||
|
export let max = 100
|
||||||
|
export let step = 1
|
||||||
|
export let value
|
||||||
|
export let showValue = false
|
||||||
|
export let showRange = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{#if label}
|
||||||
|
<Label extraSmall grey>
|
||||||
|
{label}
|
||||||
|
{#if showValue && value != null}({value}){/if}
|
||||||
|
</Label>
|
||||||
|
{/if}
|
||||||
|
<div class="container">
|
||||||
|
{#if showRange && min != null}<span>{min}</span>{/if}
|
||||||
|
<input type="range" bind:value {min} {max} {step} />
|
||||||
|
{#if showRange && max != null}<span>{max}</span>{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
color: var(--grey-5);
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"] {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
input[type="range"]:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
input[type="range"]::-webkit-slider-runnable-track {
|
||||||
|
background: var(--grey-4);
|
||||||
|
border-radius: 9px;
|
||||||
|
width: 100%;
|
||||||
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
border: none;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
input[type="range"]::-moz-range-track {
|
||||||
|
background: var(--grey-4);
|
||||||
|
border-radius: 9px;
|
||||||
|
width: 100%;
|
||||||
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
input[type="range"]::-moz-range-thumb {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Slider from "./Slider.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<View name="default">
|
||||||
|
<Slider label=Quantity value="50" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="show value">
|
||||||
|
<Slider label="Quantity" value="50" showValue />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="show range">
|
||||||
|
<Slider label="Quantity" value="25" showValue showRange min="0" max="100" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="custom min and max">
|
||||||
|
<Slider label="Quantity" value="350" showValue showRange min="50" max="500" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="custom step">
|
||||||
|
<Slider label="Quantity" value="25" step="25" showValue showRange min="0" max="100" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import Button from "../Button/Button.svelte"
|
||||||
|
import Label from "../Styleguide/Label.svelte"
|
||||||
|
import text_area_resize from "../Actions/autoresize_textarea.js"
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export let name = false
|
||||||
|
export let label = false
|
||||||
|
export let thin = false
|
||||||
|
export let extraThin = false
|
||||||
|
export let edit = false
|
||||||
|
export let disabled = false
|
||||||
|
export let placeholder
|
||||||
|
export let validator = () => {}
|
||||||
|
export let value = ""
|
||||||
|
export const getCaretPosition = () => {
|
||||||
|
return { start: textarea.selectionStart, end: textarea.selectionEnd }
|
||||||
|
}
|
||||||
|
|
||||||
|
let textarea
|
||||||
|
|
||||||
|
// This section handles the edit mode and dispatching of things to the parent when saved
|
||||||
|
let editMode = false
|
||||||
|
|
||||||
|
const save = () => {
|
||||||
|
editMode = false
|
||||||
|
dispatch("save", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const enableEdit = () => {
|
||||||
|
editMode = true
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{#if label || edit}
|
||||||
|
<div class="label-container">
|
||||||
|
{#if label}
|
||||||
|
<Label extraSmall grey forAttr={name}>{label}</Label>
|
||||||
|
{/if}
|
||||||
|
{#if edit}
|
||||||
|
<div class="controls">
|
||||||
|
<Button small secondary disabled={editMode} on:click={enableEdit}>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button small blue disabled={!editMode} on:click={save}>Save</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<textarea
|
||||||
|
class:thin
|
||||||
|
class:extraThin
|
||||||
|
bind:value
|
||||||
|
bind:this={textarea}
|
||||||
|
on:change
|
||||||
|
disabled={disabled || (edit && !editMode)}
|
||||||
|
{placeholder}
|
||||||
|
{name}
|
||||||
|
use:text_area_resize />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-bottom: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.label-container :global(label) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
align-items: center;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
grid-gap: 12px;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
.controls :global(button) {
|
||||||
|
min-width: 100px;
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
border-radius: var(--rounded-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
min-width: 0;
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
margin: 0;
|
||||||
|
border: var(--border-transparent);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
textarea::placeholder {
|
||||||
|
color: var(--grey-6);
|
||||||
|
}
|
||||||
|
textarea.thin {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
}
|
||||||
|
textarea.extraThin {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
padding: var(--spacing-s) var(--spacing-m);
|
||||||
|
}
|
||||||
|
textarea:focus {
|
||||||
|
border: var(--border-blue);
|
||||||
|
}
|
||||||
|
textarea:disabled {
|
||||||
|
background: var(--grey-4);
|
||||||
|
}
|
||||||
|
textarea:disabled {
|
||||||
|
background: var(--grey-4);
|
||||||
|
}
|
||||||
|
textarea:disabled::placeholder {
|
||||||
|
color: var(--grey-6);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,30 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import TextArea from "./TextArea.svelte";
|
||||||
|
|
||||||
|
import Button from "../Button/Button.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<View name="default">
|
||||||
|
<TextArea placeholder="Enter your email text" label="Email Body" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="disabled">
|
||||||
|
<TextArea disabled placeholder="Enter your email text" label="Email Body" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="no label">
|
||||||
|
<TextArea placeholder="Enter your email text" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="thin">
|
||||||
|
<TextArea thin placeholder="Enter your email text" label="Email Body" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="extraThin">
|
||||||
|
<TextArea extraThin placeholder="Enter your email text" label="Email Body" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="with buttons">
|
||||||
|
<TextArea edit placeholder="Enter your email text" label="Email Body" />
|
||||||
|
</View>
|
|
@ -0,0 +1,110 @@
|
||||||
|
<script>
|
||||||
|
export let name = undefined
|
||||||
|
export let text = ""
|
||||||
|
export let checked = false
|
||||||
|
export let disabled = false
|
||||||
|
export let screenreader = true
|
||||||
|
export let thin = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label for={name} class="container">
|
||||||
|
<div class="toggle">
|
||||||
|
<input
|
||||||
|
id={name}
|
||||||
|
{name}
|
||||||
|
type="checkbox"
|
||||||
|
class:screenreader
|
||||||
|
{disabled}
|
||||||
|
bind:checked
|
||||||
|
on:change />
|
||||||
|
<div class="track">
|
||||||
|
<div class="thumb" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if text}<span class="text" class:thin>{text}</span>{/if}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.container:disabled {
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
align-self: center;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track {
|
||||||
|
width: 32px;
|
||||||
|
height: 18px;
|
||||||
|
background-color: var(--grey-4);
|
||||||
|
border-radius: 9px;
|
||||||
|
transition-delay: 0.12s;
|
||||||
|
transition-duration: 0.2s;
|
||||||
|
transition-property: background;
|
||||||
|
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
transition-duration: 0.28s;
|
||||||
|
transition-property: all;
|
||||||
|
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: var(--border-radius-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked ~ .track .thumb {
|
||||||
|
transform: translateX(14px);
|
||||||
|
}
|
||||||
|
input[type="checkbox"]:checked ~ .track {
|
||||||
|
background-color: var(--blue);
|
||||||
|
}
|
||||||
|
input[type="checkbox"]:disabled ~ .track {
|
||||||
|
background-color: var(--grey-4);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
input[type="checkbox"]:disabled ~ .track .thumb {
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screenreader {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
.text.thin {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Toggle from "./Toggle.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<View name="default">
|
||||||
|
<Toggle />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="checked with text">
|
||||||
|
<Toggle text="Display on mobile?" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="thin">
|
||||||
|
<Toggle text="Display on mobile?" thin />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="disabled">
|
||||||
|
<Toggle disabled={true} />
|
||||||
|
</View>
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
height="24">
|
||||||
|
<path fill="none" d="M0 0h24v24H0z" />
|
||||||
|
<path
|
||||||
|
d="M14.121 10.48a1 1 0 0 0-1.414 0l-.707.706a2 2 0 1
|
||||||
|
1-2.828-2.828l5.63-5.632a6.5 6.5 0 0 1 6.377 10.568l-2.108
|
||||||
|
2.135-4.95-4.95zM3.161 4.468a6.503 6.503 0 0 1 8.009-.938L7.757 6.944a4 4 0
|
||||||
|
0 0 5.513 5.794l.144-.137 4.243 4.242-4.243 4.243a2 2 0 0 1-2.828 0L3.16
|
||||||
|
13.66a6.5 6.5 0 0 1 0-9.192z"
|
||||||
|
fill="rgba(128,129,146,1)" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 493 B |
|
@ -0,0 +1,34 @@
|
||||||
|
<script context="module">
|
||||||
|
import pathsByName from "./icon-paths"
|
||||||
|
export const iconOptions = Object.keys(pathsByName)
|
||||||
|
export const directions = ["n", "ne", "e", "se", "s", "sw", "w", "nw"]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export let name = "arrow"
|
||||||
|
export let direction = "n"
|
||||||
|
|
||||||
|
$: paths = pathsByName[name] || []
|
||||||
|
$: rotation = directions.indexOf(direction) * 45
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class="c"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
style={`transform: rotate(${rotation}deg)`}>
|
||||||
|
{#each paths as path}
|
||||||
|
<path d={path} />
|
||||||
|
{/each}
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.c {
|
||||||
|
width: 1.25em;
|
||||||
|
height: 1.25em;
|
||||||
|
fill: currentColor;
|
||||||
|
overflow: visible;
|
||||||
|
margin-right: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,109 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Icon from "./Icon.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<View name="add icon">
|
||||||
|
<Icon name="add" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Add Row icon">
|
||||||
|
<Icon name="addrow" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Add Column icon">
|
||||||
|
<Icon name="addcolumn" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="View icon">
|
||||||
|
<Icon name="view" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Table icon">
|
||||||
|
<Icon name="table" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Edit icon">
|
||||||
|
<Icon name="edit" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Delete icon">
|
||||||
|
<Icon name="delete" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Close icon">
|
||||||
|
<Icon name="close" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Arrow Up icon">
|
||||||
|
<Icon name="arrowup" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Arrow Right icon">
|
||||||
|
<Icon name="arrowright" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Arrow Down icon">
|
||||||
|
<Icon name="arrowdown" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Arrow Left icon">
|
||||||
|
<Icon name="arrowleft" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Search icon">
|
||||||
|
<Icon name="search" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Preview icon">
|
||||||
|
<Icon name="preview" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Settings icon">
|
||||||
|
<Icon name="settings" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Add User icon">
|
||||||
|
<Icon name="adduser" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Plugin icon">
|
||||||
|
<Icon name="plugin" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Help icon">
|
||||||
|
<Icon name="help" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Sort Ascending icon">
|
||||||
|
<Icon name="sortascending" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Sort Descending icon">
|
||||||
|
<Icon name="sortdescending" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Calculate icon">
|
||||||
|
<Icon name="calculate" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Filter icon">
|
||||||
|
<Icon name="filter" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Add Fill">
|
||||||
|
<Icon name="addfill" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Group">
|
||||||
|
<Icon name="group" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Lightning">
|
||||||
|
<Icon name="lightning" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Download">
|
||||||
|
<Icon name="download" />
|
||||||
|
</View>
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
export default {
|
||||||
|
arrow: [
|
||||||
|
"M0.200275 13.2782C0.200275 12.4153 0.89983 11.7157 1.76278 11.7157H23.6378C24.5007 11.7157 25.2003 12.4153 25.2003 13.2782C25.2003 14.1411 24.5007 14.8407 23.6378 14.8407H1.76278C0.89983 14.8407 0.200275 14.1411 0.200275 13.2782Z",
|
||||||
|
"M11.5954 1.23584C12.2056 0.62565 13.1949 0.62565 13.8051 1.23584L24.7426 12.1733C25.3528 12.7835 25.3528 13.7729 24.7426 14.3831L13.8051 25.3206C13.1949 25.9307 12.2056 25.9307 11.5954 25.3206C10.9852 24.7104 10.9852 23.721 11.5954 23.1108L21.4281 13.2782L11.5954 3.44555C10.9852 2.83536 10.9852 1.84604 11.5954 1.23584Z",
|
||||||
|
],
|
||||||
|
check: [
|
||||||
|
"M24.3522 3.64786C23.4883 2.78405 22.0878 2.78405 21.224 3.64786L8.64198 16.2299L3.77601 11.3639C2.9122 10.5001 1.51168 10.5001 0.647861 11.3639C-0.215954 12.2277 -0.215954 13.6283 0.647861 14.4921L7.07791 20.9221C7.94172 21.7859 9.34224 21.7859 10.2061 20.9221L24.3522 6.77601L23.6974 6.12128L24.3522 6.77601C25.216 5.9122 25.216 4.51168 24.3522 3.64786L23.6974 4.30259L24.3522 3.64786Z",
|
||||||
|
],
|
||||||
|
coffee: [
|
||||||
|
"M6.69069 1.96879C6.69069 1.36848 6.20405 0.881836 5.60374 0.881836C5.00343 0.881836 4.51678 1.36848 4.51678 1.96879V5.22966C4.51678 5.82997 5.00343 6.31662 5.60374 6.31662C6.20405 6.31662 6.69069 5.82997 6.69069 5.22966V1.96879ZM0.168955 9.57749C0.168955 8.97718 0.655602 8.49053 1.25591 8.49053H18.6472H19.7342C21.1756 8.49053 22.5579 9.06312 23.5771 10.0823C24.5964 11.1016 25.169 12.4839 25.169 13.9253C25.169 15.3667 24.5964 16.7491 23.5771 17.7683C22.5579 18.7875 21.1756 19.3601 19.7342 19.3601C19.7342 20.8015 19.1616 22.1838 18.1424 23.2031C17.1231 24.2223 15.7408 24.7949 14.2994 24.7949H5.60374C4.16234 24.7949 2.77998 24.2223 1.76077 23.2031C0.741547 22.1838 0.168955 20.8015 0.168955 19.3601V9.57749ZM19.7342 17.1862V10.6644C20.599 10.6644 21.4284 11.008 22.04 11.6195C22.6515 12.2311 22.995 13.0605 22.995 13.9253C22.995 14.7901 22.6515 15.6196 22.04 16.2311C21.4284 16.8426 20.599 17.1862 19.7342 17.1862ZM17.5603 10.6644V18.2731V19.3601C17.5603 20.2249 17.2167 21.0543 16.6052 21.6659C15.9936 22.2774 15.1642 22.621 14.2994 22.621H5.60374C4.7389 22.621 3.90949 22.2774 3.29795 21.6659C2.68642 21.0543 2.34287 20.2249 2.34287 19.3601V10.6644H17.5603ZM9.95156 0.881836C10.5519 0.881836 11.0385 1.36848 11.0385 1.96879V5.22966C11.0385 5.82997 10.5519 6.31662 9.95156 6.31662C9.35125 6.31662 8.86461 5.82997 8.86461 5.22966V1.96879C8.86461 1.36848 9.35125 0.881836 9.95156 0.881836ZM15.3863 1.96879C15.3863 1.36848 14.8997 0.881836 14.2994 0.881836C13.6991 0.881836 13.2124 1.36848 13.2124 1.96879V5.22966C13.2124 5.82997 13.6991 6.31662 14.2994 6.31662C14.8997 6.31662 15.3863 5.82997 15.3863 5.22966V1.96879Z",
|
||||||
|
],
|
||||||
|
copy: [
|
||||||
|
"M2.98325 3.58322C3.19636 3.37011 3.48539 3.25039 3.78678 3.25039H14.014C14.3154 3.25039 14.6045 3.37011 14.8176 3.58322C15.0307 3.79633 15.1504 4.08537 15.1504 4.38675V5.52312C15.1504 6.15071 15.6592 6.65948 16.2868 6.65948C16.9144 6.65948 17.4231 6.15071 17.4231 5.52312V4.38675C17.4231 3.4826 17.064 2.61549 16.4246 1.97616C15.7853 1.33683 14.9182 0.977661 14.014 0.977661H3.78678C2.88263 0.977661 2.01551 1.33683 1.37619 1.97616C0.736856 2.61549 0.377686 3.4826 0.377686 4.38675V14.614C0.377686 15.5182 0.736857 16.3853 1.37619 17.0246C2.01551 17.6639 2.88263 18.0231 3.78678 18.0231H4.92314C5.55074 18.0231 6.0595 17.5143 6.0595 16.8868C6.0595 16.2592 5.55074 15.7504 4.92314 15.7504H3.78678C3.48539 15.7504 3.19636 15.6307 2.98325 15.4176C2.77014 15.2044 2.65041 14.9154 2.65041 14.614V4.38675C2.65041 4.08537 2.77014 3.79633 2.98325 3.58322ZM10.605 12.3413C10.605 11.7137 11.1137 11.2049 11.7413 11.2049H21.9686C22.5962 11.2049 23.105 11.7137 23.105 12.3413V22.5686C23.105 23.1962 22.5962 23.7049 21.9686 23.7049H11.7413C11.1137 23.7049 10.605 23.1962 10.605 22.5686V12.3413ZM11.7413 8.93221C9.85853 8.93221 8.33223 10.4585 8.33223 12.3413V22.5686C8.33223 24.4514 9.85853 25.9777 11.7413 25.9777H21.9686C23.8514 25.9777 25.3777 24.4514 25.3777 22.5686V12.3413C25.3777 10.4585 23.8514 8.93221 21.9686 8.93221H11.7413Z",
|
||||||
|
],
|
||||||
|
downloadalt: [
|
||||||
|
"M2.21191 15.4277C2.90227 15.4277 3.46191 15.9874 3.46191 16.6777V21.6777C3.46191 22.0093 3.59361 22.3272 3.82803 22.5616C4.06245 22.796 4.38039 22.9277 4.71191 22.9277H22.2119C22.5434 22.9277 22.8614 22.796 23.0958 22.5616C23.3302 22.3272 23.4619 22.0093 23.4619 21.6777V16.6777C23.4619 15.9874 24.0216 15.4277 24.7119 15.4277C25.4023 15.4277 25.9619 15.9874 25.9619 16.6777V21.6777C25.9619 22.6723 25.5668 23.6261 24.8636 24.3294C24.1603 25.0326 23.2065 25.4277 22.2119 25.4277H4.71191C3.71735 25.4277 2.76352 25.0326 2.06026 24.3294C1.357 23.6261 0.961914 22.6723 0.961914 21.6777V16.6777C0.961914 15.9874 1.52156 15.4277 2.21191 15.4277Z",
|
||||||
|
"M6.32803 9.54385C6.81619 9.0557 7.60764 9.0557 8.0958 9.54385L13.4619 14.91L18.828 9.54385C19.3162 9.0557 20.1076 9.0557 20.5958 9.54385C21.084 10.032 21.084 10.8235 20.5958 11.3116L14.3458 17.5616C13.8576 18.0498 13.0662 18.0498 12.578 17.5616L6.32803 11.3116C5.83988 10.8235 5.83988 10.032 6.32803 9.54385Z",
|
||||||
|
"M13.4619 0.427734C14.1523 0.427734 14.7119 0.987378 14.7119 1.67773V16.6777C14.7119 17.3681 14.1523 17.9277 13.4619 17.9277C12.7716 17.9277 12.2119 17.3681 12.2119 16.6777V1.67773C12.2119 0.987378 12.7716 0.427734 13.4619 0.427734Z",
|
||||||
|
],
|
||||||
|
external: [
|
||||||
|
"M25.3673 1.28312C25.3077 1.13904 25.22 1.00384 25.1042 0.88591C25.0988 0.88042 25.0933 0.87498 25.0878 0.86959C24.8623 0.648163 24.5532 0.511597 24.2122 0.511597H24.2119H16.7122C16.0218 0.511597 15.4622 1.07124 15.4622 1.7616C15.4622 2.45195 16.0218 3.0116 16.7122 3.0116H21.1944L9.57827 14.6277C9.09012 15.1159 9.09012 15.9073 9.57827 16.3955C10.0664 16.8836 10.8579 16.8836 11.346 16.3955L22.9622 4.77936V9.2616C22.9622 9.95195 23.5218 10.5116 24.2122 10.5116C24.9025 10.5116 25.4622 9.95195 25.4622 9.2616V1.76269C25.4622 1.75848 25.4621 1.75427 25.4621 1.75006C25.4606 1.59108 25.429 1.43233 25.3673 1.28312ZM4.21216 6.7616C3.88064 6.7616 3.5627 6.89329 3.32827 7.12771C3.09385 7.36213 2.96216 7.68008 2.96216 8.0116V21.7616C2.96216 22.0931 3.09385 22.4111 3.32827 22.6455C3.5627 22.8799 3.88064 23.0116 4.21216 23.0116H17.9622C18.2937 23.0116 18.6116 22.8799 18.846 22.6455C19.0805 22.4111 19.2122 22.0931 19.2122 21.7616V14.2616C19.2122 13.5712 19.7718 13.0116 20.4622 13.0116C21.1525 13.0116 21.7122 13.5712 21.7122 14.2616V21.7616C21.7122 22.7562 21.3171 23.71 20.6138 24.4132C19.9105 25.1165 18.9567 25.5116 17.9622 25.5116H4.21216C3.2176 25.5116 2.26377 25.1165 1.56051 24.4132C0.857246 23.71 0.462158 22.7562 0.462158 21.7616V8.0116C0.462158 7.01704 0.857246 6.06321 1.56051 5.35995C2.26377 4.65668 3.2176 4.2616 4.21216 4.2616H11.7122C12.4025 4.2616 12.9622 4.82124 12.9622 5.5116C12.9622 6.20195 12.4025 6.7616 11.7122 6.7616H4.21216Z",
|
||||||
|
],
|
||||||
|
file: [
|
||||||
|
"M4.00045 1.63229C4.63978 0.99296 5.5069 0.633789 6.41104 0.633789H14.3656C14.667 0.633789 14.956 0.753513 15.1691 0.966622L23.1237 8.92117C23.3368 9.13428 23.4565 9.42332 23.4565 9.7247V22.2247C23.4565 23.1288 23.0973 23.996 22.458 24.6353C21.8187 25.2746 20.9516 25.6338 20.0474 25.6338H6.41104C5.5069 25.6338 4.63978 25.2746 4.00045 24.6353C3.36112 23.996 3.00195 23.1288 3.00195 22.2247V4.04288C3.00195 3.13873 3.36112 2.27162 4.00045 1.63229ZM6.41104 2.90652C6.10966 2.90652 5.82062 3.02624 5.60751 3.23935C5.3944 3.45246 5.27468 3.7415 5.27468 4.04288V22.2247C5.27468 22.5261 5.3944 22.8151 5.60751 23.0282C5.82062 23.2413 6.10966 23.3611 6.41104 23.3611H20.0474C20.3488 23.3611 20.6378 23.2413 20.8509 23.0282C21.064 22.8151 21.1838 22.5261 21.1838 22.2247V10.1954L13.8949 2.90652H6.41104Z",
|
||||||
|
"M14.3656 0.633789C14.9932 0.633789 15.502 1.14256 15.502 1.77015V8.58833H22.3202C22.9478 8.58833 23.4565 9.0971 23.4565 9.7247C23.4565 10.3523 22.9478 10.8611 22.3202 10.8611H14.3656C13.738 10.8611 13.2292 10.3523 13.2292 9.7247V1.77015C13.2292 1.14256 13.738 0.633789 14.3656 0.633789Z",
|
||||||
|
],
|
||||||
|
list: [
|
||||||
|
"M0 10.25C0 9.55964 0.559644 9 1.25 9H18.75C19.4404 9 20 9.55964 20 10.25C20 10.9404 19.4404 11.5 18.75 11.5H1.25C0.559644 11.5 0 10.9404 0 10.25Z",
|
||||||
|
"M0 5.25C0 4.55964 0.559644 4 1.25 4H23.75C24.4404 4 25 4.55964 25 5.25C25 5.94036 24.4404 6.5 23.75 6.5H1.25C0.559644 6.5 0 5.94036 0 5.25Z",
|
||||||
|
"M0 15.25C0 14.5596 0.559644 14 1.25 14H23.75C24.4404 14 25 14.5596 25 15.25C25 15.9404 24.4404 16.5 23.75 16.5H1.25C0.559644 16.5 0 15.9404 0 15.25Z",
|
||||||
|
"M0 20.25C0 19.5596 0.559644 19 1.25 19H18.75C19.4404 19 20 19.5596 20 20.25C20 20.9404 19.4404 21.5 18.75 21.5H1.25C0.559644 21.5 0 20.9404 0 20.25Z",
|
||||||
|
],
|
||||||
|
money: [
|
||||||
|
"M13.2917 0C13.867 0 14.3333 0.46637 14.3333 1.04167V23.9583C14.3333 24.5336 13.867 25 13.2917 25C12.7164 25 12.25 24.5336 12.25 23.9583V1.04167C12.25 0.46637 12.7164 0 13.2917 0Z",
|
||||||
|
"M7.37294 5.53956C8.25201 4.66049 9.4443 4.16663 10.6875 4.16663H18.5C19.0753 4.16663 19.5417 4.633 19.5417 5.20829C19.5417 5.78359 19.0753 6.24996 18.5 6.24996H10.6875C9.99683 6.24996 9.33445 6.52433 8.84608 7.0127C8.3577 7.50108 8.08333 8.16346 8.08333 8.85413C8.08333 9.54479 8.3577 10.2072 8.84608 10.6955C9.33445 11.1839 9.99683 11.4583 10.6875 11.4583H15.8958C17.139 11.4583 18.3313 11.9522 19.2104 12.8312C20.0895 13.7103 20.5833 14.9026 20.5833 16.1458C20.5833 17.389 20.0895 18.5813 19.2104 19.4604C18.3313 20.3394 17.139 20.8333 15.8958 20.8333H7.04167C6.46637 20.8333 6 20.3669 6 19.7916C6 19.2163 6.46637 18.75 7.04167 18.75H15.8958C16.5865 18.75 17.2489 18.4756 17.7373 17.9872C18.2256 17.4988 18.5 16.8365 18.5 16.1458C18.5 15.4551 18.2256 14.7927 17.7373 14.3044C17.2489 13.816 16.5865 13.5416 15.8958 13.5416H10.6875C9.4443 13.5416 8.25201 13.0478 7.37294 12.1687C6.49386 11.2896 6 10.0973 6 8.85413C6 7.61092 6.49386 6.41864 7.37294 5.53956Z",
|
||||||
|
],
|
||||||
|
paperclip: [
|
||||||
|
"M17.5359 2.82806C16.6555 2.82806 15.8112 3.17779 15.1886 3.80031L5.02747 13.9615C3.99 14.999 3.40716 16.4061 3.40716 17.8733C3.40716 19.3405 3.99 20.7476 5.02747 21.785C6.06493 22.8225 7.47204 23.4053 8.93924 23.4053C10.4064 23.4053 11.8135 22.8225 12.851 21.785L23.0122 11.6239C23.444 11.1921 24.1441 11.1921 24.5759 11.6239C25.0076 12.0556 25.0076 12.7557 24.5759 13.1875L14.4147 23.3487C12.9625 24.8009 10.9929 25.6167 8.93924 25.6167C6.88555 25.6167 4.91598 24.8009 3.4638 23.3487C2.01162 21.8965 1.1958 19.9269 1.1958 17.8733C1.1958 15.8196 2.01162 13.85 3.4638 12.3978L13.625 2.23665C14.6622 1.19941 16.069 0.616699 17.5359 0.616699C19.0028 0.616699 20.4095 1.19941 21.4468 2.23665C22.484 3.27388 23.0667 4.68068 23.0667 6.14755C23.0667 7.61442 22.484 9.02121 21.4468 10.0584L11.2745 20.2196C10.6523 20.8419 9.80824 21.1915 8.92818 21.1915C8.04812 21.1915 7.20411 20.8419 6.58181 20.2196C5.95952 19.5973 5.60992 18.7533 5.60992 17.8733C5.60992 16.9932 5.95952 16.1492 6.58181 15.5269L15.9695 6.15029C16.4015 5.71875 17.1016 5.71916 17.5331 6.15121C17.9647 6.58326 17.9643 7.28333 17.5322 7.71487L8.14548 17.0906C7.93818 17.2981 7.82127 17.5799 7.82127 17.8733C7.82127 18.1668 7.93789 18.4484 8.14548 18.656C8.35306 18.8636 8.63461 18.9802 8.92818 18.9802C9.22175 18.9802 9.50329 18.8636 9.71088 18.656L19.8831 8.49479C20.5054 7.8723 20.8554 7.02773 20.8554 6.14755C20.8554 5.26716 20.5056 4.42284 19.8831 3.80031C19.2606 3.17779 18.4163 2.82806 17.5359 2.82806Z",
|
||||||
|
],
|
||||||
|
person: [
|
||||||
|
"M3.04927 16.6449C4.23321 15.4462 5.83898 14.7727 7.51333 14.7727H17.6143C19.2887 14.7727 20.8945 15.4462 22.0784 16.6449C23.2623 17.8436 23.9275 19.4695 23.9275 21.1648V23.7216C23.9275 24.4276 23.3622 25 22.6648 25C21.9675 25 21.4022 24.4276 21.4022 23.7216V21.1648C21.4022 20.1476 21.0031 19.1721 20.2928 18.4528C19.5824 17.7336 18.6189 17.3295 17.6143 17.3295H7.51333C6.50872 17.3295 5.54526 17.7336 4.83489 18.4528C4.12453 19.1721 3.72545 20.1476 3.72545 21.1648V23.7216C3.72545 24.4276 3.16015 25 2.46282 25C1.76549 25 1.2002 24.4276 1.2002 23.7216V21.1648C1.2002 19.4695 1.86533 17.8436 3.04927 16.6449Z",
|
||||||
|
"M11.9956 2.5C9.92454 2.5 8.24561 4.17893 8.24561 6.25C8.24561 8.32107 9.92454 10 11.9956 10C14.0667 10 15.7456 8.32107 15.7456 6.25C15.7456 4.17893 14.0667 2.5 11.9956 2.5ZM5.74561 6.25C5.74561 2.79822 8.54383 0 11.9956 0C15.4474 0 18.2456 2.79822 18.2456 6.25C18.2456 9.70178 15.4474 12.5 11.9956 12.5C8.54383 12.5 5.74561 9.70178 5.74561 6.25Z",
|
||||||
|
],
|
||||||
|
refresh: [
|
||||||
|
"M6.97937 3.40529C8.70577 2.45337 10.6948 2.08834 12.6467 2.36521C14.5986 2.64207 16.4076 3.54582 17.8012 4.94028C17.8093 4.94841 17.8176 4.95642 17.8259 4.9643L21.0026 7.95574H17.048C16.4203 7.95574 15.9115 8.4646 15.9115 9.09231C15.9115 9.72002 16.4203 10.2289 17.048 10.2289H23.8643H23.8675C24.0269 10.2289 24.1787 10.196 24.3165 10.1367C24.4544 10.0775 24.5828 9.98985 24.6925 9.874C24.7014 9.86462 24.7102 9.85508 24.7187 9.8454C24.9095 9.62998 25.0041 9.36088 25.004 9.09232C25.004 9.09121 25.004 9.09009 25.004 9.08898V2.27288C25.004 1.64517 24.4952 1.13631 23.8675 1.13631C23.2397 1.13631 22.7309 1.64517 22.7309 2.27288V6.46082L19.3966 3.32095C17.6563 1.58497 15.4 0.45984 12.9659 0.11459C10.526 -0.231487 8.03977 0.224801 5.88178 1.4147C3.72379 2.60459 2.01099 4.46363 1.00148 6.71166C-0.00803095 8.9597 -0.259554 11.4749 0.284811 13.8784C0.829176 16.2818 2.13994 18.4432 4.01957 20.0368C5.89921 21.6305 8.24589 22.5701 10.706 22.714C13.1661 22.8579 15.6063 22.1984 17.6589 20.8347C19.7116 19.4711 21.2654 17.4773 22.0863 15.1538C22.2954 14.5619 21.9851 13.9126 21.3933 13.7035C20.8014 13.4944 20.1521 13.8047 19.943 14.3966C19.2863 16.2554 18.0432 17.8504 16.4011 18.9413C14.759 20.0322 12.8068 20.5599 10.8387 20.4447C8.87066 20.3296 6.99332 19.5779 5.48961 18.303C3.9859 17.0281 2.93729 15.299 2.5018 13.3762C2.06631 11.4535 2.26753 9.44129 3.07513 7.64286C3.88274 5.84443 5.25298 4.35721 6.97937 3.40529Z",
|
||||||
|
],
|
||||||
|
swoop: [
|
||||||
|
"M17.6488 0.406796C17.1064 -0.135599 16.227 -0.135599 15.6846 0.406796C15.1422 0.949191 15.1422 1.82859 15.6846 2.37098L20.258 6.94444H6.94444C5.10266 6.94444 3.33632 7.67609 2.03398 8.97843C0.731644 10.2808 0 12.0471 0 13.8889V23.6111C0 24.3782 0.621827 25 1.38889 25C2.15595 25 2.77778 24.3782 2.77778 23.6111V13.8889C2.77778 12.7838 3.21676 11.724 3.99817 10.9426C4.77957 10.1612 5.83938 9.72222 6.94444 9.72222H20.258L15.6846 14.2957C15.1422 14.8381 15.1422 15.7175 15.6846 16.2599C16.227 16.8023 17.1064 16.8023 17.6488 16.2599L24.5932 9.31543C24.8611 9.04749 24.9967 8.69732 24.9999 8.34616C25 8.34189 25 8.33761 25 8.33333C25 8.32906 25 8.32478 24.9999 8.32051C24.9983 8.13686 24.961 7.96173 24.8946 7.80169C24.8268 7.63788 24.7264 7.4844 24.5932 7.35124L17.6488 0.406796Z",
|
||||||
|
],
|
||||||
|
twitter: [
|
||||||
|
"M21.351 2.5026C20.4177 1.5026 19.0844 0.835938 17.6177 0.835938C14.8177 0.835938 12.4844 3.16927 12.4844 6.1026C12.4844 6.5026 12.551 6.9026 12.6177 7.3026C8.35104 7.1026 4.61771 4.96927 2.08438 1.83594C1.61771 2.63594 1.41771 3.5026 1.41771 4.5026C1.41771 6.3026 2.35104 7.9026 3.68437 8.9026C2.81771 8.9026 2.08437 8.63594 1.35104 8.23594V8.3026C1.35104 10.8359 3.08438 12.9693 5.48438 13.4359C5.08438 13.5693 4.61771 13.6359 4.15104 13.6359C3.81771 13.6359 3.48438 13.6359 3.21771 13.5693C3.88438 15.6359 5.75104 17.1693 8.01771 17.2359C6.28438 18.6359 4.08438 19.5026 1.68438 19.5026C1.28438 19.5026 0.884375 19.5026 0.484375 19.4359C2.75104 20.9026 5.41771 21.7693 8.35104 21.7693C17.751 21.7693 22.951 13.7693 22.951 6.83594V6.16927C23.951 5.43594 24.8177 4.5026 25.4844 3.43594C24.551 3.83594 23.551 4.16927 22.551 4.23594C23.4177 3.5026 24.2177 2.5026 24.6177 1.23594C23.6177 1.83594 22.551 2.3026 21.351 2.5026Z",
|
||||||
|
],
|
||||||
|
add: [
|
||||||
|
"M12.5 2.27273C6.85163 2.27273 2.27273 6.85163 2.27273 12.5C2.27273 18.1484 6.85163 22.7273 12.5 22.7273C18.1484 22.7273 22.7273 18.1484 22.7273 12.5C22.7273 6.85163 18.1484 2.27273 12.5 2.27273ZM0 12.5C0 5.59644 5.59644 0 12.5 0C19.4036 0 25 5.59644 25 12.5C25 19.4036 19.4036 25 12.5 25C5.59644 25 0 19.4036 0 12.5Z",
|
||||||
|
"M12.5 7.29167C13.0753 7.29167 13.5417 7.75804 13.5417 8.33333V16.6667C13.5417 17.242 13.0753 17.7083 12.5 17.7083C11.9247 17.7083 11.4583 17.242 11.4583 16.6667V8.33333C11.4583 7.75804 11.9247 7.29167 12.5 7.29167Z",
|
||||||
|
"M7.29167 12.5C7.29167 11.9247 7.75804 11.4583 8.33333 11.4583H16.6667C17.242 11.4583 17.7083 11.9247 17.7083 12.5C17.7083 13.0753 17.242 13.5417 16.6667 13.5417H8.33333C7.75804 13.5417 7.29167 13.0753 7.29167 12.5Z",
|
||||||
|
],
|
||||||
|
addcolumn: [
|
||||||
|
"M10 3c.552 0 1 .448 1 1v16c0 .552-.448 1-1 1H4c-.552 0-1-.448-1-1V4c0-.552.448-1 1-1h6zM9 5H5v14h4V5zm9 2c2.761 0 5 2.239 5 5s-2.239 5-5 5-5-2.239-5-5 2.239-5 5-5zm1 2h-2v1.999L15 11v2l2-.001V15h2v-2.001L21 13v-2l-2-.001V9z",
|
||||||
|
],
|
||||||
|
addrow: [
|
||||||
|
"M12 13c2.761 0 5 2.239 5 5s-2.239 5-5 5-5-2.239-5-5 2.239-5 5-5zm1 2h-2v1.999L9 17v2l2-.001V21h2v-2.001L15 19v-2l-2-.001V15zm7-12c.552 0 1 .448 1 1v6c0 .552-.448 1-1 1H4c-.552 0-1-.448-1-1V4c0-.552.448-1 1-1h16zM5 5v4h14V5H5z",
|
||||||
|
],
|
||||||
|
view: [
|
||||||
|
"M12 3c5.392 0 9.878 3.88 10.819 9-.94 5.12-5.427 9-10.819 9-5.392 0-9.878-3.88-10.819-9C2.121 6.88 6.608 3 12 3zm0 16a9.005 9.005 0 0 0 8.777-7 9.005 9.005 0 0 0-17.554 0A9.005 9.005 0 0 0 12 19zm0-2.5a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9zm0-2a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z",
|
||||||
|
],
|
||||||
|
table: [
|
||||||
|
"M4 8h16V5H4v3zm10 11v-9h-4v9h4zm2 0h4v-9h-4v9zm-8 0v-9H4v9h4zM3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z",
|
||||||
|
],
|
||||||
|
edit: [
|
||||||
|
"M15.728 9.686l-1.414-1.414L5 17.586V19h1.414l9.314-9.314zm1.414-1.414l1.414-1.414-1.414-1.414-1.414 1.414 1.414 1.414zM7.242 21H3v-4.243L16.435 3.322a1 1 0 0 1 1.414 0l2.829 2.829a1 1 0 0 1 0 1.414L7.243 21z",
|
||||||
|
],
|
||||||
|
delete: [
|
||||||
|
"M17 6h5v2h-2v13a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V8H2V6h5V3a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v3zm1 2H6v12h12V8zM9 4v2h6V4H9z",
|
||||||
|
],
|
||||||
|
close: [
|
||||||
|
"M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-11.414L9.172 7.757 7.757 9.172 10.586 12l-2.829 2.828 1.415 1.415L12 13.414l2.828 2.829 1.415-1.415L13.414 12l2.829-2.828-1.415-1.415L12 10.586z",
|
||||||
|
],
|
||||||
|
arrowup: ["M12 8l6 6H6z"],
|
||||||
|
arrowdown: ["M12 16l-6-6h12z"],
|
||||||
|
arrowleft: ["M8 12l6-6v12z"],
|
||||||
|
arrowright: ["M16 12l-6 6V6z"],
|
||||||
|
search: [
|
||||||
|
"M18.031 16.617l4.283 4.282-1.415 1.415-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9 9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617zm-2.006-.742A6.977 6.977 0 0 0 18 11c0-3.868-3.133-7-7-7-3.868 0-7 3.132-7 7 0 3.867 3.132 7 7 7a6.977 6.977 0 0 0 4.875-1.975l.15-.15z",
|
||||||
|
],
|
||||||
|
settings: [
|
||||||
|
"M2.132 13.63a9.942 9.942 0 0 1 0-3.26c1.102.026 2.092-.502 2.477-1.431.385-.93.058-2.004-.74-2.763a9.942 9.942 0 0 1 2.306-2.307c.76.798 1.834 1.125 2.764.74.93-.385 1.457-1.376 1.43-2.477a9.942 9.942 0 0 1 3.262 0c-.027 1.102.501 2.092 1.43 2.477.93.385 2.004.058 2.763-.74a9.942 9.942 0 0 1 2.307 2.306c-.798.76-1.125 1.834-.74 2.764.385.93 1.376 1.457 2.477 1.43a9.942 9.942 0 0 1 0 3.262c-1.102-.027-2.092.501-2.477 1.43-.385.93-.058 2.004.74 2.763a9.942 9.942 0 0 1-2.306 2.307c-.76-.798-1.834-1.125-2.764-.74-.93.385-1.457 1.376-1.43 2.477a9.942 9.942 0 0 1-3.262 0c.027-1.102-.501-2.092-1.43-2.477-.93-.385-2.004-.058-2.763.74a9.942 9.942 0 0 1-2.307-2.306c.798-.76 1.125-1.834.74-2.764-.385-.93-1.376-1.457-2.477-1.43zM12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z",
|
||||||
|
],
|
||||||
|
preview: [
|
||||||
|
"M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zM10.622 8.415a.4.4 0 0 0-.622.332v6.506a.4.4 0 0 0 .622.332l4.879-3.252a.4.4 0 0 0 0-.666l-4.88-3.252z",
|
||||||
|
],
|
||||||
|
adduser: [
|
||||||
|
"M14 14.252V22H4a8 8 0 0 1 10-7.748zM12 13c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm6 4v-3h2v3h3v2h-3v3h-2v-3h-3v-2h3z",
|
||||||
|
],
|
||||||
|
plugin: [
|
||||||
|
"M13 18v2h6v2h-6a2 2 0 0 1-2-2v-2H8a4 4 0 0 1-4-4v-4h16v4a4 4 0 0 1-4 4h-3zm3-12h3a1 1 0 0 1 1 1v2H4V7a1 1 0 0 1 1-1h3V2h2v4h4V2h2v4zm-4 8.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2z",
|
||||||
|
],
|
||||||
|
help: [
|
||||||
|
"M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm2-1.645A3.502 3.502 0 0 0 12 6.5a3.501 3.501 0 0 0-3.433 2.813l1.962.393A1.5 1.5 0 1 1 12 11.5a1 1 0 0 0-1 1V14h2v-.645z",
|
||||||
|
],
|
||||||
|
sortdescending: [
|
||||||
|
"M19 3l4 5h-3v12h-2V8h-3l4-5zm-5 15v2H3v-2h11zm0-7v2H3v-2h11zm-2-7v2H3V4h9z",
|
||||||
|
],
|
||||||
|
sortascending: [
|
||||||
|
"M20 4v12h3l-4 5-4-5h3V4h2zm-8 14v2H3v-2h9zm2-7v2H3v-2h11zm0-7v2H3V4h11z",
|
||||||
|
],
|
||||||
|
calculate: [
|
||||||
|
"M4 2h16a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1zm1 2v16h14V4H5zm2 2h10v4H7V6zm0 6h2v2H7v-2zm0 4h2v2H7v-2zm4-4h2v2h-2v-2zm0 4h2v2h-2v-2zm4-4h2v6h-2v-6z",
|
||||||
|
],
|
||||||
|
filter: [
|
||||||
|
"M21 4v2h-1l-5 7.5V22H9v-8.5L4 6H3V4h18zM6.404 6L11 12.894V20h2v-7.106L17.596 6H6.404z",
|
||||||
|
],
|
||||||
|
addfill: [
|
||||||
|
"M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-11H7v2h4v4h2v-4h4v-2h-4V7h-2v4z",
|
||||||
|
],
|
||||||
|
group: [
|
||||||
|
"M11 4h10v2H11V4zm0 4h6v2h-6V8zm0 6h10v2H11v-2zm0 4h6v2h-6v-2zM3 4h6v6H3V4zm2 2v2h2V6H5zm-2 8h6v6H3v-6zm2 2v2h2v-2H5z",
|
||||||
|
],
|
||||||
|
download: [
|
||||||
|
"M13 10h5l-6 6-6-6h5V3h2v7zm-9 9h16v-7h2v8a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-8h2v7z",
|
||||||
|
],
|
||||||
|
lightning: [
|
||||||
|
"M13 9h8L11 24v-9H4l9-15v9zm-2 2V7.22L7.532 13H13v4.394L17.263 11H11z",
|
||||||
|
],
|
||||||
|
closeline: [
|
||||||
|
"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z",
|
||||||
|
],
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<script>
|
||||||
|
export let href,
|
||||||
|
icon,
|
||||||
|
title,
|
||||||
|
active = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a {href} target="_blank" class="nav-item" class:active>
|
||||||
|
{#if icon}
|
||||||
|
<span class="nav-item-icon">
|
||||||
|
<svelte:component this={icon} />
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
{#if title}
|
||||||
|
<div class="nav-item-title">{title}</div>
|
||||||
|
{/if}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.nav-item {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 12px;
|
||||||
|
height: 2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:hover {
|
||||||
|
background-color: var(--grey-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item-title {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
color: var(--ink);
|
||||||
|
font-weight: 400;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item-icon {
|
||||||
|
color: var(--grey-7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Home from "./Home.svelte";
|
||||||
|
import Contribution from "../Icons/Contribution.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<View name="default">
|
||||||
|
<Home
|
||||||
|
href="https://github.com/Budibase/budibase/blob/master/CONTRIBUTING.md"
|
||||||
|
title="Contribute"
|
||||||
|
icon={Contribution} />
|
||||||
|
</View>
|
||||||
|
<View name="active">
|
||||||
|
<Home
|
||||||
|
active
|
||||||
|
href="https://github.com/Budibase/budibase/blob/master/CONTRIBUTING.md"
|
||||||
|
title="Contribute"
|
||||||
|
icon={Contribution} />
|
||||||
|
</View>
|
|
@ -0,0 +1,85 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export let thin = false
|
||||||
|
export let name,
|
||||||
|
show = false
|
||||||
|
|
||||||
|
const capitalize = name => name[0].toUpperCase() + name.slice(1)
|
||||||
|
|
||||||
|
const onHeaderClick = () => {
|
||||||
|
show = !show
|
||||||
|
if (show) {
|
||||||
|
dispatch("open")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="property-group-container" class:thin>
|
||||||
|
<div class="property-group-name" on:click={onHeaderClick}>
|
||||||
|
<div class:thin class="name">{capitalize(name)}</div>
|
||||||
|
<div class="icon">
|
||||||
|
<i class={show ? 'ri-arrow-down-s-fill' : 'ri-arrow-left-s-fill'} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="property-panel" class:show>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.property-group-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: auto;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: var(--border-radius-m);
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-group-name {
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
text-align: left;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.14px;
|
||||||
|
color: var(--ink);
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.name.thin {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
flex: 0 0 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-panel {
|
||||||
|
/* height: 0px;
|
||||||
|
overflow: hidden; */
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show {
|
||||||
|
/* overflow: auto;
|
||||||
|
height: auto; */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
margin-top: var(--spacing-m);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,53 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import DetailSummary from "./DetailSummary.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css" rel="stylesheet">
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<View name="default">
|
||||||
|
<div>
|
||||||
|
<DetailSummary name="Category 1">
|
||||||
|
<span>1</span>
|
||||||
|
<span>2</span>
|
||||||
|
<span>3</span>
|
||||||
|
<span>4</span>
|
||||||
|
</DetailSummary>
|
||||||
|
<DetailSummary name="Category 2">
|
||||||
|
<span>1</span>
|
||||||
|
<span>2</span>
|
||||||
|
<span>3</span>
|
||||||
|
<span>4</span>
|
||||||
|
</DetailSummary>
|
||||||
|
</div>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="thin">
|
||||||
|
<div>
|
||||||
|
<DetailSummary thin name="Category 1">
|
||||||
|
<span>1</span>
|
||||||
|
<span>2</span>
|
||||||
|
<span>3</span>
|
||||||
|
<span>4</span>
|
||||||
|
</DetailSummary>
|
||||||
|
<DetailSummary thin name="Category 2">
|
||||||
|
<span>1</span>
|
||||||
|
<span>2</span>
|
||||||
|
<span>3</span>
|
||||||
|
<span>4</span>
|
||||||
|
</DetailSummary>
|
||||||
|
</div>
|
||||||
|
</View>
|
|
@ -0,0 +1,33 @@
|
||||||
|
<script>
|
||||||
|
import Input from "../Form/Input.svelte"
|
||||||
|
|
||||||
|
export let categories = [
|
||||||
|
{
|
||||||
|
name: "Customers List - Data Row",
|
||||||
|
items: [
|
||||||
|
{ name: "Name", id: "chjaHICHc82h2" },
|
||||||
|
{ name: "Created", id: "chjaHICgr56Hc82h2" },
|
||||||
|
{ name: "Status", id: "chjaHICHc8646462h2" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Home Screen Components",
|
||||||
|
items: [{ name: "Title", id: "chjaHICHc82h2" }],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<Input thin placeholder="Search" />
|
||||||
|
{#each categories as { name, items }}
|
||||||
|
<div class="title">{name}</div>
|
||||||
|
<ul>
|
||||||
|
{#each items as { name, id }}
|
||||||
|
<li>{name}</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<script>
|
||||||
|
import Search from "./Search.svelte";
|
||||||
|
import { View } from "svench";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<View name="Name">
|
||||||
|
<Search />
|
||||||
|
</View>
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script>
|
||||||
|
import Input from "../Form/Input.svelte"
|
||||||
|
let value = ""
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Input label="Your Name" bind:value />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
font-size: var(--font-size-m);
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--grey-7);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import Context from "../context"
|
||||||
|
|
||||||
|
const { hide } = getContext(Context.Modal)
|
||||||
|
|
||||||
|
let count = 0
|
||||||
|
const clicks = 5
|
||||||
|
$: if (count === clicks) hide()
|
||||||
|
$: remaining = clicks - count
|
||||||
|
|
||||||
|
function increment() {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div on:click={increment}>
|
||||||
|
Click me
|
||||||
|
{remaining}
|
||||||
|
more time{remaining === 1 ? '' : 's'}
|
||||||
|
to close this modal!
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
padding: 40px;
|
||||||
|
background-color: var(--purple);
|
||||||
|
color: white;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
div:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,116 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher, setContext } from "svelte"
|
||||||
|
import { fade, fly } from "svelte/transition"
|
||||||
|
import Portal from "svelte-portal"
|
||||||
|
import Context from "../context"
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export let padding = true
|
||||||
|
export let width = undefined
|
||||||
|
export let border = true
|
||||||
|
|
||||||
|
let visible = false
|
||||||
|
$: dispatch(visible ? "show" : "hide")
|
||||||
|
|
||||||
|
export function show() {
|
||||||
|
if (visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hide() {
|
||||||
|
if (!visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKey(e) {
|
||||||
|
if (visible && e.key === "Escape") {
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setContext(Context.Modal, { show, hide })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={handleKey} />
|
||||||
|
|
||||||
|
{#if visible}
|
||||||
|
<Portal target="body">
|
||||||
|
<div
|
||||||
|
class="overlay"
|
||||||
|
on:click|self={hide}
|
||||||
|
transition:fade={{ duration: 200 }}>
|
||||||
|
<div
|
||||||
|
class="scroll-wrapper"
|
||||||
|
on:click|self={hide}
|
||||||
|
transition:fly={{ y: 30, duration: 200 }}>
|
||||||
|
<div class="content-wrapper" on:click|self={hide}>
|
||||||
|
<div
|
||||||
|
class="modal"
|
||||||
|
class:padding
|
||||||
|
class:border
|
||||||
|
style={width ? `flex: 0 0 ${width}` : ''}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Portal>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.overlay {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.25);
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-wrapper {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background-color: var(--background);
|
||||||
|
display: grid;
|
||||||
|
align-items: stretch;
|
||||||
|
box-shadow: 0 0 4rem 1.5rem rgba(0, 0, 0, 0.15);
|
||||||
|
position: relative;
|
||||||
|
flex: 0 0 400px;
|
||||||
|
margin: 2rem 0;
|
||||||
|
border-radius: var(--border-radius-m);
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
.modal.padding {
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
.modal.border {
|
||||||
|
border: var(--border-dark);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,117 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Modal from "./Modal.svelte";
|
||||||
|
import ModalContent from "./ModalContent.svelte";
|
||||||
|
import Button from "../Button/Button.svelte";
|
||||||
|
import Content from "./Content.svelte";
|
||||||
|
import QuizModal from "./QuizModal.svelte";
|
||||||
|
import CustomContent from "./CustomContent.svelte";
|
||||||
|
|
||||||
|
let modal1
|
||||||
|
let modal2
|
||||||
|
let modal3
|
||||||
|
|
||||||
|
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
async function longTask() {
|
||||||
|
await sleep(3000)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
p, span {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
display: inline-block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: var(--spacing-xs) var(--spacing-s);
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
color: var(--red-dark);
|
||||||
|
border-radius: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h3>Modals</h3>
|
||||||
|
<p>
|
||||||
|
Modals provide a means to render content in front of everything else on a page.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The modal module in BBUI exposes two
|
||||||
|
separate components to provide this functionality; a <code>Modal</code> component to control visibility of content,
|
||||||
|
and a <code>ModalContent</code> component to quickly construct the typical content - although this is optional.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
One of the common problems with modals and popups is stale state reappearing after hiding and showing the content
|
||||||
|
again, since the state hasn't been garbage collected if a component controls its own visibility. This is handled for
|
||||||
|
you when using the <code>Modal</code> component as it will fully unmount child components, properly resetting state
|
||||||
|
every time it appears.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<p>Use ModalContent to render typical modal content.</p>
|
||||||
|
<View name="Simple Confirmation Modal">
|
||||||
|
<Button primary on:click={modal1.show}>Delete Record</Button>
|
||||||
|
<Modal bind:this={modal1}>
|
||||||
|
<ModalContent title="Confirm Deletion" confirmText="Delete">
|
||||||
|
<span>Are you sure you want to delete this record?</span>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<p>
|
||||||
|
Width can be specified as a prop to a <code>Modal</code>. Any additional <code>ModalContent</code> props provided
|
||||||
|
will be passed to the confirmation button.
|
||||||
|
</p>
|
||||||
|
<View name="Different Buttons and Width">
|
||||||
|
<Button primary on:click={modal3.show}>Open Modal</Button>
|
||||||
|
<Modal bind:this={modal3} width="250px">
|
||||||
|
<ModalContent
|
||||||
|
title="Confirmation Required"
|
||||||
|
showCancelButton={false}
|
||||||
|
showCloseIcon={false}
|
||||||
|
confirmText="I'm sure!"
|
||||||
|
green
|
||||||
|
large
|
||||||
|
wide
|
||||||
|
>
|
||||||
|
<span>Are you sure you want to do that?</span>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<p>Any content can be rendered inside a <code>Modal</code>. Use context to close the modal from your own components.</p>
|
||||||
|
<View name="Custom Content">
|
||||||
|
<Button primary on:click={modal1.show}>Open Modal</Button>
|
||||||
|
<Modal bind:this={modal1} padding={false} border={false}>
|
||||||
|
<CustomContent/>
|
||||||
|
</Modal>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<p>Async functions passed in as the onConfirm prop will make the modal wait until the callback is completed.</p>
|
||||||
|
<View name="Async Callbacks">
|
||||||
|
<Button primary on:click={modal2.show}>Long Task</Button>
|
||||||
|
<Modal bind:this={modal2}>
|
||||||
|
<ModalContent
|
||||||
|
title="Perform Long Task"
|
||||||
|
confirmText="Submit"
|
||||||
|
onConfirm={longTask}
|
||||||
|
>
|
||||||
|
<span>Pressing submit will wait 3 seconds before finishing and disable the confirm button until it's done.</span>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<p>Returning false from a onConfirm callback will prevent the modal being closed.</p>
|
||||||
|
<View name="Callback Failure Handling">
|
||||||
|
<Button primary on:click={modal3.show}>Open Quiz</Button>
|
||||||
|
<Modal bind:this={modal3}>
|
||||||
|
<QuizModal />
|
||||||
|
</Modal>
|
||||||
|
</View>
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher, getContext } from "svelte"
|
||||||
|
import Button from "../Button/Button.svelte"
|
||||||
|
import Icon from "../Icons/Icon.svelte"
|
||||||
|
import Context from "../context"
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export let title = undefined
|
||||||
|
export let cancelText = "Cancel"
|
||||||
|
export let confirmText = "Confirm"
|
||||||
|
export let showCancelButton = true
|
||||||
|
export let showConfirmButton = true
|
||||||
|
export let showCloseIcon = true
|
||||||
|
export let onConfirm = undefined
|
||||||
|
export let disabled = false
|
||||||
|
|
||||||
|
const { hide } = getContext(Context.Modal)
|
||||||
|
let loading = false
|
||||||
|
$: confirmDisabled = disabled || loading
|
||||||
|
|
||||||
|
async function confirm() {
|
||||||
|
loading = true
|
||||||
|
if (!onConfirm || (await onConfirm()) !== false) {
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="modal-content">
|
||||||
|
{#if title}
|
||||||
|
<header>
|
||||||
|
<h5>{title}</h5>
|
||||||
|
<div class="header-content">
|
||||||
|
<slot name="header" />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
{/if}
|
||||||
|
<slot />
|
||||||
|
{#if showCancelButton || showConfirmButton}
|
||||||
|
<footer>
|
||||||
|
<div class="footer-content">
|
||||||
|
<slot name="footer" />
|
||||||
|
</div>
|
||||||
|
<div class="buttons">
|
||||||
|
{#if showCancelButton}
|
||||||
|
<Button secondary on:click={hide}>{cancelText}</Button>
|
||||||
|
{/if}
|
||||||
|
{#if showConfirmButton}
|
||||||
|
<Button
|
||||||
|
primary
|
||||||
|
{...$$restProps}
|
||||||
|
disabled={confirmDisabled}
|
||||||
|
on:click={confirm}>
|
||||||
|
{confirmText}
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
{/if}
|
||||||
|
{#if showCloseIcon}
|
||||||
|
<div class="close-icon" on:click={hide}>
|
||||||
|
<Icon name="closeline" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.modal-content {
|
||||||
|
display: grid;
|
||||||
|
position: relative;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 40px;
|
||||||
|
}
|
||||||
|
header h5 {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: var(--font-size-m);
|
||||||
|
}
|
||||||
|
.close-icon:hover {
|
||||||
|
color: var(--grey-6);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.close-icon :global(svg) {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,54 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import ModalContent from "./ModalContent.svelte"
|
||||||
|
import Input from "../Form/Input.svelte"
|
||||||
|
|
||||||
|
let modal
|
||||||
|
let answer
|
||||||
|
let error
|
||||||
|
|
||||||
|
export function show() {
|
||||||
|
modal.show()
|
||||||
|
}
|
||||||
|
export function hide() {
|
||||||
|
modal.hide
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetState() {
|
||||||
|
answer = undefined
|
||||||
|
error = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
async function answerQuiz() {
|
||||||
|
const correct = answer === "8"
|
||||||
|
error = !correct
|
||||||
|
return correct
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ModalContent
|
||||||
|
title="Quick Maths"
|
||||||
|
bind:this={modal}
|
||||||
|
confirmText="Submit"
|
||||||
|
onConfirm={answerQuiz}
|
||||||
|
on:show={resetState}>
|
||||||
|
{#if error}
|
||||||
|
<p class="error">Wrong answer! Try again.</p>
|
||||||
|
{/if}
|
||||||
|
<p>What is 4 + 4?</p>
|
||||||
|
<Input label="Answer" bind:value={answer} />
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
}
|
||||||
|
p.error {
|
||||||
|
color: #e26d69;
|
||||||
|
background-color: #ffe6e6;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,68 @@
|
||||||
|
<script>
|
||||||
|
import Portal from "svelte-portal"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import positionDropdown from "../Actions/position_dropdown"
|
||||||
|
import clickOutside from "../Actions/click_outside"
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export let anchor
|
||||||
|
export let align = "right"
|
||||||
|
let open = null
|
||||||
|
|
||||||
|
export const show = () => {
|
||||||
|
open = true
|
||||||
|
dispatch("show")
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hide = () => {
|
||||||
|
open = false
|
||||||
|
dispatch("hide")
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEscape(e) {
|
||||||
|
if (open && e.key === "Escape") {
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if open}
|
||||||
|
<Portal>
|
||||||
|
<div
|
||||||
|
tabindex="0"
|
||||||
|
class:open
|
||||||
|
use:positionDropdown={{ anchor, align }}
|
||||||
|
use:clickOutside={hide}
|
||||||
|
on:keydown={handleEscape}
|
||||||
|
class="menu-container">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</Portal>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.menu-container {
|
||||||
|
position: fixed;
|
||||||
|
margin-top: var(--spacing-xs);
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
outline: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
opacity: 0;
|
||||||
|
min-width: 400px;
|
||||||
|
z-index: 2;
|
||||||
|
color: var(--ink);
|
||||||
|
height: fit-content !important;
|
||||||
|
border: var(--border-dark);
|
||||||
|
border-radius: var(--border-radius-m);
|
||||||
|
transform: scale(0);
|
||||||
|
transition: opacity 0.13s linear, transform 0.12s cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: var(--background);
|
||||||
|
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.open {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,121 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Popover from "./Popover.svelte";
|
||||||
|
import Button from "../Button/Button.svelte";
|
||||||
|
import TextButton from "../Button/TextButton.svelte";
|
||||||
|
import Icon from "../Icons/Icon.svelte";
|
||||||
|
import Input from "../Form/Input.svelte";
|
||||||
|
import Select from "../Form/Select.svelte";
|
||||||
|
|
||||||
|
let anchorRight;
|
||||||
|
let anchorLeft;
|
||||||
|
let dropdownRight;
|
||||||
|
let dropdownLeft;
|
||||||
|
|
||||||
|
const options = ["Column 1", "Column 2", "Super cool column"];
|
||||||
|
const option1s = ["Is", "Is not", "Contains" , "Does not contain"];
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.button-group {
|
||||||
|
margin-top: var(--spacing-l);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: var(--font-size-m);
|
||||||
|
margin: 0 0 var(--spacing-l) 0;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: [boolean-start] 60px [boolean-end property-start] 120px [property-end opererator-start] 110px [operator-end value-start] auto [value-end menu-start] 32px [menu-end];
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
margin-bottom: var(--spacing-l);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin:0;
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<View name="Simple popover">
|
||||||
|
<div bind:this={anchorLeft}>
|
||||||
|
<Button text on:click={dropdownLeft.show}>
|
||||||
|
<Icon name="view" />
|
||||||
|
Add View
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Popover bind:this={dropdownLeft} anchor={anchorLeft} align="left">
|
||||||
|
<h6>Add New View</h6>
|
||||||
|
<Input thin placeholder="Enter your name" />
|
||||||
|
<div class="button-group">
|
||||||
|
<Button secondary on:click={() => alert('Clicked!')}>Cancel</Button>
|
||||||
|
<Button primary on:click={() => alert('Clicked!')}>Add New View</Button>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Stacked columns">
|
||||||
|
<div bind:this={anchorRight}>
|
||||||
|
<Button text on:click={dropdownRight.show}>
|
||||||
|
<Icon name="addrow" />
|
||||||
|
Add Row
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Popover bind:this={dropdownRight} anchor={anchorRight}>
|
||||||
|
<h6>Add New Row</h6>
|
||||||
|
<div class="input-group-column">
|
||||||
|
<Input thin placeholder="Enter your string" />
|
||||||
|
<Input thin placeholder="Enter your string" />
|
||||||
|
<Input thin placeholder="Enter your string" />
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<Button secondary on:click={() => alert('Clicked!')}>Cancel</Button>
|
||||||
|
<Button primary on:click={() => alert('Clicked!')}>Add New Row</Button>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Multiple inputs in a row">
|
||||||
|
<div bind:this={anchorLeft}>
|
||||||
|
<Button text on:click={dropdownLeft.show}>
|
||||||
|
<Icon name="filter" />
|
||||||
|
Add Filter
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Popover bind:this={dropdownLeft} anchor={anchorLeft} align="left">
|
||||||
|
<h6>Add New Filter</h6>
|
||||||
|
<div class="input-group-row">
|
||||||
|
<p>Where</p>
|
||||||
|
<Select secondary thin name="Test">
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
<Select secondary thin name="Test">
|
||||||
|
{#each option1s as option1}
|
||||||
|
<option value={option1}>{option1}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
<Input thin placeholder="Enter your name" />
|
||||||
|
<Button text on:click={() => alert('Clicked!')}>
|
||||||
|
<Icon name="close" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Button text on:click={() => alert('Clicked!')}>Add Filter</Button>
|
||||||
|
</Popover>
|
||||||
|
</View>
|
|
@ -0,0 +1,49 @@
|
||||||
|
<script>
|
||||||
|
export let extraSmall = false
|
||||||
|
export let small = false
|
||||||
|
export let medium = false
|
||||||
|
export let large = false
|
||||||
|
export let extraLarge = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<spacer
|
||||||
|
class:extraSmall
|
||||||
|
class:small
|
||||||
|
class:medium
|
||||||
|
class:large
|
||||||
|
class:extraLarge />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
spacer {
|
||||||
|
display: block;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extraSmall {
|
||||||
|
height: var(--spacing-xs);
|
||||||
|
width: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
height: var(--spacing-s);
|
||||||
|
width: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.medium {
|
||||||
|
height: var(--spacing-m);
|
||||||
|
width: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.large {
|
||||||
|
height: var(--spacing-l);
|
||||||
|
width: var(--spacing-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
.extraLarge {
|
||||||
|
height: var(--spacing-xl);
|
||||||
|
width: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,51 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
import Spacer from "./Spacer.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.block {
|
||||||
|
background: black;
|
||||||
|
height: 100px;
|
||||||
|
width: 100px;
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<View name="extraSmall">
|
||||||
|
<div class="block"></div>
|
||||||
|
<Spacer extraSmall />
|
||||||
|
<div class="block"></div>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Small">
|
||||||
|
<div class="block"></div>
|
||||||
|
<Spacer small />
|
||||||
|
<div class="block"></div>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Medium">
|
||||||
|
<div class="block"></div>
|
||||||
|
<Spacer medium />
|
||||||
|
<div class="block"></div>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="Large">
|
||||||
|
<div class="horizontal">
|
||||||
|
<div class="block"></div>
|
||||||
|
<Spacer large />
|
||||||
|
<div class="block"></div>
|
||||||
|
</div>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View name="extraLarge">
|
||||||
|
<div class="horizontal">
|
||||||
|
<div class="block"></div>
|
||||||
|
<Spacer extraLarge />
|
||||||
|
<div class="block"></div>
|
||||||
|
</div>
|
||||||
|
</View>
|
|
@ -0,0 +1,78 @@
|
||||||
|
<script>
|
||||||
|
import { flip } from 'svelte/animate';
|
||||||
|
import { fly } from "svelte/transition"
|
||||||
|
import { View } from "svench";
|
||||||
|
import { notifications } from "./notifications";
|
||||||
|
|
||||||
|
export let themes = {
|
||||||
|
danger: "#E26D69",
|
||||||
|
success: "#84C991",
|
||||||
|
warning: "#f0ad4e",
|
||||||
|
info: "#5bc0de",
|
||||||
|
default: "#aaaaaa",
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
## Notification Store
|
||||||
|
|
||||||
|
This custom can be used to display toast messages. It has 5 different methods: `send`, `danger`, `warning`, `success`, `info`.
|
||||||
|
|
||||||
|
|
||||||
|
<View name="danger">
|
||||||
|
<button on:click={() => notifications.danger('This is a danger!')}>Danger</button>
|
||||||
|
</View>
|
||||||
|
<View name="warning">
|
||||||
|
<button on:click={() => notifications.warning('This is a warning!')}>Warning</button>
|
||||||
|
</View>
|
||||||
|
<View name="success">
|
||||||
|
<button on:click={() => notifications.success('This is a success!')}>Success</button>
|
||||||
|
</View>
|
||||||
|
<View name="info">
|
||||||
|
<button on:click={() => notifications.info('This is an info toast!')}>Info</button>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<div class="notifications">
|
||||||
|
{#each $notifications as notification (notification.id)}
|
||||||
|
<div
|
||||||
|
animate:flip
|
||||||
|
class="toast"
|
||||||
|
style="background: {themes[notification.type]};"
|
||||||
|
transition:fly={{ y: -30 }}>
|
||||||
|
<div class="content">{notification.message}</div>
|
||||||
|
{#if notification.icon}<i class={notification.icon} />{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.notifications {
|
||||||
|
position: fixed;
|
||||||
|
top: 10px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
/* The toasts now support being auto sized, so this static width could be removed */
|
||||||
|
width: 40vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 10px;
|
||||||
|
display: block;
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { writable, derived } from "svelte/store"
|
||||||
|
|
||||||
|
const NOTIFICATION_TIMEOUT = 3000
|
||||||
|
|
||||||
|
const createNotificationStore = () => {
|
||||||
|
const _notifications = writable([])
|
||||||
|
|
||||||
|
const send = (message, type = "default") => {
|
||||||
|
_notifications.update(state => {
|
||||||
|
return [...state, { id: id(), type, message }]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifications = derived(_notifications, ($_notifications, set) => {
|
||||||
|
set($_notifications)
|
||||||
|
if ($_notifications.length > 0) {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
_notifications.update(state => {
|
||||||
|
state.shift()
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
set($_notifications)
|
||||||
|
}, NOTIFICATION_TIMEOUT)
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const { subscribe } = notifications
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
send,
|
||||||
|
danger: msg => send(msg, "danger"),
|
||||||
|
warning: msg => send(msg, "warning"),
|
||||||
|
info: msg => send(msg, "info"),
|
||||||
|
success: msg => send(msg, "success"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function id() {
|
||||||
|
return (
|
||||||
|
"_" +
|
||||||
|
Math.random()
|
||||||
|
.toString(36)
|
||||||
|
.substr(2, 9)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const notifications = createNotificationStore()
|
|
@ -0,0 +1,72 @@
|
||||||
|
<script>
|
||||||
|
export let extraSmall = false,
|
||||||
|
small = false,
|
||||||
|
medium = false,
|
||||||
|
large = false,
|
||||||
|
extraLarge = false,
|
||||||
|
white = false,
|
||||||
|
grey = false,
|
||||||
|
black = false,
|
||||||
|
lh = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p
|
||||||
|
class="bb-body"
|
||||||
|
class:extraSmall
|
||||||
|
class:small
|
||||||
|
class:medium
|
||||||
|
class:large
|
||||||
|
class:extraLarge
|
||||||
|
class:white
|
||||||
|
class:grey
|
||||||
|
class:black
|
||||||
|
class:lh>
|
||||||
|
<slot />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bb-body {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-weight: 400;
|
||||||
|
text-rendering: var(--text-render);
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: var(--font-size-m);
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extraSmall {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.medium {
|
||||||
|
font-size: var(--font-size-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.large {
|
||||||
|
font-size: var(--font-size-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
.extraLarge {
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.white {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grey {
|
||||||
|
color: var(--grey-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.black {
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lh {
|
||||||
|
line-height: 1.75;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,175 @@
|
||||||
|
<script>
|
||||||
|
import { View } from "svench";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>Borders</h1>
|
||||||
|
<p>Budibase has 2 border variables, light and dark.</p>
|
||||||
|
<p><strong>Light</strong> is for layouts.</p>
|
||||||
|
<p><strong>Dark</strong> is for components.</p>
|
||||||
|
<code>border: var(--border-light);</code>
|
||||||
|
<div class="border-light"></div>
|
||||||
|
<code>border: var(--border-dark);</code>
|
||||||
|
<div class="border-dark"></div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>Border Radius</h2>
|
||||||
|
<p>Budibase has 5 border-radius variables:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Var</th>
|
||||||
|
<th>rem</th>
|
||||||
|
<th>px</th>
|
||||||
|
<th>Visual</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Extra Small</td>
|
||||||
|
<td>var(--border-radius-xs)</td>
|
||||||
|
<td>0.125</td>
|
||||||
|
<td>2</td>
|
||||||
|
<td><div class="border-radius-xs">Extra small</div></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Small</td>
|
||||||
|
<td>var(--border-radius-s)</td>
|
||||||
|
<td>0.35</td>
|
||||||
|
<td>5.6px</td>
|
||||||
|
<td><div class="border-radius-s">Small</div></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Medium</td>
|
||||||
|
<td>var(--border-radius-m)</td>
|
||||||
|
<td>0.5</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td><div class="border-radius-m">Medium</div></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Large</td>
|
||||||
|
<td>var(--border-radius-l)</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>16</td>
|
||||||
|
<td><div class="border-radius-l">Large</div></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Extra Large</td>
|
||||||
|
<td>var(--border-radius-xl)</td>
|
||||||
|
<td>100</td>
|
||||||
|
<td>1600</td>
|
||||||
|
<td><div class="border-radius-xl">Extra large</div></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
table {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
display: inline-block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: var(--spacing-s) var(--spacing-l);
|
||||||
|
margin: var(--spacing-xl) 0 var(--spacing-s) 0;
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
color: var(--red-dark);
|
||||||
|
border-radius: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
border: 1px solid var(--grey-4);
|
||||||
|
text-align: left;
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background-color: var(--grey-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: var(--layout-xl) 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-light {
|
||||||
|
width: 40rem;
|
||||||
|
height: 10rem;
|
||||||
|
background-color: white;
|
||||||
|
border: var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-dark {
|
||||||
|
width: 40rem;
|
||||||
|
height: 10rem;
|
||||||
|
background-color: white;
|
||||||
|
border: var(--border-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-radius-xs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
width: 8rem;
|
||||||
|
height: 3rem;
|
||||||
|
background-color: black;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
margin-right: var(--spacing-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-radius-s {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
width: 8rem;
|
||||||
|
height: 3rem;
|
||||||
|
background-color: black;
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
margin-right: var(--spacing-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-radius-m {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
width: 8rem;
|
||||||
|
height: 3rem;
|
||||||
|
background-color: black;
|
||||||
|
border-radius: var(--border-radius-m);
|
||||||
|
margin-right: var(--spacing-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-radius-l {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
width: 8rem;
|
||||||
|
height: 3rem;
|
||||||
|
background-color: black;
|
||||||
|
border-radius: var(--border-radius-l);
|
||||||
|
margin-right: var(--spacing-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-radius-xl {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
width: 8rem;
|
||||||
|
height: 3rem;
|
||||||
|
background-color: black;
|
||||||
|
border-radius: var(--border-radius-xl);
|
||||||
|
margin-right: var(--spacing-l);
|
||||||
|
}
|
||||||
|
</style>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue