Skip to content

Commit

Permalink
refactor: cards
Browse files Browse the repository at this point in the history
  • Loading branch information
jrasm91 committed Nov 15, 2024
1 parent 2b64b49 commit e2d5dac
Show file tree
Hide file tree
Showing 31 changed files with 456 additions and 190 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"dependencies": {
"@mdi/js": "^7.4.47",
"bits-ui": "^1.0.0-next.46",
"tailwind-merge": "^2.5.4",
"tailwind-variants": "^0.3.0"
}
}
2 changes: 1 addition & 1 deletion src/docs/components/DualThemeLayout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
</a>
<div>
<button onclick={handleClick}>
<Icon path={themeIcon} size="24" class="cursor" />
<Icon icon={themeIcon} size="24" class="cursor" />
</button>
</div>
</nav>
Expand Down
12 changes: 12 additions & 0 deletions src/docs/components/Lorem.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script lang="ts">
type Props = { count?: number };
const { count = 1 }: Props = $props();
</script>

{#each Array(count) as i}
<p data-index={i}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. A ipsam tenetur accusantium impedit
beatae omnis necessitatibus. Voluptatum blanditiis libero impedit, harum eius inventore nihil,
officia voluptate dolorum error consequatur animi.
</p>
{/each}
23 changes: 23 additions & 0 deletions src/lib/common/use-context.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ContextKey } from '$lib/constants.js';
import { withPrefix } from '$lib/utils.js';
import { setContext, type Snippet } from 'svelte';
import { SvelteMap } from 'svelte/reactivity';

export const withChildrenSnippets = (key: ContextKey) => {
const map = $state(new SvelteMap<ContextKey, Snippet>());

setContext(withPrefix(key), {
register: async (child: ContextKey, snippet: Snippet) => {
if (map.has(child)) {
console.warn(`Snippet with key ${child} already exists in the context`);
return;
}

map.set(child, snippet);
},
});

return {
getChildren: (key: ContextKey) => map.get(key),
};
};
File renamed without changes.
20 changes: 0 additions & 20 deletions src/lib/components/Card.svelte

This file was deleted.

