Merge branch 'js-ai-gen' of github.com:budibase/budibase into js-ai-gen

This commit is contained in:
Sam Rose 2025-03-12 17:25:02 +00:00
commit 9b4a1b99c8
No known key found for this signature in database
32 changed files with 223 additions and 681 deletions

View File

@ -1,132 +0,0 @@
<script>
import Field from "./Field.svelte"
import PickerDropdown from "./Core/PickerDropdown.svelte"
import { createEventDispatcher } from "svelte"
export let primaryValue = null
export let secondaryValue = null
export let inputType = "text"
export let label = null
export let labelPosition = "above"
export let secondaryPlaceholder = null
export let autocomplete
export let placeholder = null
export let disabled = false
export let readonly = false
export let error = null
export let getSecondaryOptionLabel = option =>
extractProperty(option, "label")
export let getSecondaryOptionValue = option =>
extractProperty(option, "value")
export let getSecondaryOptionColour = () => {}
export let getSecondaryOptionIcon = () => {}
export let quiet = false
export let autofocus
export let primaryOptions = []
export let secondaryOptions = []
export let searchTerm
export let showClearIcon = true
export let helpText = null
let primaryLabel
let secondaryLabel
const dispatch = createEventDispatcher()
$: secondaryFieldText = getSecondaryFieldText(
secondaryValue,
secondaryOptions,
secondaryPlaceholder
)
$: secondaryFieldIcon = getSecondaryFieldAttribute(
getSecondaryOptionIcon,
secondaryValue,
secondaryOptions
)
$: secondaryFieldColour = getSecondaryFieldAttribute(
getSecondaryOptionColour,
secondaryValue,
secondaryOptions
)
const getSecondaryFieldAttribute = (getAttribute, value, options) => {
// Wait for options to load if there is a value but no options
if (!options?.length) {
return ""
}
const index = options.findIndex(
(option, idx) => getSecondaryOptionValue(option, idx) === value
)
return index !== -1 ? getAttribute(options[index], index) : null
}
const getSecondaryFieldText = (value, options, placeholder) => {
// Always use placeholder if no value
if (value == null || value === "") {
return placeholder || "Choose an option"
}
return getSecondaryFieldAttribute(getSecondaryOptionLabel, value, options)
}
const onPickPrimary = e => {
primaryLabel = e?.detail?.label || null
primaryValue = e?.detail?.value || null
dispatch("pickprimary", e?.detail?.value || {})
}
const onPickSecondary = e => {
secondaryValue = e.detail
dispatch("picksecondary", e.detail)
}
const extractProperty = (value, property) => {
if (value && typeof value === "object") {
return value[property]
}
return value
}
const updateSearchTerm = e => {
searchTerm = e.detail
}
</script>
<Field {helpText} {label} {labelPosition} {error}>
<PickerDropdown
{searchTerm}
{autocomplete}
{error}
{disabled}
{readonly}
{placeholder}
{inputType}
{quiet}
{autofocus}
{primaryOptions}
{secondaryOptions}
{getSecondaryOptionLabel}
{getSecondaryOptionValue}
{getSecondaryOptionIcon}
{getSecondaryOptionColour}
{secondaryFieldText}
{secondaryFieldIcon}
{secondaryFieldColour}
{primaryValue}
{secondaryValue}
{primaryLabel}
{secondaryLabel}
{showClearIcon}
on:pickprimary={onPickPrimary}
on:picksecondary={onPickSecondary}
on:search={updateSearchTerm}
on:click
on:input
on:blur
on:focus
on:keyup
on:closed
/>
</Field>

View File

@ -1,16 +1,15 @@
<script> <script lang="ts">
import Icon from "./Icon.svelte" import Icon from "./Icon.svelte"
import Tooltip from "../Tooltip/Tooltip.svelte" import Tooltip from "../Tooltip/Tooltip.svelte"
import { fade } from "svelte/transition" import { fade } from "svelte/transition"
export let icon export let icon: string | undefined = undefined
export let background export let background: string | undefined = undefined
export let color export let color: string | undefined = undefined
export let size = "M" export let size: "XS" | "S" | "M" | "L" = "M"
export let tooltip export let tooltip: string | undefined = undefined
let showTooltip = false let showTooltip: boolean = false
</script> </script>
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y-no-static-element-interactions -->

View File

@ -1,19 +1,24 @@
<script> <script lang="ts">
import "@spectrum-css/popover/dist/index-vars.css" import "@spectrum-css/popover/dist/index-vars.css"
import clickOutside from "../Actions/click_outside" import clickOutside from "../Actions/click_outside"
import { fly } from "svelte/transition" import { fly } from "svelte/transition"
import Icon from "../Icon/Icon.svelte" import Icon from "../Icon/Icon.svelte"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
export let value export let value: string | undefined
export let size = "M" export let size: "S" | "M" | "L" = "M"
export let alignRight = false export let alignRight: boolean = false
let open = false let open: boolean = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const iconList = [ interface IconCategory {
label: string
icons: string[]
}
const iconList: IconCategory[] = [
{ {
label: "Icons", label: "Icons",
icons: [ icons: [
@ -45,12 +50,12 @@
}, },
] ]
const onChange = value => { const onChange = (value: string) => {
dispatch("change", value) dispatch("change", value)
open = false open = false
} }
const handleOutsideClick = event => { const handleOutsideClick = (event: MouseEvent) => {
if (open) { if (open) {
event.stopPropagation() event.stopPropagation()
open = false open = false
@ -77,11 +82,11 @@
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open" class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
class:spectrum-Popover--align-right={alignRight} class:spectrum-Popover--align-right={alignRight}
> >
{#each iconList as icon} {#each iconList as iconList}
<div class="category"> <div class="category">
<div class="heading">{icon.label}</div> <div class="heading">{iconList.label}</div>
<div class="icons"> <div class="icons">
{#each icon.icons as icon} {#each iconList.icons as icon}
<div <div
on:click={() => { on:click={() => {
onChange(icon) onChange(icon)

View File

@ -1,14 +0,0 @@
<div class="icon-side-nav">
<slot />
</div>
<style>
.icon-side-nav {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding: var(--spacing-s);
gap: var(--spacing-xs);
}
</style>

View File

@ -1,58 +0,0 @@
<script>
import Icon from "../Icon/Icon.svelte"
import Tooltip from "../Tooltip/Tooltip.svelte"
import { fade } from "svelte/transition"
export let icon
export let active = false
export let tooltip
let showTooltip = false
</script>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="icon-side-nav-item"
class:active
on:mouseover={() => (showTooltip = true)}
on:focus={() => (showTooltip = true)}
on:mouseleave={() => (showTooltip = false)}
on:click
>
<Icon name={icon} hoverable />
{#if tooltip && showTooltip}
<div class="tooltip" in:fade={{ duration: 130, delay: 250 }}>
<Tooltip textWrapping direction="right" text={tooltip} />
</div>
{/if}
</div>
<style>
.icon-side-nav-item {
width: 36px;
height: 36px;
display: grid;
place-items: center;
border-radius: 4px;
position: relative;
cursor: pointer;
transition: background 130ms ease-out;
}
.icon-side-nav-item:hover :global(svg),
.active :global(svg) {
color: var(--spectrum-global-color-gray-900);
}
.active {
background: var(--spectrum-global-color-gray-300);
}
.tooltip {
position: absolute;
pointer-events: none;
left: calc(100% - 4px);
top: 50%;
white-space: nowrap;
transform: translateY(-50%);
z-index: 1;
}
</style>

View File

@ -1,22 +1,22 @@
<script> <script lang="ts">
import "@spectrum-css/inlinealert/dist/index-vars.css" import "@spectrum-css/inlinealert/dist/index-vars.css"
import Button from "../Button/Button.svelte" import Button from "../Button/Button.svelte"
import Icon from "../Icon/Icon.svelte" import Icon from "../Icon/Icon.svelte"
export let type = "info" export let type: "info" | "error" | "success" | "help" | "negative" = "info"
export let header = "" export let header: string = ""
export let message = "" export let message: string = ""
export let onConfirm = undefined export let onConfirm: (() => void) | undefined = undefined
export let buttonText = "" export let buttonText: string = ""
export let cta = false export let cta: boolean = false
export let link = "" export let link: string = ""
export let linkText = "" export let linkText: string = ""
$: icon = selectIcon(type) $: icon = selectIcon(type)
// if newlines used, convert them to different elements // if newlines used, convert them to different elements
$: split = message.split("\n") $: split = message.split("\n")
function selectIcon(alertType) { function selectIcon(alertType: string): string {
switch (alertType) { switch (alertType) {
case "error": case "error":
case "negative": case "negative":

View File

@ -1,19 +1,21 @@
<script> <script lang="ts">
import Input from "../Form/Input.svelte" import Input from "../Form/Input.svelte"
import Icon from "../Icon/Icon.svelte" import Icon from "../Icon/Icon.svelte"
import { notifications } from "../Stores/notifications" import { notifications } from "../Stores/notifications"
export let label = null export let label: string | undefined = undefined
export let value export let value: string | undefined = undefined
const copyToClipboard = val => { const copyToClipboard = (val: string | undefined) => {
const dummy = document.createElement("textarea") if (val) {
document.body.appendChild(dummy) const dummy = document.createElement("textarea")
dummy.value = val document.body.appendChild(dummy)
dummy.select() dummy.value = val
document.execCommand("copy") dummy.select()
document.body.removeChild(dummy) document.execCommand("copy")
notifications.success(`Copied to clipboard`) document.body.removeChild(dummy)
notifications.success(`Copied to clipboard`)
}
} }
</script> </script>

View File

@ -1,10 +1,10 @@
<script> <script lang="ts">
import "@spectrum-css/fieldlabel/dist/index-vars.css" import "@spectrum-css/fieldlabel/dist/index-vars.css"
import TooltipWrapper from "../Tooltip/TooltipWrapper.svelte" import TooltipWrapper from "../Tooltip/TooltipWrapper.svelte"
export let size = "M" export let size: "S" | "M" | "L" = "M"
export let tooltip = "" export let tooltip: string = ""
export let muted = undefined export let muted: boolean | undefined = undefined
</script> </script>
<TooltipWrapper {tooltip} {size}> <TooltipWrapper {tooltip} {size}>

View File

@ -1,12 +1,17 @@
<script> <script lang="ts">
export let horizontal = false export let horizontal: boolean = false
export let paddingX = "M" export let paddingX: "S" | "M" | "L" | "XL" | "XXL" = "M"
export let paddingY = "M" export let paddingY: "S" | "M" | "L" | "XL" | "XXL" = "M"
export let noPadding = false export let noPadding: boolean = false
export let gap = "M" export let gap: "XXS" | "XS" | "S" | "M" | "L" | "XL" = "M"
export let noGap = false export let noGap: boolean = false
export let alignContent = "normal" export let alignContent:
export let justifyItems = "stretch" | "start"
| "center"
| "space-between"
| "space-around"
| "normal" = "normal"
export let justifyItems: "stretch" | "start" | "center" | "end" = "stretch"
</script> </script>
<div <div

View File

@ -1,13 +1,13 @@
<script> <script lang="ts">
import { setContext } from "svelte" import { setContext } from "svelte"
import clickOutside from "../Actions/click_outside" import clickOutside from "../Actions/click_outside"
export let wide = false export let wide: boolean = false
export let narrow = false export let narrow: boolean = false
export let narrower = false export let narrower: boolean = false
export let noPadding = false export let noPadding: boolean = false
let sidePanelVisible = false let sidePanelVisible: boolean = false
setContext("side-panel", { setContext("side-panel", {
open: () => (sidePanelVisible = true), open: () => (sidePanelVisible = true),

View File

@ -1,7 +1,7 @@
<script> <script lang="ts">
import Detail from "../Typography/Detail.svelte" import Detail from "../Typography/Detail.svelte"
export let title = null export let title: string | null = null
</script> </script>
<div> <div>

View File

@ -1,20 +1,20 @@
<script> <script lang="ts">
import SpectrumMDE from "./SpectrumMDE.svelte" import SpectrumMDE from "./SpectrumMDE.svelte"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
export let value = null export let value: string | null = null
export let height = null export let height: string | null = null
export let placeholder = null export let placeholder: string | null = null
export let id = null export let id: string | null = null
export let fullScreenOffset = 0 export let fullScreenOffset: { x: string; y: string } | null = null
export let disabled = false export let disabled: boolean = false
export let readonly = false export let readonly: boolean = false
export let easyMDEOptions export let easyMDEOptions: Record<string, any> = {}
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let latestValue let latestValue: string | null
let mde let mde: any
// Ensure the value is updated if the value prop changes outside the editor's // Ensure the value is updated if the value prop changes outside the editor's
// control // control
@ -24,7 +24,7 @@
mde?.togglePreview() mde?.togglePreview()
} }
const checkValue = val => { const checkValue = (val: string | null) => {
if (mde && val !== latestValue) { if (mde && val !== latestValue) {
mde.value(val) mde.value(val)
} }

View File

@ -1,10 +1,10 @@
<script> <script lang="ts">
import SpectrumMDE from "./SpectrumMDE.svelte" import SpectrumMDE from "./SpectrumMDE.svelte"
export let value export let value: string | null = null
export let height export let height: string | null = null
let mde let mde: any
// Keep the value up to date // Keep the value up to date
$: mde && mde.value(value || "") $: mde && mde.value(value || "")

View File

@ -1,17 +1,17 @@
<script> <script lang="ts">
import EasyMDE from "easymde" import EasyMDE from "easymde"
import "easymde/dist/easymde.min.css" import "easymde/dist/easymde.min.css"
import { onMount } from "svelte" import { onMount } from "svelte"
export let height = null export let height: string | null = null
export let scroll = true export let scroll: boolean = true
export let easyMDEOptions = null export let easyMDEOptions: Record<string, any> | null = null
export let mde = null export let mde: EasyMDE | null = null
export let id = null export let id: string | null = null
export let fullScreenOffset = null export let fullScreenOffset: { x: string; y: string } | null = null
export let disabled = false export let disabled: boolean = false
let element let element: HTMLTextAreaElement | undefined = undefined
onMount(() => { onMount(() => {
height = height || "200px" height = height || "200px"
@ -27,13 +27,13 @@
// Revert the editor when we unmount // Revert the editor when we unmount
return () => { return () => {
mde.toTextArea() mde?.toTextArea()
} }
}) })
$: styleString = getStyleString(fullScreenOffset) $: styleString = getStyleString(fullScreenOffset)
const getStyleString = offset => { const getStyleString = (offset: { x?: string; y?: string } | null) => {
let string = "" let string = ""
string += `--fullscreen-offset-x:${offset?.x || "0px"};` string += `--fullscreen-offset-x:${offset?.x || "0px"};`
string += `--fullscreen-offset-y:${offset?.y || "0px"};` string += `--fullscreen-offset-y:${offset?.y || "0px"};`

View File

@ -1,18 +1,18 @@
<script> <script lang="ts">
import { createEventDispatcher, getContext } from "svelte" import { createEventDispatcher, getContext } from "svelte"
import Icon from "../Icon/Icon.svelte" import Icon from "../Icon/Icon.svelte"
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const actionMenu = getContext("actionMenu") const actionMenu = getContext("actionMenu") as { hideAll: () => void }
export let icon = undefined export let icon: string | undefined = undefined
export let disabled = undefined export let disabled: boolean | undefined = undefined
export let noClose = false export let noClose: boolean = false
export let keyBind = undefined export let keyBind: string | undefined = undefined
$: keys = getKeys(keyBind) $: keys = getKeys(keyBind)
const getKeys = keyBind => { const getKeys = (keyBind: string | undefined): string[] => {
let keys = keyBind?.split("+") || [] let keys = keyBind?.split("+") || []
for (let i = 0; i < keys.length; i++) { for (let i = 0; i < keys.length; i++) {
if ( if (

View File

@ -1,4 +1,4 @@
<script> <script lang="ts">
import "@spectrum-css/menu/dist/index-vars.css" import "@spectrum-css/menu/dist/index-vars.css"
</script> </script>

View File

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

@ -1,5 +1,5 @@
<script> <script lang="ts">
export let heading export let heading: string
</script> </script>
<li role="presentation"> <li role="presentation">

View File

@ -1,7 +1,7 @@
<script> <script lang="ts">
import Input from "../Form/Input.svelte" import Input from "../Form/Input.svelte"
let value = "" let value: string = ""
</script> </script>
<Input label="Your Name" bind:value /> <Input label="Your Name" bind:value />

View File

@ -1,11 +1,11 @@
<script> <script lang="ts">
import { getContext } from "svelte" import { getContext } from "svelte"
import Context from "../context" import Context from "../context"
const { hide } = getContext(Context.Modal) const { hide } = getContext(Context.Modal) as { hide: () => void }
let count = 0 let count: number = 0
const clicks = 5 const clicks: number = 5
$: if (count === clicks) hide() $: if (count === clicks) hide()
$: remaining = clicks - count $: remaining = clicks - count

View File

@ -1,4 +1,4 @@
<script> <script lang="ts">
import "@spectrum-css/modal/dist/index-vars.css" import "@spectrum-css/modal/dist/index-vars.css"
import "@spectrum-css/underlay/dist/index-vars.css" import "@spectrum-css/underlay/dist/index-vars.css"
import { createEventDispatcher, setContext, tick, onMount } from "svelte" import { createEventDispatcher, setContext, tick, onMount } from "svelte"
@ -6,33 +6,37 @@
import Portal from "svelte-portal" import Portal from "svelte-portal"
import Context from "../context" import Context from "../context"
export let fixed = false export let fixed: boolean = false
export let inline = false export let inline: boolean = false
export let disableCancel = false export let disableCancel: boolean = false
export let autoFocus = true export let autoFocus: boolean = true
export let zIndex = 1001 export let zIndex: number = 1001
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher<{
let visible = fixed || inline show: void
let modal hide: void
cancel: void
}>()
let visible: boolean = fixed || inline
let modal: HTMLElement | undefined
$: dispatch(visible ? "show" : "hide") $: dispatch(visible ? "show" : "hide")
export function show() { export function show(): void {
if (visible) { if (visible) {
return return
} }
visible = true visible = true
} }
export function hide() { export function hide(): void {
if (!visible || fixed || inline) { if (!visible || fixed || inline) {
return return
} }
visible = false visible = false
} }
export function toggle() { export function toggle(): void {
if (visible) { if (visible) {
hide() hide()
} else { } else {
@ -40,7 +44,7 @@
} }
} }
export function cancel() { export function cancel(): void {
if (!visible || disableCancel) { if (!visible || disableCancel) {
return return
} }
@ -48,34 +52,33 @@
hide() hide()
} }
function handleKey(e) { function handleKey(e: KeyboardEvent): void {
if (visible && e.key === "Escape") { if (visible && e.key === "Escape") {
cancel() cancel()
} }
} }
async function focusModal(node) { function focusModal(node: HTMLElement): void {
if (!autoFocus) { if (!autoFocus) return
return tick().then(() => {
} const inputs = node.querySelectorAll("input")
await tick() if (inputs?.length) {
inputs[0].focus()
// Try to focus first input } else if (modal) {
const inputs = node.querySelectorAll("input") const confirm = modal.querySelector(".confirm-wrap .spectrum-Button")
if (inputs?.length) { if (confirm) {
inputs[0].focus() ;(confirm as HTMLElement).focus()
} }
// Otherwise try to focus confirmation button
else if (modal) {
const confirm = modal.querySelector(".confirm-wrap .spectrum-Button")
if (confirm) {
confirm.focus()
} }
} })
} }
setContext(Context.Modal, { show, hide, toggle, cancel }) setContext(Context.Modal, {
show,
hide,
toggle,
cancel,
} as { show: () => void; hide: () => void; toggle: () => void; cancel: () => void })
onMount(() => { onMount(() => {
document.addEventListener("keydown", handleKey) document.addEventListener("keydown", handleKey)

View File

@ -1,116 +0,0 @@
<script>
import { View } from "svench";
import Modal from "./Modal.svelte";
import ModalContent from "./ModalContent.svelte";
import Button from "../Button/Button.svelte";
import Content from "./Content.svelte";
import QuizModal from "./QuizModal.svelte";
import CustomContent from "./CustomContent.svelte";
let modal1
let modal2
let modal3
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
async function longTask() {
await sleep(3000)
}
</script>
<style>
p, span {
font-size: var(--font-size-s);
}
code {
display: inline-block;
box-sizing: border-box;
padding: var(--spacing-xs) var(--spacing-s);
background-color: var(--grey-2);
color: var(--red-dark);
border-radius: var(--spacing-xs);
}
</style>
<h3>Modals</h3>
<p>
Modals provide a means to render content in front of everything else on a page.
</p>
<p>
The modal module in BBUI exposes two
separate components to provide this functionality; a <code>Modal</code> component to control visibility of content,
and a <code>ModalContent</code> component to quickly construct the typical content - although this is optional.
</p>
<p>
One of the common problems with modals and popups is stale state reappearing after hiding and showing the content
again, since the state hasn't been garbage collected if a component controls its own visibility. This is handled for
you when using the <code>Modal</code> component as it will fully unmount child components, properly resetting state
every time it appears.
</p>
<br/>
<p>Use ModalContent to render typical modal content.</p>
<View name="Simple Confirmation Modal">
<Button primary on:click={modal1.show}>Delete Record</Button>
<Modal bind:this={modal1}>
<ModalContent title="Confirm Deletion" confirmText="Delete">
<span>Are you sure you want to delete this record?</span>
</ModalContent>
</Modal>
</View>
<br/>
<p>
Width can be specified as a prop to a <code>Modal</code>. Any additional <code>ModalContent</code> props provided
will be passed to the confirmation button.
</p>
<View name="Different Buttons and Width">
<Button primary on:click={modal3.show}>Open Modal</Button>
<Modal bind:this={modal3} width="250px">
<ModalContent
title="Confirmation Required"
showCancelButton={false}
showCloseIcon={false}
confirmText="I'm sure!"
green
large
wide
>
<span>Are you sure you want to do that?</span>
</ModalContent>
</Modal>
</View>
<br/>
<p>Any content can be rendered inside a <code>Modal</code>. Use context to close the modal from your own components.</p>
<View name="Custom Content">
<Button primary on:click={modal1.show}>Open Modal</Button>
<Modal bind:this={modal1} padding={false} border={false}>
<CustomContent/>
</Modal>
</View>
<br/>
<p>Async functions passed in as the onConfirm prop will make the modal wait until the callback is completed.</p>
<View name="Async Callbacks">
<Button primary on:click={modal2.show}>Long Task</Button>
<Modal bind:this={modal2}>
<ModalContent
title="Perform Long Task"
confirmText="Submit"
onConfirm={longTask}
>
<span>Pressing submit will wait 3 seconds before finishing and disable the confirm button until it's done.</span>
</ModalContent>
</Modal>
</View>
<br/>
<p>Returning false from a onConfirm callback will prevent the modal being closed.</p>
<View name="Callback Failure Handling">
<Button primary on:click={modal3.show}>Open Quiz</Button>
<Modal bind:this={modal3}>
<QuizModal />
</Modal>
</View>

View File

@ -1,8 +1,8 @@
<script context="module"> <script context="module" lang="ts">
export const keepOpen = Symbol("keepOpen") export const keepOpen = Symbol("keepOpen")
</script> </script>
<script> <script lang="ts">
import "@spectrum-css/dialog/dist/index-vars.css" import "@spectrum-css/dialog/dist/index-vars.css"
import { getContext } from "svelte" import { getContext } from "svelte"
import Button from "../Button/Button.svelte" import Button from "../Button/Button.svelte"
@ -11,31 +11,36 @@
import Context from "../context" import Context from "../context"
import ProgressCircle from "../ProgressCircle/ProgressCircle.svelte" import ProgressCircle from "../ProgressCircle/ProgressCircle.svelte"
export let title = undefined export let title: string | undefined = undefined
export let size = "S" export let size: "S" | "M" | "L" | "XL" = "S"
export let cancelText = "Cancel" export let cancelText: string = "Cancel"
export let confirmText = "Confirm" export let confirmText: string = "Confirm"
export let showCancelButton = true export let showCancelButton: boolean = true
export let showConfirmButton = true export let showConfirmButton: boolean = true
export let showCloseIcon = true export let showCloseIcon: boolean = true
export let onConfirm = undefined export let onConfirm: (() => Promise<any> | any) | undefined = undefined
export let onCancel = undefined export let onCancel: (() => Promise<any> | any) | undefined = undefined
export let disabled = false export let disabled: boolean = false
export let showDivider = true export let showDivider: boolean = true
export let showSecondaryButton = false export let showSecondaryButton: boolean = false
export let secondaryButtonText = undefined export let secondaryButtonText: string | undefined = undefined
export let secondaryAction = undefined export let secondaryAction: ((_e: Event) => Promise<any> | any) | undefined =
export let secondaryButtonWarning = false undefined
export let custom = false export let secondaryButtonWarning: boolean = false
export let custom: boolean = false
const { hide, cancel } = getContext(Context.Modal) const { hide, cancel } = getContext(Context.Modal) as {
hide: () => void
cancel: () => void
}
let loading = false let loading: boolean = false
let confirmDisabled: boolean
$: confirmDisabled = disabled || loading $: confirmDisabled = disabled || loading
async function secondary(e) { async function secondary(e: Event): Promise<void> {
loading = true loading = true
if (!secondaryAction || (await secondaryAction(e)) !== keepOpen) { if (!secondaryAction || (await secondaryAction(e)) !== keepOpen) {
hide() hide()
@ -43,7 +48,7 @@
loading = false loading = false
} }
export async function confirm() { export async function confirm(): Promise<void> {
loading = true loading = true
if (!onConfirm || (await onConfirm()) !== keepOpen) { if (!onConfirm || (await onConfirm()) !== keepOpen) {
hide() hide()
@ -51,7 +56,7 @@
loading = false loading = false
} }
async function close() { async function close(): Promise<void> {
loading = true loading = true
if (!onCancel || (await onCancel()) !== keepOpen) { if (!onCancel || (await onCancel()) !== keepOpen) {
cancel() cancel()
@ -90,7 +95,6 @@
{/if} {/if}
{/if} {/if}
<!-- TODO: Remove content-grid class once Layout components are in bbui -->
<section class="spectrum-Dialog-content content-grid"> <section class="spectrum-Dialog-content content-grid">
<slot {loading} /> <slot {loading} />
</section> </section>
@ -102,7 +106,6 @@
{#if showSecondaryButton && secondaryButtonText && secondaryAction} {#if showSecondaryButton && secondaryButtonText && secondaryAction}
<div class="secondary-action"> <div class="secondary-action">
<Button <Button
group
secondary secondary
warning={secondaryButtonWarning} warning={secondaryButtonWarning}
on:click={secondary}>{secondaryButtonText}</Button on:click={secondary}>{secondaryButtonText}</Button
@ -111,14 +114,13 @@
{/if} {/if}
{#if showCancelButton} {#if showCancelButton}
<Button group secondary on:click={close}> <Button secondary on:click={close}>
{cancelText} {cancelText}
</Button> </Button>
{/if} {/if}
{#if showConfirmButton} {#if showConfirmButton}
<span class="confirm-wrap"> <span class="confirm-wrap">
<Button <Button
group
cta cta
{...$$restProps} {...$$restProps}
disabled={confirmDisabled} disabled={confirmDisabled}

View File

@ -1,53 +0,0 @@
<script>
import ModalContent from "./ModalContent.svelte"
import Input from "../Form/Input.svelte"
let modal
let answer
let error
export function show() {
modal.show()
}
export function hide() {
modal.hide
}
function resetState() {
answer = undefined
error = undefined
}
async function answerQuiz() {
const correct = answer === "8"
error = !correct
return correct
}
</script>
<ModalContent
title="Quick Maths"
bind:this={modal}
confirmText="Submit"
onConfirm={answerQuiz}
on:show={resetState}
>
{#if error}
<p class="error">Wrong answer! Try again.</p>
{/if}
<p>What is 4 + 4?</p>
<Input label="Answer" bind:value={answer} />
</ModalContent>
<style>
p {
margin: 0;
font-size: var(--font-size-s);
}
p.error {
color: #e26d69;
background-color: #ffe6e6;
padding: 8px;
border-radius: 4px;
}
</style>

View File

@ -1,17 +1,17 @@
<script> <script lang="ts">
import { ActionButton } from "../" import { ActionButton } from "../"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
export let type = "info" export let type: string = "info"
export let icon = "Info" export let icon: string = "Info"
export let message = "" export let message: string = ""
export let dismissable = false export let dismissable: boolean = false
export let actionMessage = null export let actionMessage: string | null = null
export let action = null export let action: ((_dismiss: () => void) => void) | null = null
export let wide = false export let wide: boolean = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher<{ dismiss: void }>()
</script> </script>
<div class="spectrum-Toast spectrum-Toast--{type}" class:wide> <div class="spectrum-Toast spectrum-Toast--{type}" class:wide>

View File

@ -1,4 +1,4 @@
<script> <script lang="ts">
import "@spectrum-css/toast/dist/index-vars.css" import "@spectrum-css/toast/dist/index-vars.css"
import Portal from "svelte-portal" import Portal from "svelte-portal"
import { notifications } from "../Stores/notifications" import { notifications } from "../Stores/notifications"

View File

@ -1,20 +1,19 @@
<script> <script lang="ts">
import "@spectrum-css/pagination/dist/index-vars.css" import "@spectrum-css/pagination/dist/index-vars.css"
import "@spectrum-css/actionbutton/dist/index-vars.css" import "@spectrum-css/actionbutton/dist/index-vars.css"
import "@spectrum-css/typography/dist/index-vars.css" import "@spectrum-css/typography/dist/index-vars.css"
export let page export let page: number
export let goToPrevPage export let goToPrevPage: () => void
export let goToNextPage export let goToNextPage: () => void
export let hasPrevPage = true export let hasPrevPage: boolean = true
export let hasNextPage = true export let hasNextPage: boolean = true
</script> </script>
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<nav class="spectrum-Pagination spectrum-Pagination--explicit"> <nav class="spectrum-Pagination spectrum-Pagination--explicit">
<div <div
href="#"
class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-ActionButton--quiet spectrum-Pagination-prevButton" class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-ActionButton--quiet spectrum-Pagination-prevButton"
on:click={hasPrevPage ? goToPrevPage : null} on:click={hasPrevPage ? goToPrevPage : null}
class:is-disabled={!hasPrevPage} class:is-disabled={!hasPrevPage}
@ -32,7 +31,6 @@
Page {page} Page {page}
</span> </span>
<div <div
href="#"
class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-ActionButton--quiet spectrum-Pagination-nextButton" class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-ActionButton--quiet spectrum-Pagination-nextButton"
on:click={hasNextPage ? goToNextPage : null} on:click={hasNextPage ? goToNextPage : null}
class:is-disabled={!hasNextPage} class:is-disabled={!hasNextPage}

View File

@ -1,25 +1,24 @@
<script> <script lang="ts">
import "@spectrum-css/progressbar/dist/index-vars.css" import "@spectrum-css/progressbar/dist/index-vars.css"
export let value = false export let value: number | boolean = false
export let duration = 1000 export let duration: number = 1000
export let width = false export let width: string | boolean = false
export let sideLabel = false export let sideLabel: boolean = false
export let hidePercentage = true export let hidePercentage: boolean = true
export let color // red, green, default = blue export let color: "red" | "green" | undefined = undefined // red, green, default = blue
export let size = "M" export let size: string = "M"
</script> </script>
<div <div
class:spectrum-ProgressBar--indeterminate={!value && value !== 0} class:spectrum-ProgressBar--indeterminate={!value && value !== 0}
class:spectrum-ProgressBar--sideLabel={sideLabel} class:spectrum-ProgressBar--sideLabel={sideLabel}
class="spectrum-ProgressBar spectrum-ProgressBar--size{size}" class="spectrum-ProgressBar spectrum-ProgressBar--size{size}"
{value}
role="progressbar" role="progressbar"
aria-valuenow={value} aria-valuenow={typeof value === "number" ? value : undefined}
aria-valuemin="0" aria-valuemin="0"
aria-valuemax="100" aria-valuemax="100"
style={width ? `width: ${width};` : ""} style={width ? `width: ${typeof width === "string" ? width : ""};` : ""}
> >
{#if $$slots} {#if $$slots}
<div <div
@ -32,7 +31,7 @@
<div <div
class="spectrum-FieldLabel spectrum-ProgressBar-percentage spectrum-FieldLabel--size{size}" class="spectrum-FieldLabel spectrum-ProgressBar-percentage spectrum-FieldLabel--size{size}"
> >
{Math.round(value)}% {Math.round(Number(value))}%
</div> </div>
{/if} {/if}
<div class="spectrum-ProgressBar-track"> <div class="spectrum-ProgressBar-track">
@ -40,10 +39,12 @@
class="spectrum-ProgressBar-fill" class="spectrum-ProgressBar-fill"
class:color-green={color === "green"} class:color-green={color === "green"}
class:color-red={color === "red"} class:color-red={color === "red"}
style="width: {value}%; --duration: {duration}ms;" style="width: {typeof value === 'number'
? value
: 0}%; --duration: {duration}ms;"
/> />
</div> </div>
<div class="spectrum-ProgressBar-label" hidden="" /> <div class="spectrum-ProgressBar-label" hidden={false} />
</div> </div>
<style> <style>

View File

@ -1,8 +1,8 @@
<script> <script lang="ts">
import "@spectrum-css/progresscircle/dist/index-vars.css" import "@spectrum-css/progresscircle/dist/index-vars.css"
export let size = "M" export let size: "S" | "M" | "L" = "M"
function convertSize(size) { function convertSize(size: "S" | "M" | "L"): string | undefined {
switch (size) { switch (size) {
case "S": case "S":
return "small" return "small"
@ -13,18 +13,18 @@
} }
} }
export let value = null export let value: number | null = null
export let minValue = 0 export let minValue: number = 0
export let maxValue = 100 export let maxValue: number = 100
let subMask1Style let subMask1Style: string | undefined
let subMask2Style let subMask2Style: string | undefined
$: calculateSubMasks(value) $: calculateSubMasks(value)
function calculateSubMasks(value) { function calculateSubMasks(value: number | null): void {
if (value) { if (value) {
let percentage = ((value - minValue) / (maxValue - minValue)) * 100 let percentage = ((value - minValue) / (maxValue - minValue)) * 100
let angle let angle: number
if (percentage > 0 && percentage <= 50) { if (percentage > 0 && percentage <= 50) {
angle = -180 + (percentage / 50) * 180 angle = -180 + (percentage / 50) * 180
subMask1Style = `transform: rotate(${angle}deg);` subMask1Style = `transform: rotate(${angle}deg);`
@ -37,7 +37,7 @@
} }
} }
export let overBackground = false export let overBackground: boolean = false
</script> </script>
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y-no-static-element-interactions -->

View File

@ -1,78 +0,0 @@
<script>
import { flip } from 'svelte/animate';
import { fly } from "svelte/transition"
import { View } from "svench";
import { notifications } from "./notifications";
export let themes = {
danger: "#E26D69",
success: "#84C991",
warning: "#f0ad4e",
info: "#5bc0de",
default: "#aaaaaa",
}
</script>
## Notification Store
This custom can be used to display toast messages. It has 5 different methods: `send`, `danger`, `warning`, `success`, `info`.
<View name="danger">
<button on:click={() => notifications.error('This is a danger!')}>Danger</button>
</View>
<View name="warning">
<button on:click={() => notifications.warning('This is a warning!')}>Warning</button>
</View>
<View name="success">
<button on:click={() => notifications.success('This is a success!')}>Success</button>
</View>
<View name="info">
<button on:click={() => notifications.info('This is an info toast!')}>Info</button>
</View>
<div class="notifications">
{#each $notifications as notification (notification.id)}
<div
animate:flip
class="toast"
style="background: {themes[notification.type]};"
transition:fly={{ y: -30 }}>
<div class="content">{notification.message}</div>
{#if notification.icon}<i class={notification.icon} />{/if}
</div>
{/each}
</div>
<style>
.notifications {
position: fixed;
top: 10px;
left: 0;
right: 0;
margin: 0 auto;
padding: 0;
z-index: 9999;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
pointer-events: none;
}
.toast {
flex: 0 0 auto;
margin-bottom: 10px;
border-radius: var(--border-radius-s);
/* The toasts now support being auto sized, so this static width could be removed */
width: 40vw;
}
.content {
padding: 10px;
display: block;
color: white;
font-weight: 500;
}
</style>

View File

@ -3,7 +3,7 @@
import AbsTooltip from "./AbsTooltip.svelte" import AbsTooltip from "./AbsTooltip.svelte"
export let tooltip: string = "" export let tooltip: string = ""
export let size: "S" | "M" = "M" export let size: "S" | "M" | "L" = "M"
export let disabled: boolean = true export let disabled: boolean = true
</script> </script>

View File

@ -17,7 +17,6 @@ export { default as Toggle } from "./Form/Toggle.svelte"
export { default as RadioGroup } from "./Form/RadioGroup.svelte" export { default as RadioGroup } from "./Form/RadioGroup.svelte"
export { default as Checkbox } from "./Form/Checkbox.svelte" export { default as Checkbox } from "./Form/Checkbox.svelte"
export { default as InputDropdown } from "./Form/InputDropdown.svelte" export { default as InputDropdown } from "./Form/InputDropdown.svelte"
export { default as PickerDropdown } from "./Form/PickerDropdown.svelte"
export { default as EnvDropdown } from "./Form/EnvDropdown.svelte" export { default as EnvDropdown } from "./Form/EnvDropdown.svelte"
export { default as Multiselect } from "./Form/Multiselect.svelte" export { default as Multiselect } from "./Form/Multiselect.svelte"
export { default as Search } from "./Form/Search.svelte" export { default as Search } from "./Form/Search.svelte"
@ -87,8 +86,6 @@ export { default as MarkdownEditor } from "./Markdown/MarkdownEditor.svelte"
export { default as MarkdownViewer } from "./Markdown/MarkdownViewer.svelte" export { default as MarkdownViewer } from "./Markdown/MarkdownViewer.svelte"
export { default as List } from "./List/List.svelte" export { default as List } from "./List/List.svelte"
export { default as ListItem } from "./List/ListItem.svelte" export { default as ListItem } from "./List/ListItem.svelte"
export { default as IconSideNav } from "./IconSideNav/IconSideNav.svelte"
export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte"
export { default as Accordion } from "./Accordion/Accordion.svelte" export { default as Accordion } from "./Accordion/Accordion.svelte"
export { default as AbsTooltip } from "./Tooltip/AbsTooltip.svelte" export { default as AbsTooltip } from "./Tooltip/AbsTooltip.svelte"