Skip to content

Commit 1963cb9

Browse files
committed
feat: add show_x prop to modal allowing it to be turned off
1 parent 7c8d39e commit 1963cb9

File tree

5 files changed

+123
-84
lines changed

5 files changed

+123
-84
lines changed

src/lib/ui/BottomSheet.variants.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Variant } from 'kitbook'
2+
import type Component from './BottomSheet.svelte'
3+
4+
const shared = {
5+
startType: 'pixels',
6+
start: 400,
7+
} satisfies Partial<Variant<Component>>
8+
9+
export const First: Variant<Component> = {
10+
...shared,
11+
}

src/lib/ui/Modal.composition

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@
1616
<Button onclick={toggle}>Open modal</Button>
1717
<input placeholder="testing focus" />
1818

19+
<!-- Doesn't seem to work -->
20+
<!-- on:keydown={event => event.stopPropagation()} -->
21+
<!-- <div>Press the right arrow to trigger an alert (and then try it again once the modal is opened to see that it no longer triggers)</div> -->
22+
1923
<!-- <div style="height: 500px;">I'm tall to test toggling of document scroll - doesn't work since iframe body height is set to 100%, could add flag to turn this off?</div> -->
2024
{#if show}
21-
<Modal on:close={toggle} noscroll>
25+
<Modal on_close={toggle} noscroll>
2226
<div slot="heading">Heading</div>
2327
<form
2428
on:submit|preventDefault={(e) => {
@@ -36,3 +40,8 @@
3640
</Modal>
3741
{/if}
3842
</ShowHide>
43+
44+
<!-- <svelte:window
45+
on:keydown={(e) => {
46+
if (e.key === 'ArrowRight') alert('right')
47+
}} /> -->

src/lib/ui/Modal.no_x.composition

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script context="module" lang="ts">
2+
import { defineComposition } from 'kitbook'
3+
4+
export const config = defineComposition({
5+
// viewports: [{ height: 200 }],
6+
})
7+
</script>
8+
9+
<script lang="ts">
10+
import Modal from './Modal.svelte'
11+
// props will be added here automatically and also editable in the future, for the moment you need to add them and pass to your component.
12+
</script>
13+
14+
<Modal on_close={() => alert('closed')} show_x={false}>
15+
<div class="bg-red">Hi, I want to be higher up and without the X</div>
16+
</Modal>

src/lib/ui/Modal.svelte

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,72 @@
11
<script lang="ts">
2-
import { createEventDispatcher, onMount } from 'svelte';
3-
import { fade } from 'svelte/transition';
4-
import { portal } from '../actions/portal';
5-
import { trapFocus } from './trapFocus';
2+
import { createEventDispatcher, onMount } from 'svelte'
3+
import { fade } from 'svelte/transition'
4+
import { portal } from '../actions/portal'
5+
import { trapFocus } from './trapFocus'
66
7-
export let noscroll = false;
8-
export let zIndex = 60;
9-
export let duration = 200;
7+
export let noscroll = false
8+
export let zIndex = 60
9+
export let duration = 200
10+
export let on_close: () => void = undefined
11+
export let show_x = true
1012
11-
const dispatch = createEventDispatcher<{ close: boolean }>();
12-
const close = () => dispatch('close');
13+
const dispatch = createEventDispatcher<{ close: boolean }>()
14+
const close = () => dispatch('close') && on_close?.()
1315
14-
let modal: HTMLElement;
16+
let modal: HTMLElement
1517
1618
onMount(() => {
17-
const previouslyFocused = typeof document !== 'undefined' && (document.activeElement as HTMLElement);
18-
noscroll && (document.body.style.overflow = 'hidden');
19+
const previouslyFocused = typeof document !== 'undefined' && (document.activeElement as HTMLElement)
20+
noscroll && (document.body.style.overflow = 'hidden')
1921
2022
return () => {
2123
if (previouslyFocused) {
22-
previouslyFocused.focus();
23-
document.body.style.overflow = 'auto';
24+
previouslyFocused.focus()
25+
document.body.style.overflow = 'auto'
2426
}
25-
};
26-
});
27+
}
28+
})
2729
</script>
2830

