Merge pull request #1431 from Budibase/spectrum-bbui

Spectrum Redesign
This commit is contained in:
Andrew Kingston 2021-05-04 09:23:30 +01:00 committed by GitHub
commit d465fd8893
508 changed files with 7353 additions and 17535 deletions

View File

@ -1,11 +1,7 @@
{
"editor.formatOnSave": true,
"eslint.format.enable": true,
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"[svelte]": {
"editor.defaultFormatter": "JamesBirtles.svelte-vscode"
},
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "svelte.svelte-vscode"
}

View File

@ -10,8 +10,8 @@
"eslint-plugin-svelte3": "^2.7.3",
"kill-port": "^1.6.1",
"lerna": "3.14.1",
"prettier": "^1.19.1",
"prettier-plugin-svelte": "^1.4.0",
"prettier": "^2.2.1",
"prettier-plugin-svelte": "^2.2.0",
"rimraf": "^3.0.2",
"rollup-plugin-replace": "^2.2.0",
"svelte": "^3.30.0"
@ -38,5 +38,12 @@
"test:e2e:ci": "lerna run cy:ci",
"build:docker": "cd hosting/scripts/linux/ && ./release-to-docker-hub.sh && cd -",
"build:docker:staging": "cd hosting/scripts/linux/ && ./release-to-docker-hub.sh staging && cd -"
},
"dependencies": {
"@spectrum-css/actionbutton": "^1.0.1",
"@spectrum-css/actiongroup": "^1.0.1",
"@spectrum-css/link": "^3.1.1",
"@spectrum-css/menu": "^3.0.1",
"@spectrum-css/toast": "^3.0.1"
}
}

View File

