Skip to content

Commit

Permalink
[feat](@svelteui/core): add slots for all components with icons
Browse files Browse the repository at this point in the history
  • Loading branch information
BeeMargarida committed Jul 28, 2023
1 parent 718a1f9 commit ef64c79
Show file tree
Hide file tree
Showing 23 changed files with 178 additions and 39 deletions.
2 changes: 2 additions & 0 deletions apps/docs/src/routes/core/alert/+page.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ docs: 'core/alert'

<Demo demo={AlertDemos.configurator} />

The icon can also be passed by using the slot `icon`.

## Accessibility

- Root element role set to `alert`
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/src/routes/core/blockquote/+page.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ To render a custom icon, use the `icon` prop:
Set `icon` to `null` to show no icon:

<Demo demo={BlockquoteDemos.noicon} />

The icon can also be set by using the `icon` slot.
2 changes: 1 addition & 1 deletion apps/docs/src/routes/core/input/+page.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Input has 3 variants, all of which are available on all SvelteUI inputs. Note th

## Icon and right section

The Input component has two ways to render an Icon. The left Icon is passed in as a prop, and it is any valid Svelte Component. The right Icon is passed in through a named slot.
The Input component has two ways to render an Icon. The left icon is passed in as the prop `icon` or as a slot called `icon`, and it is any valid Svelte Component. The right section is passed in through a named slot called `rightSection`.

<Demo demo={InputDemos.sections} />

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/routes/core/menu/+page.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ In this case, menu will use `mouseenter` and `focus` events instead of `click`:
- `disabled` – disables item
- `...others``Menu.Item` produces a button element, all other props will be spread to it

The right section of `Menu.Item` can be customized with the slot `rightSection`.
The right section of `Menu.Item` can be customized with the slot `rightSection`. The icon can also be set by using the `icon` slot.

