forked from tsai/budibase
162 changed files with 2486 additions and 9523 deletions
@ -1,45 +0,0 @@ |
|||||
<script> |
|
||||
import Flatpickr from "svelte-flatpickr" |
|
||||
import { Label } 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> |
|
||||
@ -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> |
|
||||
@ -1,14 +1,16 @@ |
|||||
<script> |
<script> |
||||
import "@spectrum-css/divider/dist/index-vars.css" |
import "@spectrum-css/divider/dist/index-vars.css" |
||||
export let l = false |
export let l = false |
||||
export let m = false |
export let m = false |
||||
export let s = false |
export let s = false |
||||
|
|
||||
export let vertical = false |
export let vertical = false |
||||
|
|
||||
|
$: useDefault = ![l, m, s].includes(true) |
||||
</script> |
</script> |
||||
|
|
||||
<hr |
<hr |
||||
class:spectrum-Divider--sizeL={l} |
class:spectrum-Divider--sizeL={l} |
||||
class:spectrum-Divider--sizeM={m} |
class:spectrum-Divider--sizeM={m || useDefault} |
||||
class:spectrum-Divider--sizeS={s} |
class:spectrum-Divider--sizeS={s} |
||||
class="spectrum-Divider spectrum-Divider--{vertical ? 'vertical' : 'horizontal'} spectrum-Dialog-divider"> |
class="spectrum-Divider spectrum-Divider--{vertical ? 'vertical' : 'horizontal'} spectrum-Dialog-divider" /> |
||||
|
|||||
@ -1,140 +1,22 @@ |
|||||
<script> |
<script> |
||||
|
import Field from "./Field.svelte" |
||||
|
import Checkbox from "./Core/Checkbox.svelte" |
||||
import { createEventDispatcher } from "svelte" |
import { createEventDispatcher } from "svelte" |
||||
|
|
||||
const dispatch = createEventDispatcher() |
export let value = null |
||||
|
export let label = null |
||||
export let checked = false |
export let labelPosition = "above" |
||||
export let value |
export let text = null |
||||
export let name |
export let disabled = false |
||||
export let disabled |
export let error = null |
||||
|
|
||||
function handleChange() { |
const dispatch = createEventDispatcher() |
||||
if (disabled) return |
const onChange = e => { |
||||
checked = !checked |
value = e.detail |
||||
dispatch("change", checked) |
dispatch("change", e.detail) |
||||
} |
} |
||||
</script> |
</script> |
||||
|
|
||||
<div class="container"> |
<Field {label} {labelPosition} {disabled} {error}> |
||||
<input |
<Checkbox {error} {disabled} {text} {value} on:change={onChange} /> |
||||
{disabled} |
</Field> |
||||
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> |
|
||||
|
|||||
@ -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> |
|
||||
@ -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> |
||||
@ -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> |
||||
@ -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> |
||||
@ -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> |
||||
@ -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} /> |
||||
@ -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> |
||||
@ -0,0 +1,36 @@ |
|||||
|
<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 |
||||
|
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> |
||||
@ -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> |
||||
@ -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} /> |
||||
@ -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> |
||||
@ -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> |
||||
@ -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> |
||||
@ -0,0 +1,10 @@ |
|||||
|
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" |
||||
@ -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> |
|
||||
@ -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> |
|
||||
|
|
||||
@ -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> |
||||
@ -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> |
||||
@ -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> |
||||
@ -1,190 +1,33 @@ |
|||||
<script> |
<script> |
||||
|
import Field from "./Field.svelte" |
||||
|
import TextField from "./Core/TextField.svelte" |
||||
import { createEventDispatcher } from "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 value = null |
||||
export let label = undefined |
export let label = null |
||||
export let outline = false |
export let labelPosition = "above" |
||||
export let presentation = false |
export let placeholder = null |
||||
export let thin = false |
export let type = "text" |
||||
export let extraThin = false |
|
||||
export let large = false |
|
||||
export let border = false |
|
||||
export let edit = false |
|
||||
export let disabled = false |
export let disabled = false |
||||
export let type = undefined |
export let readonly = false |
||||
export let placeholder = "" |
export let error = null |
||||
export let value = "" |
|
||||
export let error = false |
|
||||
export let validator = () => {} |
|
||||
|
|
||||
// This section handles the edit mode and dispatching of things to the parent when saved |
|
||||
let editMode = false |
|
||||
|
|
||||
const updateValue = e => { |
const dispatch = createEventDispatcher() |
||||
if (type === "number") { |
const onChange = e => { |
||||
const num = parseFloat(e.target.value) |
value = e.detail |
||||
value = isNaN(num) ? "" : num |
dispatch("change", e.detail) |
||||
} else { |
|
||||
value = e.target.value |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const save = () => { |
|
||||
editMode = false |
|
||||
dispatch("save", value) |
|
||||
} |
|
||||
|
|
||||
const enableEdit = () => { |
|
||||
editMode = true |
|
||||
} |
} |
||||
</script> |
</script> |
||||
|
|
||||
<div class="container"> |
<Field {label} {labelPosition} {disabled} {error}> |
||||
{#if label || edit} |
<TextField |
||||
<div class="label-container"> |
{error} |
||||
{#if label} |
{disabled} |
||||
<Label extraSmall grey forAttr={name}>{label}</Label> |
{readonly} |
||||
{/if} |
{value} |
||||
{#if edit} |
{placeholder} |
||||
<div class="controls"> |
|
||||
<Button small secondary disabled={editMode} on:click={enableEdit}> |
|
||||
Edit |
|
||||
</Button> |
|
||||
<Button small blue disabled={!editMode} on:click={save}>Save</Button> |
|
||||
</div> |
|
||||
{/if} |
|
||||
</div> |
|
||||
{/if} |
|
||||
<input |
|
||||
class:outline |
|
||||
class:presentation |
|
||||
class:thin |
|
||||
class:extraThin |
|
||||
class:large |
|
||||
class:border |
|
||||
on:change |
|
||||
on:input |
|
||||
on:change={updateValue} |
|
||||
on:input={updateValue} |
|
||||
on:blur={updateValue} |
|
||||
use:validator |
|
||||
disabled={disabled || (edit && !editMode)} |
|
||||
value={value == null ? '' : value} |
|
||||
{type} |
{type} |
||||
{name} |
on:change={onChange} |
||||
{placeholder} /> |
on:click |
||||
{#if error} |
on:input /> |
||||
<div class="error">{error}</div> |
</Field> |
||||
{/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> |
|
||||
|
|||||
@ -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> |
|
||||
@ -1,324 +1,35 @@ |
|||||
<script> |
<script> |
||||
import Portal from "svelte-portal" |
|
||||
import { afterUpdate } from "svelte" |
|
||||
import { createEventDispatcher } from "svelte" |
import { createEventDispatcher } from "svelte" |
||||
import { fly } from "svelte/transition" |
import Multiselect from "./Core/Multiselect.svelte" |
||||
import Label from "../Styleguide/Label.svelte" |
import Field from "./Field.svelte" |
||||
const xPath = |
|
||||
"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" |
|
||||
|
|
||||
import positionDropdown from "../Actions/position_dropdown" |
|
||||
import clickOutside from "../Actions/click_outside" |
|
||||
|
|
||||
const dispatch = createEventDispatcher() |
|
||||
|
|
||||
export let value = [] |
export let value = [] |
||||
export let label = undefined |
export let label = null |
||||
export let align = "left" |
|
||||
export let secondary = false |
|
||||
export let outline = false |
|
||||
export let disabled = false |
export let disabled = false |
||||
export let placeholder = undefined |
export let readonly = false |
||||
export let extraThin = false |
export let labelPosition = "above" |
||||
|
export let error = null |
||||
let options = [] |
export let placeholder = null |
||||
let optionsVisible = false |
export let options = [] |
||||
let slot |
export let getOptionLabel = option => option |
||||
let anchor |
export let getOptionValue = option => option |
||||
$: 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) { |
const dispatch = createEventDispatcher() |
||||
value = [...value, val] |
const onChange = e => { |
||||
dispatch("change", value) |
value = e.detail |
||||
} |
dispatch("change", e.detail) |
||||
|
|
||||
function remove(val) { |
|
||||
value = value.filter(option => option !== val) |
|
||||
dispatch("change", value) |
|
||||
} |
|
||||
|
|
||||
function showOptions(show) { |
|
||||
optionsVisible = show |
|
||||
} |
|
||||
|
|
||||
function handleClick() { |
|
||||
showOptions(!optionsVisible) |
|
||||
} |
|
||||
|
|
||||
function handleOptionMousedown(e) { |
|
||||
const value = e.target.dataset.value |
|
||||
if (value == null) { |
|
||||
return |
|
||||
} |
|
||||
if (lookupMap[value]) { |
|
||||
remove(value) |
|
||||
} else { |
|
||||
add(value) |
|
||||
} |
|
||||
} |
} |
||||
</script> |
</script> |
||||
|
|
||||
{#if label} |
<Field {label} {labelPosition} {disabled} {error}> |
||||
<Label extraSmall grey>{label}</Label> |
<Multiselect |
||||
{/if} |
{error} |
||||
<div class="multiselect" bind:this={anchor}> |
{disabled} |
||||
<div class="tokens-wrapper"> |
{value} |
||||
<div |
{options} |
||||
class="tokens" |
{placeholder} |
||||
class:outline |
{getOptionLabel} |
||||
class:disabled |
{getOptionValue} |
||||
class:secondary |
on:change={onChange} |
||||
class:extraThin |
on:click /> |
||||
class:optionsVisible |
</Field> |
||||
on:click|self={handleClick} |
|
||||
class:empty={!value || !value.length}> |
|
||||
{#each selectedOptions as option} |
|
||||
<div |
|
||||
class="token" |
|
||||
class:extraThin |
|
||||
data-id={option.value} |
|
||||
on:click|self={handleClick}> |
|
||||
<span>{option.name}</span> |
|
||||
<div |
|
||||
class="token-remove" |
|
||||
title="Remove {option.name}" |
|
||||
on:click={() => remove(option.value)}> |
|
||||
<svg |
|
||||
class="icon-clear" |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
width="18" |
|
||||
height="18" |
|
||||
viewBox="0 0 24 24"> |
|
||||
<path d={xPath} /> |
|
||||
</svg> |
|
||||
</div> |
|
||||
</div> |
|
||||
{/each} |
|
||||
{#if !value || !value.length} |
|
||||
{#if placeholder && placeholder.length} |
|
||||
<div class:disabled class="placeholder">{placeholder}</div> |
|
||||
{:else} |
|
||||
<div class="placeholder"> </div> |
|
||||
{/if} |
|
||||
{/if} |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<select bind:this={slot} type="multiple" class="hidden"> |
|
||||
<slot /> |
|
||||
</select> |
|
||||
|
|
||||
{#if optionsVisible} |
|
||||
<Portal> |
|
||||
<ul |
|
||||
class="options" |
|
||||
use:positionDropdown={{ anchor, align }} |
|
||||
use:clickOutside={() => showOptions(false)} |
|
||||
transition:fly={{ duration: 200, y: 5 }} |
|
||||
on:mousedown|preventDefault={handleOptionMousedown}> |
|
||||
{#each options as option} |
|
||||
<li |
|
||||
class:selected={lookupMap[option.value]} |
|
||||
data-value={option.value}> |
|
||||
{option.name} |
|
||||
</li> |
|
||||
{/each} |
|
||||
{#if !options.length} |
|
||||
<li class="no-results">No results</li> |
|
||||
{/if} |
|
||||
</ul> |
|
||||
</Portal> |
|
||||
{/if} |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
.multiselect { |
|
||||
position: relative; |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
justify-content: flex-start; |
|
||||
align-items: stretch; |
|
||||
font-family: var(--font-sans); |
|
||||
min-width: 0; |
|
||||
} |
|
||||
.multiselect:hover { |
|
||||
border-bottom-color: hsl(0, 0%, 50%); |
|
||||
} |
|
||||
|
|
||||
.tokens-wrapper { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: flex-start; |
|
||||
align-items: center; |
|
||||
flex: 0 1 auto; |
|
||||
} |
|
||||
|
|
||||
.tokens { |
|
||||
align-items: center; |
|
||||
display: flex; |
|
||||
flex-wrap: wrap; |
|
||||
position: relative; |
|
||||
width: 0; |
|
||||
flex: 1 1 auto; |
|
||||
background-color: var(--background); |
|
||||
border-radius: var(--border-radius-m); |
|
||||
padding: 0 var(--spacing-m) calc(var(--spacing-m) - var(--spacing-xs)) |
|
||||
calc(var(--spacing-m) / 2); |
|
||||
border: var(--border-transparent); |
|
||||
} |
|
||||
.tokens.disabled { |
|
||||
background-color: var(--grey-4); |
|
||||
pointer-events: none; |
|
||||
} |
|
||||
.tokens.outline { |
|
||||
border: var(--border-dark); |
|
||||
} |
|
||||
.tokens.secondary { |
|
||||
background-color: var(--grey-2); |
|
||||
} |
|
||||
.tokens.extraThin { |
|
||||
padding: 0 var(--spacing-m) calc(var(--spacing-s) - var(--spacing-xs)) |
|
||||
calc(var(--spacing-m) / 2); |
|
||||
} |
|
||||
.tokens:hover { |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
.tokens.optionsVisible { |
|
||||
border: var(--border-blue); |
|
||||
} |
|
||||
.tokens.empty { |
|
||||
padding: var(--spacing-m); |
|
||||
font-size: var(--font-size-xs); |
|
||||
user-select: none; |
|
||||
} |
|
||||
.tokens.empty.extraThin { |
|
||||
padding: var(--spacing-s) var(--spacing-m); |
|
||||
} |
|
||||
.tokens::after { |
|
||||
width: 100%; |
|
||||
left: 0; |
|
||||
} |
|
||||
|
|
||||
.token { |
|
||||
font-size: var(--font-size-xs); |
|
||||
background-color: var(--ink); |
|
||||
color: var(--background); |
|
||||
border-radius: var(--border-radius-l); |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
align-items: center; |
|
||||
margin: calc(var(--spacing-m) - var(--spacing-xs)) 0 0 |
|
||||
calc(var(--spacing-m) / 2); |
|
||||
max-height: 1.3rem; |
|
||||
padding: var(--spacing-xs) var(--spacing-s); |
|
||||
transition: background-color 0.3s; |
|
||||
white-space: nowrap; |
|
||||
overflow: hidden; |
|
||||
} |
|
||||
.token.extraThin { |
|
||||
margin: calc(var(--spacing-s) - var(--spacing-xs)) 0 0 |
|
||||
calc(var(--spacing-m) / 2); |
|
||||
} |
|
||||
.token span { |
|
||||
pointer-events: none; |
|
||||
user-select: none; |
|
||||
flex: 1 1 auto; |
|
||||
overflow: hidden; |
|
||||
white-space: nowrap; |
|
||||
text-overflow: ellipsis; |
|
||||
} |
|
||||
.token .token-remove { |
|
||||
align-items: center; |
|
||||
background-color: var(--grey-7); |
|
||||
border-radius: 50%; |
|
||||
display: flex; |
|
||||
justify-content: center; |
|
||||
height: 1rem; |
|
||||
width: 1rem; |
|
||||
margin: calc(-1 * var(--spacing-xs)) 0 calc(-1 * var(--spacing-xs)) |
|
||||
var(--spacing-xs); |
|
||||
flex: 0 0 auto; |
|
||||
} |
|
||||
.token path { |
|
||||
fill: var(--background); |
|
||||
} |
|
||||
.token .token-remove:hover { |
|
||||
background-color: var(--grey-6); |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.placeholder { |
|
||||
pointer-events: none; |
|
||||
color: var(--ink); |
|
||||
} |
|
||||
.placeholder.disabled { |
|
||||
color: var(--grey-6); |
|
||||
} |
|
||||
|
|
||||
.icon-clear path { |
|
||||
fill: white; |
|
||||
} |
|
||||
.options { |
|
||||
left: 0; |
|
||||
list-style: none; |
|
||||
margin-block-end: 0; |
|
||||
margin-block-start: 0; |
|
||||
overflow-y: auto; |
|
||||
padding-inline-start: 0; |
|
||||
position: absolute; |
|
||||
border: var(--border-dark); |
|
||||
border-radius: var(--border-radius-m); |
|
||||
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.15); |
|
||||
margin: var(--spacing-xs) 0; |
|
||||
padding: var(--spacing-s) 0; |
|
||||
background-color: var(--background); |
|
||||
max-height: 200px; |
|
||||
} |
|
||||
li { |
|
||||
cursor: pointer; |
|
||||
padding: var(--spacing-s) var(--spacing-m); |
|
||||
font-size: var(--font-size-xs); |
|
||||
color: var(--ink); |
|
||||
} |
|
||||
li.selected { |
|
||||
background-color: var(--blue); |
|
||||
color: white; |
|
||||
} |
|
||||
li:not(.selected):hover { |
|
||||
background-color: var(--grey-1); |
|
||||
} |
|
||||
li.no-results:hover { |
|
||||
background-color: white; |
|
||||
cursor: initial; |
|
||||
} |
|
||||
|
|
||||
.hidden { |
|
||||
height: 0; |
|
||||
overflow: hidden; |
|
||||
visibility: hidden; |
|
||||
padding: 0; |
|
||||
margin: 0; |
|
||||
border: 0; |
|
||||
outline: 0; |
|
||||
} |
|
||||
</style> |
|
||||
|
|||||
@ -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> |
|
||||
@ -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> |
|
||||
@ -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> |
|
||||
@ -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> |
||||
@ -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> |
|
||||
@ -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> |
|
||||
@ -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> |
||||
@ -1,95 +1,42 @@ |
|||||
<script> |
<script> |
||||
import Icon from "../Icons/Icon.svelte" |
import Field from "./Field.svelte" |
||||
import Label from "../Styleguide/Label.svelte" |
import Select from "./Core/Select.svelte" |
||||
|
import { createEventDispatcher } from "svelte" |
||||
|
|
||||
export let value = "" |
export let value = null |
||||
export let name = undefined |
|
||||
export let label = undefined |
export let label = undefined |
||||
export let thin = false |
|
||||
export let extraThin = false |
|
||||
export let secondary = false |
|
||||
export let outline = false |
|
||||
export let disabled = false |
export let disabled = false |
||||
</script> |
export let readonly = false |
||||
|
export let labelPosition = "above" |
||||
<div> |
export let error = null |
||||
{#if label} |
export let placeholder = "Choose an option" |
||||
<Label extraSmall grey forAttr={name}>{label}</Label> |
export let options = [] |
||||
{/if} |
export let getOptionLabel = option => extractProperty(option, "label") |
||||
<div class="relative"> |
export let getOptionValue = option => extractProperty(option, "value") |
||||
<select |
|
||||
{name} |
|
||||
class:thin |
|
||||
class:extraThin |
|
||||
class:secondary |
|
||||
class:outline |
|
||||
{disabled} |
|
||||
on:change |
|
||||
bind:value> |
|
||||
<slot /> |
|
||||
</select> |
|
||||
<div class="pointer"> |
|
||||
<Icon name="arrowdown" /> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<style> |
const dispatch = createEventDispatcher() |
||||
select { |
const onChange = e => { |
||||
font-family: var(--font-sans); |
value = e.detail |
||||
display: block !important; |
dispatch("change", e.detail) |
||||
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 { |
const extractProperty = (value, property) => { |
||||
border: var(--border-blue); |
if (value && typeof value === "object") { |
||||
} |
return value[property] |
||||
select:disabled { |
} |
||||
background: var(--grey-4); |
return value |
||||
color: var(--grey-6); |
|
||||
} |
|
||||
|
|
||||
.relative { |
|
||||
position: relative !important; |
|
||||
display: block; |
|
||||
} |
} |
||||
|
</script> |
||||
|
|
||||
.pointer { |
<Field {label} {labelPosition} {disabled} {error}> |
||||
right: 0 !important; |
<Select |
||||
top: 0 !important; |
{error} |
||||
bottom: 0 !important; |
{disabled} |
||||
position: absolute !important; |
{readonly} |
||||
pointer-events: none !important; |
{value} |
||||
padding-left: 0.5rem !important; |
{options} |
||||
align-items: center !important; |
{placeholder} |
||||
display: flex !important; |
{getOptionLabel} |
||||
color: var(--ink); |
{getOptionValue} |
||||
} |
on:change={onChange} |
||||
</style> |
on:click /> |
||||
|
</Field> |
||||
|
|||||
@ -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> |
|
||||
|
|
||||
@ -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> |
|
||||
@ -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> |
|
||||
|
|
||||
|
|
||||
@ -1,132 +1,29 @@ |
|||||
<script> |
<script> |
||||
|
import Field from "./Field.svelte" |
||||
|
import TextArea from "./Core/TextArea.svelte" |
||||
import { createEventDispatcher } from "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" |
|
||||
|
|
||||
const dispatch = createEventDispatcher() |
export let value = null |
||||
|
export let label = null |
||||
export let name = false |
export let labelPosition = "above" |
||||
export let label = false |
export let placeholder = null |
||||
export let thin = false |
|
||||
export let extraThin = false |
|
||||
export let edit = false |
|
||||
export let disabled = false |
export let disabled = false |
||||
export let placeholder |
export let error = null |
||||
export let validator = () => {} |
export let getCaretPosition = null |
||||
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 = () => { |
const dispatch = createEventDispatcher() |
||||
editMode = true |
const onChange = e => { |
||||
|
value = e.detail |
||||
|
dispatch("change", e.detail) |
||||
} |
} |
||||
</script> |
</script> |
||||
|
|
||||
<div class="container"> |
<Field {label} {labelPosition} {disabled} {error}> |
||||
{#if label || edit} |
<TextArea |
||||
<div class="label-container"> |
bind:getCaretPosition |
||||
{#if label} |
{error} |
||||
<Label extraSmall grey forAttr={name}>{label}</Label> |
{disabled} |
||||
{/if} |
{value} |
||||
{#if edit} |
|
||||
<div class="controls"> |
|
||||
<Button small secondary disabled={editMode} on:click={enableEdit}> |
|
||||
Edit |
|
||||
</Button> |
|
||||
<Button small blue disabled={!editMode} on:click={save}>Save</Button> |
|
||||
</div> |
|
||||
{/if} |
|
||||
</div> |
|
||||
{/if} |
|
||||
<textarea |
|
||||
class:thin |
|
||||
class:extraThin |
|
||||
bind:value |
|
||||
bind:this={textarea} |
|
||||
on:change |
|
||||
disabled={disabled || (edit && !editMode)} |
|
||||
{placeholder} |
{placeholder} |
||||
{name} |
on:change={onChange} /> |
||||
use:text_area_resize /> |
</Field> |
||||
</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> |
|
||||
|
|||||
@ -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> |
|
||||
@ -1,110 +1,22 @@ |
|||||
<script> |
<script> |
||||
export let name = undefined |
import Field from "./Field.svelte" |
||||
export let text = "" |
import Switch from "./Core/Switch.svelte" |
||||
export let checked = false |
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 disabled = false |
||||
export let screenreader = true |
export let error = null |
||||
export let thin = false |
|
||||
</script> |
|
||||
|
|
||||
<label for={name} class="container"> |
|
||||
<div class="toggle"> |
|
||||
<input |
|
||||
id={name} |
|
||||
{name} |
|
||||
type="checkbox" |
|
||||
class:screenreader |
|
||||
{disabled} |
|
||||
bind:checked |
|
||||
on:change /> |
|
||||
<div class="track"> |
|
||||
<div class="thumb" /> |
|
||||
</div> |
|
||||
</div> |
|
||||
{#if text}<span class="text" class:thin>{text}</span>{/if} |
|
||||
</label> |
|
||||
|
|
||||
<style> |
|
||||
.container { |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
gap: var(--spacing-s); |
|
||||
} |
|
||||
.container:disabled { |
|
||||
background-color: var(--grey-2); |
|
||||
cursor: not-allowed; |
|
||||
} |
|
||||
|
|
||||
.toggle { |
const dispatch = createEventDispatcher() |
||||
position: relative; |
const onChange = e => { |
||||
display: inline-block; |
value = e.detail |
||||
align-self: center; |
dispatch("change", e.detail) |
||||
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; |
|
||||
} |
} |
||||
|
</script> |
||||
|
|
||||
.text { |
<Field {label} {labelPosition} {disabled} {error}> |
||||
font-size: var(--font-size-s); |
<Switch {error} {disabled} {text} {value} on:change={onChange} /> |
||||
font-family: var(--font-sans); |
</Field> |
||||
cursor: pointer; |
|
||||
user-select: none; |
|
||||
color: var(--ink); |
|
||||
} |
|
||||
.text.thin { |
|
||||
font-size: var(--font-size-xs); |
|
||||
} |
|
||||
</style> |
|
||||
|
|||||
@ -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> |
|
||||
|
|
||||
@ -1,19 +1,30 @@ |
|||||
<script context="module"> |
<script context="module"> |
||||
export const directions = ["n", "ne", "e", "se", "s", "sw", "w", "nw"] |
export const directions = ["n", "ne", "e", "se", "s", "sw", "w", "nw"] |
||||
</script> |
</script> |
||||
|
|
||||
<script> |
<script> |
||||
export let direction = "n" |
export let direction = "n" |
||||
export let name = "Add" |
export let name = "Add" |
||||
export let hidden = true |
export let hidden = false |
||||
export let s = false |
export let s = false |
||||
export let m = false; |
export let m = false |
||||
export let l = false; |
export let l = false |
||||
export let xl = false |
export let xl = false |
||||
|
|
||||
$: rotation = directions.indexOf(direction) * 45 |
$: rotation = directions.indexOf(direction) * 45 |
||||
|
$: useDefault = ![s, m, l, xl].includes(true) |
||||
</script> |
</script> |
||||
|
|
||||
<svg on:click class:spectrum-Icon--sizeS={s} class:spectrum-Icon--sizeM={m} class:spectrum-Icon--sizeL={l} class:spectrum-Icon--sizeXL={xl} class="spectrum-Icon" focusable="false" aria-hidden={hidden} aria-label="{name}" style={`transform: rotate(${rotation}deg)`}> |
<svg |
||||
<use xlink:href="#spectrum-icon-18-{name}" /> |
on:click |
||||
</svg> |
class:spectrum-Icon--sizeS={s} |
||||
|
class:spectrum-Icon--sizeM={m || useDefault} |
||||
|
class:spectrum-Icon--sizeL={l} |
||||
|
class:spectrum-Icon--sizeXL={xl} |
||||
|
class="spectrum-Icon" |
||||
|
focusable="false" |
||||
|
aria-hidden={hidden} |
||||
|
aria-label={name} |
||||
|
style={`transform: rotate(${rotation}deg)`}> |
||||
|
<use xlink:href="#spectrum-icon-18-{name}" /> |
||||
|
</svg> |
||||
|
|||||
@ -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> |
|
||||
@ -1,8 +0,0 @@ |
|||||
<script> |
|
||||
import Search from "./Search.svelte"; |
|
||||
import { View } from "svench"; |
|
||||
</script> |
|
||||
|
|
||||
<View name="Name"> |
|
||||
<Search /> |
|
||||
</View> |
|
||||
@ -1,69 +1,14 @@ |
|||||
<script> |
<script> |
||||
export let forAttr = "", |
import "@spectrum-css/fieldlabel/dist/index-vars.css" |
||||
extraSmall = false, |
|
||||
small = false, |
|
||||
medium = false, |
|
||||
large = false, |
|
||||
extraLarge = false, |
|
||||
white = false, |
|
||||
grey = false, |
|
||||
black = false |
|
||||
</script> |
</script> |
||||
|
|
||||
<label |
<label |
||||
class="bb-label" |
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel`}> |
||||
class:extraSmall |
|
||||
class:small |
|
||||
class:medium |
|
||||
class:large |
|
||||
class:extraLarge |
|
||||
class:white |
|
||||
class:grey |
|
||||
class:black |
|
||||
for={forAttr}> |
|
||||
<slot /> |
<slot /> |
||||
</label> |
</label> |
||||
|
|
||||
<style> |
<style> |
||||
.bb-label { |
label { |
||||
font-family: var(--font-sans); |
white-space: nowrap; |
||||
font-weight: 500; |
|
||||
text-rendering: var(--text-render); |
|
||||
color: var(--ink); |
|
||||
font-size: var(--font-size-s); |
|
||||
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); |
|
||||
} |
} |
||||
</style> |
</style> |
||||
|
|||||
@ -1,78 +1,28 @@ |
|||||
<script> |
<script> |
||||
import "@spectrum-css/typography/dist/index-vars.css" |
import "@spectrum-css/typography/dist/index-vars.css" |
||||
|
|
||||
// Level |
// Sizes |
||||
export let h1 = false; |
export let xxxl = false |
||||
export let h2 = false; |
export let xxl = false |
||||
export let h3 = false; |
export let xl = false |
||||
export let h4 = false; |
export let l = false |
||||
|
export let m = false |
||||
// Sizes |
export let s = false |
||||
export let xxxl = false; |
export let xs = false |
||||
export let xxl = false; |
export let xxs = false |
||||
export let xl = false; |
|
||||
export let l = false; |
|
||||
export let m = false; |
|
||||
export let s = false; |
|
||||
export let xs = false; |
|
||||
export let xxs = false; |
|
||||
|
|
||||
export let serif = false; |
|
||||
|
|
||||
|
$: useDefault = ![xxxl, xxl, xl, l, m, s, xs, xxs].includes(true) |
||||
</script> |
</script> |
||||
|
|
||||
{#if h1} |
<h1 |
||||
<h1 class="spectrum-Heading" |
class="spectrum-Heading" |
||||
class:spectrum-Heading--serif={serif} |
class:spectrum-Heading--sizeXXXL={xxxl} |
||||
class:spectrum-Heading--sizeXXXL={xxxl} |
class:spectrum-Heading--sizeXXL={xxl} |
||||
class:spectrum-Heading--sizeXXL={xxl} |
class:spectrum-Heading--sizeXL={xl} |
||||
class:spectrum-Heading--sizeXL={xl} |
class:spectrum-Heading--sizeL={l} |
||||
class:spectrum-Heading--sizeL={l} |
class:spectrum-Heading--sizeM={m || useDefault} |
||||
class:spectrum-Heading--sizeM={m} |
class:spectrum-Heading--sizeS={s} |
||||
class:spectrum-Heading--sizeS={s} |
class:spectrum-Heading--sizeXS={xs} |
||||
class:spectrum-Heading--sizeXS={xs} |
class:spectrum-Heading--sizeXXS={xxs}> |
||||
class:spectrum-Heading--sizeXXS={xxs}> |
<slot /> |
||||
<slot /> |
</h1> |
||||
</h1> |
|
||||
{:else if h2} |
|
||||
<h2 class="spectrum-Heading" |
|
||||
class:spectrum-Heading--serif={serif} |
|
||||
class:spectrum-Heading--sizeXXXL={xxxl} |
|
||||
class:spectrum-Heading--sizeXXL={xxl} |
|
||||
class:spectrum-Heading--sizeXL={xl} |
|
||||
class:spectrum-Heading--sizeL={l} |
|
||||
class:spectrum-Heading--sizeM={m} |
|
||||
class:spectrum-Heading--sizeS={s} |
|
||||
class:spectrum-Heading--sizeXS={xs} |
|
||||
class:spectrum-Heading--sizeXXS={xxs}> |
|
||||
<slot /> |
|
||||
</h2> |
|
||||
{:else if h3} |
|
||||
<h3 class="spectrum-Heading" |
|
||||
class:spectrum-Heading--serif={serif} |
|
||||
class:spectrum-Heading--sizeXXXL={xxxl} |
|
||||
class:spectrum-Heading--sizeXXL={xxl} |
|
||||
class:spectrum-Heading--sizeXL={xl} |
|
||||
class:spectrum-Heading--sizeL={l} |
|
||||
class:spectrum-Heading--sizeM={m} |
|
||||
class:spectrum-Heading--sizeS={s} |
|
||||
class:spectrum-Heading--sizeXS={xs} |
|
||||
class:spectrum-Heading--sizeXXS={xxs}> |
|
||||
<slot /> |
|
||||
</h3> |
|
||||
{:else if h4} |
|
||||
<h4 class="spectrum-Heading" |
|
||||
class:spectrum-Heading--serif={serif} |
|
||||
class:spectrum-Heading--sizeXXXL={xxxl} |
|
||||
class:spectrum-Heading--sizeXXL={xxl} |
|
||||
class:spectrum-Heading--sizeXL={xl} |
|
||||
class:spectrum-Heading--sizeL={l} |
|
||||
class:spectrum-Heading--sizeM={m} |
|
||||
class:spectrum-Heading--sizeS={s} |
|
||||
class:spectrum-Heading--sizeXS={xs} |
|
||||
class:spectrum-Heading--sizeXXS={xxs}> |
|
||||
<slot /> |
|
||||
</h4> |
|
||||
{:else} |
|
||||
SPECIFY HEADING SIZE! |
|
||||
{/if} |
|
||||
|
|||||
@ -1,8 +1,7 @@ |
|||||
import "@spectrum-css/icon/dist/index-vars.css" |
|
||||
import SpectrumUIIcons from "@spectrum-css/icon/dist/spectrum-css-icons.svg" |
import SpectrumUIIcons from "@spectrum-css/icon/dist/spectrum-css-icons.svg" |
||||
import SpectrumWorkflowIcons from "@adobe/spectrum-css-workflow-icons/dist/spectrum-icons.svg" |
import SpectrumWorkflowIcons from "@adobe/spectrum-css-workflow-icons/dist/spectrum-icons.svg" |
||||
|
|
||||
export const loadSpectrumIcons = () => { |
export default () => { |
||||
loadIconSet("Spectrum UI Icons", SpectrumUIIcons) |
loadIconSet("Spectrum UI Icons", SpectrumUIIcons) |
||||
loadIconSet("Spectrum Workflow Icons", SpectrumWorkflowIcons) |
loadIconSet("Spectrum Workflow Icons", SpectrumWorkflowIcons) |
||||
} |
} |
||||
@ -1,8 +1,7 @@ |
|||||
import "@spectrum-css/icon/dist/index-vars.css" |
|
||||
import SpectrumUIIcons from "@spectrum-css/icon/dist/spectrum-css-icons.svg?raw" |
import SpectrumUIIcons from "@spectrum-css/icon/dist/spectrum-css-icons.svg?raw" |
||||
import SpectrumWorkflowIcons from "@adobe/spectrum-css-workflow-icons/dist/spectrum-icons.svg?raw" |
import SpectrumWorkflowIcons from "@adobe/spectrum-css-workflow-icons/dist/spectrum-icons.svg?raw" |
||||
|
|
||||
export const loadSpectrumIcons = () => { |
export default () => { |
||||
loadIconSet("Spectrum UI Icons", SpectrumUIIcons) |
loadIconSet("Spectrum UI Icons", SpectrumUIIcons) |
||||
loadIconSet("Spectrum Workflow Icons", SpectrumWorkflowIcons) |
loadIconSet("Spectrum Workflow Icons", SpectrumWorkflowIcons) |
||||
} |
} |
||||
@ -1,14 +0,0 @@ |
|||||
export default function buildStyle(styles) { |
|
||||
const convertCamel = str => { |
|
||||
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`) |
|
||||
} |
|
||||
|
|
||||
let str = "" |
|
||||
for (let s in styles) { |
|
||||
if (styles[s]) { |
|
||||
let key = convertCamel(s) |
|
||||
str += `${key}: ${styles[s]}; ` |
|
||||
} |
|
||||
} |
|
||||
return str |
|
||||
} |
|
||||
@ -0,0 +1,8 @@ |
|||||
|
export const generateID = () => { |
||||
|
const rand = Math.random() |
||||
|
.toString(32) |
||||
|
.substring(2) |
||||
|
|
||||
|
// Starts with a letter so that its a valid DOM ID
|
||||
|
return `A${rand}` |
||||
|
} |
||||
@ -1,22 +0,0 @@ |
|||||
import svelte from "@sveltejs/vite-plugin-svelte" |
|
||||
|
|
||||
export default ({ mode }) => { |
|
||||
const isProduction = mode === "production" |
|
||||
return { |
|
||||
build: { |
|
||||
lib: { |
|
||||
entry: "src/index.js", |
|
||||
name: "bbui", |
|
||||
formats: ["es"], |
|
||||
}, |
|
||||
minify: isProduction, |
|
||||
}, |
|
||||
plugins: [svelte()], |
|
||||
resolve: { |
|
||||
dedupe: ["svelte", "svelte/internal"], |
|
||||
}, |
|
||||
rollupOptions: { |
|
||||
external: ["svelte", "svelte/internal"], |
|
||||
}, |
|
||||
} |
|
||||
} |
|
||||
File diff suppressed because it is too large
@ -1,171 +0,0 @@ |
|||||
<script> |
|
||||
import { onMount, onDestroy } from "svelte" |
|
||||
import { Modal, ModalContent } from "@budibase/bbui" |
|
||||
import CreateEditColumn from "../modals/CreateEditColumn.svelte" |
|
||||
import { FIELDS } from "constants/backend" |
|
||||
|
|
||||
const SORT_ICON_MAP = { |
|
||||
asc: "ri-arrow-down-fill", |
|
||||
desc: "ri-arrow-up-fill", |
|
||||
} |
|
||||
|
|
||||
export let field |
|
||||
export let displayName |
|
||||
export let column |
|
||||
export let enableSorting = true |
|
||||
export let showColumnMenu |
|
||||
export let progressSort |
|
||||
export let editable |
|
||||
|
|
||||
let menuButton |
|
||||
let sortDirection = "" |
|
||||
let modal |
|
||||
let hovered |
|
||||
let filterActive |
|
||||
|
|
||||
function toggleMenu() { |
|
||||
showColumnMenu(menuButton) |
|
||||
} |
|
||||
|
|
||||
function onSort(event) { |
|
||||
progressSort(event.shiftKey) |
|
||||
} |
|
||||
|
|
||||
function showModal() { |
|
||||
modal.show() |
|
||||
} |
|
||||
|
|
||||
function setSort() { |
|
||||
sortDirection = column.getSort() |
|
||||
} |
|
||||
|
|
||||
function setFilterActive(e) { |
|
||||
filterActive = e.column.filterActive |
|
||||
} |
|
||||
|
|
||||
onMount(() => { |
|
||||
column.addEventListener("sortChanged", setSort) |
|
||||
column.addEventListener("filterActiveChanged", setFilterActive) |
|
||||
}) |
|
||||
|
|
||||
onDestroy(() => { |
|
||||
column.removeEventListener("sortChanged", setSort) |
|
||||
column.removeEventListener("filterActiveChanged", setFilterActive) |
|
||||
}) |
|
||||
|
|
||||
$: type = FIELDS[field?.type?.toUpperCase()]?.name |
|
||||
</script> |
|
||||
|
|
||||
<header |
|
||||
on:click={onSort} |
|
||||
data-cy="table-header" |
|
||||
on:mouseover={() => (hovered = true)} |
|
||||
on:mouseleave={() => (hovered = false)}> |
|
||||
<div class="column-header"> |
|
||||
<div class="column-header-text"> |
|
||||
<div class="column-header-name"> |
|
||||
{displayName} |
|
||||
{#if field.autocolumn}<i class="auto ri-magic-fill" />{/if} |
|
||||
</div> |
|
||||
{#if type} |
|
||||
<div class="column-header-type">{type}</div> |
|
||||
{/if} |
|
||||
</div> |
|
||||
<i class={`${SORT_ICON_MAP[sortDirection]} icon`} /> |
|
||||
</div> |
|
||||
<Modal bind:this={modal}> |
|
||||
<ModalContent |
|
||||
size="large" |
|
||||
showCancelButton={false} |
|
||||
showConfirmButton={false} |
|
||||
title={`Edit Column: ${field.name}`}> |
|
||||
<CreateEditColumn onClosed={modal.hide} {field} /> |
|
||||
</ModalContent> |
|
||||
</Modal> |
|
||||
<section class:show={hovered || filterActive}> |
|
||||
{#if editable && hovered} |
|
||||
<span on:click|stopPropagation={showModal}> |
|
||||
<i class="ri-pencil-line icon" /> |
|
||||
</span> |
|
||||
{/if} |
|
||||
<span on:click|stopPropagation={toggleMenu} bind:this={menuButton}> |
|
||||
<i class="ri-filter-line icon" class:active={filterActive} /> |
|
||||
</span> |
|
||||
</section> |
|
||||
</header> |
|
||||
|
|
||||
<style> |
|
||||
header { |
|
||||
font-family: Inter; |
|
||||
font-weight: 600; |
|
||||
display: flex; |
|
||||
justify-content: space-between; |
|
||||
height: 100%; |
|
||||
width: 100%; |
|
||||
align-items: center; |
|
||||
color: var(--ink); |
|
||||
} |
|
||||
|
|
||||
section { |
|
||||
opacity: 0; |
|
||||
transition: 0.3s all; |
|
||||
} |
|
||||
|
|
||||
section.show { |
|
||||
opacity: 1; |
|
||||
} |
|
||||
|
|
||||
.column-header { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: flex-start; |
|
||||
align-items: center; |
|
||||
gap: var(--spacing-s); |
|
||||
} |
|
||||
|
|
||||
.column-header-text { |
|
||||
flex: 1 1 auto; |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
justify-content: flex-start; |
|
||||
align-items: stretch; |
|
||||
gap: var(--spacing-xs); |
|
||||
} |
|
||||
|
|
||||
.column-header-name { |
|
||||
white-space: normal !important; |
|
||||
text-overflow: ellipsis; |
|
||||
display: -webkit-box; |
|
||||
-webkit-box-orient: vertical; |
|
||||
-webkit-line-clamp: 1; |
|
||||
overflow: hidden; |
|
||||
} |
|
||||
|
|
||||
.column-header-type { |
|
||||
font-size: var(--font-size-xs); |
|
||||
color: var(--grey-6); |
|
||||
} |
|
||||
|
|
||||
.icon { |
|
||||
transition: 0.2s all; |
|
||||
font-size: var(--font-size-m); |
|
||||
font-weight: 500; |
|
||||
} |
|
||||
.auto { |
|
||||
font-size: 9px; |
|
||||
transition: none; |
|
||||
position: relative; |
|
||||
margin-left: 2px; |
|
||||
top: -3px; |
|
||||
color: var(--grey-6); |
|
||||
} |
|
||||
|
|
||||
.icon:hover { |
|
||||
color: var(--blue); |
|
||||
} |
|
||||
|
|
||||
.icon.active, |
|
||||
.icon:hover { |
|
||||
color: var(--blue); |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,35 +0,0 @@ |
|||||
import TableHeader from "./TableHeader.svelte" |
|
||||
|
|
||||
export default class TableHeaderWrapper { |
|
||||
constructor() {} |
|
||||
|
|
||||
init(params) { |
|
||||
this.agParams = params |
|
||||
this.container = document.createElement("div") |
|
||||
this.container.style.height = "100%" |
|
||||
this.container.style.width = "100%" |
|
||||
|
|
||||
this.headerComponent = new TableHeader({ |
|
||||
target: this.container, |
|
||||
props: params, |
|
||||
}) |
|
||||
this.gui = this.container |
|
||||
} |
|
||||
|
|
||||
// can get called more than once, you should return the HTML element
|
|
||||
getGui() { |
|
||||
return this.gui |
|
||||
} |
|
||||
|
|
||||
// gets called when a new Column Definition has been set for this header
|
|
||||
refresh(params) { |
|
||||
this.agParams = params |
|
||||
this.headerComponent = new TableHeader({ |
|
||||
target: this.container, |
|
||||
props: params, |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
// optional method, gets called once, when component is destroyed
|
|
||||
destroy() {} |
|
||||
} |
|
||||
@ -1,27 +0,0 @@ |
|||||
<script> |
|
||||
import Spinner from "components/common/Spinner.svelte" |
|
||||
import { fade } from "svelte/transition" |
|
||||
import Logo from "/assets/bb-logo.svg" |
|
||||
</script> |
|
||||
|
|
||||
<div class="ag-overlay-loading-center loading-container"> |
|
||||
<div transition:fade class="loading-overlay"> |
|
||||
<img height="30" width="30" src={Logo} alt="Budibase icon" /> |
|
||||
<span> Loading Your Data </span> |
|
||||
<Spinner size="12" /> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
.loading-overlay { |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
font-family: var(--font-sans); |
|
||||
font-weight: 500; |
|
||||
color: var(--ink); |
|
||||
} |
|
||||
|
|
||||
.loading-overlay > * { |
|
||||
margin-right: var(--spacing-m); |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,17 +0,0 @@ |
|||||
import LoadingOverlay from "./LoadingOverlay.svelte" |
|
||||
|
|
||||
export default class LoadingOverlayWrapper { |
|
||||
init(params) { |
|
||||
this.gui = document.createElement("div") |
|
||||
new LoadingOverlay({ |
|
||||
target: this.gui, |
|
||||
props: { |
|
||||
params, |
|
||||
}, |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
getGui() { |
|
||||
return this.gui |
|
||||
} |
|
||||
} |
|
||||
@ -1,114 +0,0 @@ |
|||||
<script> |
|
||||
import { createEventDispatcher } from "svelte" |
|
||||
|
|
||||
const dispatch = createEventDispatcher() |
|
||||
|
|
||||
export let checked = false |
|
||||
|
|
||||
function handleChange() { |
|
||||
checked = !checked |
|
||||
dispatch("change", checked) |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<input type="checkbox" class="checkbox" id="_checkbox" /> |
|
||||
<div class="checkbox-container" on:click={handleChange}> |
|
||||
<div class="check-div" class:checked> |
|
||||
<div class="tick_mark" /> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
.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); |
|
||||
} |
|
||||
|
|
||||
.checked { |
|
||||
background-color: var(--grey-2); |
|
||||
} |
|
||||
|
|
||||
.checked:before { |
|
||||
width: 0; |
|
||||
height: 0; |
|
||||
} |
|
||||
|
|
||||
.checked .tick_mark:before, |
|
||||
.checked .tick_mark:after { |
|
||||
transform: translate(0); |
|
||||
opacity: 1; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,13 +0,0 @@ |
|||||
<script> |
|
||||
import { DatePicker } from "@budibase/bbui" |
|
||||
|
|
||||
export let label |
|
||||
export let value |
|
||||
|
|
||||
function onChange(event) { |
|
||||
const [selectedDates] = event.detail |
|
||||
value = selectedDates[0] |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<DatePicker {label} on:change={onChange} {value} /> |
|
||||
@ -1,7 +0,0 @@ |
|||||
<script> |
|
||||
import Checkbox from "components/common/Checkbox.svelte" |
|
||||
|
|
||||
export let value |
|
||||
</script> |
|
||||
|
|
||||
<Checkbox checked={value} on:change /> |
|
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue