Skip to content

Commit

Permalink
feat(frontend): Button with popup (#639)
Browse files Browse the repository at this point in the history
* feat(frontend): Add ButtonPopup component
  • Loading branch information
adam-kov committed Oct 6, 2022
1 parent 1b2994a commit fcb1c39
Show file tree
Hide file tree
Showing 16 changed files with 611 additions and 130 deletions.
142 changes: 52 additions & 90 deletions frontend/src/lib/components/common/button/Button.svelte
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { goto } from '$app/navigation'
import { classNames } from '$lib/utils'
import Icon from 'svelte-awesome'
import type { Button } from './model'
import { ButtonType } from './model'
export let size: Button.Size = 'md'
export let spacingSize: Button.Size = size
export let color: Button.Color = 'blue'
export let variant: Button.Variant = 'contained'
export let size: ButtonType.Size = 'md'
export let spacingSize: ButtonType.Size = size
export let color: ButtonType.Color = 'blue'
export let variant: ButtonType.Variant = 'contained'
export let btnClasses: string = ''
export let disabled: boolean = false
export let href: string | undefined = undefined
export let target: Button.Target = '_self'
export let target: ButtonType.Target = '_self'
export let iconOnly: boolean = false
export let startIcon: ButtonType.Icon | undefined = undefined
export let endIcon: ButtonType.Icon | undefined = undefined
export let element: ButtonType.Element | undefined = undefined
export let id: string = ''
export let startIcon: { icon: any; classes?: string } | undefined = undefined
export let endIcon: { icon: any; classes?: string } | undefined = undefined
const dispatch = createEventDispatcher()
// Order of classes: border, border modifier, bg, bg modifier, text, text modifier, everything else
const colorVariants: Record<Button.Color, Record<Button.Variant, string>> = {
const colorVariants: Record<ButtonType.Color, Record<ButtonType.Variant, string>> = {
blue: {
border:
'border-blue-500 hover:border-blue-700 bg-white hover:bg-blue-100 text-blue-500 hover:text-blue-700 focus:ring-blue-300',
contained: 'bg-blue-500 hover:bg-blue-700 text-white focus:ring-blue-300'
'border-blue-500 hover:border-blue-700 focus:border-blue-700 bg-white hover:bg-blue-100 focus:bg-blue-100 text-blue-500 hover:text-blue-700 focus:text-blue-700 focus:ring-blue-300',
contained: 'bg-blue-500 hover:bg-blue-700 focus:bg-blue-700 text-white focus:ring-blue-300'
},
red: {
border:
Expand All @@ -32,102 +34,62 @@
},
dark: {
border:
'border-gray-800 hover:border-gray-900 bg-white hover:bg-gray-200 text-gray-800 hover:text-gray-900 focus:ring-gray-300',
contained: 'bg-gray-700 hover:bg-gray-900 text-white focus:ring-gray-300'
'border-gray-800 hover:border-gray-900 focus:border-gray-900 bg-white hover:bg-gray-200 focus:bg-gray-200 text-gray-800 hover:text-gray-900 focus:text-gray-900 focus:ring-gray-300',
contained: 'bg-gray-700 hover:bg-gray-900 focus:bg-gray-900 text-white focus:ring-gray-300'
},
light: {
border:
'border bg-white hover:bg-gray-100 text-gray-700 hover:text-gray-800 focus:ring-gray-300',
contained: 'bg-white hover:bg-gray-100 text-gray-700 focus:ring-gray-300'
'border bg-white hover:bg-gray-100 focus:bg-gray-100 text-gray-700 hover:text-gray-800 focus:text-gray-800 focus:ring-gray-300',
contained: 'bg-white hover:bg-gray-100 focus:bg-gray-100 text-gray-700 focus:ring-gray-300'
}
}
const fontSizeClasses: Record<Button.Size, string> = {
xs: 'text-xs',
sm: 'text-sm',
md: 'text-md',
lg: 'text-lg',
xl: 'text-xl'
}
const spacingClasses: Record<Button.Size, string> = {
xs: 'px-3 py-1.5',
sm: 'px-3 py-1.5',
md: 'px-4 py-2',
lg: 'px-4 py-2',
xl: 'px-4 py-2'
}
const iconScale: Record<Button.Size, number> = {
xs: 0.6,
sm: 0.8,
md: 1,
lg: 1.1,
xl: 1.2
}
$: buttonProps = {
class: classNames(
colorVariants[color][variant],
variant === 'border' ? 'border' : '',
fontSizeClasses[size],
spacingClasses[spacingSize],
ButtonType.FontSizeClasses[size],
ButtonType.SpacingClasses[spacingSize],
'focus:ring-4 font-medium',
'rounded-md',
'flex justify-center items-center text-center whitespace-nowrap',
btnClasses,
disabled ? 'pointer-events-none cursor-default filter grayscale' : ''
),
disabled
disabled,
href,
target,
tabindex: disabled ? -1 : 0
}
function onClick(event: MouseEvent) {
dispatch('click', event)
if (href) goto(href)
}
$: isSmall = size === 'xs' || size === 'sm'
$: startIconClass = classNames(
iconOnly ? undefined : isSmall ? 'mr-1' : 'mr-2',
startIcon?.classes
)
$: endIconClass = classNames(iconOnly ? undefined : isSmall ? 'ml-1' : 'ml-2', endIcon?.classes)
</script>

{#if href}
<button
{id}
type="button"
on:click|stopPropagation={() => goto(href ?? '#')}
{target}
tabindex={disabled ? -1 : 0}
{...buttonProps}
>
{#if startIcon}
<Icon
data={startIcon.icon}
class={classNames(iconOnly ? undefined : 'mr-2', startIcon.classes)}
scale={iconScale[size]}
/>
{/if}
{#if !iconOnly}
<slot />
{/if}
{#if endIcon}
<Icon
data={endIcon.icon}
class={classNames(iconOnly ? undefined : 'ml-2', endIcon.classes)}
scale={iconScale[size]}
/>
{/if}
</button>
{:else}
<button {id} type="button" on:click|stopPropagation {...buttonProps} {...$$restProps}>
{#if startIcon}
<Icon
data={startIcon.icon}
class={classNames(iconOnly ? undefined : 'mr-2', startIcon.classes)}
scale={iconScale[size]}
/>
{/if}
{#if !iconOnly}
<slot />
{/if}
{#if endIcon}
<Icon
data={endIcon.icon}
class={classNames(iconOnly ? undefined : 'ml-2', endIcon.classes)}
scale={iconScale[size]}
/>
{/if}
</button>
{/if}
<svelte:element
this={href ? 'a' : 'button'}
bind:this={element}
on:click|stopPropagation={onClick}
on:focus
on:blur
{...buttonProps}
>
{#if startIcon}
<Icon data={startIcon.icon} class={startIconClass} scale={ButtonType.IconScale[size]} />
{/if}
{#if !iconOnly}
<slot />
{/if}
{#if endIcon}
<Icon data={endIcon.icon} class={endIconClass} scale={ButtonType.IconScale[size]} />
{/if}
</svelte:element>
72 changes: 72 additions & 0 deletions frontend/src/lib/components/common/button/ButtonPopup.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<script lang="ts">
import { faChevronDown } from '@fortawesome/free-solid-svg-icons'
import { setContext } from 'svelte'
import Icon from 'svelte-awesome'
import { Button, ButtonType, Popup } from '..'
export let size: ButtonType.Size = 'md'
export let color: ButtonType.Color = 'blue'
export let variant: ButtonType.Variant = 'contained'
export let mainClasses: string = ''
export let toggleClasses: string = ''
export let disabled: boolean = false
export let href: string | undefined = undefined
export let target: ButtonType.Target = '_self'
export let startIcon: ButtonType.Icon | undefined = undefined
export let endIcon: ButtonType.Icon | undefined = undefined
let ref: ButtonType.Element
setContext<ButtonType.ItemProps>(ButtonType.ItemContextKey, { size, color })
$: separator = color === 'red' || color === 'blue' ? 'border-gray-200' : 'border-gray-400'
$: commonProps = {
size,
color,
variant,
disabled
}
</script>

<div class="flex justy-start items-center">
{#if $$slots.main}
<Button
{...commonProps}
{href}
{target}
{startIcon}
{endIcon}
btnClasses="!rounded-r-none !border-r-0 {mainClasses}"
on:click
>
<slot name="main" />
</Button>
{/if}
<span class={$$slots.main && variant === 'contained' ? 'border-l ' + separator : ''}>
<Button
bind:element={ref}
{...commonProps}
btnClasses="{$$slots.main ? '!rounded-l-none' : ''} {toggleClasses}"
>
<slot name="toggle">
<!-- Invisible, but needed to match the height of the 'main' button -->
<span class="!opacity-0 !w-0">A</span>
<Icon data={faChevronDown} scale={ButtonType.IconScale[size]} />
</slot>
</Button>
</span>
</div>
{#if ref}
<Popup
{ref}
options={{
placement: $$slots.main ? 'bottom-end' : 'bottom',
strategy: 'absolute',
modifiers: [{ name: 'offset', options: { offset: [0, 0] } }]
}}
>
<ul class="bg-white rounded-t border pt-1 pb-2 max-h-40 overflow-auto">
<slot />
</ul>
</Popup>
{/if}
52 changes: 52 additions & 0 deletions frontend/src/lib/components/common/button/ButtonPopupItem.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<script lang="ts">
import { getContext } from 'svelte'
import { Button, ButtonType } from '..'
import { classNames } from '../../../utils'
export let btnClasses: string = ''
export let disabled: boolean = false
export let href: string | undefined = undefined
export let target: ButtonType.Target = '_self'
export let iconOnly: boolean = false
export let startIcon: ButtonType.Icon | undefined = undefined
export let endIcon: ButtonType.Icon | undefined = undefined
const props = getContext<ButtonType.ItemProps | undefined>(ButtonType.ItemContextKey)
const iconWidthClass: Record<ButtonType.Size, string> = {
xs: '!w-[12px]',
sm: '!w-[14px]',
md: '!w-[16px]',
lg: '!w-[18px]',
xl: '!w-[20px]'
}
const getWidthClass = () => (props?.size ? iconWidthClass[props.size] : undefined)
$: buttonProps = {
...props,
variant: <ButtonType.Variant>'border',
btnClasses: classNames(btnClasses, '!justify-start !border-0 !rounded-none !w-full'),
disabled,
href,
target,
iconOnly,
startIcon: startIcon
? {
icon: startIcon.icon,
classes: classNames(startIcon?.classes, getWidthClass())
}
: undefined,
endIcon: endIcon
? {
icon: endIcon.icon,
classes: classNames(endIcon?.classes, getWidthClass())
}
: undefined
}
</script>

<li class="mt-1">
<Button {...buttonProps} on:click>
<slot />
</Button>
</li>
40 changes: 39 additions & 1 deletion frontend/src/lib/components/common/button/model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,44 @@
export namespace Button {
export namespace ButtonType {
export type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
export type Color = 'blue' | 'red' | 'dark' | 'light'
export type Variant = 'contained' | 'border'
export type Target = '_self' | '_blank'
export type Element = HTMLButtonElement | HTMLAnchorElement
export interface Icon {
icon: any
classes?: string
}

export const FontSizeClasses: Record<ButtonType.Size, string> = {
xs: 'text-xs',
sm: 'text-sm',
md: 'text-md',
lg: 'text-lg',
xl: 'text-xl'
} as const

export const SpacingClasses: Record<ButtonType.Size, string> = {
xs: 'px-3 py-1.5',
sm: 'px-3 py-1.5',
md: 'px-4 py-2',
lg: 'px-4 py-2',
xl: 'px-4 py-2'
} as const

export const IconScale: Record<ButtonType.Size, number> = {
xs: 0.7,
sm: 0.8,
md: 1,
lg: 1.1,
xl: 1.2
} as const

// ButtonPopup types

export const ItemContextKey = 'popupItemProps' as const

export interface ItemProps {
size: Size
color: Color
}
}
2 changes: 2 additions & 0 deletions frontend/src/lib/components/common/drawer/Drawer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
open = !open
}
$: open ? dispatch('open') : dispatch('close')
onMount(() => {
mounted = true
scrollLock(open)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
const dispatch = createEventDispatcher()
</script>

<div class="divide-y h-screen">
<div class="flex flex-col divide-y h-screen">
<div class="flex justify-between items-center py-2 px-4">
<span class="text-sm font-bold">{title}</span>
<button on:click={() => dispatch('close')}>
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/lib/components/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
export { default as ActionRow } from './actionRow/ActionRow.svelte'
export { default as Badge } from './badge/Badge.svelte'
export { default as Button } from './button/Button.svelte'
export { default as ButtonPopup } from './button/ButtonPopup.svelte'
export { default as ButtonPopupItem } from './button/ButtonPopupItem.svelte'
export { default as Drawer } from './drawer/Drawer.svelte'
export { default as DrawerContent } from './drawer/DrawerContent.svelte'
export { default as Kbd } from './kbd/Kbd.svelte'
export { default as Menu } from './menu/Menu.svelte'
export { default as MenuItem } from './menu/MenuItem.svelte'
export { default as Popup } from './popup/Popup.svelte'
export { default as Tab } from './tabs/Tab.svelte'
export { default as TabContent } from './tabs/TabContent.svelte'
export { default as Tabs } from './tabs/Tabs.svelte'
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/lib/components/common/kbd/Kbd.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<kbd
class="mx-1 px-2 py-1.5 text-xs font-semibold text-gray-800
bg-gray-100 border border-gray-200 rounded-lg
dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500 {$$props.class}"
>
<slot />
</kbd>

0 comments on commit fcb1c39

Please sign in to comment.