Skip to content
This repository has been archived by the owner on Nov 22, 2024. It is now read-only.

Commit

Permalink
Merge pull request #21 from illright/feature/modals
Browse files Browse the repository at this point in the history
Modals
  • Loading branch information
illright authored May 18, 2020
2 parents d90aded + 834bf64 commit e98f06c
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 65 deletions.
141 changes: 76 additions & 65 deletions demo/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
Switch,
RadioChipGroup,
CheckboxChipGroup,
ModalOverlay,
Modal,
Tab, Tabs,
Label,
Headline,
Dot,
} from '../src/index.js';
import ModalCard from './modal-card.svelte';
import { ChevronDownIcon } from 'svelte-feather-icons';
let items = [
Expand All @@ -34,75 +37,83 @@
let item = items[1].value;
let colorItem = colorItems[1].value;
let open1 = false;
let open2 = false;
let dropdownTabSelected = false;
</script>

<a href="https://googles.com">say my name</a>
<Card>
<Label>and you know it</Label>
<Headline>you messin with my head</Headline>
<TextField label="Write something" outline />
<Button filled>
<Dot success />
<Dot attention small class="ml" />
<Dot info small class="ml" />
<Dot danger class="ml" title="You're in trouble now, son." />
</Button>
<RadioGroup items={colorItems} color bind:value={colorItem} name="colors" />
<RadioGroup items={items} bind:value={item} name="numbers" />
<CheckboxGroup items={items} max={1} name="numbers-check" />
<div class="dropdown-holder">
<DropdownShell let:toggle on:change={() => console.log('yay')}>
<Button on:click={toggle}>
test me!
<ChevronDownIcon size="24" class="ml dropdown-chevron" />
</Button>
<Dropdown top>
<div class="padded">
I'm a little dropdown short and stout
</div>
</Dropdown>
</DropdownShell>
</div>
<ModalOverlay>
<a href="https://googles.com">say my name</a>
<Card>
<TextField label="Write something" outline />
<Button filled>
<Dot success />
<Dot attention small class="ml" />
<Dot info small class="ml" />
<Dot danger class="ml" title="You're in trouble now, son." />
</Button>
<RadioGroup items={colorItems} color bind:value={colorItem} name="colors" />
<RadioGroup items={items} bind:value={item} name="numbers" />
<CheckboxGroup items={items} max={1} name="numbers-check" />
<div class="dropdown-holder">
<DropdownShell let:toggle on:change={() => console.log('yay')}>
<Button on:click={toggle}>
test me!
<ChevronDownIcon size="24" class="ml dropdown-chevron" />
</Button>
<Dropdown top>
<div class="padded">
I'm a little dropdown short and stout
</div>
</Dropdown>
</DropdownShell>
</div>

<Switch>
<span class="padded">
default
</span>
</Switch>
<Switch slotLeft value={true}>
<span class="padded">
on
</span>
</Switch>
<Switch slotLeft value={true} disabled>
<span class="padded">
disabled
</span>
</Switch>
<div class="flex">
<RadioChipGroup {items} name="radio-chip-group" outline />
<CheckboxChipGroup {items} name="checkbox-chip-group" max={2} small />
</div>
<div class="flex">
<Tab
class={dropdownTabSelected && 'selected'}
value="page1"
name="nav1"
on:change={() => dropdownTabSelected = true}
>
Components
<ChevronDownIcon size="24" class="tab-chevron" />
</Tab>
<Tab value="page2" name="nav1" on:change={() => dropdownTabSelected = false}>
Installation
</Tab>
<Tab value="Showcase" name="nav1" />
</div>
<div class="flex">
<Tabs name="nav2" items={['Showcase', 'Components']} />
</div>
</Card>
<Switch>
<span class="padded">
default
</span>
</Switch>
<Switch slotLeft value={true}>
<span class="padded">
on
</span>
</Switch>
<Switch slotLeft value={true} disabled>
<span class="padded">
disabled
</span>
</Switch>
<div class="flex">
<RadioChipGroup {items} name="radio-chip-group" outline />
<CheckboxChipGroup {items} name="checkbox-chip-group" max={2} small />
</div>
<div class="flex">
<Tab
class={dropdownTabSelected && 'selected'}
value="page1"
name="nav1"
on:change={() => dropdownTabSelected = true}
>
Components
<ChevronDownIcon size="24" class="tab-chevron" />
</Tab>
<Tab value="page2" name="nav1" on:change={() => dropdownTabSelected = false}>
Installation
</Tab>
<Tab value="Showcase" name="nav1" />
</div>
<div class="flex">
<Tabs name="nav2" items={['Showcase', 'Components']} />
</div>
<div class="flex">
<Button on:click={() => open1 = true}>open modal 1</Button>
<Button on:click={() => open2 = true}>open modal 2</Button>
</div>
<Modal component={ModalCard} props={{ doYou: 'hear what I hear' }} bind:open={open1} />
<Modal component={ModalCard} props={{ doYou: 'care if I care' }} bind:open={open2} />
</Card>
</ModalOverlay>

<style>
.padded {
Expand Down
16 changes: 16 additions & 0 deletions demo/modal-card.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script>
import {
Button,
Card,
TextField,
} from '../src/index.js';
export let doYou;
export let closeCallback;
</script>

<Card>
<h1>Do you {doYou}?</h1>
<TextField />
<Button on:click={closeCallback}>close this</Button>
</Card>
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export { default as RadioChipGroup } from './chip/radio-chip-group.svelte';
export { default as CheckboxChip } from './chip/checkbox-chip.svelte';
export { default as CheckboxChipGroup } from './chip/checkbox-chip-group.svelte';

export { default as ModalOverlay } from './modal/modal-overlay.svelte';
export { default as Modal } from './modal/modal.svelte';

export { default as Tab } from './tab/tab.svelte';
export { default as Tabs } from './tab/tabs.svelte';

Expand Down
1 change: 1 addition & 0 deletions src/modal/modal-context-key.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default {};
20 changes: 20 additions & 0 deletions src/modal/modal-overlay.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@import '../var-defaults.scss';
@import '_attractions-theme.scss';

.modal-overlay {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: $modal-overlay-bg;
z-index: 1000;
display: none;

&.open {
display: flex;
align-items: center;
justify-content: center;
}
}
77 changes: 77 additions & 0 deletions src/modal/modal-overlay.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<script>
import { setContext, onDestroy } from 'svelte';
import { writable } from 'svelte/store';
import modalContextKey from './modal-context-key.js';
export const modalOverlayKey = {};
/* Stores modal registration objects.
A key for such a registration is a store object
that stores the open state of the modal. */
let registeredModals = new Map();
/* Meant to be bound to registration objects and used as a subscription function. */
function updateOpenState(newState) {
this.open = newState;
registeredModals = registeredModals;
}
/* Register and render a new modal from the `component` and `props`.
Will return the open state store
– the object that is used for identifying the registration. */
function register(component, props) {
const openState = writable(false);
const registration = { component, props, open: false };
registration.unsubscribe = openState.subscribe(updateOpenState.bind(registration));
registeredModals.set(openState, registration);
return openState;
}
/* Unregister and destroy the modal that corresponds to this `openState`. */
function unregister(openState) {
const registration = registeredModals.get(openState);
if (registration == null) {
return;
}
registration.unsubscribe();
registeredModals.delete(openState);
}
/* Pass new props to the modal that corresponds to this `openState`.
Needed to support reactivity. */
function updateProps(openState, newProps) {
const registration = registeredModals.get(openState);
if (registration == null) {
return;
}
registration.props = newProps;
}
setContext(modalContextKey, { register, update: updateProps, unregister });
onDestroy(() => {
for (let registration of registeredModals.values()) {
registration.unsubscribe();
}
});
</script>

<slot />
{#each [...registeredModals.entries()] as [openState, registration] (openState)}
<div
class="modal-overlay"
class:open={registration.open}
on:click|self={() => openState.set(false)}
>
<svelte:component
this={registration.component}
{...registration.props}
closeCallback={() => openState.set(false)}
/>
</div>
{/each}

<style src="./modal-overlay.scss"></style>
44 changes: 44 additions & 0 deletions src/modal/modal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script>
import { getContext, onMount, onDestroy } from 'svelte';
import modalContextKey from './modal-context-key.js';
const { register, update, unregister } = getContext(modalContextKey);
export let open = false;
/* The component to use in creating a modal. */
export let component;
/* The props to pass to the modal component.
It will also receive the `closeCallback` prop which can be used to close the modal. */
export let props;
let openState = null;
let unsubscribe = null;
$: syncOpenState(open);
$: update(openState, props);
/* Written out as a separate function to ensure
that the reactive statement only depends on `open`. */
function syncOpenState(openStateValue) {
openState && openState.set(openStateValue);
}
onMount(() => {
openState = register(component, props);
unsubscribe = openState.subscribe((openStateValue) => {
/* To prevent unneeded invalidation (if Svelte isn't smart enough not to) */
if (open !== openStateValue) {
open = openStateValue;
}
});
});
onDestroy(() => {
unregister(openState);
if (unsubscribe != null) {
unsubscribe();
}
});
</script>
1 change: 1 addition & 0 deletions src/var-defaults.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ $textfield-item: #7a7a7a;
$off-state: #aaa;
$chip-bg: transparentize(black, .92);
$chip-fg: #656565;
$modal-overlay-bg: transparentize(black, .75);

$chip-radius: 1.5625em;
$button-radius: 1.5625em;
Expand Down

0 comments on commit e98f06c

Please sign in to comment.