Skip to content

Commit

Permalink
Toggle buttons (#1138)
Browse files Browse the repository at this point in the history
* toggle buttons

* docs radio/checkbox added

* gen:xxx done

* trigger second try
  • Loading branch information
jjagielka committed Oct 30, 2023
1 parent 7990630 commit fe563e6
Show file tree
Hide file tree
Showing 9 changed files with 333 additions and 27 deletions.
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,10 @@
"types": "./dist/forms/Checkbox.svelte.d.ts",
"svelte": "./dist/forms/Checkbox.svelte"
},
"./CheckboxButton.svelte": {
"types": "./dist/forms/CheckboxButton.svelte.d.ts",
"svelte": "./dist/forms/CheckboxButton.svelte"
},
"./Dropzone.svelte": {
"types": "./dist/forms/Dropzone.svelte.d.ts",
"svelte": "./dist/forms/Dropzone.svelte"
Expand Down Expand Up @@ -366,6 +370,10 @@
"types": "./dist/forms/Radio.svelte.d.ts",
"svelte": "./dist/forms/Radio.svelte"
},
"./RadioButton.svelte": {
"types": "./dist/forms/RadioButton.svelte.d.ts",
"svelte": "./dist/forms/RadioButton.svelte"
},
"./Range.svelte": {
"types": "./dist/forms/Range.svelte.d.ts",
"svelte": "./dist/forms/Range.svelte"
Expand Down Expand Up @@ -743,4 +751,4 @@
"svelte": "./dist/video/Video.svelte"
}
}
}
}
143 changes: 118 additions & 25 deletions src/lib/buttons/Button.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<script lang="ts">
import { twMerge } from 'tailwind-merge';
import { getContext } from 'svelte';
import type { ButtonType } from '../types';
import type { SizeType } from '$lib/types';
import type { HTMLButtonAttributes } from 'svelte/elements';
type ButtonColor = keyof typeof colorClasses;
Expand All @@ -12,33 +12,52 @@
export let outline: boolean = false;
export let size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' = group ? 'sm' : 'md';
export let href: string | undefined = undefined;
export let type: ButtonType = 'button';
export let type: HTMLButtonAttributes['type'] = 'button';
export let color: ButtonColor = group ? (outline ? 'dark' : 'alternative') : 'primary';
export let shadow: boolean = false;
export let tag: string = 'button';
export let checked: boolean | undefined = undefined;
const colorClasses = {
alternative: 'text-gray-900 bg-white border border-gray-200 hover:bg-gray-100 dark:bg-gray-800 dark:text-gray-400 hover:text-primary-700 focus:text-primary-700 dark:focus:text-white dark:hover:text-white',
alternative:
'text-gray-900 bg-white border border-gray-200 hover:bg-gray-100 dark:bg-gray-800 dark:text-gray-400 hover:text-primary-700 focus-within:text-primary-700 dark:focus-within:text-white dark:hover:text-white',
blue: 'text-white bg-blue-700 hover:bg-blue-800 dark:bg-blue-600 dark:hover:bg-blue-700',
dark: 'text-white bg-gray-800 hover:bg-gray-900 dark:bg-gray-800 dark:hover:bg-gray-700',
green: 'text-white bg-green-700 hover:bg-green-800 dark:bg-green-600 dark:hover:bg-green-700',
light: 'text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600',
light:
'text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600',
primary: 'text-white bg-primary-700 hover:bg-primary-800 dark:bg-primary-600 dark:hover:bg-primary-700',
purple: 'text-white bg-purple-700 hover:bg-purple-800 dark:bg-purple-600 dark:hover:bg-purple-700',
red: 'text-white bg-red-700 hover:bg-red-800 dark:bg-red-600 dark:hover:bg-red-700',
yellow: 'text-white bg-yellow-400 hover:bg-yellow-500 ',
none: ''
};
const colorCheckedClasses = {
alternative:
'text-primary-700 border dark:text-primary-500 bg-gray-100 dark:bg-gray-700 border-gray-300 shadow-gray-300 dark:shadow-gray-800 shadow-inner',
blue: 'text-blue-900 bg-blue-400 dark:bg-blue-500 shadow-blue-700 dark:shadow-blue-800 shadow-inner',
dark: 'text-white bg-gray-500 dark:bg-gray-600 shadow-gray-800 dark:shadow-gray-900 shadow-inner',
green: 'text-green-900 bg-green-400 dark:bg-green-500 shadow-green-700 dark:shadow-green-800 shadow-inner',
light:
'text-gray-900 bg-gray-100 border border-gray-300 dark:bg-gray-500 dark:text-gray-900 dark:border-gray-700 shadow-gray-300 dark:shadow-gray-700 shadow-inner',
primary: 'text-primary-900 bg-primary-400 dark:bg-primary-500 shadow-primary-700 dark:shadow-primary-800 shadow-inner',
purple: 'text-purple-900 bg-purple-400 dark:bg-purple-500 shadow-purple-700 dark:shadow-purple-800 shadow-inner',
red: 'text-red-900 bg-red-400 dark:bg-red-500 shadow-red-700 dark:shadow-red-800 shadow-inner',
yellow: 'text-yellow-900 bg-yellow-300 dark:bg-yellow-400 shadow-yellow-500 dark:shadow-yellow-700 shadow-inner',
none: ''
};
const coloredFocusClasses = {
alternative: 'focus:ring-gray-200 dark:focus:ring-gray-700',
blue: 'focus:ring-blue-300 dark:focus:ring-blue-800',
dark: 'focus:ring-gray-300 dark:focus:ring-gray-700',
green: 'focus:ring-green-300 dark:focus:ring-green-800',
light: 'focus:ring-gray-200 dark:focus:ring-gray-700',
primary: 'focus:ring-primary-300 dark:focus:ring-primary-800',
purple: 'focus:ring-purple-300 dark:focus:ring-purple-900',
red: 'focus:ring-red-300 dark:focus:ring-red-900',
yellow: 'focus:ring-yellow-300 dark:focus:ring-yellow-900',
alternative: 'focus-within:ring-gray-200 dark:focus-within:ring-gray-700',
blue: 'focus-within:ring-blue-300 dark:focus-within:ring-blue-800',
dark: 'focus-within:ring-gray-300 dark:focus-within:ring-gray-700',
green: 'focus-within:ring-green-300 dark:focus-within:ring-green-800',
light: 'focus-within:ring-gray-200 dark:focus-within:ring-gray-700',
primary: 'focus-within:ring-primary-300 dark:focus-within:ring-primary-800',
purple: 'focus-within:ring-purple-300 dark:focus-within:ring-purple-900',
red: 'focus-within:ring-red-300 dark:focus-within:ring-red-900',
yellow: 'focus-within:ring-yellow-300 dark:focus-within:ring-yellow-900',
none: ''
};
Expand All @@ -56,15 +75,21 @@
};
const outlineClasses = {
alternative: 'text-gray-900 hover:text-white border border-gray-800 hover:bg-gray-900 focus:bg-gray-900 focus:text-white focus:ring-gray-300 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-800',
alternative:
'text-gray-900 dark:text-gray-400 hover:text-white border border-gray-800 hover:bg-gray-900 focus-within:bg-gray-900 focus-within:text-white focus-within:ring-gray-300 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-600 dark:focus-within:ring-gray-800',
blue: 'text-blue-700 hover:text-white border border-blue-700 hover:bg-blue-800 dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-600',
dark: 'text-gray-900 hover:text-white border border-gray-800 hover:bg-gray-900 focus:bg-gray-900 focus:text-white dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-600',
green: 'text-green-700 hover:text-white border border-green-700 hover:bg-green-800 dark:border-green-500 dark:text-green-500 dark:hover:text-white dark:hover:bg-green-600',
light: 'text-gray-500 hover:text-gray-900 bg-white border border-gray-200 dark:border-gray-600 dark:hover:text-white dark:text-gray-400 hover:bg-gray-50 dark:bg-gray-700 dark:hover:bg-gray-600',
primary: 'text-primary-700 hover:text-white border border-primary-700 hover:bg-primary-700 dark:border-primary-500 dark:text-primary-500 dark:hover:text-white dark:hover:bg-primary-600',
purple: 'text-purple-700 hover:text-white border border-purple-700 hover:bg-purple-800 dark:border-purple-400 dark:text-purple-400 dark:hover:text-white dark:hover:bg-purple-500',
dark: 'text-gray-900 hover:text-white border border-gray-800 hover:bg-gray-900 focus-within:bg-gray-900 focus-within:text-white dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-600',
green:
'text-green-700 hover:text-white border border-green-700 hover:bg-green-800 dark:border-green-500 dark:text-green-500 dark:hover:text-white dark:hover:bg-green-600',
light:
'text-gray-500 hover:text-gray-900 bg-white border border-gray-200 dark:border-gray-600 dark:hover:text-white dark:text-gray-400 hover:bg-gray-50 dark:bg-gray-700 dark:hover:bg-gray-600',
primary:
'text-primary-700 hover:text-white border border-primary-700 hover:bg-primary-700 dark:border-primary-500 dark:text-primary-500 dark:hover:text-white dark:hover:bg-primary-600',
purple:
'text-purple-700 hover:text-white border border-purple-700 hover:bg-purple-800 dark:border-purple-400 dark:text-purple-400 dark:hover:text-white dark:hover:bg-purple-500',
red: 'text-red-700 hover:text-white border border-red-700 hover:bg-red-800 dark:border-red-500 dark:text-red-500 dark:hover:text-white dark:hover:bg-red-600',
yellow: 'text-yellow-400 hover:text-white border border-yellow-400 hover:bg-yellow-500 dark:border-yellow-300 dark:text-yellow-300 dark:hover:text-white dark:hover:bg-yellow-400',
yellow:
'text-yellow-400 hover:text-white border border-yellow-400 hover:bg-yellow-500 dark:border-yellow-300 dark:text-yellow-300 dark:hover:text-white dark:hover:bg-yellow-400',
none: ''
};
Expand All @@ -79,12 +104,78 @@
const hasBorder = () => outline || color === 'alternative' || color === 'light';
let buttonClass: string;
$: buttonClass = twMerge('text-center font-medium', group ? 'focus:ring-2' : 'focus:ring-4', group && 'focus:z-10', group || 'focus:outline-none', 'inline-flex items-center justify-center ' + sizeClasses[size], outline ? outlineClasses[color] : colorClasses[color], color === 'alternative' && (group ? 'dark:bg-gray-700 dark:text-white dark:border-gray-700 dark:hover:border-gray-600 dark:hover:bg-gray-600' : 'dark:bg-transparent dark:border-gray-600 dark:hover:border-gray-700'), outline && color === 'dark' && (group ? 'dark:text-white dark:border-white' : 'dark:text-gray-400 dark:border-gray-700'), coloredFocusClasses[color], hasBorder() && group && 'border-l-0 first:border-l', group ? (pill && 'first:rounded-l-full last:rounded-r-full') || 'first:rounded-l-lg last:rounded-r-lg' : (pill && 'rounded-full') || 'rounded-lg', shadow && 'shadow-lg', shadow && coloredShadowClasses[color], $$props.disabled && 'cursor-not-allowed opacity-50', $$props.class);
$: buttonClass = twMerge(
'text-center font-medium',
group ? 'focus-within:ring-2' : 'focus-within:ring-4',
group && 'focus-within:z-10',
group || 'focus-within:outline-none',
'inline-flex items-center justify-center ' + sizeClasses[size],
outline && checked && 'border dark:border-gray-900',
outline && checked && colorCheckedClasses[color],
outline && !checked && outlineClasses[color],
!outline && checked && colorCheckedClasses[color],
!outline && !checked && colorClasses[color],
color === 'alternative' &&
(group && !checked
? 'dark:bg-gray-700 dark:text-white dark:border-gray-700 dark:hover:border-gray-600 dark:hover:bg-gray-600'
: 'dark:bg-transparent dark:border-gray-600 dark:hover:border-gray-700'),
outline &&
color === 'dark' &&
(group
? checked
? 'bg-gray-900 border-gray-800 dark:border-white dark:bg-gray-600'
: 'dark:text-white border-gray-800 dark:border-white'
: 'dark:text-gray-400 dark:border-gray-700'),
coloredFocusClasses[color],
hasBorder() && group && 'border-l-0 first:border-l',
group
? (pill && 'first:rounded-l-full last:rounded-r-full') || 'first:rounded-l-lg last:rounded-r-lg'
: (pill && 'rounded-full') || 'rounded-lg',
shadow && 'shadow-lg',
shadow && coloredShadowClasses[color],
$$props.disabled && 'cursor-not-allowed opacity-50',
$$props.class
);
</script>