```svelte
<script lang="ts">
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/src/routes/core/native-select/+page.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Just like with regular inputs you may bind to the value for two way data binding

## With icon

The icon can also be set by using the `icon` slot.

<Demo demo={NativeSelectDemos.icon} />

## Right section
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/src/routes/core/notification/+page.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ It has 3 variants:
- **icon** – line is replaced with icon
- **loading** – icon or line are replaced with [Loader]({base}/core/loader)

The icon can also be set by using the `icon` slot.

<Demo demo={NotificationDemos.usage} />

## Colors and state
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/src/routes/core/number-input/+page.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ NumberInput exposes increment/decrement functions that allow external controls t

## With icon

The icon can also be set by using the `icon` slot.

<Demo demo={NumberInputDemos.icon} />

## Accessibility
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/src/routes/core/text-input/+page.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ docs: 'core/text-input'

## With icon

The icon can also be set by using the `icon` slot.

<Demo demo={TextInputDemos.icon} />

## With right section
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ Item of an accordion.
class={classes.chevron}
data-rotate={!$ctx.disableChevronRotation && $ctx.isItemActive(value)}
>
<svelte:component this={chevron || $ctx.chevron} />
<slot name="chevron">
<svelte:component this={chevron || $ctx.chevron} />
</slot>
</span>
<span class={classes.controlContent}>
<slot name="control" {disabled} />
Expand Down
8 changes: 5 additions & 3 deletions packages/svelteui-core/src/components/Alert/Alert.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@

<Box {use} bind:element role="alert" class={cx(className, variant, classes.root)} {...$$restProps}>
<div class={classes.wrapper}>
{#if icon}
<IconRenderer {icon} className={classes.icon} {iconSize} {iconProps} />
{/if}
<slot name="icon">
{#if icon}
<IconRenderer {icon} className={classes.icon} {iconSize} {iconProps} />
{/if}
</slot>

<div class={classes.content}>
{#if title}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ Blockquote with icon and citation

<Box bind:element class={cx(className, classes.root)} {root} {...$$restProps}>
<div class={classes.inner}>
{#if icon}
<div class={classes.icon}>
<IconRenderer {icon} {iconSize} />
</div>
{/if}
<slot name="icon">
{#if icon}
<div class={classes.icon}>
<IconRenderer {icon} {iconSize} />
</div>
{/if}
</slot>
<div class={classes.body}>
<slot />
{#if $$slots.cite}
Expand Down
16 changes: 12 additions & 4 deletions packages/svelteui-core/src/components/FileUpload/FileUpload.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,17 @@
fileUploadComponent.click();
}}
>
<IconRenderer slot="leftIcon" {icon} />
<slot name="leftIcon">
<IconRenderer {icon} />
</slot>

{label}
</Button>
{#if reset}
<Button {size} color={resetColor} disabled={files.length == 0} on:click={resetFiles}>
<IconRenderer slot="leftIcon" icon={resetIcon} />
<slot name="leftIcon">
<IconRenderer icon={resetIcon} />
</slot>
{resetLabel}
</Button>
{/if}
Expand All @@ -150,15 +154,19 @@
{#each files as { file }, i}
<div class={classes.fileItemWrapper}>
<div class={classes.fileItemIcon}>
<IconRenderer iconSize={fontSizes[size] * 1.8} icon={fileIcon} />
<slot name="fileIcon">
<IconRenderer iconSize={fontSizes[size] * 1.8} icon={fileIcon} />
</slot>
</div>
<span class={classes.fileItemName}>
{file.name}
</span>
<span class={classes.fileItemAction}>
<span>
<Button variant="default" {size} on:click={() => remove(i)}>
<IconRenderer iconSize={fontSizes[size] * 1.5} icon={removeIcon} />
<slot name="removeIcon">
<IconRenderer iconSize={fontSizes[size] * 1.5} icon={removeIcon} />
</slot>
</Button>
</span>
</span>
Expand Down
33 changes: 33 additions & 0 deletions packages/svelteui-core/src/components/Image/Image.stories.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<script lang="ts">
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
import { Image } from './index';
import { Center } from '../Center';
import { createStyles } from '../../styles';
const url =
'https://images.unsplash.com/photo-1511216335778-7cb8f49fa7a3?auto=format&fit=crop&w=720&q=80';
const useStyles = createStyles(() => ({
root: {
'& img': {
pointerEvents: 'none',
'user-drag': 'none',
'user-select': 'none'
}
}
}));
$: ({ getStyles } = useStyles());
</script>

<Meta title="Components/Image" component={Image} />

<Template let:args>
<Center>
<Image radius="md" src={url} alt="Random unsplash image" {...args} />
</Center>
</Template>

<Story name="Default" id="imageStory" />

<Story name="Override" id="imageOverrideStory" args={{ override: { pointerEvents: 'none' } }} />
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,11 @@
<Story name="Disabled" id="inputDisabledStory" args={{ disabled: true }} />

<Story name="With icon" id="inputIconStory" args={{ icon: EnvelopeClosed }} />

<Story name="With icon (slot)" id="inputIconSlotStory">
<Input bind:value>
<svelte:fragment slot="icon">
<EnvelopeClosed />
</svelte:fragment>
</Input>
</Story>
22 changes: 12 additions & 10 deletions packages/svelteui-core/src/components/Input/Input.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,18 @@ export default createStyles(
withIcon: {
paddingLeft: typeof iconWidth === 'number' ? `${iconWidth}px` : sizes[size] ?? sizes.md
},
iconWrapper: {
pointerEvents: 'none',
position: 'absolute',
zIndex: 1,
left: 0,
top: 0,
bottom: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: iconWidth ? `${iconWidth}px` : sizes[size] ?? sizes.md
},
disabled: {
backgroundColor: theme.fn.themeColor('gray', 1),
color: theme.fn.themeColor('dark', 2),
Expand Down Expand Up @@ -209,16 +221,6 @@ export default createStyles(
}
},
icon: {
pointerEvents: 'none',
position: 'absolute',
zIndex: 1,
left: 0,
top: 0,
bottom: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: iconWidth ? `${iconWidth}px` : sizes[size] ?? sizes.md,
color: invalid ? theme.fn.themeColor('red', 7) : theme.fn.themeColor('gray', 5),
darkMode: {
color: invalid ? theme.fn.themeColor('red', 6) : theme.fn.themeColor('dark', 2)
Expand Down
41 changes: 34 additions & 7 deletions packages/svelteui-core/src/components/Input/Input.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@
let isHTMLElement = true;
let isComponent = false;
// @TODO
// Slot forwarding and conditional slots will be reworked for Svelte 5. This is waiting
// for that fix, since currently setting a slot and then checking for $$slot.icon
// for the `withIcon` class won't work.
// Discussion here: https://github.com/sveltejs/svelte/pull/8304 and
// https://github.com/sveltejs/svelte/issues/8765
let iconElement: HTMLElement;
$: isIconSlotUsed = Boolean(iconElement?.innerHTML);
function onChange() {
// the 'this' keyword in this case is the
// HTML element provided in prop 'root'
Expand Down Expand Up @@ -111,11 +120,23 @@ Base component to create custom inputs

<!-- svelte-ignore a11y-autofocus -->
<Box {...wrapperProps} class={cx(classes.root, getStyles({ css: override }))} {...$$restProps}>
{#if icon}
<div class={classes.icon}>
<!-- @TODO: This is a workaround for current limitations of slot forwarding, see comment above -->
<span bind:this={iconElement} class={cx({ [classes.iconWrapper]: !!icon || isIconSlotUsed })}>
<slot name="icon">
{#if icon}
<div class={classes.icon}>
<IconRenderer {icon} {iconProps} iconSize={16} />
</div>
{/if}
</slot>
</span>
<!-- @TODO: This is a workaround for current limitations of slot forwarding, see comment above -->
{#if icon && !isIconSlotUsed}
<div class={cx(classes.icon, classes.iconWrapper)}>
<IconRenderer {icon} {iconProps} iconSize={16} />
</div>
{/if}

{#if isHTMLElement && root === 'input'}
<input
{value}
Expand All @@ -130,11 +151,14 @@ Base component to create custom inputs
{autocomplete}
{autofocus}
aria-invalid={invalid}
class:withIcon={icon}
class={cx(
className,
classes.input,
{ [classes.disabled]: disabled, [classes.invalid]: invalid },
{
[classes.disabled]: disabled,
[classes.invalid]: invalid,
[classes.withIcon]: icon || isIconSlotUsed
},
classes[`${variant}Variant`] ?? {}
)}
{...$$restProps}
Expand All @@ -158,11 +182,14 @@ Base component to create custom inputs
aria-invalid={invalid}
class:disabled
class:invalid
class:withIcon={icon}
class={cx(
className,
classes.input,
{ [classes.disabled]: disabled, [classes.invalid]: invalid },
{
[classes.disabled]: disabled,
[classes.invalid]: invalid,
[classes.withIcon]: icon || isIconSlotUsed
},
classes[`${variant}Variant`] ?? {}
)}
on:change={onChange}
Expand All @@ -184,7 +211,7 @@ Base component to create custom inputs
{
[classes.disabled]: disabled,
[classes.invalid]: invalid,
[classes.withIcon]: icon
[classes.withIcon]: icon || isIconSlotUsed
},
classes[`${variant}Variant`] ?? {}
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@
{...$$restProps}
>
<div class={classes.itemInner}>
{#if icon}
<IconRenderer {icon} className={classes.itemIcon} {iconSize} {iconProps} />
{/if}
<slot name="icon">
{#if icon}
<IconRenderer {icon} className={classes.itemIcon} {iconSize} {iconProps} />
{/if}
</slot>
<div class={classes.itemBody}>
<div class={classes.itemLabel}>
<slot />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ Capture user feedback limited to large set of options
{placeholder}
</option>
{/if}
<slot slot="icon" name="icon" />
{#each formattedData as item}
<option value={item.value} disabled={item.disabled} selected={item.value === value}>
{item.label ?? item.value}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@
})}
{...$$restProps}
>
{#if icon && !loading}
<slot name="icon">
<slot name="icon">
{#if icon && !loading}
<IconRenderer {icon} className={classes.icon} {...iconProps} />
</slot>
{/if}
{/if}
</slot>
{#if loading}
<Loader class={classes.loader} size={28} {color} />
{/if}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
import { EnvelopeClosed } from 'radix-icons-svelte';
import { NumberInput } from './index';
</script>

Expand All @@ -12,3 +13,21 @@
<Story name="NumberInput" id="numberInputStory" />

<Story name="Decimals" id="numberInputDecimalsStory" args={{ precision: 0.01, step: 0.01 }} />

<Story
name="With icon"
id="numberInputIconStory"
args={{
label: 'Price',
placeholder: 'Your price',
icon: EnvelopeClosed
}}
/>

<Story name="With icon (slot)" id="numbertInputIconSlotStory">
<NumberInput label="Price" placeholder="Your price">
<svelte:fragment slot="icon">
<EnvelopeClosed />
</svelte:fragment>
</NumberInput>
</Story>
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ values and add custom parsers and formatters.
on:blur={onBlur}
use={[forwardEvents, [useActions, use]]}
>
<slot slot="icon" name="icon" />
<div
slot="rightSection"
class={cx(className, classes.controls, getStyles({ css: overrideControls }))}
Expand Down
Loading

0 comments on commit ef64c79

Please sign in to comment.