@ -1,9 +1,9 @@
let Pouch
module.exports.setDB = pouch => {
module.exports.setDB = (pouch) => {
Pouch = pouch
}
module.exports.getDB = dbName => {
module.exports.getDB = (dbName) => {
return new Pouch(dbName)
}

View File

@ -48,7 +48,7 @@ exports.getGroupParams = (id = "", otherProps = {}) => {
* Generates a new global user ID.
* @returns {string} The new user ID which the user doc can be stored under.
*/
exports.generateGlobalUserID = id => {
exports.generateGlobalUserID = (id) => {
return `${DocumentTypes.USER}${SEPARATOR}${id || newid()}`
}
@ -70,7 +70,7 @@ exports.getGlobalUserParams = (globalId, otherProps = {}) => {
* Generates a template ID.
* @param ownerId The owner/user of the template, this could be global or a group level.
*/
exports.generateTemplateID = ownerId => {
exports.generateTemplateID = (ownerId) => {
return `${DocumentTypes.TEMPLATE}${SEPARATOR}${ownerId}${newid()}`
}
@ -123,7 +123,7 @@ const getConfigParams = ({ type, group, user }, otherProps = {}) => {
* @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 determineScopedConfig = async function (db, { type, user, group }) {
const response = await db.allDocs(
getConfigParams(
{ type, user, group },
@ -132,7 +132,7 @@ const determineScopedConfig = async function(db, { type, user, group }) {
}
)
)
const configs = response.rows.map(row => {
const configs = response.rows.map((row) => {
const config = row.doc
// Config is specific to a user and a group

View File

@ -4,7 +4,7 @@ const { v4 } = require("uuid")
const SALT_ROUNDS = env.SALT_ROUNDS || 10
exports.hash = async data => {
exports.hash = async (data) => {
const salt = await bcrypt.genSalt(SALT_ROUNDS)
return bcrypt.hash(data, salt)
}
@ -13,6 +13,6 @@ exports.compare = async (data, encrypted) => {
return bcrypt.compare(data, encrypted)
}
exports.newid = function() {
exports.newid = function () {
return v4().replace(/-/g, "")
}

View File

@ -51,7 +51,7 @@ async function authenticate(token, tokenSecret, profile, done) {
* 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) {
exports.strategyFactory = async function (config) {
try {
const { clientID, clientSecret, callbackURL } = config

View File

@ -3,12 +3,12 @@ const env = require("../../environment")
exports.options = {
secretOrKey: env.JWT_SECRET,
jwtFromRequest: function(ctx) {
jwtFromRequest: function (ctx) {
return ctx.cookies.get(Cookies.Auth)
},
}
exports.authenticate = async function(jwt, done) {
exports.authenticate = async function (jwt, done) {
try {
return done(null, jwt)
} catch (err) {

View File

@ -15,7 +15,7 @@ exports.options = {}
* @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) {
exports.authenticate = async function (email, password, done) {
if (!email) return done(null, false, "Email Required.")
if (!password) return done(null, false, "Password Required.")

View File

@ -22,7 +22,7 @@ function confirmAppId(possibleAppId) {
* @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 => {
exports.getAppId = (ctx) => {
const options = [ctx.headers["x-budibase-app-id"], ctx.params.appId]
if (ctx.subdomains) {
options.push(ctx.subdomains[1])
@ -41,7 +41,7 @@ exports.getAppId = ctx => {
}
let appPath =
ctx.request.headers.referrer ||
ctx.path.split("/").filter(subPath => subPath.startsWith(APP_PREFIX))
ctx.path.split("/").filter((subPath) => subPath.startsWith(APP_PREFIX))
if (!appId && appPath.length !== 0) {
appId = confirmAppId(appPath[0])
}
@ -101,11 +101,11 @@ exports.clearCookie = (ctx, name) => {
* @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 => {
exports.isClient = (ctx) => {
return ctx.headers["x-budibase-type"] === "client"
}
exports.getGlobalUserByEmail = async email => {
exports.getGlobalUserByEmail = async (email) => {
const db = getDB(StaticDatabases.GLOBAL.name)
try {
let users = (
@ -114,7 +114,7 @@ exports.getGlobalUserByEmail = async email => {
include_docs: true,
})
).rows
users = users.map(user => user.doc)
users = users.map((user) => user.doc)
return users.length <= 1 ? users[0] : users
} catch (err) {
if (err != null && err.name === "not_found") {

View File

@ -2,6 +2,7 @@
"name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.",
"version": "1.58.13",
"license": "AGPL-3.0",
"svelte": "src/index.js",
"module": "dist/bbui.es.js",
"exports": {
@ -9,31 +10,24 @@
"import": "./dist/bbui.es.js"
},
"./package.json": "./package.json",
"./dist/style.css": "./dist/style.css"
"./spectrum-icons-rollup.js": "./src/spectrum-icons-rollup.js",
"./spectrum-icons-vite.js": "./src/spectrum-icons-vite.js"
},
"scripts": {
"dev:builder": "vite build",
"build": "vite build"
"build": "rollup -c"
},
"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",
"@rollup/plugin-node-resolve": "^11.2.1",
"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": "^2.45.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"
"rollup-plugin-svelte": "^7.1.0",
"rollup-plugin-terser": "^7.0.2",
"svelte": "^3.37.0"
},
"keywords": [
"svelte"
@ -43,11 +37,44 @@
"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"
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
"@spectrum-css/actionbutton": "^1.0.1",
"@spectrum-css/actiongroup": "^1.0.1",
"@spectrum-css/avatar": "^3.0.2",
"@spectrum-css/button": "^3.0.1",
"@spectrum-css/buttongroup": "^3.0.2",
"@spectrum-css/checkbox": "^3.0.2",
"@spectrum-css/dialog": "^3.0.1",
"@spectrum-css/divider": "^1.0.1",
"@spectrum-css/dropzone": "^3.0.2",
"@spectrum-css/fieldgroup": "^3.0.2",
"@spectrum-css/fieldlabel": "^3.0.1",
"@spectrum-css/icon": "^3.0.1",
"@spectrum-css/illustratedmessage": "^3.0.2",
"@spectrum-css/inputgroup": "^3.0.2",
"@spectrum-css/label": "^2.0.9",
"@spectrum-css/link": "^3.1.1",
"@spectrum-css/menu": "^3.0.1",
"@spectrum-css/modal": "^3.0.1",
"@spectrum-css/picker": "^1.0.1",
"@spectrum-css/popover": "^3.0.1",
"@spectrum-css/progressbar": "^1.0.2",
"@spectrum-css/progresscircle": "^1.0.2",
"@spectrum-css/radio": "^3.0.2",
"@spectrum-css/search": "^3.0.2",
"@spectrum-css/sidenav": "^3.0.2",
"@spectrum-css/switch": "^1.0.2",
"@spectrum-css/table": "^3.0.1",
"@spectrum-css/tabs": "^3.0.1",
"@spectrum-css/tags": "^3.0.2",
"@spectrum-css/textfield": "^3.0.1",
"@spectrum-css/toast": "^3.0.1",
"@spectrum-css/treeview": "^3.0.2",
"@spectrum-css/typography": "^3.0.1",
"@spectrum-css/underlay": "^2.0.9",
"@spectrum-css/vars": "^3.0.1",
"dayjs": "^1.10.4",
"svelte-flatpickr": "^3.1.0",
"svelte-portal": "^1.0.0"
}
}

View File

@ -1,140 +1,25 @@
import * as path from "path"
import svelte from "rollup-plugin-svelte-hot"
import svelte from "rollup-plugin-svelte"
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 { terser } from "rollup-plugin-terser"
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: () => ({
export default {
input: "src/index.js",
output: [{ file: "dist/bundle.mjs", format: "es" }],
output: {
sourcemap: true,
format: "esm",
file: "dist/bbui.es.js",
},
plugins: [
resolve(),
commonjs(),
svelte({
dev: !PRODUCTION,
extensions: [".svelte"],
emitCss: true,
}),
postcss(),
copy({
targets: [
{
src: ".svench/svench.css",
dest: "public",
rename: "global.css",
},
],
}),
resolve(),
commonjs(),
terser(),
json(),
],
}),
}
export default configs[SVENCH ? "svench" : "lib"]()

View File

@ -0,0 +1,66 @@
<script>
import "@spectrum-css/actionbutton/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher()
export let quiet = false
export let emphasized = false
export let selected = false
export let longPressable = false
export let disabled = false
export let icon = ""
export let dataCy = null
export let size = "M"
function longPress(element) {
if (!longPressable) return
let timer
const listener = () => {
timer = setTimeout(() => {
dispatch("longpress")
}, 700)
}
element.addEventListener("pointerdown", listener)
return {
destroy() {
clearTimeout(timer)
element.removeEventListener("pointerdown", longPress)
},
}
}
</script>
<button
data-cy={dataCy}
use:longPress
class:spectrum-ActionButton--quiet={quiet}
class:spectrum-ActionButton--emphasized={emphasized}
class:is-selected={selected}
class="spectrum-ActionButton spectrum-ActionButton--size{size}"
{disabled}
on:longPress
on:click|preventDefault>
{#if longPressable}
<svg
class="spectrum-Icon spectrum-UIIcon-CornerTriangle100 spectrum-ActionButton-hold"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-CornerTriangle100" />
</svg>
{/if}
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--size{size}"
focusable="false"
aria-hidden="true"
aria-label={icon}>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}
{#if $$slots}
<span class="spectrum-ActionButton-label"><slot /></span>
{/if}
</button>

View File

@ -0,0 +1,26 @@
<script>
import "@spectrum-css/actiongroup/dist/index-vars.css"
export let vertical = false
export let justified = false
export let quiet = false
export let compact = false
// Attaches a spectrum-ActionGroup-item class to buttons inside the div
function group(element) {
const buttons = Array.from(element.getElementsByTagName("button"))
buttons.forEach((button) => {
button.classList.add("spectrum-ActionGroup-item")
})
}
</script>
<div
use:group
class:spectrum-ActionGroup--vertical={vertical}
class:spectrum-ActionGroup--justified={justified}
class:spectrum-ActionGroup--quiet={quiet}
class:spectrum-ActionGroup--compact={compact}
class="spectrum-ActionGroup"
>
<slot />
</div>

View File

@ -0,0 +1,38 @@
<script>
import { setContext } from "svelte"
import Popover from "../Popover/Popover.svelte"
import Menu from "../Menu/Menu.svelte"
export let disabled = false
let anchor
let dropdown
// This is needed because display: contents is considered "invisible".
// It should only ever be an action button, so should be fine.
function getAnchor(node) {
anchor = node.firstChild
}
export const hide = () => {
dropdown.hide()
}
export const show = () => {
dropdown.show()
}
const openMenu = () => {
if (!disabled) show()
}
setContext("actionMenu", { show, hide })
</script>
<div use:getAnchor on:click={openMenu}>
<slot name="control" />
</div>
<Popover bind:this={dropdown} {anchor} align="left">
<Menu>
<slot />
</Menu>
</Popover>

View File

@ -5,14 +5,14 @@ export default function clickOutside(element, callbackFunction) {
}
}
document.body.addEventListener("click", onClick, true)
document.body.addEventListener("mousedown", onClick, true)
return {
update(newCallbackFunction) {
callbackFunction = newCallbackFunction
},
destroy() {
document.body.removeEventListener("click", onClick, true)
document.body.removeEventListener("mousedown", onClick, true)
},
}
}

View File

@ -18,10 +18,10 @@ export default function positionDropdown(element, { anchor, align }) {
if (spaceAbove > spaceBelow) {
positionSide = "bottom"
maxHeight = spaceAbove - 20
y = window.innerHeight - spaceAbove
y = window.innerHeight - spaceAbove + 5
} else {
positionSide = "top"
y = bottom
y = bottom + 5
maxHeight = spaceBelow - 20
}
@ -47,7 +47,7 @@ export default function positionDropdown(element, { anchor, align }) {
element.style[positionSide] = `${dimensions[positionSide]}px`
element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px`
const resizeObserver = new ResizeObserver(entries => {
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach(() => {
dimensions = getDimensions()
element.style[positionSide] = `${dimensions[positionSide]}px`

View File

@ -0,0 +1,12 @@
<script>
import "@spectrum-css/avatar/dist/index-vars.css"
export let url = ""
export let disabled = false
</script>
<img
class:is-disabled={disabled}
class="spectrum-Avatar"
src={url}
alt="Avatar"
/>

View File

@ -1,212 +1,50 @@
<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
import "@spectrum-css/button/dist/index-vars.css"
export let disabled = false
export let size = "M"
export let cta = false
export let primary = false
export let secondary = false
export let warning = false
export let overBackground = false
export let quiet = false
export let icon = undefined
export let active = 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
<button
class:spectrum-Button--cta={cta}
class:spectrum-Button--primary={primary}
class:spectrum-Button--secondary={secondary}
class:spectrum-Button--warning={warning}
class:spectrum-Button--overBackground={overBackground}
class:spectrum-Button--quiet={quiet}
class:active
class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
{disabled}
on:click|preventDefault>
<slot />
</button>
{/if}
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--size{size.toUpperCase()}"
focusable="false"
aria-hidden="true"
aria-label={icon}>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}
{#if $$slots}
<span class="spectrum-Button-label"><slot /></span>
{/if}
</button>
<style>
button,
a {
font-family: var(--font-sans);
cursor: pointer;
font-weight: 600;
box-sizing: border-box;
.spectrum-Button-label {
white-space: nowrap;
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;
text-overflow: ellipsis;
}
.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);
.active {
color: var(--spectrum-global-color-blue-600) !important;
}
</style>

View File

@ -1,59 +0,0 @@
<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>

View File

@ -1,36 +0,0 @@
<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>

View File

@ -1,128 +0,0 @@
<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>

View File

@ -1,69 +0,0 @@
<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>

View File

@ -0,0 +1,15 @@
<script>
import "@spectrum-css/buttongroup/dist/index-vars.css"
export let vertical = false
function group(element) {
const buttons = Array.from(element.getElementsByTagName('button'))
buttons.forEach(button => {
button.classList.add('spectrum-ButtonGroup-item')
})
}
</script>
<div use:group class="spectrum-ButtonGroup" class:spectrum-ButtonGroup--vertical={vertical}>
<slot />
</div>

View File

@ -0,0 +1,19 @@
<script>
export let small = false
export let disabled
</script>
<button
on:click
class="spectrum-ClearButton"
class:spectrum-ClearButton--small={small}
{disabled}
>
<svg
class="spectrum-Icon spectrum-UIIcon-Cross75"
focusable="false"
aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Cross75" />
</svg>
</button>

View File

@ -1,45 +0,0 @@
<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>

View File

@ -1,17 +0,0 @@
<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>

View File

@ -1,4 +1,5 @@
<script>
import Icon from "../Icon/Icon.svelte"
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher()
@ -7,8 +8,6 @@
export let name,
show = false
const capitalize = name => name[0].toUpperCase() + name.slice(1)
const onHeaderClick = () => {
show = !show
if (show) {
@ -19,9 +18,9 @@
<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:thin class="name">{name}</div>
<div class="icon">
<i class={show ? 'ri-arrow-down-s-fill' : 'ri-arrow-left-s-fill'} />
<Icon size="S" name={show ? "Remove" : "Add"} />
</div>
</div>
<div class="property-panel" class:show>
@ -57,10 +56,12 @@
flex: 1 1 auto;
overflow: hidden;
text-overflow: ellipsis;
text-transform: capitalize;
white-space: nowrap;
user-select: none;
}
.name.thin {
font-size: var(--font-size-xs);
font-size: var(--spectrum-global-dimension-font-size-75);
}
.icon {

View File

@ -0,0 +1,25 @@
<script>
import "@spectrum-css/divider/dist/index-vars.css"
export let size = "M"
export let vertical = false
export let noMargin = false
export let noGrid = false
</script>
<hr
class:noMargin
class:noGrid
class="spectrum-Divider spectrum-Divider--{vertical
? 'vertical'
: 'horizontal'} spectrum-Dialog-divider spectrum-Divider--size{size}"
/>
<style>
hr.noMargin {
margin: 0;
}
hr.noGrid {
grid-area: auto;
}
</style>

View File

@ -1,7 +1,9 @@
<script>
import { slide } from "svelte/transition"
import Portal from "svelte-portal"
import clickOutside from "../Actions/click_outside"
import ActionButton from "../ActionButton/ActionButton.svelte"
import Body from "../Typography/Body.svelte"
import Heading from "../Typography/Heading.svelte"
export let title
@ -35,12 +37,12 @@
<section class="drawer" transition:slide>
<header>
<div class="text">
<div class="title">{title}</div>
<slot name="description" />
<Heading size="XS">{title}</Heading>
<Body size="XXS"><slot name="description" /></Body>
</div>
<div class="controls">
<div class="buttons">
<slot name="buttons" />
<i class="ri-close-fill close" on:click={hide} />
<ActionButton quiet icon="Close" on:click={hide} />
</div>
</header>
<slot name="body" />
@ -64,25 +66,17 @@
justify-content: space-between;
align-items: center;
border-bottom: var(--border-light);
padding: var(--spacing-m);
padding: var(--spectrum-alias-item-padding-s) 0;
}
header :global(*) + :global(*) {
margin: 0 var(--spectrum-alias-grid-baseline);
}
.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;
flex-direction: row;
justify-content: flex-start;
align-items: center;
margin-left: var(--spectrum-alias-item-padding-s);
}
</style>

View File

@ -0,0 +1,45 @@
<div class="drawer-contents">
<div class:no-sidebar={!$$slots.sidebar} class="container">
{#if $$slots.sidebar}
<div class="sidebar">
<slot name="sidebar" />
</div>
{/if}
<div class="main">
<slot />
</div>
</div>
</div>
<style>
.drawer-contents {
height: 40vh;
overflow-y: auto;
}
.container {
height: 100%;
display: grid;
grid-template-columns: 290px 1fr;
}
.no-sidebar {
grid-template-columns: 1fr;
}
.sidebar {
border-right: var(--border-light);
overflow: auto;
}
.sidebar::-webkit-scrollbar {
display: none;
}
.main {
font-family: var(--font-sans);
}
.main :global(textarea) {
min-height: 200px;
}
.main :global(p) {
margin: 0;
}
</style>

View File

@ -1,77 +0,0 @@
<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>

View File

@ -1,161 +0,0 @@
<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>

View File

@ -1,295 +0,0 @@
<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>

View File

@ -1,17 +0,0 @@
<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>

View File

@ -1,5 +0,0 @@
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"],
}

View File

@ -1,140 +1,22 @@
<script>
import Field from "./Field.svelte"
import Checkbox from "./Core/Checkbox.svelte"
import { createEventDispatcher } from "svelte"
export let value = null
export let label = null
export let labelPosition = "above"
export let text = null
export let disabled = false
export let error = null
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)
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
</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>
<Field {label} {labelPosition} {disabled} {error}>
<Checkbox {error} {disabled} {text} {value} on:change={onChange} />
</Field>

View File

@ -1,67 +0,0 @@
<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>

View File

@ -0,0 +1,39 @@
<script>
import Field from "./Field.svelte"
import Combobox from "./Core/Combobox.svelte"
import { createEventDispatcher } from "svelte"
export let value = null
export let label = undefined
export let disabled = false
export let labelPosition = "above"
export let error = null
export let placeholder = "Choose an option"
export let options = []
export let getOptionLabel = option => extractProperty(option, "label")
export let getOptionValue = option => extractProperty(option, "value")
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
const extractProperty = (value, property) => {
if (value && typeof value === "object") {
return value[property]
}
return value
}
</script>
<Field {label} {labelPosition} {disabled} {error}>
<Combobox
{error}
{disabled}
{value}
{options}
{placeholder}
{getOptionLabel}
{getOptionValue}
on:change={onChange} />
</Field>

View File

@ -0,0 +1,43 @@
<script>
import "@spectrum-css/checkbox/dist/index-vars.css"
import "@spectrum-css/fieldgroup/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
export let value = false
export let error = null
export let id = null
export let text = null
export let disabled = false
const dispatch = createEventDispatcher()
const onChange = event => {
dispatch("change", event.target.checked)
}
</script>
<label
class="spectrum-Checkbox spectrum-Checkbox--sizeM spectrum-Checkbox--emphasized"
class:is-invalid={!!error}>
<input
checked={value}
{disabled}
on:change={onChange}
type="checkbox"
class="spectrum-Checkbox-input"
{id} />
<span class="spectrum-Checkbox-box">
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Checkbox-checkmark"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
<svg
class="spectrum-Icon spectrum-UIIcon-Dash100 spectrum-Checkbox-partialCheckmark"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Dash100" />
</svg>
</span>
<span class="spectrum-Checkbox-label">{text || ''}</span>
</label>

View File

@ -0,0 +1,128 @@
<script>
import "@spectrum-css/inputgroup/dist/index-vars.css"
import "@spectrum-css/popover/dist/index-vars.css"
import "@spectrum-css/menu/dist/index-vars.css"
import { fly } from "svelte/transition"
import { createEventDispatcher } from "svelte"
export let value = null
export let id = null
export let placeholder = "Choose an option"
export let disabled = false
export let error = null
export let options = []
export let getOptionLabel = option => option
export let getOptionValue = option => option
const dispatch = createEventDispatcher()
let open = false
let focus = false
$: fieldText = getFieldText(value, options, placeholder)
const getFieldText = (value, options, placeholder) => {
// Always use placeholder if no value
if (value == null || value === "") {
return placeholder || "Choose an option"
}
// Wait for options to load if there is a value but no options
if (!options?.length) {
return ""
}
// Render the label if the selected option is found, otherwise raw value
const selected = options.find(option => getOptionValue(option) === value)
return selected ? getOptionLabel(selected) : value
}
const selectOption = value => {
dispatch("change", value)
open = false
}
const onChange = e => {
selectOption(e.target.value)
}
</script>
<div class="spectrum-InputGroup" class:is-focused={open || focus}>
<div
class="spectrum-Textfield spectrum-InputGroup-textfield"
class:is-disabled={!!error}
class:is-focused={open || focus}>
<input
type="text"
on:focus={() => (focus = true)}
on:blur={() => (focus = false)}
on:change={onChange}
{value}
{placeholder}
class="spectrum-Textfield-input spectrum-InputGroup-input" />
</div>
<button
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
tabindex="-1"
aria-haspopup="true"
disabled={!!error}
on:click={() => (open = true)}>
<svg
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon spectrum-InputGroup-icon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Chevron100" />
</svg>
</button>
{#if open}
<div class="overlay" on:mousedown|self={() => (open = false)} />
<div
transition:fly={{ y: -20, duration: 200 }}
class="spectrum-Popover spectrum-Popover--bottom is-open">
<ul class="spectrum-Menu" role="listbox">
{#if options && Array.isArray(options)}
{#each options as option}
<li
class="spectrum-Menu-item"
class:is-selected={getOptionValue(option) === value}
role="option"
aria-selected="true"
tabindex="0"
on:click={() => selectOption(getOptionValue(option))}>
<span
class="spectrum-Menu-itemLabel">{getOptionLabel(option)}</span>
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
</li>
{/each}
{/if}
</ul>
</div>
{/if}
</div>
<style>
.spectrum-InputGroup {
min-width: 0;
width: 100%;
}
.spectrum-Textfield-input {
width: 0;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 999;
}
.spectrum-Popover {
max-height: 240px;
width: 100%;
z-index: 999;
top: 100%;
}
</style>

View File

@ -0,0 +1,142 @@
<script>
import Flatpickr from "svelte-flatpickr"
import "flatpickr/dist/flatpickr.css"
import "@spectrum-css/inputgroup/dist/index-vars.css"
import "@spectrum-css/textfield/dist/index-vars.css"
import "@spectrum-css/picker/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
import { generateID } from "../../utils/helpers"
export let id = null
export let disabled = false
export let error = null
export let isPlaceholder = false
export let enableTime = true
export let value = null
export let placeholder = null
const dispatch = createEventDispatcher()
const flatpickrId = `${generateID()}-wrapper`
let open = false
let flatpickr
$: flatpickrOptions = {
element: `#${flatpickrId}`,
enableTime: enableTime || false,
altInput: true,
altFormat: enableTime ? "F j Y, H:i" : "F j, Y",
wrap: true,
}
const handleChange = event => {
const [dates] = event.detail
dispatch("change", dates[0])
}
const clearDateOnBackspace = event => {
if (["Backspace", "Clear", "Delete"].includes(event.key)) {
dispatch("change", null)
flatpickr.close()
}
}
const onOpen = () => {
open = true
document.addEventListener("keyup", clearDateOnBackspace)
}
const onClose = () => {
open = false
document.removeEventListener("keyup", clearDateOnBackspace)
// Manually blur all input fields since flatpickr creates a second
// duplicate input field.
// We need to blur both because the focus styling does not get properly
// applied.
const els = document.querySelectorAll(`#${flatpickrId} input`)
els.forEach(el => el.blur())
}
</script>
<Flatpickr
bind:flatpickr
{value}
on:open={onOpen}
on:close={onClose}
options={flatpickrOptions}
on:change={handleChange}
element={`#${flatpickrId}`}>
<div
id={flatpickrId}
class:is-disabled={disabled}
class:is-invalid={!!error}
class="flatpickr spectrum-InputGroup spectrum-Datepicker"
class:is-focused={open}
aria-readonly="false"
aria-required="false"
aria-haspopup="true">
<div
on:click={flatpickr?.open}
class="spectrum-Textfield spectrum-InputGroup-textfield"
class:is-disabled={disabled}
class:is-invalid={!!error}>
{#if !!error}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
<input
data-input
type="text"
{disabled}
class="spectrum-Textfield-input spectrum-InputGroup-input"
{placeholder}
{id}
{value} />
</div>
<button
type="button"
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
tabindex="-1"
{disabled}
class:is-invalid={!!error}
on:click={flatpickr?.open}>
<svg
class="spectrum-Icon spectrum-Icon--sizeM"
focusable="false"
aria-hidden="true"
aria-label="Calendar">
<use xlink:href="#spectrum-icon-18-Calendar" />
</svg>
</button>
</div>
</Flatpickr>
{#if open}
<div class="overlay" on:mousedown|self={flatpickr?.close} />
{/if}
<style>
.spectrum-Textfield-input {
pointer-events: none;
}
.spectrum-Textfield:not(.is-disabled):hover {
cursor: pointer;
}
.flatpickr {
width: 100%;
overflow: hidden;
}
.flatpickr .spectrum-Textfield {
width: 100%;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 999;
}
</style>

View File

@ -0,0 +1,334 @@
<script>
import "@spectrum-css/dropzone/dist/index-vars.css"
import "@spectrum-css/typography/dist/index-vars.css"
import "@spectrum-css/illustratedmessage/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
import { generateID } from "../../utils/helpers"
import Icon from "../../Icon/Icon.svelte"
const BYTES_IN_KB = 1000
const BYTES_IN_MB = 1000000
export let value = []
export let id = null
export let disabled = false
export let fileSizeLimit = BYTES_IN_MB * 20
export let processFiles = null
export let handleFileTooLarge = null
const dispatch = createEventDispatcher()
const imageExtensions = [
"png",
"tiff",
"gif",
"raw",
"jpg",
"jpeg",
"svg",
"bmp",
"jfif",
]
const onChange = event => {
dispatch("change", event.target.checked)
}
const fieldId = id || generateID()
let selectedImageIdx = 0
let fileDragged = false
$: selectedImage = value?.[selectedImageIdx] ?? null
$: fileCount = value?.length ?? 0
$: isImage = imageExtensions.includes(selectedImage?.extension?.toLowerCase())
async function processFileList(fileList) {
if (
handleFileTooLarge &&
Array.from(fileList).some(file => file.size >= fileSizeLimit)
) {
handleFileTooLarge(fileSizeLimit, value)
return
}
if (processFiles) {
const processedFiles = await processFiles(fileList)
const newValue = [...value, ...processedFiles]
dispatch("change", newValue)
selectedImageIdx = newValue.length - 1
}
}
async function removeFile() {
dispatch(
"change",
value.filter((x, idx) => idx !== selectedImageIdx)
)
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="container">
{#if selectedImage}
<div class="gallery">
<div class="title">
<div class="filename">{selectedImage.name}</div>
<div class="filesize">
{#if selectedImage.size <= BYTES_IN_MB}
{`${selectedImage.size / BYTES_IN_KB} KB`}
{:else}{`${selectedImage.size / BYTES_IN_MB} MB`}{/if}
</div>
{#if !disabled}
<div class="delete-button" on:click={removeFile}>
<Icon name="Close" />
</div>
{/if}
</div>
{#if isImage}
<img alt="preview" src={selectedImage.url} />
{:else}
<div class="placeholder">
<div class="extension">{selectedImage.extension}</div>
<div>Preview not supported</div>
</div>
{/if}
<div
class="nav left"
class:visible={selectedImageIdx > 0}
on:click={navigateLeft}>
<Icon name="ChevronLeft" />
</div>
<div
class="nav right"
class:visible={selectedImageIdx < fileCount - 1}
on:click={navigateRight}>
<Icon name="ChevronRight" />
</div>
<div class="footer">File {selectedImageIdx + 1} of {fileCount}</div>
</div>
{/if}
<div
class="spectrum-Dropzone"
class:disabled
role="region"
tabindex="0"
on:dragover={handleDragOver}
on:dragleave={handleDragLeave}
on:dragenter={handleDragOver}
on:drop={handleDrop}
class:is-dragged={fileDragged}>
<div class="spectrum-IllustratedMessage spectrum-IllustratedMessage--cta">
<input
id={fieldId}
{disabled}
type="file"
multiple
on:change={handleFile} />
<svg
class="spectrum-IllustratedMessage-illustration"
width="125"
height="60"
viewBox="0 0 199 97.7"><defs>
<style>
.cls-1,
.cls-2 {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
.cls-1 {
stroke-width: 3px;
}
.cls-2 {
stroke-width: 2px;
}
</style>
</defs>
<path
class="cls-1"
d="M110.53,85.66,100.26,95.89a1.09,1.09,0,0,1-1.52,0L88.47,85.66" />
<line class="cls-1" x1="99.5" y1="95.5" x2="99.5" y2="58.5" />
<path class="cls-1" d="M105.5,73.5h19a2,2,0,0,0,2-2v-43" />
<path
class="cls-1"
d="M126.5,22.5h-19a2,2,0,0,1-2-2V1.5h-31a2,2,0,0,0-2,2v68a2,2,0,0,0,2,2h19" />
<line class="cls-1" x1="105.5" y1="1.5" x2="126.5" y2="22.5" />
<path
class="cls-2"
d="M47.93,50.49a5,5,0,1,0-4.83-5A4.93,4.93,0,0,0,47.93,50.49Z" />
<path
class="cls-2"
d="M36.6,65.93,42.05,60A2.06,2.06,0,0,1,45,60l12.68,13.2" />
<path
class="cls-2"
d="M3.14,73.23,22.42,53.76a1.65,1.65,0,0,1,2.38,0l19.05,19.7" />
<path
class="cls-1"
d="M139.5,36.5H196A1.49,1.49,0,0,1,197.5,38V72A1.49,1.49,0,0,1,196,73.5H141A1.49,1.49,0,0,1,139.5,72V32A1.49,1.49,0,0,1,141,30.5H154a2.43,2.43,0,0,1,1.67.66l6,5.66" />
<rect
class="cls-1"
x="1.5"
y="34.5"
width="58"
height="39"
rx="2"
ry="2" />
</svg>
<h2
class="spectrum-Heading spectrum-Heading--sizeL spectrum-Heading--light spectrum-IllustratedMessage-heading">
Drag and drop your file
</h2>
{#if !disabled}
<p
class="spectrum-Body spectrum-Body--sizeS spectrum-IllustratedMessage-description">
<label for={fieldId} class="spectrum-Link">Select a file to upload</label>
<br />
from your computer
</p>
{/if}
</div>
</div>
</div>
<style>
.container {
--spectrum-dropzone-padding: var(--spectrum-global-dimension-size-400);
--spectrum-heading-l-text-size: var(
--spectrum-global-dimension-font-size-400
);
}
.container * {
font-family: "Inter", sans-serif !important;
}
.gallery,
.spectrum-Dropzone {
user-select: none;
}
input[type="file"] {
display: none;
}
.gallery {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
background-color: var(--spectrum-global-color-gray-50);
color: var(--spectrum-alias-text-color);
font-size: var(--spectrum-alias-item-text-size-m);
box-sizing: border-box;
border: var(--spectrum-alias-border-size-thin)
var(--spectrum-alias-border-color) solid;
border-radius: var(--spectrum-alias-border-radius-regular);
padding: 10px;
margin-bottom: 10px;
position: relative;
}
.placeholder,
img {
height: 120px;
max-width: 100%;
object-fit: contain;
margin: 20px 30px;
}
.title {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: stretch;
}
.filename {
flex: 1 1 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 0;
margin-right: 10px;
}
.placeholder {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.extension {
color: var(--spectrum-global-color-gray-600);
text-transform: uppercase;
font-weight: 500;
margin-bottom: 5px;
}
.nav {
padding: var(--spacing-xs);
position: absolute;
top: 50%;
border-radius: 5px;
display: none;
transition: all 0.3s;
}
.nav.visible {
display: block;
}
.nav:hover {
cursor: pointer;
color: var(--blue);
}
.left {
left: 5px;
}
.right {
right: 5px;
}
i {
font-size: 2rem;
color: var(--ink);
}
i:hover {
cursor: pointer;
color: var(--background);
}
.delete-button {
transition: all 0.3s;
margin-left: 10px;
}
.delete-button i {
font-size: 2em;
}
.delete-button:hover {
cursor: pointer;
color: var(--red);
}
.spectrum-Dropzone.disabled {
pointer-events: none;
background-color: var(--spectrum-global-color-gray-200);
}
.disabled .spectrum-Heading--sizeL {
color: var(--spectrum-alias-text-color-disabled);
}
</style>

View File

@ -0,0 +1,81 @@
<script>
import Picker from "./Picker.svelte"
import { createEventDispatcher } from "svelte"
export let value = []
export let id = null
export let placeholder = null
export let disabled = false
export let error = null
export let options = []
export let getOptionLabel = option => option
export let getOptionValue = option => option
const dispatch = createEventDispatcher()
$: selectedLookupMap = getSelectedLookupMap(value)
$: optionLookupMap = getOptionLookupMap(options)
$: fieldText = getFieldText(value, optionLookupMap, placeholder)
$: isOptionSelected = optionValue => selectedLookupMap[optionValue] === true
$: toggleOption = makeToggleOption(selectedLookupMap, value)
const getFieldText = (value, map, placeholder) => {
if (value?.length) {
if (!map) {
return ""
}
const vals = value.map(option => map[option] || option).join(", ")
return `(${value.length}) ${vals}`
} else {
return placeholder || "Choose some options"
}
}
const getSelectedLookupMap = value => {
let map = {}
if (value?.length) {
value.forEach(option => {
if (option) {
map[option] = true
}
})
}
return map
}
const getOptionLookupMap = options => {
let map = null
if (options?.length) {
map = {}
options.forEach((option, idx) => {
const optionValue = getOptionValue(option, idx)
if (optionValue != null) {
map[optionValue] = getOptionLabel(option, idx) || ""
}
})
}
return map
}
const makeToggleOption = (map, value) => {
return optionValue => {
if (map[optionValue]) {
const filtered = value.filter(option => option !== optionValue)
dispatch("change", filtered)
} else {
dispatch("change", [...value, optionValue])
}
}
}
</script>
<Picker
{id}
{error}
{disabled}
{fieldText}
{options}
isPlaceholder={!value?.length}
{isOptionSelected}
{getOptionLabel}
{getOptionValue}
onSelectOption={toggleOption} />

View File

@ -0,0 +1,123 @@
<script>
import "@spectrum-css/picker/dist/index-vars.css"
import "@spectrum-css/popover/dist/index-vars.css"
import "@spectrum-css/menu/dist/index-vars.css"
import { fly } from "svelte/transition"
import { createEventDispatcher } from "svelte"
import clickOutside from "../../Actions/click_outside"
export let id = null
export let disabled = false
export let error = null
export let fieldText = ""
export let isPlaceholder = false
export let placeholderOption = null
export let options = []
export let isOptionSelected = () => false
export let onSelectOption = () => {}
export let getOptionLabel = option => option
export let getOptionValue = option => option
export let open = false
export let readonly = false
const dispatch = createEventDispatcher()
const onClick = e => {
dispatch("click")
if (readonly) {
return
}
open = true
}
</script>
<button
{id}
class="spectrum-Picker spectrum-Picker--sizeM"
{disabled}
class:is-invalid={!!error}
class:is-open={open}
aria-haspopup="listbox"
on:mousedown={onClick}>
<span class="spectrum-Picker-label" class:is-placeholder={isPlaceholder}>
{fieldText}
</span>
{#if error}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Picker-validationIcon"
focusable="false"
aria-hidden="true"
aria-label="Folder">
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
<svg
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Chevron100" />
</svg>
</button>
{#if open}
<div
use:clickOutside={() => (open = false)}
transition:fly={{ y: -20, duration: 200 }}
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open">
<ul class="spectrum-Menu" role="listbox">
{#if placeholderOption}
<li
class="spectrum-Menu-item"
class:is-selected={isPlaceholder}
role="option"
aria-selected="true"
tabindex="0"
on:click={() => onSelectOption(null)}>
<span class="spectrum-Menu-itemLabel">{placeholderOption}</span>
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
</li>
{/if}
{#if options && Array.isArray(options)}
{#each options as option, idx}
<li
class="spectrum-Menu-item"
class:is-selected={isOptionSelected(getOptionValue(option, idx))}
role="option"
aria-selected="true"
tabindex="0"
on:click={() => onSelectOption(getOptionValue(option, idx))}>
<span
class="spectrum-Menu-itemLabel">{getOptionLabel(option, idx)}</span>
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
</li>
{/each}
{/if}
</ul>
</div>
{/if}
<style>
.spectrum-Popover {
max-height: 240px;
width: 100%;
z-index: 999;
top: 100%;
}
.spectrum-Picker {
width: 100%;
}
.spectrum-Picker-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 0;
}
</style>

View File

@ -0,0 +1,37 @@
<script>
import "@spectrum-css/fieldgroup/dist/index-vars.css"
import "@spectrum-css/radio/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
export let direction = "vertical"
export let value = null
export let options = []
export let error = null
export let disabled = false
export let getOptionLabel = option => option
export let getOptionValue = option => option
const dispatch = createEventDispatcher()
const onChange = e => dispatch("change", e.target.value)
</script>
<div class={`spectrum-FieldGroup spectrum-FieldGroup--${direction}`}>
{#if options && Array.isArray(options)}
{#each options as option}
<div
title={getOptionLabel(option)}
class="spectrum-Radio spectrum-FieldGroup-item spectrum-Radio--emphasized"
class:is-invalid={!!error}>
<input
on:change={onChange}
bind:group={value}
value={getOptionValue(option)}
type="radio"
class="spectrum-Radio-input"
{disabled} />
<span class="spectrum-Radio-button" />
<label class="spectrum-Radio-label">{getOptionLabel(option)}</label>
</div>
{/each}
{/if}
</div>

View File

@ -0,0 +1,82 @@
<script>
import "@spectrum-css/search/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
export let value = ""
export let placeholder = null
export let disabled = false
export let id = null
const dispatch = createEventDispatcher()
let focus = false
const updateValue = value => {
dispatch("change", value)
}
const onFocus = () => {
focus = true
}
const onBlur = event => {
focus = false
updateValue(event.target.value)
}
const updateValueOnEnter = event => {
if (event.key === "Enter") {
updateValue(event.target.value)
}
}
</script>
<div class="spectrum-Search" class:is-disabled={disabled}>
<div
class="spectrum-Textfield"
class:is-focused={focus}
class:is-disabled={disabled}>
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-icon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-icon-18-Magnify" />
</svg>
<input
on:click
on:keyup={updateValueOnEnter}
{disabled}
{id}
value={value || ''}
placeholder={placeholder || ''}
on:blur={onBlur}
on:focus={onFocus}
on:input
type="search"
class="spectrum-Textfield-input spectrum-Search-input"
autocomplete="off" />
</div>
<button
on:click={() => updateValue('')}
type="reset"
class="spectrum-ClearButton spectrum-Search-clearButton">
<svg
class="spectrum-Icon spectrum-UIIcon-Cross75"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Cross75" />
</svg>
</button>
</div>
<style>
.spectrum-Search,
.spectrum-Textfield {
width: 100%;
}
.spectrum-Search-input {
padding-right: 24px;
}
.is-disabled {
pointer-events: none;
}
</style>

View File

@ -0,0 +1,57 @@
<script>
import { createEventDispatcher } from "svelte"
import Picker from "./Picker.svelte"
export let value = null
export let id = null
export let placeholder = "Choose an option"
export let disabled = false
export let error = null
export let options = []
export let getOptionLabel = option => option
export let getOptionValue = option => option
export let readonly = false
const dispatch = createEventDispatcher()
let open = false
$: fieldText = getFieldText(value, options, placeholder)
const getFieldText = (value, options, placeholder) => {
// Always use placeholder if no value
if (value == null || value === "") {
return placeholder || "Choose an option"
}
// Wait for options to load if there is a value but no options
if (!options?.length) {
return ""
}
// Render the label if the selected option is found, otherwide raw value
const index = options.findIndex(
(option, idx) => getOptionValue(option, idx) === value
)
return index !== -1 ? getOptionLabel(options[index], index) : value
}
const selectOption = value => {
dispatch("change", value)
open = false
}
</script>
<Picker
on:click
bind:open
{id}
{error}
{disabled}
{readonly}
{fieldText}
{options}
{getOptionLabel}
{getOptionValue}
isPlaceholder={value == null || value === ''}
placeholderOption={placeholder}
isOptionSelected={option => option === value}
onSelectOption={selectOption} />

View File

@ -0,0 +1,27 @@
<script>
import "@spectrum-css/switch/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
export let value = false
export let error = null
export let id = null
export let text = null
export let disabled = false
const dispatch = createEventDispatcher()
const onChange = event => {
dispatch("change", event.target.checked)
}
</script>
<div class="spectrum-Switch spectrum-Switch--emphasized">
<input
checked={value}
{disabled}
on:change={onChange}
{id}
type="checkbox"
class="spectrum-Switch-input" />
<span class="spectrum-Switch-switch" />
<label class="spectrum-Switch-label" for={id}>{text}</label>
</div>

View File

@ -0,0 +1,55 @@
<script>
import "@spectrum-css/textfield/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
export let value = ""
export let placeholder = null
export let disabled = false
export let error = null
export let id = null
export const getCaretPosition = () => ({
start: textarea.selectionStart,
end: textarea.selectionEnd,
})
let focus = false
let textarea
const dispatch = createEventDispatcher()
const onChange = event => {
dispatch("change", event.target.value)
focus = false
}
</script>
<div
class="spectrum-Textfield spectrum-Textfield--multiline"
class:is-invalid={!!error}
class:is-disabled={disabled}
class:is-focused={focus}>
{#if error}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
<textarea
bind:this={textarea}
placeholder={placeholder || ''}
class="spectrum-Textfield-input"
{disabled}
{id}
on:focus={() => (focus = true)}
on:blur={onChange}>{value || ''}</textarea>
</div>
<style>
.spectrum-Textfield {
width: 100%;
}
textarea {
resize: vertical;
min-height: 80px !important;
}
</style>

View File

@ -0,0 +1,84 @@
<script>
import "@spectrum-css/textfield/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
export let value = ""
export let placeholder = null
export let type = "text"
export let disabled = false
export let error = null
export let id = null
export let readonly = false
const dispatch = createEventDispatcher()
let focus = false
const updateValue = value => {
if (readonly) {
return
}
if (type === "number") {
const float = parseFloat(value)
value = isNaN(float) ? null : float
}
dispatch("change", value)
}
const onFocus = () => {
if (readonly) {
return
}
focus = true
}
const onBlur = event => {
if (readonly) {
return
}
focus = false
updateValue(event.target.value)
}
const updateValueOnEnter = event => {
if (readonly) {
return
}
if (event.key === "Enter") {
updateValue(event.target.value)
}
}
</script>
<div
class="spectrum-Textfield"
class:is-invalid={!!error}
class:is-disabled={disabled}
class:is-focused={focus}>
{#if error}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
<input
on:click
on:keyup={updateValueOnEnter}
{disabled}
{readonly}
{id}
value={value || ''}
placeholder={placeholder || ''}
on:blur={onBlur}
on:focus={onFocus}
on:input
{type}
class="spectrum-Textfield-input" />
</div>
<style>
.spectrum-Textfield {
width: 100%;
}
</style>

View File

@ -0,0 +1,11 @@
export { default as CoreTextField } from "./TextField.svelte"
export { default as CoreSelect } from "./Select.svelte"
export { default as CoreMultiselect } from "./Multiselect.svelte"
export { default as CoreCheckbox } from "./Checkbox.svelte"
export { default as CoreRadioGroup } from "./RadioGroup.svelte"
export { default as CoreTextArea } from "./TextArea.svelte"
export { default as CoreCombobox } from "./Combobox.svelte"
export { default as CoreSwitch } from "./Switch.svelte"
export { default as CoreSearch } from "./Search.svelte"
export { default as CoreDatePicker } from "./DatePicker.svelte"
export { default as CoreDropzone } from "./Dropzone.svelte"

View File

@ -1,158 +0,0 @@
<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>

View File

@ -1,57 +0,0 @@
<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>

View File

@ -0,0 +1,29 @@
<script>
import Field from "./Field.svelte"
import DatePicker from "./Core/DatePicker.svelte"
import { createEventDispatcher } from "svelte"
export let value = null
export let label = null
export let labelPosition = "above"
export let disabled = false
export let error = null
export let enableTime = true
export let placeholder = null
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
</script>
<Field {label} {labelPosition} {disabled} {error}>
<DatePicker
{error}
{disabled}
{value}
{placeholder}
{enableTime}
on:change={onChange} />
</Field>

View File

@ -0,0 +1,31 @@
<script>
import Field from "./Field.svelte"
import CoreDropzone from "./Core/Dropzone.svelte"
import { createEventDispatcher } from "svelte"
export let value = []
export let label = null
export let labelPosition = "above"
export let disabled = false
export let error = null
export let fileSizeLimit = undefined
export let processFiles = undefined
export let handleFileTooLarge = undefined
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
</script>
<Field {label} {labelPosition} {disabled} {error}>
<CoreDropzone
{error}
{disabled}
{value}
{fileSizeLimit}
{processFiles}
{handleFileTooLarge}
on:change={onChange} />
</Field>

View File

@ -0,0 +1,42 @@
<script>
import "@spectrum-css/fieldlabel/dist/index-vars.css"
import FieldLabel from "./FieldLabel.svelte"
export let id = null
export let label = null
export let labelPosition = "above"
export let disabled = false
export let error = null
</script>
<div class="spectrum-Form-item" class:above={labelPosition === 'above'}>
{#if label}
<FieldLabel forId={id} {label} position={labelPosition} />
{/if}
<div class="spectrum-Form-itemField">
<slot />
{#if error}
<div class="error">{error}</div>
{/if}
</div>
</div>
<style>
.spectrum-Form-item.above {
display: flex;
flex-direction: column;
}
.spectrum-Form-itemField {
position: relative;
width: 100%;
}
.error {
color: var(
--spectrum-semantic-negative-color-default,
var(--spectrum-global-color-red-500)
);
font-size: var(--spectrum-global-dimension-font-size-75);
margin-top: var(--spectrum-global-dimension-size-75);
}
</style>

View File

@ -0,0 +1,26 @@
<script>
import "@spectrum-css/fieldlabel/dist/index-vars.css"
export let forId
export let label
export let position = "above"
$: className = position === "above" ? "" : `spectrum-FieldLabel--${position}`
</script>
<label
for={forId}
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${className}`}>
{label || ''}
</label>
<style>
label {
white-space: nowrap;
}
.spectrum-FieldLabel--right,
.spectrum-FieldLabel--left {
padding-right: var(--spectrum-global-dimension-size-200);
}
</style>

View File

@ -1,190 +1,33 @@
<script>
import Field from "./Field.svelte"
import TextField from "./Core/TextField.svelte"
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 value = null
export let label = null
export let labelPosition = "above"
export let placeholder = null
export let type = "text"
export let disabled = false
export let type = undefined
export let placeholder = ""
export let value = ""
export let error = false
export let validator = () => {}
export let readonly = false
export let error = null
// 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
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
</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}
<Field {label} {labelPosition} {disabled} {error}>
<TextField
{error}
{disabled}
{readonly}
{value}
{placeholder}
{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>
on:change={onChange}
on:click
on:input />
</Field>

View File

@ -1,62 +0,0 @@
<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>

View File

@ -1,324 +1,35 @@
<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()
import Multiselect from "./Core/Multiselect.svelte"
import Field from "./Field.svelte"
export let value = []
export let label = undefined
export let align = "left"
export let secondary = false
export let outline = false
export let label = null
export let disabled = false
export let placeholder = undefined
export let extraThin = false
export let readonly = false
export let labelPosition = "above"
export let error = null
export let placeholder = null
export let options = []
export let getOptionLabel = option => option
export let getOptionValue = option => option
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)
}
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
</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">&nbsp;</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>
<Field {label} {labelPosition} {disabled} {error}>
<Multiselect
{error}
{disabled}
{value}
{options}
{placeholder}
{getOptionLabel}
{getOptionValue}
on:change={onChange}
on:click />
</Field>

View File

@ -1,63 +0,0 @@
<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>

View File

@ -1,140 +0,0 @@
<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>

View File

@ -1,64 +0,0 @@
<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>

View File

@ -0,0 +1,37 @@
<script>
import Field from "./Field.svelte"
import RadioGroup from "./Core/RadioGroup.svelte"
import { createEventDispatcher } from "svelte"
export let value = null
export let label = null
export let disabled = false
export let labelPosition = "above"
export let error = null
export let options = []
export let getOptionLabel = option => extractProperty(option, "label")
export let getOptionValue = option => extractProperty(option, "value")
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
const extractProperty = (value, property) => {
if (value && typeof value === "object") {
return value[property]
}
return value
}
</script>
<Field {label} {labelPosition} {disabled} {error}>
<RadioGroup
{error}
{disabled}
{value}
{options}
{getOptionLabel}
{getOptionValue}
on:change={onChange} />
</Field>

View File

@ -1,59 +0,0 @@
<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>

View File

@ -1,40 +0,0 @@
<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>

View File

@ -0,0 +1,27 @@
<script>
import Field from "./Field.svelte"
import Search from "./Core/Search.svelte"
import { createEventDispatcher } from "svelte"
export let value = null
export let label = null
export let labelPosition = "above"
export let placeholder = null
export let disabled = false
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
</script>
<Field {label} {labelPosition} {disabled}>
<Search
{disabled}
{value}
{placeholder}
on:change={onChange}
on:click
on:input />
</Field>

View File

@ -1,95 +1,42 @@
<script>
import Icon from "../Icons/Icon.svelte"
import Label from "../Styleguide/Label.svelte"
import Field from "./Field.svelte"
import Select from "./Core/Select.svelte"
import { createEventDispatcher } from "svelte"
export let value = ""
export let name = undefined
export let value = null
export let label = undefined
export let thin = false
export let extraThin = false
export let secondary = false
export let outline = false
export let disabled = false
export let readonly = false
export let labelPosition = "above"
export let error = null
export let placeholder = "Choose an option"
export let options = []
export let getOptionLabel = option => extractProperty(option, "label")
export let getOptionValue = option => extractProperty(option, "value")
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
const extractProperty = (value, property) => {
if (value && typeof value === "object") {
return value[property]
}
return value
}
</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
<Field {label} {labelPosition} {disabled} {error}>
<Select
{error}
{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>
{readonly}
{value}
{options}
{placeholder}
{getOptionLabel}
{getOptionValue}
on:change={onChange}
on:click />
</Field>

View File

@ -1,62 +0,0 @@
<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>

View File

@ -1,88 +0,0 @@
<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>

View File

@ -1,26 +0,0 @@
<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>

View File

@ -1,132 +1,29 @@
<script>
import Field from "./Field.svelte"
import TextArea from "./Core/TextArea.svelte"
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"
export let value = null
export let label = null
export let labelPosition = "above"
export let placeholder = null
export let disabled = false
export let error = null
export let getCaretPosition = null
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
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
</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)}
<Field {label} {labelPosition} {disabled} {error}>
<TextArea
bind:getCaretPosition
{error}
{disabled}
{value}
{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>
on:change={onChange} />
</Field>

View File

@ -1,30 +0,0 @@
<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>

View File

@ -1,110 +1,22 @@
<script>
export let name = undefined
export let text = ""
export let checked = false
import Field from "./Field.svelte"
import Switch from "./Core/Switch.svelte"
import { createEventDispatcher } from "svelte"
export let value = null
export let label = null
export let labelPosition = "above"
export let text = null
export let disabled = false
export let screenreader = true
export let thin = false
export let error = null
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
</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>
<Field {label} {labelPosition} {disabled} {error}>
<Switch {error} {disabled} {text} {value} on:change={onChange} />
</Field>

View File

@ -1,21 +0,0 @@
<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>

View File

@ -0,0 +1,43 @@
<script context="module">
export const directions = ["n", "ne", "e", "se", "s", "sw", "w", "nw"]
</script>
<script>
export let direction = "n"
export let name = "Add"
export let hidden = false
export let size = "M"
export let hoverable = false
export let disabled = false
$: rotation = directions.indexOf(direction) * 45
</script>
<svg
on:click
class:hoverable
class:disabled
class="spectrum-Icon spectrum-Icon--size{size}"
focusable="false"
aria-hidden={hidden}
aria-label={name}
style={`transform: rotate(${rotation}deg)`}
>
<use xlink:href="#spectrum-icon-18-{name}" />
</svg>
<style>
svg.hoverable {
pointer-events: all;
transition: color var(--spectrum-global-animation-duration-100, 130ms);
}
svg.hoverable:hover {
color: var(--spectrum-alias-icon-color-selected);
cursor: pointer;
}
svg.disabled {
color: var(--spectrum-global-color-gray-500) !important;
pointer-events: none !important;
}
</style>

View File

@ -1,14 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 493 B

View File

@ -1,34 +0,0 @@
<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>

View File

@ -1,109 +0,0 @@
<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>

View File

@ -1,128 +0,0 @@
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",
],
}

View File

@ -20,7 +20,8 @@
class:white
class:grey
class:black
for={forAttr}>
for={forAttr}
>
<slot />
</label>
@ -34,35 +35,27 @@
margin-bottom: var(--spacing-s);
display: block;
}
.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);
}

View File

@ -0,0 +1,65 @@
<script>
export let horizontal = false
export let paddingX = "M"
export let paddingY = "M"
export let noPadding = false
export let gap = "M"
export let noGap = false
</script>
<div
class:horizontal
class="container paddingX-{!noPadding && paddingX} paddingY-{!noPadding &&
paddingY} gap-{!noGap && gap}"
>
<slot />
</div>
<style>
.container {
display: grid;
grid-template-columns: 1fr;
}
.paddingX-S {
padding-left: var(--spacing-s);
padding-right: var(--spacing-s);
}
.paddingX-M {
padding-left: var(--spacing-m);
padding-right: var(--spacing-m);
}
.paddingX-L {
padding-left: var(--spacing-l);
padding-right: var(--spacing-l);
}
.paddingY-S {
padding-top: var(--spacing-s);
padding-bottom: var(--spacing-s);
}
.paddingY-M {
padding-top: var(--spacing-m);
padding-bottom: var(--spacing-m);
}
.paddingY-L {
padding-top: var(--spacing-l);
padding-bottom: var(--spacing-l);
}
.gap-S {
grid-gap: var(--spectrum-alias-grid-gutter-xsmall);
}
.gap-M {
grid-gap: var(--spectrum-alias-grid-gutter-small);
}
.gap-L {
grid-gap: var(--spectrum-alias-grid-gutter-medium);
}
.horizontal.gap-S :global(*) + :global(*) {
margin-left: var(--spectrum-alias-grid-gutter-xsmall);
}
.horizontal.gap-M :global(*) + :global(*) {
margin-left: var(--spectrum-alias-grid-gutter-small);
}
.horizontal.gap-L :global(*) + :global(*) {
margin-left: var(--spectrum-alias-grid-gutter-medium);
}
</style>

View File

@ -0,0 +1,20 @@
<script>
import "@spectrum-css/link/dist/index-vars.css"
export let href = "#"
export let size = "M"
export let quiet = false
export let primary = false
export let secondary = false
export let overBackground = false
export let target
</script>
<a
{href}
{target}
class:spectrum-Link--primary={primary}
class:spectrum-Link--secondary={secondary}
class:spectrum-Link--overBackground={overBackground}
class:spectrum-Link--quiet={quiet}
class="spectrum-Link spectrum-Link--size{size}"><slot /></a>

View File

@ -1,50 +0,0 @@
<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>

View File

@ -1,19 +0,0 @@
<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>

View File

@ -1,33 +0,0 @@
<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>

View File

@ -1,8 +0,0 @@
<script>
import Search from "./Search.svelte";
import { View } from "svench";
</script>
<View name="Name">
<Search />
</View>

View File

@ -0,0 +1,39 @@
<script>
import { createEventDispatcher, getContext } from "svelte"
const dispatch = createEventDispatcher()
const actionMenu = getContext("actionMenu")
export let dataCy
export let icon = undefined
export let disabled = undefined
export let noClose = false
const onClick = () => {
if (actionMenu && !noClose) {
actionMenu.hide()
}
dispatch("click")
}
</script>
<li
data-cy={dataCy}
on:click|preventDefault={onClick}
class="spectrum-Menu-item"
class:is-disabled={disabled}
role="menuitem"
tabindex="0"
>
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Menu-itemIcon"
focusable="false"
aria-hidden="true"
aria-label={icon}
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}
<span class="spectrum-Menu-itemLabel"><slot /></span>
</li>

View File

@ -0,0 +1,7 @@
<script>
import "@spectrum-css/menu/dist/index-vars.css"
</script>
<ul class="spectrum-Menu" role="menu">
<slot />
</ul>

View File

@ -0,0 +1,19 @@
<script>
import Menu from './Menu.svelte'
import Separator from './Separator.svelte'
import Section from './Section.svelte'
import Item from './Item.svelte'
</script>
<Menu>
<Section heading="Section heading">
<Item>Some Item 1</Item>
<Item>Some Item 2</Item>
<Item>Some Item 3</Item>
</Section>
<Separator />
<Section heading="Section heading">
<Item icon="SaveFloppy">Save</Item>
<Item disabled icon="DataDownload">Download</Item>
</Section>
</Menu>

View File

@ -0,0 +1,9 @@
<script>
export let heading
</script>
<li role="presentation">
<span class="spectrum-Menu-sectionHeading">{heading}</span>
<ul class="spectrum-Menu" role="group">
<slot />
</ul>
</li>

View File

@ -0,0 +1 @@
<li class="spectrum-Menu-divider" role="separator"></li>

View File

@ -1,15 +1,15 @@
<script>
import { createEventDispatcher, setContext } from "svelte"
import "@spectrum-css/modal/dist/index-vars.css"
import "@spectrum-css/underlay/dist/index-vars.css"
import { createEventDispatcher, setContext, tick } from "svelte"
import { fade, fly } from "svelte/transition"
import Portal from "svelte-portal"
import Context from "../context"
export let fixed = false
const dispatch = createEventDispatcher()
export let padding = true
export let width = undefined
export let border = true
let visible = false
let visible = !!fixed
$: dispatch(visible ? "show" : "hide")
export function show() {
@ -20,7 +20,7 @@
}
export function hide() {
if (!visible) {
if (!visible || fixed) {
return
}
visible = false
@ -32,27 +32,31 @@
}
}
async function focusFirstInput(node) {
const inputs = node.querySelectorAll("input")
if (inputs?.length) {
await tick()
inputs[0].focus()
}
}
setContext(Context.Modal, { show, hide })
</script>
<svelte:window on:keydown={handleKey} />
{#if visible}
<Portal target="body">
<Portal target=".modal-container">
<div
class="overlay"
on:click|self={hide}
transition:fade={{ duration: 200 }}>
class="spectrum-Underlay is-open"
transition:fade={{ duration: 200 }}
on:mousedown|self={hide}>
<div class="modal-wrapper" on:mousedown|self={hide}>
<div class="modal-inner-wrapper" on:mousedown|self={hide}>
<div
class="scroll-wrapper"
on:click|self={hide}
use:focusFirstInput
class="spectrum-Modal is-open"
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>
@ -62,55 +66,42 @@
{/if}
<style>
.overlay {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
overflow-x: hidden;
overflow-y: auto;
.spectrum-Underlay {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.25);
z-index: 999;
overflow: auto;
overflow-x: hidden;
}
.scroll-wrapper {
.modal-wrapper {
flex: 1 1 auto;
display: flex;
flex-direction: row;
-moz-box-pack: center;
justify-content: center;
align-items: flex-start;
max-height: 100%;
}
.content-wrapper {
.modal-inner-wrapper {
flex: 1 1 auto;
display: flex;
flex-direction: row;
-moz-box-pack: center;
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);
.spectrum-Modal {
overflow: visible;
max-height: none;
margin: 40px 0;
transform: none;
--spectrum-dialog-confirm-border-radius: var(
--spectrum-global-dimension-size-100
);
}
</style>

View File

@ -1,11 +1,13 @@
<script>
import { createEventDispatcher, getContext } from "svelte"
import "@spectrum-css/dialog/dist/index-vars.css"
import { getContext } from "svelte"
import Button from "../Button/Button.svelte"
import Icon from "../Icons/Icon.svelte"
import Divider from "../Divider/Divider.svelte"
import Icon from "../Icon/Icon.svelte"
import Context from "../context"
const dispatch = createEventDispatcher()
export let title = undefined
export let size = "small"
export let cancelText = "Cancel"
export let confirmText = "Confirm"
export let showCancelButton = true
@ -27,28 +29,33 @@
}
</script>
<div class="modal-content">
{#if title}
<header>
<h5>{title}</h5>
<div class="header-content">
<slot name="header" />
</div>
</header>
{/if}
<div
class="spectrum-Dialog spectrum-Dialog--{size}"
style="position: relative;"
role="dialog"
tabindex="-1"
aria-modal="true">
<div class="spectrum-Dialog-grid">
<h1 class="spectrum-Dialog-heading spectrum-Dialog-heading--noHeader">
{title}
</h1>
<Divider size="M" />
<!-- TODO: Remove content-grid class once Layout components are in bbui -->
<section class="spectrum-Dialog-content content-grid">
<slot />
</section>
{#if showCancelButton || showConfirmButton}
<footer>
<div class="footer-content">
<div
class="spectrum-ButtonGroup spectrum-Dialog-buttonGroup spectrum-Dialog-buttonGroup--noFooter">
<slot name="footer" />
</div>
<div class="buttons">
{#if showCancelButton}
<Button secondary on:click={hide}>{cancelText}</Button>
<Button group secondary on:click={hide}>{cancelText}</Button>
{/if}
{#if showConfirmButton}
<Button
primary
group
cta
{...$$restProps}
disabled={confirmDisabled}
on:click={confirm}>
@ -56,46 +63,38 @@
</Button>
{/if}
</div>
</footer>
{/if}
{#if showCloseIcon}
<div class="close-icon" on:click={hide}>
<Icon name="closeline" />
<Icon hoverable name="Close" />
</div>
{/if}
</div>
</div>
<style>
.modal-content {
.content-grid {
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;
.spectrum-Dialog-content {
overflow: visible;
}
header h5 {
margin: 0;
font-weight: 500;
.spectrum-Dialog-heading {
font-family: var(--spectrum-alias-body-text-font-family);
}
.header-content {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
.spectrum-Dialog-buttonGroup {
gap: var(--spectrum-global-dimension-static-size-200);
}
.close-icon {
position: absolute;
top: 0;
right: 0;
top: 15px;
right: 15px;
color: var(--ink);
font-size: var(--font-size-m);
}
@ -106,27 +105,4 @@
.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>

View File

@ -0,0 +1,44 @@
<script>
import "@spectrum-css/toast/dist/index-vars.css"
import Portal from "svelte-portal"
import { flip } from "svelte/animate"
import { fly } from "svelte/transition"
import { notifications } from '../Stores/notifications'
</script>
<Portal target=".modal-container">
<div class="notifications">
{#each $notifications as {type, icon, message, id} (id)}
<div animate:flip transition:fly={{ y: -30 }} class="spectrum-Toast spectrum-Toast--{type} notification-offset">
{#if icon}
<svg class="spectrum-Icon spectrum-Icon--sizeM spectrum-Toast-typeIcon" focusable="false" aria-hidden="true">
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}
<div class="spectrum-Toast-body">
<div class="spectrum-Toast-content">{message}</div>
</div>
</div>
{/each}
</div>
</Portal>
<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;
}
.notification-offset {
margin-bottom: 10px;
}
</style>

View File

@ -1,24 +1,27 @@
<script>
import "@spectrum-css/popover/dist/index-vars.css"
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 = () => {
dispatch("open")
open = true
dispatch("show")
}
export const hide = () => {
dispatch("close")
open = false
dispatch("hide")
}
let open = null
function handleEscape(e) {
if (open && e.key === "Escape") {
hide()
@ -30,39 +33,18 @@
<Portal>
<div
tabindex="0"
class:open
use:positionDropdown={{ anchor, align }}
use:clickOutside={hide}
on:keydown={handleEscape}
class="menu-container">
class="spectrum-Popover is-open"
role="presentation">
<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;
.spectrum-Popover {
min-width: var(--spectrum-global-dimension-size-2000) !important;
}
</style>

View File

@ -0,0 +1,63 @@
<script>
import "@spectrum-css/progressbar/dist/index-vars.css"
import { tweened } from "svelte/motion"
import { cubicOut } from "svelte/easing"
export let value = false
export let easing = cubicOut
export let duration = 1000
export let width = false
export let sideLabel = false
export let overBackground = false
export let size = "M"
const progress = tweened(0, {
duration: duration,
easing: easing,
})
$: if (value) $progress = value
</script>
<div
class:spectrum-ProgressBar--indeterminate={!value}
class:spectrum-ProgressBar--sideLabel={sideLabel}
class="spectrum-ProgressBar spectrum-ProgressBar--size{size}"
value={$progress}
role="progressbar"
aria-valuenow={$progress}
aria-valuemin="0"
aria-valuemax="100"
style={width ? `width: ${width}px;` : ""}
>
{#if $$slots}
<div
class:spectrum-FieldLabel--sizeS={s}
class:spectrum-FieldLabel--sizeM={m}
class:spectrum-FieldLabel--sizeL={l}
class:spectrum-FieldLabel--sizeXL={xl}
class="spectrum-FieldLabel spectrum-ProgressBar-label"
>
<slot />
</div>
{/if}
{#if value}
<div
class:spectrum-FieldLabel--sizeS={s}
class:spectrum-FieldLabel--sizeM={m}
class:spectrum-FieldLabel--sizeL={l}
class:spectrum-FieldLabel--sizeXL={xl}
class="spectrum-FieldLabel spectrum-ProgressBar-percentage"
>
{Math.round($progress)}%
</div>
{/if}
<div class="spectrum-ProgressBar-track">
<div
class="spectrum-ProgressBar-fill"
style={value ? `width: ${$progress}%` : ""}
/>
</div>
<div class="spectrum-ProgressBar-label" hidden="" />
</div>

View File

@ -0,0 +1,29 @@
<script>
// WIP! Does not yet work.
import "@spectrum-css/progresscircle/dist/index-vars.css"
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
export let value = false
export let small;
export let large;
export let overBackground;
</script>
<div class:spectrum-ProgressBar--indeterminate={!value} class:spectrum-ProgressCircle--small={small} class:spectrum-ProgressCircle--large={large} class="spectrum-ProgressCircle">
<div class="spectrum-ProgressCircle-track"></div>
<div class="spectrum-ProgressCircle-fills">
<div class="spectrum-ProgressCircle-fillMask1">
<div class="spectrum-ProgressCircle-fillSubMask1">
<div class="spectrum-ProgressCircle-fill"></div>
</div>
</div>
<div class="spectrum-ProgressCircle-fillMask2">
<div class="spectrum-ProgressCircle-fillSubMask2">
<div class="spectrum-ProgressCircle-fill"></div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,30 @@
<script>
import { getContext } from 'svelte'
const multilevel = getContext('sidenav-type');
export let href = "";
export let external = false;
export let heading = ""
export let icon = "";
export let selected = false;
export let disabled = false;
</script>
<li class="spectrum-SideNav-item" class:is-selected={selected} class:is-disabled={disabled}>
{#if heading}
<h2 class="spectrum-SideNav-heading" id="nav-heading-{heading}">{heading}</h2>
{/if}
<a target={external ? '_blank' : '_self'} {href} class="spectrum-SideNav-itemLink" aria-current="page">
{#if icon}
<svg class="spectrum-Icon spectrum-Icon--sizeM spectrum-SideNav-itemIcon" focusable="false" aria-hidden="true">
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}
<slot />
</a>
{#if multilevel && $$slots.subnav}
<ul class="spectrum-SideNav">
<slot name="subnav" />
</ul>
{/if}
</li>

View File

@ -0,0 +1,12 @@
<script>
import { setContext } from 'svelte'
import "@spectrum-css/sidenav/dist/index-vars.css"
export let multilevel = false;
setContext('sidenav-type', multilevel)
</script>
<nav>
<ul class="spectrum-SideNav" class:spectrum-SideNav--multiLevel={multilevel}>
<slot />
</ul>
</nav>

Some files were not shown because too many files have changed in this diff Show More