2931
<svelte:window
3032
on:keydown={(e) => {
31-
if (e.key === 'Escape') return close();
32-
if (e.key === 'Tab') trapFocus(e, modal);
33-
}}
34-
/>
33+
if (e.key === 'Escape') return close()
34+
if (e.key === 'Tab') trapFocus(e, modal)
35+
}} />
3536

3637
<div
3738
use:portal
3839
class="fixed inset-0 p-4 flex items-center justify-center"
39-
style="z-index: {zIndex};"
40-
>
40+
style="z-index: {zIndex};">
4141
<div class="fixed inset-0 transition-opacity" transition:fade={{ duration }}>
4242
<button type="button" class="absolute inset-0 bg-black opacity-50" on:click={close} />
4343
</div>
4444

4545
<div
4646
transition:fade={{ duration }}
4747
class="{$$props.class} bg-white rounded-lg overflow-hidden shadow-xl transform
48-
transition-all sm:max-w-lg w-full max-h-full flex flex-col z-1"
48+
transition-all sm:max-w-lg w-full max-h-full flex flex-col z-1"
4949
role="dialog"
5050
aria-modal="true"
5151
aria-labelledby="modal-headline"
52-
bind:this={modal}
53-
>
52+
bind:this={modal}>
5453
<div class="p-4 sm:p-6 overflow-y-auto flex-1">
55-
<div class="flex">
56-
<h3 class="text-lg leading-6 font-medium text-gray-900 flex-grow" id="modal-headline">
57-
<slot name="heading" />
58-
</h3>
59-
<button
60-
on:click={close}
61-
type="button"
62-
class="h-12 w-12 -m-4 flex justify-center items-center text-gray-400 hover:text-gray-700 focus:outline-none
63-
focus:text-gray-700 focus:ring-2 rounded"
64-
aria-label="Close"
65-
>
66-
<span class="i-fa-solid-times" />
67-
</button>
68-
</div>
69-
<div class="mt-2">
54+
{#if show_x}
55+
<div class="flex">
56+
<h3 class="text-lg leading-6 font-medium text-gray-900 flex-grow" id="modal-headline">
57+
<slot name="heading" />
58+
</h3>
59+
<button
60+
on:click={close}
61+
type="button"
62+
class="h-12 w-12 -m-4 flex justify-center items-center text-gray-400 hover:text-gray-700 focus:outline-none
63+
focus:text-gray-700 focus:ring-2 rounded"
64+
aria-label="Close">
65+
<span class="i-fa-solid-times" />
66+
</button>
67+
</div>
68+
{/if}
69+
<div class:mt-2={show_x}>
7070
<slot />
7171
</div>
7272
</div>

src/lib/ui/MultiSelect.svelte

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,49 @@
11
<script lang="ts">
2-
import { fly } from 'svelte/transition';
3-
import { onMount } from 'svelte';
4-
import { clickoutside } from '../actions/clickoutside';
5-
2+
import { fly } from 'svelte/transition'
3+
import { onMount } from 'svelte'
4+
import { clickoutside } from '../actions/clickoutside'
5+
66
interface SelectOption {
7-
value: string;
8-
name: string;
7+
value: string
8+
name: string
99
}
1010
11-
export let selectedOptions: Record<string, SelectOption>;
12-
export let options: SelectOption[];
13-
export let placeholder = 'Select...';
14-
export let canWriteIn = false;
15-
export let autofocus = false;
11+
export let selectedOptions: Record<string, SelectOption>
12+
export let options: SelectOption[]
13+
export let placeholder = 'Select...'
14+
export let canWriteIn = false
15+
export let autofocus = false
1616
17-
let input: HTMLInputElement;
18-
let inputValue: string;
19-
let activeOption: SelectOption;
20-
let showOptions = false;
17+
let input: HTMLInputElement
18+
let inputValue: string
19+
let activeOption: SelectOption
20+
let showOptions = false
2121
2222
onMount(() => {
23-
if (autofocus) input.focus();
23+
if (autofocus) input.focus()
2424
})
2525
26-
$: filtered = options.filter((o) =>
27-
inputValue ? o.name.toLowerCase().includes(inputValue.trim().toLowerCase()) : o
28-
);
26+
$: filtered = options.filter(o =>
27+
inputValue ? o.name.toLowerCase().includes(inputValue.trim().toLowerCase()) : o,
28+
)
2929
$: if ((activeOption && !filtered.includes(activeOption)) || (!activeOption && inputValue))
30-
[activeOption] = filtered;
30+
[activeOption] = filtered
3131
3232
function add(option: SelectOption) {
33-
selectedOptions[option.value] = option;
34-
input.focus();
35-
inputValue = '';
33+
selectedOptions[option.value] = option
34+
input.focus()
35+
inputValue = ''
3636
}
3737
3838
function remove(value: string) {
39-
const { [value]: option, ...restOfOptions } = selectedOptions;
40-
selectedOptions = restOfOptions;
39+
const { [value]: _option, ...restOfOptions } = selectedOptions
40+
selectedOptions = restOfOptions
4141
}
4242
4343
function setShowOptions(show: boolean) {
44-
showOptions = show;
45-
if (show) input.focus();
46-
if (!show) activeOption = undefined;
44+
showOptions = show
45+
if (show) input.focus()
46+
if (!show) activeOption = undefined
4747
}
4848
4949
function handleKeydown(e: KeyboardEvent) {
@@ -52,45 +52,48 @@
5252
if (e.key === ' ' && activeOption)
5353
add(activeOption)
5454
if (e.key === 'Backspace' && !inputValue)
55-
remove(Object.keys(selectedOptions).pop());
55+
remove(Object.keys(selectedOptions).pop())
5656
if (e.key === 'Enter') {
57-
e.preventDefault(); // keep form from submitting and closing modal
57+
e.preventDefault() // keep form from submitting and closing modal
5858
if (activeOption)
5959
selectOption(activeOption)
6060
else
6161
addWriteInIfApplicable()
6262
}
6363
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
64-
const increment = e.key === 'ArrowUp' ? -1 : 1;
65-
const calcIndex = filtered.indexOf(activeOption) + increment;
66-
activeOption =
67-
calcIndex < 0
64+
const increment = e.key === 'ArrowUp' ? -1 : 1
65+
const calcIndex = filtered.indexOf(activeOption) + increment
66+
activeOption
67+
= calcIndex < 0
6868
? filtered[filtered.length - 1]
6969
: calcIndex === filtered.length
7070
? filtered[0]
71-
: filtered[calcIndex];
71+
: filtered[calcIndex]
7272
}
7373
}
7474
7575
function addWriteInIfApplicable() {
7676
if (!canWriteIn) return
77-
const value = inputValue.trim();
77+
const value = inputValue.trim()
7878
if (value)
79-
add({name: value, value})
79+
add({ name: value, value })
8080
}
8181
8282
function selectOption(option: SelectOption) {
8383
if (selectedOptions[option.value])
84-
remove(option.value);
84+
remove(option.value)
8585
else
86-
add(option);
86+
add(option)
8787
}
8888
</script>
8989

90-
<div class="multiselect" use:clickoutside on:clickoutside={() => {
91-
setShowOptions(false)
92-
addWriteInIfApplicable()
93-
}}>
90+
<div
91+
class="multiselect"
92+
use:clickoutside
93+
on:clickoutside={() => {
94+
setShowOptions(false)
95+
addWriteInIfApplicable()
96+
}}>
9497
<div class="tokens" class:showOptions on:click={() => setShowOptions(true)}>
9598
{#each Object.values(selectedOptions) as option}
9699
<div

0 commit comments

Comments
 (0)