<svelte:element this={href ? 'a' : 'button'} type={href ? undefined : type} {href} role={href ? 'link' : 'button'} {...$$restProps} class={buttonClass} on:click on:change on:keydown on:keyup on:touchstart|passive on:touchend on:touchcancel on:mouseenter on:mouseleave>
<slot />
</svelte:element>
{#if href}
<a
{href}
{...$$restProps}
class={buttonClass}
role="button"
on:click
on:change
on:keydown
on:keyup
on:touchstart|passive
on:touchend
on:touchcancel
on:mouseenter
on:mouseleave>
<slot />
</a>
{:else if tag === 'button'}
<button
{type}
{...$$restProps}
class={buttonClass}
on:click
on:change
on:keydown
on:keyup
on:touchstart|passive
on:touchend
on:touchcancel
on:mouseenter
on:mouseleave>
<slot />
</button>
{:else}
<svelte:element this={tag} {...$$restProps} class={buttonClass}>
<slot />
</svelte:element>
{/if}

<!--
@component
Expand All @@ -94,7 +185,9 @@
@prop export let outline: boolean = false;
@prop export let size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' = group ? 'sm' : 'md';
@prop export let href: string | undefined = undefined;
@prop export let type: ButtonType = 'button';
@prop export let type: HTMLButtonAttributes['type'] = 'button';
@prop export let color: ButtonColor = group ? (outline ? 'dark' : 'alternative') : 'primary';
@prop export let shadow: boolean = false;
@prop export let tag: string = 'button';
@prop export let checked: boolean | undefined = undefined;
-->
91 changes: 91 additions & 0 deletions src/lib/forms/CheckboxButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<script lang="ts">
import Button from '$lib/buttons/Button.svelte';
import type { ButtonColorType, SizeType } from '$lib/types';
import { twMerge } from 'tailwind-merge';
export let group: (string | number)[] = [];
export let value: string | number = 'on';
export let checked: boolean | undefined = undefined;
export let inline: boolean = true;
// Button properties forwarding
export let pill: boolean = false;
export let outline: boolean = false;
export let size: SizeType | undefined = undefined;
export let color: ButtonColorType | undefined = undefined;
export let shadow: boolean = false;
// react on external group changes
function init(_: HTMLElement, _group: (string | number)[]) {
if (checked === undefined) checked = _group.includes(value);
onChange();
return {
update(_group: (string | number)[]) {
checked = _group.includes(value);
}
};
}
function onChange() {
// There's a bug in Svelte and bind:group is not working with wrapped checkbox
// This workaround is taken from:
// https://svelte.dev/repl/de117399559f4e7e9e14e2fc9ab243cc?version=3.12.1
const index = group.indexOf(value);
if (checked === undefined) checked = index >= 0;
if (checked) {
if (index < 0) {
group.push(value);
group = group;
}
} else {
if (index >= 0) {
group.splice(index, 1);
group = group;
}
}
}
let buttonClass: string;
$: buttonClass = twMerge(inline ? 'inline-flex' : 'flex', $$props.class);
</script>

<Button tag="label" {checked} {pill} {outline} {size} {color} {shadow} class={buttonClass}>
<input
use:init={group}
type="checkbox"
bind:checked
{value}
{...$$restProps}
class="sr-only"
on:keyup
on:keydown
on:keypress
on:focus
on:blur
on:click
on:mouseover
on:mouseenter
on:mouseleave
on:paste
on:change={onChange}
on:change />
<slot />
</Button>

<!--
@component
[Go to docs](https://flowbite-svelte.com/)
## Props
@prop export let group: (string | number)[] = [];
@prop export let value: string | number = 'on';
@prop export let checked: boolean | undefined = undefined;
@prop export let inline: boolean = true;
@prop export let pill: boolean = false;
@prop export let outline: boolean = false;
@prop export let size: SizeType | undefined = undefined;
@prop export let color: ButtonColorType | undefined = undefined;
@prop export let shadow: boolean = false;
-->
54 changes: 54 additions & 0 deletions src/lib/forms/RadioButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<script lang="ts">
import Button from '$lib/buttons/Button.svelte';
import type { ButtonColorType, SizeType } from '$lib/types';
import { twMerge } from 'tailwind-merge';
export let group: string | number = '';
export let value: string | number = '';
export let inline: boolean = true;
// Button properties forwarding
export let pill: boolean = false;
export let outline: boolean = false;
export let size: SizeType | undefined = undefined;
export let color: ButtonColorType | undefined = undefined;
export let shadow: boolean = false;
let buttonClass: string;
$: buttonClass = twMerge(inline ? 'inline-flex' : 'flex', $$props.class);
</script>

<Button tag="label" checked={value === group} {pill} {outline} {size} {color} {shadow} class={buttonClass}>
<input
type="radio"
bind:group
{value}
{...$$restProps}
class="sr-only"
on:keyup
on:keydown
on:keypress
on:focus
on:blur
on:click
on:mouseover
on:mouseenter
on:mouseleave
on:paste
on:change />
<slot />
</Button>

<!--
@component
[Go to docs](https://flowbite-svelte.com/)
## Props
@prop export let group: string | number = '';
@prop export let value: string | number = '';
@prop export let inline: boolean = true;
@prop export let pill: boolean = false;
@prop export let outline: boolean = false;
@prop export let size: SizeType | undefined = undefined;
@prop export let color: ButtonColorType | undefined = undefined;
@prop export let shadow: boolean = false;
-->
2 changes: 2 additions & 0 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export { default as FooterLinkGroup } from './footer/FooterLinkGroup.svelte';

// Forms
export { default as Checkbox } from './forms/Checkbox.svelte';
export { default as CheckboxButton } from './forms/CheckboxButton.svelte';
export { default as Dropzone } from './forms/Dropzone.svelte';
export { default as Fileupload } from './forms/Fileupload.svelte';
export { default as FloatingLabelInput } from './forms/FloatingLabelInput.svelte';
Expand All @@ -79,6 +80,7 @@ export { default as Label } from './forms/Label.svelte';
export { default as MultiSelect } from './forms/MultiSelect.svelte';
export { default as NumberInput } from './forms/NumberInput.svelte';
export { default as Radio } from './forms/Radio.svelte';
export { default as RadioButton } from './forms/RadioButton.svelte';
export { default as Range } from './forms/Range.svelte';
export { default as Search } from './forms/Search.svelte';
export { default as Select } from './forms/Select.svelte';
Expand Down
1 change: 1 addition & 0 deletions src/routes/component-data/CheckboxButton.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"CheckboxButton","slots":[],"events":["on:keyup","on:keydown","on:keypress","on:focus","on:blur","on:click","on:mouseover","on:mouseenter","on:mouseleave","on:paste","on:change","on:change"],"props":[["group","(string | number)[]","[]"],["value","string | number","'on'"],["checked","boolean | undefined","undefined"],["inline","boolean","true"],["pill","boolean","false"],["outline","boolean","false"],["size","SizeType | undefined","undefined"],["color","ButtonColorType | undefined","undefined"],["shadow","boolean","false"]]}
1 change: 1 addition & 0 deletions src/routes/component-data/RadioButton.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"RadioButton","slots":[],"events":["on:keyup","on:keydown","on:keypress","on:focus","on:blur","on:click","on:mouseover","on:mouseenter","on:mouseleave","on:paste","on:change"],"props":[["group","string | number","''"],["value","string | number","''"],["inline","boolean","true"],["pill","boolean","false"],["outline","boolean","false"],["size","SizeType | undefined","undefined"],["color","ButtonColorType | undefined","undefined"],["shadow","boolean","false"]]}
Loading

2 comments on commit fe563e6

@vercel
Copy link

@vercel vercel bot commented on fe563e6 Oct 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on fe563e6 Oct 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.