180 changes: 180 additions & 0 deletions src/lib/components/Card/Card.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<script lang="ts">
import { withChildrenSnippets } from '$lib/common/use-context.svelte.js';
import { ContextKey } from '$lib/constants.js';
import type { Color, Shape } from '$lib/types.js';
import { cleanClass } from '$lib/utils.js';
import { IconButton } from '@immich/ui';
import { mdiChevronDown } from '@mdi/js';
import { type Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
import { tv } from 'tailwind-variants';
type Props = HTMLAttributes<HTMLDivElement> & {
color?: Color;
shape?: Shape;
variant?: 'filled' | 'outline';
expandable?: boolean;
children: Snippet;
};
let {
color = 'secondary',
class: className,
expandable = false,
variant,
children,
...restProps
}: Props = $props();
const cardStyles = tv({
base: 'rounded-2xl bg-light text-dark shadow-sm dark:border-gray-600 w-full overflow-hidden',
variants: {
defaultStyle: {
true: 'border border-gray-300 dark:border-gray-600',
false: '',
default: '',
},
outlineColor: {
primary: 'border border-primary',
secondary: 'border border-gray-300 dark:border-gray-600',
success: 'border border-success',
danger: 'border border-danger',
warning: 'border border-warning',
info: 'border border-info',
},
},
});
const iconColor = tv({
variants: {
filledColor: {
primary: 'text-light',
secondary: 'text-light',
success: 'text-light',
danger: 'text-light',
warning: 'text-light',
info: 'text-light',
},
outlineColor: {
primary: 'text-primary',
secondary: 'text-dark',
success: 'text-success',
danger: 'text-danger',
warning: 'text-warning',
info: 'text-info',
},
},
});
const headerContainerStyles = tv({
variants: {
bottomPadding: {
true: '',
false: 'pb-0',
},
filledColor: {
primary: 'bg-primary text-light rounded-t-xl',
secondary: 'bg-dark text-light rounded-t-xl',
success: 'bg-success text-light rounded-t-xl',
danger: 'bg-danger text-light rounded-t-xl',
warning: 'bg-warning text-light rounded-t-xl',
info: 'bg-info text-light rounded-t-xl',
},
outlineColor: {
primary: 'text-primary',
secondary: 'text-dark',
success: 'text-success',
danger: 'text-danger',
warning: 'text-warning',
info: 'text-info',
},
},
});
let expanded = $state(!expandable);
const onToggle = () => {
expanded = !expanded;
};
const { getChildren: getChildSnippet } = withChildrenSnippets(ContextKey.Card);
const headerChildren = $derived(getChildSnippet(ContextKey.CardHeader));
const bodyChildren = $derived(getChildSnippet(ContextKey.CardBody));
const footerChildren = $derived(getChildSnippet(ContextKey.CardFooter));
const headerClasses = 'flex flex-col space-y-1.5';
const headerContainerClasses = $derived(
cleanClass(
headerContainerStyles({
bottomPadding: !expanded || !bodyChildren || variant === 'filled' || variant === 'outline',
outlineColor: variant === 'outline' ? color : undefined,
filledColor: variant === 'filled' ? color : undefined,
}),
),
);
</script>

{#snippet header()}
{#if expandable}
<button type="button" onclick={onToggle} class="w-full">
<div class={cleanClass(headerContainerClasses, 'flex items-center justify-between px-4')}>
<div class={cleanClass(headerClasses, 'py-4')}>
{@render headerChildren?.()}
</div>
<div>
<IconButton
class={iconColor({
filledColor: variant === 'filled' ? color : undefined,
outlineColor: variant === 'outline' ? color : undefined,
}) as Color}
icon={mdiChevronDown}
flopped={expanded}
variant="ghost"
shape="round"
size="large"
/>
</div>
</div>
</button>
{:else}
<div class={cleanClass(headerClasses, headerContainerClasses, 'p-4')}>
{@render headerChildren?.()}
</div>
{/if}
{/snippet}

{#snippet body()}
<div class="p-4">
{@render bodyChildren?.()}
</div>
{/snippet}

{#snippet footer()}
<div class="flex items-center p-4 pt-0">
{@render footerChildren?.()}
</div>
{/snippet}

<div
class={cleanClass(
cardStyles({
defaultStyle: variant === undefined,
outlineColor: variant === 'outline' || variant === 'filled' ? color : undefined,
}),
className,
)}
{...restProps}
>
{#if headerChildren}
{@render header()}
{/if}

{#if bodyChildren && expanded}
{@render body()}
{/if}

{#if footerChildren}
{@render footer()}
{/if}

{@render children()}
</div>
15 changes: 15 additions & 0 deletions src/lib/components/Card/CardBody.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts">
import { ContextKey } from '$lib/constants.js';
import Child from '$lib/internal/Child.svelte';
import type { Snippet } from 'svelte';
type Props = {
children: Snippet;
};
let { children }: Props = $props();
</script>

<Child for={ContextKey.Card} as={ContextKey.CardBody}>
{@render children?.()}
</Child>
File renamed without changes.
15 changes: 15 additions & 0 deletions src/lib/components/Card/CardFooter.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts">
import { ContextKey } from '$lib/constants.js';
import Child from '$lib/internal/Child.svelte';
import type { Snippet } from 'svelte';
type Props = {
children: Snippet;
};
let { children }: Props = $props();
</script>

<Child for={ContextKey.Card} as={ContextKey.CardFooter}>
{@render children?.()}
</Child>
15 changes: 15 additions & 0 deletions src/lib/components/Card/CardHeader.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts">
import { ContextKey } from '$lib/constants.js';
import Child from '$lib/internal/Child.svelte';
import type { Snippet } from 'svelte';
type Props = {
children: Snippet;
};
let { children }: Props = $props();
</script>

<Child for={ContextKey.Card} as={ContextKey.CardHeader}>
{@render children?.()}
</Child>
File renamed without changes.
16 changes: 0 additions & 16 deletions src/lib/components/CardBody.svelte

This file was deleted.

16 changes: 0 additions & 16 deletions src/lib/components/CardFooter.svelte

This file was deleted.

17 changes: 0 additions & 17 deletions src/lib/components/CardHeader.svelte

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import { Checkbox as CheckboxPrimitive, type WithoutChildrenOrChild } from 'bits-ui';
import { mdiCheck, mdiMinus } from '@mdi/js';
import Icon from '$lib/components/Icon.svelte';
import Icon from '$lib/components/Icon/Icon.svelte';
import { tv } from 'tailwind-variants';
import type { Color, Shape, Size } from '$lib/types.js';
import { cleanClass } from '$lib/utils.js';
Expand Down Expand Up @@ -84,9 +84,9 @@
{#snippet children({ checked })}
<div class={cleanClass('flex items-center justify-center text-current')}>
{#if checked === true}
<Icon path={mdiCheck} size="100%" class={cleanClass(icon({ color }))} />
<Icon icon={mdiCheck} size="100%" class={cleanClass(icon({ color }))} />
{:else if checked === 'indeterminate'}
<Icon path={mdiMinus} size="100%" class={cleanClass(icon({ color }))} />
<Icon icon={mdiMinus} size="100%" class={cleanClass(icon({ color }))} />
{/if}
</div>
{/snippet}
Expand Down
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit e2d5dac

Please sign in to comment.