Make it so you can include DropdownMenus on all kinds of buttons

This commit is contained in:
Henrik Giesel 2021-03-30 18:57:50 +02:00
parent 5eb07d3fc7
commit 391f64f648
13 changed files with 178 additions and 88 deletions

View File

@ -1,10 +1,6 @@
<script lang="typescript" context="module">
export type Buttons =
| { component: SvelteComponent; [...arg: string]: unknown }
| Buttons[];
</script>
<script lang="typescript">
import type { Buttons } from "./types";
export let buttons: Buttons;
</script>

View File

@ -1,15 +1,14 @@
<script lang="typescript">
export let className: string;
export let id = "";
export let className = "";
export let props: Record<string, string> = {};
export let onChange: (event: ChangeEvent) => void;
</script>
<style lang="scss">
button {
display: inline-block;
vertical-align: middle;
width: 28px;
height: 28px;
padding: 0;
&:hover {
@ -24,19 +23,23 @@
span {
display: inline-block;
vertical-align: middle;
width: 100%;
height: 100%;
cursor: pointer;
}
input {
display: inline-block;
opacity: 0;
width: 100%;
height: 100%;
width: 28px;
height: calc(28px - 4px);
cursor: pointer;
}
</style>
<button tabindex="-1" on:mousedown|preventDefault>
<span class={className}> <input type="color" on:change={onChange} /> </span>
<button tabindex="-1" {id} class={className} {...props} on:mousedown|preventDefault>
<span> <input type="color" on:change={onChange} /> </span>
</button>

View File

@ -34,9 +34,12 @@
</script>
<script lang="typescript">
import InnerButton from "./InnerButton.svelte";
import SquareButton from "./SquareButton.svelte";
export let id = "";
export let className = "";
export let props: Record<string, string> = {};
export let icon = "";
export let command: string;
export let activatable = true;
@ -57,6 +60,6 @@
}
</script>
<InnerButton {className} {active} {onClick}>
<SquareButton {id} {className} {props} {active} {onClick}>
{@html icon}
</InnerButton>
</SquareButton>

View File

@ -1,25 +0,0 @@
<script lang="typescript">
import { onMount } from "svelte";
export let buttonId = `dropdownMenuButton123`;
// ${Math.random().toString(36).substring(2)}`
let dropdownButtonRef: HTMLButtonElement;
onMount(() => {
/* Prevent focus on menu activation */
const noop = () => {};
Object.defineProperty(dropdownButtonRef, 'focus', { value: noop });
/* Set custom menu without using .dropdown
* Rendering the menu here would cause the menu to
* be displayed outside of the visible area
*/
const dropdown = new bootstrap.Dropdown(dropdownButtonRef);
dropdown._menu = document.getElementById(buttonId);
})
</script>
<button bind:this={dropdownButtonRef} class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" on:mousedown|preventDefault>
Dropdown button
</button>

View File

@ -0,0 +1,40 @@
<script lang="typescript">
import { onMount } from "svelte";
import type { ButtonDefinition } from "./types";
export let button: ButtonDefinition;
export let menuId: string;
function extend({ className, props, ...rest }: ButtonDefinition): ButtonDefinition {
return {
className: `${className} dropdown-toggle`,
props: {
"data-bs-toggle": "dropdown",
"aria-expanded": "false",
...props,
},
...rest,
};
}
function createDropdown({ detail }: CustomEvent): void {
const button: HTMLButtonElement = detail.button;
/* Prevent focus on menu activation */
const noop = () => {};
Object.defineProperty(button, "focus", { value: noop });
/* Set custom menu without using .dropdown
* Rendering the menu here would cause the menu to
* be displayed outside of the visible area
*/
const dropdown = new bootstrap.Dropdown(button);
dropdown._menu = button.getRootNode().getElementById(menuId);
}
</script>
<svelte:component
this={button.component}
{...extend(button)}
on:mount={createDropdown} />

View File

@ -5,7 +5,7 @@
import { disabledKey, nightModeKey } from "./contextKeys";
import ButtonGroup from "./ButtonGroup.svelte";
import type { Buttons } from "./ButtonGroup.svelte";
import type { Buttons } from "./types";
export let buttons: Buttons = [];
export let nightMode: boolean;
@ -25,7 +25,7 @@
background: var(--bg-color);
border-bottom: 1px solid var(--border);
/* Remove most outer marigns */
/* Remove outermost marigns */
& > :global(ul) {
& > :global(li:nth-child(1)) {
margin-left: 0;

View File

@ -1,11 +1,14 @@
<script lang="typescript">
import InnerButton from "./InnerButton.svelte";
import SquareButton from "./SquareButton.svelte";
export let id = "";
export let className = "";
export let props: Record<string, string> = {};
export let icon = "";
export let onClick: (event: ClickEvent) => void;
</script>
<InnerButton {className} {onClick}>
<SquareButton {id} {className} {props} {onClick}>
{@html icon}
</InnerButton>
</SquareButton>

View File

@ -1,5 +1,27 @@
<script lang="typescript">
import { onMount, createEventDispatcher, getContext } from "svelte";
import type { Readable } from "svelte/store";
import { disabledKey } from "./contextKeys";
export let id = "";
export let className = "";
export let props: Record<string, string> = {};
export let label: string;
export let onClick: (event: ClickEvent) => void;
let buttonRef: HTMLButtonElement;
function extendClassName(className: string): string {
return `${className} btn btn-secondary`;
}
const dispatch = createEventDispatcher();
onMount(() => dispatch("mount", { button: buttonRef }));
const disabledStore = getContext(disabledKey);
$: disabled = $disabledStore;
</script>
<style lang="scss">
@ -7,15 +29,34 @@
display: inline-block;
vertical-align: middle;
width: auto;
height: 26px;
height: calc(28px + 2px);
padding: 0 10px;
margin: 0 3px;
border-radius: 0;
border-color: var(--faint-border);
&:focus {
box-shadow: 0 0 12px 4px rgb(255 255 255 / 0.5);
}
&[disabled] {
opacity: 0.4;
cursor: not-allowed;
border-color: var(--faint-border);
}
}
</style>
<button class="btn btn-secondary" tabindex="-1" on:click> {label} </button>
<button
bind:this={buttonRef}
{disabled}
{id}
class={extendClassName(className)}
{...props}
tabindex="-1"
on:click={onClick}
on:mousedown|preventDefault>
{label}
</button>

View File

@ -3,31 +3,24 @@
import type { Readable } from "svelte/store";
import { disabledKey } from "./contextKeys";
export let className: string = "";
export let id = "";
export let className = "";
export let props: Record<string, string> = {};
export let onClick: (event: ClickEvent) => void;
export let active = false;
const disabledStore = getContext(disabledKey)
const disabledStore = getContext(disabledKey);
$: disabled = $disabledStore;
</script>
<style lang="scss">
button {
display: inline-block;
vertical-align: middle;
width: 28px;
height: 28px;
padding: 0;
background-color: white;
& > :global(svg),
& > :global(img) {
fill: currentColor;
vertical-align: unset;
width: 100%;
height: 100%;
}
&:hover {
background-color: #eee;
}
@ -57,14 +50,38 @@
}
}
}
span {
display: inline-block;
vertical-align: middle;
/* constrain icon */
width: 28px;
height: 28px;
& > :global(svg),
& > :global(img) {
fill: currentColor;
vertical-align: unset;
width: 100%;
height: 100%;
}
}
/* for DropdownMenu */
:global(.dropdown-toggle)::after {
margin-right: 0.25rem;
}
</style>
<button
class="p-1 {className}"
{id}
class={className}
{...props}
class:active
tabindex="-1"
{disabled}
on:click={onClick}
on:mousedown|preventDefault>
<slot />
<span class="p-1"><slot /></span>
</button>

