extracted modal from flowbite to make it more customized and added cancel button functionality
This commit is contained in:
parent
eeb4a62f26
commit
091ca82f69
@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import {anilistModal} from "./GetAniListSingleItemAndOpenModal.svelte";
|
||||||
import {anime} from "./GetAniListSingleItemAndOpenModal.svelte";
|
import {anime} from "./GetAniListSingleItemAndOpenModal.svelte";
|
||||||
import {Button} from "flowbite-svelte";
|
import {Button} from "flowbite-svelte";
|
||||||
import StarRatting from "@ernane/svelte-star-rating"
|
import StarRatting from "@ernane/svelte-star-rating"
|
||||||
@ -18,6 +19,11 @@
|
|||||||
10: "Masterpiece",
|
10: "Masterpiece",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hide = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
anilistModal.set(false)
|
||||||
|
};
|
||||||
|
|
||||||
const title = anime.data.MediaList.media.title.english !== "" ?
|
const title = anime.data.MediaList.media.title.english !== "" ?
|
||||||
anime.data.MediaList.media.title.english :
|
anime.data.MediaList.media.title.english :
|
||||||
anime.data.MediaList.media.title.romaji
|
anime.data.MediaList.media.title.romaji
|
||||||
@ -111,7 +117,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div id="inapp-data">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-10 grid-flow-col gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-10 grid-flow-col gap-4">
|
||||||
<div class="md:col-span-2 space-y-3">
|
<div class="md:col-span-2 space-y-3">
|
||||||
<img class="rounded-lg" src={anime.data.MediaList.media.coverImage.large} alt="{title} Cover Image">
|
<img class="rounded-lg" src={anime.data.MediaList.media.coverImage.large} alt="{title} Cover Image">
|
||||||
@ -234,6 +240,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="external-data">
|
||||||
|
<div id="anilist-data" class="flex flex-col md:flex-row md:pl-10 md:pr-10 pt-5 pb-5 justify-center md:gap-x-16 lg:gap-x-36 group">
|
||||||
|
<h2 class="text-left mb-1 text-base font-semibold text-gray-900 dark:text-white">AniList</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<footer class="bg-white rounded-lg shadow max-w-4-4 dark:bg-gray-800">
|
<footer class="bg-white rounded-lg shadow max-w-4-4 dark:bg-gray-800">
|
||||||
<div class="w-full mx-auto max-w-screen-xl p-4 md:flex md:items-center md:justify-end">
|
<div class="w-full mx-auto max-w-screen-xl p-4 md:flex md:items-center md:justify-end">
|
||||||
<Button
|
<Button
|
||||||
@ -245,7 +256,8 @@
|
|||||||
<Button
|
<Button
|
||||||
class="text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4
|
class="text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4
|
||||||
focus:ring-gray-100 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-800 dark:text-white
|
focus:ring-gray-100 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-800 dark:text-white
|
||||||
dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700">
|
dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700"
|
||||||
|
on:click={hide}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
155
frontend/src/modal/Modal.svelte
Normal file
155
frontend/src/modal/Modal.svelte
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
<script>import { twMerge } from "tailwind-merge";
|
||||||
|
import { Frame } from "flowbite-svelte";
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
import {CloseButton} from "flowbite-svelte";
|
||||||
|
import focusTrap from "../../node_modules/flowbite-svelte/dist/utils/focusTrap.js";
|
||||||
|
export let open = false;
|
||||||
|
export let title = "";
|
||||||
|
export let size = "md";
|
||||||
|
export let color = "default";
|
||||||
|
export let placement = "center";
|
||||||
|
export let autoclose = false;
|
||||||
|
export let outsideclose = false;
|
||||||
|
export let dismissable = true;
|
||||||
|
export let backdropClass = "fixed inset-0 z-20 bg-gray-900 bg-opacity-50 dark:bg-opacity-80";
|
||||||
|
export let classBackdrop = void 0;
|
||||||
|
export let dialogClass = "fixed top-0 start-0 end-0 h-modal md:inset-0 md:h-full z-30 w-full p-4 flex";
|
||||||
|
export let classDialog = void 0;
|
||||||
|
export let defaultClass = "relative flex flex-col mx-auto";
|
||||||
|
export let headerClass = "flex justify-between items-center p-4 md:p-5 rounded-t-lg";
|
||||||
|
export let classHeader = void 0;
|
||||||
|
export let bodyClass = "p-4 md:p-5 space-y-4 flex-1 overflow-y-auto overscroll-contain";
|
||||||
|
export let classBody = void 0;
|
||||||
|
export let footerClass = "flex items-center p-4 md:p-5 space-x-3 rtl:space-x-reverse rounded-b-lg";
|
||||||
|
export let classFooter = void 0;
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
$: dispatch(open ? "open" : "close");
|
||||||
|
function prepareFocus(node) {
|
||||||
|
const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);
|
||||||
|
let n;
|
||||||
|
while (n = walker.nextNode()) {
|
||||||
|
if (n instanceof HTMLElement) {
|
||||||
|
const el = n;
|
||||||
|
const [x, y] = isScrollable(el);
|
||||||
|
if (x || y) el.tabIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.focus();
|
||||||
|
}
|
||||||
|
const getPlacementClasses = (placement2) => {
|
||||||
|
switch (placement2) {
|
||||||
|
case "top-left":
|
||||||
|
return ["justify-start", "items-start"];
|
||||||
|
case "top-center":
|
||||||
|
return ["justify-center", "items-start"];
|
||||||
|
case "top-right":
|
||||||
|
return ["justify-end", "items-start"];
|
||||||
|
case "center-left":
|
||||||
|
return ["justify-start", "items-center"];
|
||||||
|
case "center":
|
||||||
|
return ["justify-center", "items-center"];
|
||||||
|
case "center-right":
|
||||||
|
return ["justify-end", "items-center"];
|
||||||
|
case "bottom-left":
|
||||||
|
return ["justify-start", "items-end"];
|
||||||
|
case "bottom-center":
|
||||||
|
return ["justify-center", "items-end"];
|
||||||
|
case "bottom-right":
|
||||||
|
return ["justify-end", "items-end"];
|
||||||
|
default:
|
||||||
|
return ["justify-center", "items-center"];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const sizes = {
|
||||||
|
xs: "max-w-md",
|
||||||
|
sm: "max-w-lg",
|
||||||
|
md: "max-w-2xl",
|
||||||
|
lg: "max-w-4xl",
|
||||||
|
xl: "max-w-6xl"
|
||||||
|
};
|
||||||
|
const onAutoClose = (e) => {
|
||||||
|
const target = e.target;
|
||||||
|
if (autoclose && target?.tagName === "BUTTON") hide(e);
|
||||||
|
};
|
||||||
|
const onOutsideClose = (e) => {
|
||||||
|
const target = e.target;
|
||||||
|
if (outsideclose && target === e.currentTarget) hide(e);
|
||||||
|
};
|
||||||
|
const hide = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
open = false;
|
||||||
|
};
|
||||||
|
const isScrollable = (e) => [e.scrollWidth > e.clientWidth && ["scroll", "auto"].indexOf(getComputedStyle(e).overflowX) >= 0, e.scrollHeight > e.clientHeight && ["scroll", "auto"].indexOf(getComputedStyle(e).overflowY) >= 0];
|
||||||
|
function handleKeys(e) {
|
||||||
|
if (e.key === "Escape" && dismissable) return hide(e);
|
||||||
|
}
|
||||||
|
$: backdropCls = twMerge(backdropClass, classBackdrop);
|
||||||
|
$: dialogCls = twMerge(dialogClass, classDialog, getPlacementClasses(placement));
|
||||||
|
$: frameCls = twMerge(defaultClass, "w-full divide-y", $$props.class);
|
||||||
|
$: headerCls = twMerge(headerClass, classHeader);
|
||||||
|
$: bodyCls = twMerge(bodyClass, classBody);
|
||||||
|
$: footerCls = twMerge(footerClass, classFooter);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if open}
|
||||||
|
<!-- backdrop -->
|
||||||
|
<div class={backdropCls}></div>
|
||||||
|
<!-- dialog -->
|
||||||
|
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||||
|
<div on:keydown={handleKeys} on:wheel|preventDefault|nonpassive use:prepareFocus use:focusTrap on:click={onAutoClose} on:mousedown={onOutsideClose} class={dialogCls} tabindex="-1" aria-modal="true" role="dialog">
|
||||||
|
<div class="flex relative {sizes[size]} w-full max-h-full">
|
||||||
|
<!-- Modal content -->
|
||||||
|
<Frame rounded shadow {...$$restProps} class={frameCls} {color}>
|
||||||
|
<!-- Modal header -->
|
||||||
|
{#if $$slots.header || title}
|
||||||
|
<Frame class={headerCls} {color}>
|
||||||
|
<slot name="header">
|
||||||
|
<h3 class="text-xl font-semibold {color === 'default' ? '' : 'text-gray-900 dark:text-white'} p-0">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
</slot>
|
||||||
|
{#if dismissable}<CloseButton name="Close modal" {color} on:click={hide} />{/if}
|
||||||
|
</Frame>
|
||||||
|
{/if}
|
||||||
|
<!-- Modal body -->
|
||||||
|
<div class={bodyCls} role="document" on:keydown|stopPropagation={handleKeys} on:wheel|stopPropagation|passive>
|
||||||
|
{#if dismissable && !$$slots.header && !title}
|
||||||
|
<CloseButton name="Close modal" class="absolute top-3 end-2.5" {color} on:click={hide} />
|
||||||
|
{/if}
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<!-- Modal footer -->
|
||||||
|
{#if $$slots.footer}
|
||||||
|
<Frame class={footerCls} {color}>
|
||||||
|
<slot name="footer"></slot>
|
||||||
|
</Frame>
|
||||||
|
{/if}
|
||||||
|
</Frame>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@component
|
||||||
|
[Go to docs](https://flowbite-svelte.com/)
|
||||||
|
## Props
|
||||||
|
@prop export let open: boolean = false;
|
||||||
|
@prop export let title: string = '';
|
||||||
|
@prop export let size: SizeType = 'md';
|
||||||
|
@prop export let color: ComponentProps<Frame>['color'] = 'default';
|
||||||
|
@prop export let placement: ModalPlacementType = 'center';
|
||||||
|
@prop export let autoclose: boolean = false;
|
||||||
|
@prop export let outsideclose: boolean = false;
|
||||||
|
@prop export let dismissable: boolean = true;
|
||||||
|
@prop export let backdropClass: string = 'fixed inset-0 z-40 bg-gray-900 bg-opacity-50 dark:bg-opacity-80';
|
||||||
|
@prop export let classBackdrop: string | undefined = undefined;
|
||||||
|
@prop export let dialogClass: string = 'fixed top-0 start-0 end-0 h-modal md:inset-0 md:h-full z-50 w-full p-4 flex';
|
||||||
|
@prop export let classDialog: string | undefined = undefined;
|
||||||
|
@prop export let defaultClass: string = 'relative flex flex-col mx-auto';
|
||||||
|
@prop export let headerClass: string = 'flex justify-between items-center p-4 md:p-5 rounded-t-lg';
|
||||||
|
@prop export let classHeader: string | undefined = undefined;
|
||||||
|
@prop export let bodyClass: string = 'p-4 md:p-5 space-y-4 flex-1 overflow-y-auto overscroll-contain';
|
||||||
|
@prop export let classBody: string | undefined = undefined;
|
||||||
|
@prop export let footerClass: string = 'flex items-center p-4 md:p-5 space-x-3 rtl:space-x-reverse rounded-b-lg';
|
||||||
|
@prop export let classFooter: string | undefined = undefined;
|
||||||
|
-->
|
77
frontend/src/modal/Modal.svelte.d.ts
vendored
Normal file
77
frontend/src/modal/Modal.svelte.d.ts
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { SvelteComponentTyped } from "svelte";
|
||||||
|
import type { Dismissable, SizeType } from '../types';
|
||||||
|
import type { ModalPlacementType } from '../types';
|
||||||
|
declare const __propDef: {
|
||||||
|
props: import("svelte/elements").HTMLAnchorAttributes & {
|
||||||
|
tag?: string;
|
||||||
|
color?: import("../utils/Frame.svelte").FrameColor;
|
||||||
|
rounded?: boolean;
|
||||||
|
border?: boolean;
|
||||||
|
shadow?: boolean;
|
||||||
|
node?: HTMLElement | undefined;
|
||||||
|
use?: import("svelte/action").Action<HTMLElement, any>;
|
||||||
|
options?: object;
|
||||||
|
class?: string;
|
||||||
|
role?: string;
|
||||||
|
open?: boolean;
|
||||||
|
transition?: (node: HTMLElement, params: any) => import("svelte/transition").TransitionConfig;
|
||||||
|
params?: any;
|
||||||
|
} & Dismissable & {
|
||||||
|
open?: boolean;
|
||||||
|
title?: string;
|
||||||
|
size?: SizeType;
|
||||||
|
placement?: ModalPlacementType;
|
||||||
|
autoclose?: boolean;
|
||||||
|
outsideclose?: boolean;
|
||||||
|
backdropClass?: string;
|
||||||
|
classBackdrop?: string;
|
||||||
|
dialogClass?: string;
|
||||||
|
classDialog?: string;
|
||||||
|
defaultClass?: string;
|
||||||
|
headerClass?: string;
|
||||||
|
classHeader?: string;
|
||||||
|
bodyClass?: string;
|
||||||
|
classBody?: string;
|
||||||
|
footerClass?: string;
|
||||||
|
classFooter?: string;
|
||||||
|
};
|
||||||
|
events: {
|
||||||
|
wheel: WheelEvent;
|
||||||
|
} & {
|
||||||
|
[evt: string]: CustomEvent<any>;
|
||||||
|
};
|
||||||
|
slots: {
|
||||||
|
header: {};
|
||||||
|
default: {};
|
||||||
|
footer: {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type ModalProps = typeof __propDef.props;
|
||||||
|
export type ModalEvents = typeof __propDef.events;
|
||||||
|
export type ModalSlots = typeof __propDef.slots;
|
||||||
|
/**
|
||||||
|
* [Go to docs](https://flowbite-svelte.com/)
|
||||||
|
* ## Props
|
||||||
|
* @prop export let open: boolean = false;
|
||||||
|
* @prop export let title: string = '';
|
||||||
|
* @prop export let size: SizeType = 'md';
|
||||||
|
* @prop export let color: ComponentProps<Frame>['color'] = 'default';
|
||||||
|
* @prop export let placement: ModalPlacementType = 'center';
|
||||||
|
* @prop export let autoclose: boolean = false;
|
||||||
|
* @prop export let outsideclose: boolean = false;
|
||||||
|
* @prop export let dismissable: boolean = true;
|
||||||
|
* @prop export let backdropClass: string = 'fixed inset-0 z-40 bg-gray-900 bg-opacity-50 dark:bg-opacity-80';
|
||||||
|
* @prop export let classBackdrop: string | undefined = undefined;
|
||||||
|
* @prop export let dialogClass: string = 'fixed top-0 start-0 end-0 h-modal md:inset-0 md:h-full z-50 w-full p-4 flex';
|
||||||
|
* @prop export let classDialog: string | undefined = undefined;
|
||||||
|
* @prop export let defaultClass: string = 'relative flex flex-col mx-auto';
|
||||||
|
* @prop export let headerClass: string = 'flex justify-between items-center p-4 md:p-5 rounded-t-lg';
|
||||||
|
* @prop export let classHeader: string | undefined = undefined;
|
||||||
|
* @prop export let bodyClass: string = 'p-4 md:p-5 space-y-4 flex-1 overflow-y-auto overscroll-contain';
|
||||||
|
* @prop export let classBody: string | undefined = undefined;
|
||||||
|
* @prop export let footerClass: string = 'flex items-center p-4 md:p-5 space-x-3 rtl:space-x-reverse rounded-b-lg';
|
||||||
|
* @prop export let classFooter: string | undefined = undefined;
|
||||||
|
*/
|
||||||
|
export default class Modal extends SvelteComponentTyped<ModalProps, ModalEvents, ModalSlots> {
|
||||||
|
}
|
||||||
|
export {};
|
Loading…
Reference in New Issue
Block a user