PHANTOM
🇮🇳 IN
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 48 additions & 14 deletions src/components/alert/alert.stories.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<script context="module">
import Alert, { types } from './alert.svelte'
import Alert, { types, sizes } from './alert.svelte'

export const meta = {
title: "Components/Alert",
component: Alert,
argTypes: {
types: { table: { disable: true } },
type: { control: 'select', options: types },
size: { control: 'select', options: sizes },
hasActions: { control: 'boolean' },
title: { type: 'string', defaultValue: 'Title' },
'--leo-alert-center-width': {
Expand Down Expand Up @@ -42,6 +43,8 @@
let title = 'Title'
let canDismiss = true
let hasAction = false
let isThin = false
let isSmall = false
let duration = 2000
let customButton = false

Expand All @@ -51,6 +54,8 @@
content,
title: title || undefined,
type: type ?? 'error',
isThin,
size: isSmall ? "small" : "default",
actions: hasAction
? [
{
Expand Down Expand Up @@ -113,8 +118,8 @@
>
<Alert {...args}>
<div slot="actions">
<Button kind="outline">Don't!</Button>
<Button>Do the thing!</Button>
<Button size={args.size === "small" ? "tiny" : "medium"} kind="outline">Don't!</Button>
<Button size={args.size === "small" ? "tiny" : "medium"}>Do the thing!</Button>
</div>
Some content
</Alert>
Expand All @@ -127,25 +132,39 @@
<div slot="title">{args.title}</div>
Alert content
<div slot="actions">
<Button kind="filled">Primary</Button>
<Button kind="plain-faint">Secondary</Button>
<Button size={args.size === "small" ? "tiny" : "medium"} kind="filled">Primary</Button>
<Button size={args.size === "small" ? "tiny" : "medium"} kind="plain-faint">Secondary</Button>
</div>
</Alert>
</Template>

<Story name="Default Alert" />

<Story name="All" let:args>
<div class="container">
{#each [true, false] as hasTitle}
{#each types as type}
<Alert {type} {...args}>
<div slot="title">{#if hasTitle}{args.title}{/if}</div>
Alert content
<div slot="actions" class="actions">
<Button kind="filled">Primary</Button>
<Button kind="plain-faint">Secondary</Button>
</div>
</Alert>
<div class="row">
{#each sizes as size}
{#each [false, true] as isThin}
{#each [false, true] as hasContentAfter}
<Alert {type} {...args} {size} {isThin} {hasContentAfter}>
<div slot="title">{#if hasTitle}{args.title}{/if}</div>
Alert content
<div slot="actions" class="actions">
<Button size={size === "small" ? "tiny" : "medium"} kind="filled">Primary</Button>
<Button size={size === "small" ? "tiny" : "medium"} kind="plain-faint">Secondary</Button>
</div>
<div slot="content-after">
<Button fab kind="plain-faint">
<Icon name="close" />
</Button>
</div>
</Alert>
{/each}
{/each}
{/each}
</div>
{/each}
{/each}
</div>
Expand Down Expand Up @@ -173,6 +192,14 @@
Has action
<input type="checkbox" bind:checked={hasAction} />
</label>
<label>
Is "thin"
<input type="checkbox" bind:checked={isThin} />
</label>
<label>
Is "small"
<input type="checkbox" bind:checked={isSmall} />
</label>
{#if hasAction}
<label>
Use custom button
Expand All @@ -192,7 +219,14 @@
display: flex;
flex-direction: column;
gap: 16px;
max-width: 500px;
}

.row {
display: grid;
align-items: start;
grid-auto-flow: column;
grid-auto-columns: max-content;
gap: 16px;
}

.options {
Expand Down
50 changes: 36 additions & 14 deletions src/components/alert/alert.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
export const types = ['info', 'warning', 'error', 'success', 'notice'] as const
export type AlertType = (typeof types)[number]

export const sizes = ['default', 'small'] as const
export type Size = (typeof sizes)[number]

const defaultIcons: { [P in AlertType]: IconName } = {
'info': 'info-filled',
'error': 'warning-circle-filled',
Expand All @@ -18,7 +21,9 @@

export let type: AlertType = 'error'
export let isToast = false
export let isThin = false
export let hideIcon = false
export let size: Size = 'default'

// TODO: Remove when only supporting svelte >5 which can render slotted content conditionally
export let hasActions = $$slots.actions
Expand All @@ -28,8 +33,9 @@
</script>

<div
class="leo-alert {currentType}"
class="leo-alert {currentType} size-{size}"
class:toast={isToast}
class:thin={isThin}
>
{#if !hideIcon}
<div class="icon">
Expand Down Expand Up @@ -70,21 +76,24 @@
gap: var(--leo-spacing-m);
}
.leo-alert {
--padding: var(--leo-spacing-xl);
--gap: var(--leo-spacing-xl);
--leo-icon-color: var(--leo-alert-icon-color, var(--default-icon-color));
background-color: var(
--leo-alert-background-color,
var(--default-background)
);
color: var(--default-text-color, var(--leo-color-text-primary));
padding: var(--leo-alert-padding, var(--leo-spacing-xl));
padding: var(--leo-alert-padding, var(--padding));
border-radius: var(--leo-radius-l);
border: var(--leo-alert-border-width, var(--default-border-width)) solid
var(--leo-alert-border-color, var(--default-border-color));
gap: var(--leo-spacing-xl) 0;
gap: var(--gap);
font: var(--leo-font-default-regular);

display: grid;
grid-template-columns: min-content 1fr;
grid-template-columns: [icon-start] min-content [icon-end main-start] 1fr [main-end];
grid-template-rows: [main-start] auto [main-end];

&.notice {
--default-background: transparent;
Expand Down Expand Up @@ -114,33 +123,46 @@
--default-text-color: var(--leo-color-systemfeedback-warning-text);
}

&:has(.content-after) {
grid-template-columns: min-content 1fr auto;
}

& .icon {
--leo-icon-size: var(--leo-icon-m);

color: var(--leo-icon-color);
margin-right: var(--leo-spacing-xl);
}

& .title {
font: var(--leo-font-heading-h4);
}

& .content {
grid-column: 2;
grid-column: main;
align-content: center;
}

& .content-after {
grid-column: 3;
margin-left: var(--leo-spacing-xl);
&:has(.content-after) .content-after {
grid-row: main;
grid-column: -1; // No named track necessary as it should just always be at the end.
}

& .actions {
grid-column: 2;
grid-column: main;
}

&.thin {
align-items: center;

.actions {
grid-column: main-end; // Actions when `.thin` should always be after the `main` content
}

// If both `actions` and `content-after` exist, we have to add at least an explicit track for content-after since -1 doesn't work for implicit tracks
&:has(.content-after):has(.actions) {
grid-template-columns: [icon-start] min-content [icon-end main-start] 1fr [main-end content-after] auto [content-end];
}
}

&.size-small {
--padding: var(--leo-spacing-m) var(--leo-spacing-l);
--gap: var(--leo-spacing-m);
}
}

Expand Down
31 changes: 22 additions & 9 deletions src/components/alert/alertCenter.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@

type Action = {
kind?: ButtonKind
isDisabled?: boolean,
isLoading?: boolean,
component?: ComponentType<SvelteComponent>,
size?: ButtonSize
isDisabled?: boolean
isLoading?: boolean
component?: ComponentType<SvelteComponent>
action: (alert: AlertInfo) => void
} & ({
text: string
Expand All @@ -25,6 +26,8 @@

export interface AlertOptions {
type: AlertType
size?: Size
isThin?: boolean
content: string
title?: string
icon?: IconName
Expand All @@ -37,6 +40,8 @@
id = ++nextId

type: AlertType
size?: Size
isThin: boolean = false
content: string
title?: string
icon?: IconName
Expand Down Expand Up @@ -89,30 +94,33 @@
</script>

<script lang="ts">
import Alert from './alert.svelte'
import Alert, { type Size } from './alert.svelte'
import Button from '../button/button.svelte'
import type { ButtonKind } from '../button/props'
import type { ButtonKind, ButtonSize } from '../button/props'
import { fly } from 'svelte/transition'
import type { ComponentType, SvelteComponent } from 'svelte'
import type { IconName } from '../../../icons/meta'
import Icon from '../icon/icon.svelte'

export let position: `${'top' | 'bottom'}-${'left' | 'right' | 'center'}` =
'top-center'
export let size: Size = 'default'

$: style = `${position.includes('right') ? 'right' : 'left'}: ${
position.includes('center') ? 'calc(50% - (var(--width) / 2))' : '0'
}; ${position.includes('top') ? 'top' : 'bottom'}: 0`
</script>

<div class="leo-alert-center" {style}>
<div class="leo-alert-center size-{size}" {style}>
{#each $alerts as alert (alert.id)}
{@const alertSize = alert.size || size}
<div
class="alert-container"
transition:fly={transitionOptions}
on:mouseenter={() => alert.pauseDismiss()}
on:mouseleave={() => alert.resumeDismiss()}
>
<svelte:component this={alert.component || Alert} {...alert} hasActions={alert.actions.length > 0} hasContentAfter={alert.canDismiss} isToast>
<svelte:component this={alert.component || Alert} {...alert} hasActions={alert.actions.length > 0} hasContentAfter={alert.canDismiss} size={alertSize} isThin={alert.isThin} isToast>
<div slot="title">
{alert.title ?? ""}
</div>
Expand All @@ -128,7 +136,7 @@
{#each alert.actions as action}
<svelte:component
this={action.component || ButtonComponent}
size="small"
size={action.size || alertSize === "small" ? "tiny" : "medium"}
fab={action.icon && !action.text}
kind={action.kind || 'filled'}
onClick={() => action.action(alert)}
Expand All @@ -152,7 +160,8 @@

<style lang="scss">
.leo-alert-center {
--width: var(--leo-alert-center-width, min(540px, 100vw));
--min-width: 540px;
--width: var(--leo-alert-center-width, min(var(--min-width), 100vw));
z-index: var(--leo-alert-center-z-index, 1000);
position: var(--leo-alert-center-position, fixed);
width: var(--width);
Expand All @@ -162,5 +171,9 @@
display: flex;
flex-direction: column;
gap: var(--leo-spacing-m);

&.size-small {
--min-width: 0;
}
}
</style>
Loading