View File

@ -7,11 +7,11 @@
}
.rainbow {
background: linear-gradient(217deg, rgba(255, 0, 0, 0.8), rgba(255, 0, 0, 0) 70.71%),
linear-gradient(127deg, rgba(0, 255, 0, 0.8), rgba(0, 255, 0, 0) 70.71%),
linear-gradient(336deg, rgba(0, 0, 255, 0.8), rgba(0, 0, 255, 0) 70.71%);
background-clip: content-box;
/* Boostrap .rounded has .25rem */
border-radius: 0.375rem;
background: content-box
linear-gradient(217deg, rgba(255, 0, 0, 0.8), rgba(255, 0, 0, 0) 70.71%),
content-box
linear-gradient(127deg, rgba(0, 255, 0, 0.8), rgba(0, 255, 0, 0) 70.71%),
content-box
linear-gradient(336deg, rgba(0, 0, 255, 0.8), rgba(0, 0, 255, 0) 70.71%),
border-box white;
}

View File

@ -20,12 +20,12 @@ function wrapWithForecolor(color: string): void {
export const forecolorButton = {
component: IconButton,
icon: squareFillIcon,
className: "forecolor p-1",
className: "forecolor",
onClick: () => wrapWithForecolor(getForecolor()),
};
export const colorpickerButton = {
component: ColorPicker,
className: "rainbow p-1",
className: "rainbow",
onChange: ({ currentTarget }) => setForegroundColor(currentTarget.value),
};

View File

@ -6,7 +6,7 @@ import { setupI18n, ModuleName } from "anki/i18n";
import EditorToolbarSvelte from "./EditorToolbar.svelte";
import LabelButton from "./LabelButton.svelte";
import DropdownIconButton from "./DropdownIconButton.svelte";
import DropdownMenu from "./DropdownMenu.svelte";
// @ts-ignore
export { updateActiveButtons, clearActiveButtons } from "./CommandIconButton.svelte";
@ -46,8 +46,6 @@ const defaultButtons = [
],
[forecolorButton, colorpickerButton],
[attachmentButton, recordButton, clozeButton, mathjaxButton, htmlButton],
[ { component: DropdownIconButton }],
];
class EditorToolbar extends HTMLElement {
@ -57,16 +55,18 @@ class EditorToolbar extends HTMLElement {
connectedCallback(): void {
this.disabled = writable(false);
setupI18n({ modules: [ModuleName.STATISTICS, ModuleName.SCHEDULING] }).then(() => {
this.component = new EditorToolbarSvelte({
target: this,
props: {
buttons: defaultButtons,
nightMode: checkNightMode(),
disabled: this.disabled,
},
});
})
setupI18n({ modules: [ModuleName.STATISTICS, ModuleName.SCHEDULING] }).then(
() => {
this.component = new EditorToolbarSvelte({
target: this,
props: {
buttons: defaultButtons,
nightMode: checkNightMode(),
disabled: this.disabled,
},
});
}
);
}
enableButtons(): void {

View File

@ -0,0 +1,12 @@
import type { SvelteComponent } from "svelte/internal";
export interface ButtonDefinition {
component: SvelteComponent;
id?: string;
className?: string;
props?: Record<string, string>;
button: HTMLButtonElement;
[arg: string]: unknown;
}
export type Buttons = ButtonDefinition | Buttons[];