Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: icon button #14

Merged
merged 1 commit into from
Nov 15, 2024
Merged
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
105 changes: 105 additions & 0 deletions src/docs/examples/IconButtonExamples.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<script lang="ts">
import { IconButton, Card, CardBody, CardHeader, CardTitle } from '@immich/ui';
import type { Color, Shape, Size } from '@immich/ui';
import { mdiMagnify } from '@mdi/js';

const colors: Color[] = ['primary', 'secondary', 'success', 'danger', 'warning', 'info'];
const sizes: Size[] = ['tiny', 'small', 'medium', 'large', 'giant'];
const shapes: Shape[] = ['rectangle', 'semi-round', 'round'];
const icon = mdiMagnify;
</script>

<div class="flex flex-col gap-4">
<Card>
<CardHeader>
<CardTitle>Shapes</CardTitle>
</CardHeader>
<CardBody>
<div class="flex flex-wrap gap-4">
{#each shapes as shape}
<IconButton {icon} {shape} />
{/each}
</div>
</CardBody>
</Card>

<Card>
<CardHeader>
<CardTitle>Colors</CardTitle>
</CardHeader>
<CardBody>
<div class="flex flex-wrap gap-4">
{#each colors as color}
<IconButton {icon} {color}></IconButton>
{/each}
</div>
</CardBody>
</Card>

<Card>
<CardHeader>
<CardTitle>Outline</CardTitle>
</CardHeader>
<CardBody>
<div class="flex flex-wrap gap-4">
{#each colors as color}
<IconButton {icon} variant="outline" {color}></IconButton>
{/each}
</div>
</CardBody>
</Card>

<Card>
<CardHeader>
<CardTitle>Ghost</CardTitle>
</CardHeader>
<CardBody>
<div class="flex flex-wrap gap-4">
{#each colors as color}
<IconButton {icon} variant="ghost" {color}></IconButton>
{/each}
</div>
</CardBody>
</Card>

<Card>
<CardHeader>
<CardTitle>Hero</CardTitle>
</CardHeader>
<CardBody>
<div class="flex flex-wrap gap-4">
{#each colors as color}
<IconButton {icon} variant="hero" {color}></IconButton>
{/each}
</div>
</CardBody>
</Card>

<Card>
<CardHeader>
<CardTitle>Disabled</CardTitle>
</CardHeader>
<CardBody>
<div class="flex flex-wrap gap-4">
{#each colors as color}
<IconButton {icon} {color} disabled></IconButton>
{/each}
</div>
</CardBody>
</Card>

<Card>
<CardHeader>
<CardTitle>Sizes</CardTitle>
</CardHeader>
<CardBody>
<div class="flex flex-wrap gap-4">
{#each sizes as size}
<div>
<IconButton {icon} {size}></IconButton>
</div>
{/each}
</div>
</CardBody>
</Card>
</div>
2 changes: 2 additions & 0 deletions src/docs/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import ButtonExamples from '$docs/examples/ButtonExamples.svelte';
import CardExamples from '$docs/examples/CardExamples.svelte';
import CheckboxExamples from '$docs/examples/CheckboxExamples.svelte';
import LogoExamples from '$docs/examples/LogoExamples.svelte';
import IconButtonExamples from '$docs/examples/IconButtonExamples.svelte';
import type { Component } from 'svelte';

type Route = {
Expand All @@ -14,5 +15,6 @@ export const routes: Route[] = [
{ name: 'Button', link: '/examples/button', component: ButtonExamples },
{ name: 'Checkbox', link: '/examples/checkbox', component: CheckboxExamples },
{ name: 'Card', link: '/examples/card', component: CardExamples },
{ name: 'IconButton', link: '/examples/icon-button', component: IconButtonExamples },
{ name: 'Logo', link: '/examples/logo', component: LogoExamples },
];
127 changes: 4 additions & 123 deletions src/lib/components/Button.svelte
Original file line number Diff line number Diff line change
@@ -1,127 +1,8 @@
<script lang="ts">
import type { Color, Shape, Size } from '$lib/types.js';
import { cleanClass } from '$lib/utils.js';
import { Button as ButtonPrimitive } from 'bits-ui';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { tv } from 'tailwind-variants';
import InternalButton from '$lib/internal/InternalButton.svelte';
import type { ButtonProps } from '$lib/types.js';

type ButtonVariant = 'filled' | 'outline' | 'ghost' | 'hero';
type ButtonProps = {
class?: string;
color?: Color;
size?: Size;
shape?: Shape;
variant?: ButtonVariant;
fullWidth?: boolean;
} & (({ href?: never } & HTMLButtonAttributes) | ({ href: string } & HTMLAnchorAttributes));

const asOverride = (variant: ButtonVariant, colors: Record<Color, string>) =>
(Object.keys(colors) as Color[]).map((color) => ({ variant, color, class: colors[color] }));

const colorPlaceholder = {
primary: '',
secondary: '',
success: '',
danger: '',
warning: '',
info: '',
};

const variantPlaceholder = {
outline: '',
filled: '',
ghost: '',
hero: '',
};

const buttonVariants = tv({
base: 'ring-offset-background focus-visible:ring-ring flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
variants: {
shape: {
rectangle: 'rounded-none',
'semi-round': 'rounded-xl',
round: 'rounded-full',
},
size: {
tiny: 'px-2 py-1 text-xs',
small: 'px-2 py-1 text-sm',
medium: 'px-4 py-2 text-md',
large: 'px-4 py-2 text-lg',
giant: 'px-8 py-4 text-xl',
},
fullWidth: {
true: 'w-full',
},
color: colorPlaceholder,
variant: variantPlaceholder,
},
compoundVariants: [
...asOverride('filled', {
primary: 'bg-primary text-light hover:bg-primary/80',
secondary: 'bg-dark text-light hover:bg-dark/80',
success: 'bg-success text-light hover:bg-success/80',
danger: 'bg-danger text-light hover:bg-danger/80',
warning: 'bg-warning text-light hover:bg-warning/80',
info: 'bg-info text-light hover:bg-info/80',
}),

...asOverride('outline', {
primary: 'bg-primary/10 text-primary border border-primary hover:bg-primary/20',
secondary: 'bg-dark/10 text-dark border border-dark hover:bg-dark/20',
success: 'bg-success/10 text-success border border-success hover:bg-success/20',
danger: 'bg-danger/10 text-danger border border-danger hover:bg-danger/20',
warning: 'bg-warning/10 text-warning border border-warning hover:bg-warning/20',
info: 'bg-info/10 text-info border border-info hover:bg-info/20',
}),

...asOverride('ghost', {
primary: 'text-primary hover:bg-primary/10',
secondary: 'text-dark hover:bg-dark/10',
success: 'text-success hover:bg-success/10',
danger: 'text-danger hover:bg-danger/10',
warning: 'text-warning hover:bg-warning/10',
info: 'text-info hover:bg-info/10',
}),

...asOverride('hero', {
primary: 'bg-gradient-to-tr from-primary to-primary/60 text-light hover:bg-primary',
secondary: 'bg-gradient-to-tr from-dark to-dark/60 text-light hover:bg-dark',
success: 'bg-gradient-to-tr from-success to-success/60 text-light hover:bg-success',
danger: 'bg-gradient-to-tr from-danger to-danger/60 text-light hover:bg-danger',
warning: 'bg-gradient-to-tr from-warning to-warning/60 text-light hover:bg-warning',
info: 'bg-gradient-to-tr from-info to-info/60 text-light hover:bg-info',
}),
],
});

const {
type = 'button',
href,
variant = 'filled',
color = 'primary',
shape = 'semi-round',
size = 'medium',
fullWidth = false,
class: className = '',
children,
...restProps
}: ButtonProps = $props();

const classList = $derived(
cleanClass(buttonVariants({ variant, color, shape, size, fullWidth }), className),
);
const props: ButtonProps = $props();
</script>

{#if href}
<a {href} class={classList} {...restProps as HTMLAnchorAttributes}>
{@render children?.()}
</a>
{:else}
<ButtonPrimitive.Root
class={classList}
type={type as HTMLButtonAttributes['type']}
{...restProps as HTMLButtonAttributes}
>
{@render children?.()}
</ButtonPrimitive.Root>
{/if}
<InternalButton {...props} />
11 changes: 11 additions & 0 deletions src/lib/components/IconButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script lang="ts">
import InternalButton from '$lib/internal/InternalButton.svelte';
import { Icon } from '$lib/components/index.js';
import type { IconButtonProps } from '$lib/types.js';

const { icon, ...buttonProps }: IconButtonProps = $props();
</script>

<InternalButton icon {...buttonProps}>
<Icon path={icon}></Icon>
</InternalButton>
1 change: 1 addition & 0 deletions src/lib/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { default as CardHeader } from '$lib/components/CardHeader.svelte';
export { default as CardTitle } from '$lib/components/CardTitle.svelte';
export { default as Checkbox } from '$lib/components/Checkbox.svelte';
export { default as Icon } from '$lib/components/Icon.svelte';
export { default as IconButton } from '$lib/components/IconButton.svelte';
export { default as Input } from '$lib/components/Input.svelte';
export { default as Label } from '$lib/components/Label.svelte';
export { default as Logo } from '$lib/components/Logo.svelte';
Loading