diff --git a/.changeset/common-bushes-move.md b/.changeset/common-bushes-move.md new file mode 100644 index 000000000..423762bea --- /dev/null +++ b/.changeset/common-bushes-move.md @@ -0,0 +1,5 @@ +--- +'svelte-ux': minor +--- + +feat: Setup default icons on settings() for easy overriding. Change default to lucide (from mdi) diff --git a/.changeset/true-fans-crash.md b/.changeset/true-fans-crash.md new file mode 100644 index 000000000..e7a89f86d --- /dev/null +++ b/.changeset/true-fans-crash.md @@ -0,0 +1,5 @@ +--- +'svelte-ux': minor +--- + +feat: Support component icons (Unplugin icons, etc) diff --git a/packages/svelte-ux/package.json b/packages/svelte-ux/package.json index 1fcf26abe..062cbf146 100644 --- a/packages/svelte-ux/package.json +++ b/packages/svelte-ux/package.json @@ -61,7 +61,6 @@ "@layerstack/svelte-table": "1.0.1-next.12", "@layerstack/tailwind": "2.0.0-next.15", "@layerstack/utils": "2.0.0-next.12", - "@mdi/js": "^7.4.47", "d3-array": "^3.2.4", "d3-scale": "^4.0.2", "esm-env": "^1.2.2", diff --git a/packages/svelte-ux/src/app.d.ts b/packages/svelte-ux/src/app.d.ts index 8083eaaef..c2b1ea970 100644 --- a/packages/svelte-ux/src/app.d.ts +++ b/packages/svelte-ux/src/app.d.ts @@ -1,53 +1,55 @@ -// export * from 'unplugin-icons/types/svelte'; +import 'unplugin-icons/types/svelte'; // See https://kit.svelte.dev/docs/types#app // for information about these interfaces // and what to do when importing types -declare namespace App { - // interface Error {} - // interface Locals {} - - interface PageData { - meta: { - title?: string; - description?: string; - source?: string; - pageSource?: string; - api?: SveldJson; - features?: string[]; - related?: string[]; - hideUsage?: boolean; - hideTableOfContents?: boolean; - status?: string; - }; +declare global { + namespace App { + // interface Error {} + // interface Locals {} + + interface PageData { + meta: { + title?: string; + description?: string; + source?: string; + pageSource?: string; + api?: SveldJson; + features?: string[]; + related?: string[]; + hideUsage?: boolean; + hideTableOfContents?: boolean; + status?: string; + }; + } + + // interface PageState {} + // interface Platform {} } - // interface PageState {} - // interface Platform {} -} - -// TODO: Can this be referenced from `@layerstack/svelte-actions` types.d.ts without breaking other things? -// https://github.com/sveltejs/language-tools/blob/master/docs/preprocessors/typescript.md -declare namespace svelteHTML { - interface HTMLAttributes { - // use:intersection - 'on:intersecting'?: (event: CustomEvent) => void; + // TODO: Can this be referenced from `@layerstack/svelte-actions` types.d.ts without breaking other things? + // https://github.com/sveltejs/language-tools/blob/master/docs/preprocessors/typescript.md + namespace svelteHTML { + interface HTMLAttributes { + // use:intersection + 'on:intersecting'?: (event: CustomEvent) => void; - // use:mutate - 'on:mutate'?: (event: CustomEvent) => void; + // use:mutate + 'on:mutate'?: (event: CustomEvent) => void; - // use:movable - 'on:movestart'?: (event: CustomEvent<{ x: number; y: number }>) => void; - 'on:move'?: (event: CustomEvent<{ x: number; y: number; dx: number; dy: number }>) => void; - 'on:moveend'?: (event: CustomEvent<{ x: number; y: number }>) => void; + // use:movable + 'on:movestart'?: (event: CustomEvent<{ x: number; y: number }>) => void; + 'on:move'?: (event: CustomEvent<{ x: number; y: number; dx: number; dy: number }>) => void; + 'on:moveend'?: (event: CustomEvent<{ x: number; y: number }>) => void; - // use:popover - 'on:clickOutside'?: (event: CustomEvent) => void; + // use:popover + 'on:clickOutside'?: (event: CustomEvent) => void; - // use:overflow - 'on:overflow'?: (event: CustomEvent<{ overflowX: number; overflowY: number }>) => void; + // use:overflow + 'on:overflow'?: (event: CustomEvent<{ overflowX: number; overflowY: number }>) => void; - // use:longpress - 'on:longpress'?: (event: CustomEvent) => void; + // use:longpress + 'on:longpress'?: (event: CustomEvent) => void; + } } } diff --git a/packages/svelte-ux/src/docs/Blockquote.svelte b/packages/svelte-ux/src/docs/Blockquote.svelte index 535fd3b07..c84b1f541 100644 --- a/packages/svelte-ux/src/docs/Blockquote.svelte +++ b/packages/svelte-ux/src/docs/Blockquote.svelte @@ -1,7 +1,7 @@
a]:font-medium [&>a]:underline [&>a]:decoration-dashed [&>a]:decoration-primary/50 [&>a]:underline-offset-2' )} > - +
diff --git a/packages/svelte-ux/src/docs/ViewSourceButton.svelte b/packages/svelte-ux/src/docs/ViewSourceButton.svelte index 56156d96d..63fd38c51 100644 --- a/packages/svelte-ux/src/docs/ViewSourceButton.svelte +++ b/packages/svelte-ux/src/docs/ViewSourceButton.svelte @@ -1,6 +1,6 @@ -
+
{#each data as item} {@const valuePercent = item.value / (total ?? sum(data, (d) => d.value))} diff --git a/packages/svelte-ux/src/lib/components/Breadcrumb.svelte b/packages/svelte-ux/src/lib/components/Breadcrumb.svelte index 8b3cd8294..30a16ecd6 100644 --- a/packages/svelte-ux/src/lib/components/Breadcrumb.svelte +++ b/packages/svelte-ux/src/lib/components/Breadcrumb.svelte @@ -1,9 +1,7 @@ @@ -449,7 +459,6 @@ {type} {...$$restProps} class={_class} - style={$$props.style ?? ''} {disabled} aria-disabled={disabled ? 'true' : 'false'} use:multi={actions} @@ -465,7 +474,11 @@ {:else if icon} - {#if typeof icon === 'string' || 'icon' in icon} + {#if typeof icon === 'function'} + + {@const Icon = icon} + + {:else if typeof icon === 'string' || 'icon' in icon} - import { mdiCheck, mdiMinus } from '@mdi/js'; import { cls } from '@layerstack/tailwind'; import { uniqueId } from '@layerstack/utils'; - import Icon from './Icon.svelte'; import { getComponentClasses } from './theme.js'; + import { getSettings } from './settings.js'; export let id = uniqueId('checkbox-'); export let name = ''; @@ -18,6 +17,9 @@ export let size: 'xs' | 'sm' | 'md' | 'lg' = 'sm'; export let circle = false; + let className: string | undefined = undefined; + export { className as class }; + export let classes: { root?: string; input?: string; @@ -25,6 +27,8 @@ label?: string; icon?: string; } = {}; + + const { icons } = getSettings(); const settingsClasses = getComponentClasses('Checkbox'); // Update when group changes. Separate function to break reactivity loop @@ -48,6 +52,8 @@ } } } + + $: Icon = indeterminate ? icons.minus : icons.check;
@@ -117,10 +122,10 @@ 'flex-1', 'pl-1 peer-disabled:opacity-50', { - xs: 'text-xs', // 12px - sm: 'text-sm', // 14px - md: 'text-md', // 16px - lg: 'text-lg', // 18px + xs: 'text-xs', + sm: 'text-sm', + md: 'text-md', + lg: 'text-lg', }[size], settingsClasses.label, classes.label diff --git a/packages/svelte-ux/src/lib/components/Code.svelte b/packages/svelte-ux/src/lib/components/Code.svelte index 2a686da6e..cd7121410 100644 --- a/packages/svelte-ux/src/lib/components/Code.svelte +++ b/packages/svelte-ux/src/lib/components/Code.svelte @@ -11,6 +11,9 @@ ? Prism.highlight(source, Prism.languages[language] ?? Prism.languages.text, language) : ''; + let className: string | undefined = undefined; + export { className as class }; + export let classes: { root?: string; pre?: string; @@ -18,7 +21,7 @@ } = {}; -
+
{#if source}
{@html highlightedSource}
       
-
+
import { createEventDispatcher } from 'svelte'; import { slide } from 'svelte/transition'; - import { mdiChevronDown } from '@mdi/js'; import { cls } from '@layerstack/tailwind'; import Icon from './Icon.svelte'; import type { TransitionParams } from '../types/index.js'; + import { getSettings } from './settings.js'; import { getComponentClasses } from './theme.js'; /** @@ -14,23 +14,29 @@ const dispatch = createEventDispatcher(); + const { icons } = getSettings(); + export let name = ''; export let value: any = undefined; export let group: any = undefined; export let open = false; export let popout = false; export let disabled = false; - export let icon = mdiChevronDown; + export let icon = icons.chevronDown; export let transition = slide; export let transitionParams: TransitionParams = {}; + let className: string | undefined = undefined; + export { className as class }; + export let classes: { root?: string; trigger?: string; icon?: string; content?: string; } = {}; + const settingsClasses = getComponentClasses('Collapse'); /** @@ -56,7 +62,7 @@ popout && list === 'group' && 'group-first:mt-0 group-last:mb-0', settingsClasses.root, classes.root, - $$props.class + className )} aria-expanded={open} > diff --git a/packages/svelte-ux/src/lib/components/CopyButton.svelte b/packages/svelte-ux/src/lib/components/CopyButton.svelte index d9bbeb720..f4e372b1d 100644 --- a/packages/svelte-ux/src/lib/components/CopyButton.svelte +++ b/packages/svelte-ux/src/lib/components/CopyButton.svelte @@ -1,9 +1,7 @@
diff --git a/packages/svelte-ux/src/lib/components/Pagination.svelte b/packages/svelte-ux/src/lib/components/Pagination.svelte index 981e2ddd8..5b0440e9a 100644 --- a/packages/svelte-ux/src/lib/components/Pagination.svelte +++ b/packages/svelte-ux/src/lib/components/Pagination.svelte @@ -1,12 +1,9 @@ @@ -52,7 +52,7 @@ 'flex items-center gap-1', settingsClasses.root, classes.root, - $$props.class + className )} > {#each show as component} @@ -63,7 +63,7 @@ {#if component === 'firstPage'} import Prism from 'prismjs'; import 'prism-svelte'; - import { mdiCodeTags } from '@mdi/js'; import { slide } from 'svelte/transition'; import Button from './Button.svelte'; import Code from './Code.svelte'; import { cls } from '@layerstack/tailwind'; + import { getSettings } from './settings.js'; export let code: string | null = null; export let language = 'svelte'; export let highlightedCode = code ? Prism.highlight(code, Prism.languages.svelte, language) : ''; export let showCode = false; + + let className: string | undefined = undefined; + export { className as class }; + + const { icons } = getSettings(); -
+
@@ -29,7 +34,7 @@ {#if code}
diff --git a/packages/svelte-ux/src/lib/components/ResponsiveMenu.svelte b/packages/svelte-ux/src/lib/components/ResponsiveMenu.svelte index 0ebbb12ba..d67987cf6 100644 --- a/packages/svelte-ux/src/lib/components/ResponsiveMenu.svelte +++ b/packages/svelte-ux/src/lib/components/ResponsiveMenu.svelte @@ -13,6 +13,9 @@ export let menuProps: ComponentProps | undefined = undefined; export let drawerProps: ComponentProps | undefined = undefined; + let className: string | undefined = undefined; + export { className as class }; + const isLargeScreen = matchMediaWidth(screenWidth); @@ -23,7 +26,7 @@ on:close explicitClose {...menuProps} - class={cls('ResponsiveMenu', $$props.class, menuProps?.class)} + class={cls('ResponsiveMenu', className, menuProps?.class)} > @@ -33,7 +36,7 @@ placement="bottom" on:close {...drawerProps} - class={cls('ResponsiveMenu', $$props.class, drawerProps?.class)} + class={cls('ResponsiveMenu', className, drawerProps?.class)} > diff --git a/packages/svelte-ux/src/lib/components/ScrollContainer.svelte b/packages/svelte-ux/src/lib/components/ScrollContainer.svelte index e80a9ac6e..b604a357f 100644 --- a/packages/svelte-ux/src/lib/components/ScrollContainer.svelte +++ b/packages/svelte-ux/src/lib/components/ScrollContainer.svelte @@ -7,8 +7,11 @@ function scrollIntoView(node: HTMLElement) { return (options?: Parameters['0']) => node.scrollIntoView(options); } + + let className: string | undefined = undefined; + export { className as class }; -
+
diff --git a/packages/svelte-ux/src/lib/components/ScrollingValue.svelte b/packages/svelte-ux/src/lib/components/ScrollingValue.svelte index f6a6b1151..cbeae65b1 100644 --- a/packages/svelte-ux/src/lib/components/ScrollingValue.svelte +++ b/packages/svelte-ux/src/lib/components/ScrollingValue.svelte @@ -10,6 +10,9 @@ export let format: (value: number) => string | number = (value) => value; export let axis: 'x' | 'y' = 'y'; + let className: string | undefined = undefined; + export { className as class }; + export let classes: { root?: string; value?: string; @@ -31,7 +34,7 @@ 'inline-grid overflow-hidden', settingsClasses.root, classes.root, - $$props.class + className )} >
diff --git a/packages/svelte-ux/src/lib/components/SelectField.svelte b/packages/svelte-ux/src/lib/components/SelectField.svelte index 148901ca5..79fdde325 100644 --- a/packages/svelte-ux/src/lib/components/SelectField.svelte +++ b/packages/svelte-ux/src/lib/components/SelectField.svelte @@ -2,7 +2,6 @@ import { createEventDispatcher, type ComponentProps, type ComponentEvents } from 'svelte'; import type { Placement } from '@floating-ui/dom'; - import { mdiChevronDown, mdiChevronLeft, mdiChevronRight, mdiClose } from '@mdi/js'; import { cls, clsMerge, normalizeClasses } from '@layerstack/tailwind'; import { autoFocus, selectOnFocus, type ScrollIntoViewOptions } from '@layerstack/svelte-actions'; import { Logger } from '@layerstack/utils'; @@ -13,14 +12,16 @@ import MenuItem from './MenuItem.svelte'; import SelectListOptions from './_SelectListOptions.svelte'; import TextField from './TextField.svelte'; - import { getComponentSettings } from './settings.js'; - import type { IconInput } from '../utils/icons.js'; - import type { MenuOption } from '../types/index.js'; + import { getComponentSettings, getSettings } from './settings.js'; + import type { IconProp, MenuOption } from '../types/index.js'; + import { asIconData } from '$lib/utils/icons.js'; const dispatch = createEventDispatcher<{ change: { value: typeof value; option: typeof selected }; inputChange: string; }>(); + + const { icons } = getSettings(); const { classes: settingsClasses, defaults } = getComponentSettings('SelectField'); const logger = new Logger('SelectField'); @@ -35,10 +36,10 @@ export let required = false; export let disabled: boolean = false; export let readonly: boolean = false; - export let icon: IconInput = undefined; + export let icon: IconProp = undefined; export let inlineOptions = false; - export let toggleIcon: IconInput = !inlineOptions ? mdiChevronDown : null; - export let closeIcon: IconInput = mdiClose; + export let toggleIcon: IconProp = !inlineOptions ? icons.chevronDown : null; + export let closeIcon: IconProp = icons.close; export let activeOptionIcon: boolean = false; export let clearable = true; export let base = false; @@ -60,6 +61,9 @@ export let scrollIntoView: Partial = {}; + let className: string | undefined = undefined; + export { className as class }; + export let classes: { root?: string; field?: string | ComponentProps['classes']; @@ -378,7 +382,7 @@ if (!selected?.icon) { icon = originalIcon; } else { - icon = selected.icon; + icon = asIconData(selected.icon); } } @@ -428,7 +432,7 @@ 'SelectField block w-full cursor-default text-left', settingsClasses.root, classes.root, - $$props.class + className )} bind:this={selectFieldEl} on:click={onClick} @@ -472,7 +476,7 @@ {#if stepper}
{/if} diff --git a/packages/svelte-ux/src/lib/components/ThemeSelect.svelte b/packages/svelte-ux/src/lib/components/ThemeSelect.svelte index 2b090eb13..8543dc7d7 100644 --- a/packages/svelte-ux/src/lib/components/ThemeSelect.svelte +++ b/packages/svelte-ux/src/lib/components/ThemeSelect.svelte @@ -1,20 +1,17 @@
diff --git a/packages/svelte-ux/src/lib/components/ToggleOption.svelte b/packages/svelte-ux/src/lib/components/ToggleOption.svelte index 0a5c3ecbc..ad2ee28cd 100644 --- a/packages/svelte-ux/src/lib/components/ToggleOption.svelte +++ b/packages/svelte-ux/src/lib/components/ToggleOption.svelte @@ -8,6 +8,9 @@ export let value: any; + let className: string | undefined = undefined; + export { className as class }; + export let classes: { root?: string; option?: string; @@ -55,7 +58,7 @@ $classesContext.label, settingsClasses.root, classes.root, - $$props.class + className )} > diff --git a/packages/svelte-ux/src/lib/components/Tooltip.svelte b/packages/svelte-ux/src/lib/components/Tooltip.svelte index 47a624bad..db2f5c11c 100644 --- a/packages/svelte-ux/src/lib/components/Tooltip.svelte +++ b/packages/svelte-ux/src/lib/components/Tooltip.svelte @@ -23,6 +23,9 @@ export let autoPlacement = false; export let matchWidth: boolean = false; + let className: string | undefined = undefined; + export { className as class }; + export let classes: { root?: string; popover?: string; @@ -115,14 +118,14 @@ on:click={hideTooltip} bind:this={containerEl} > - {#if $$props.class || underline || cursor} + {#if className || underline || cursor} diff --git a/packages/svelte-ux/src/lib/components/TreeList.svelte b/packages/svelte-ux/src/lib/components/TreeList.svelte index b3e283374..e4caaeb1b 100644 --- a/packages/svelte-ux/src/lib/components/TreeList.svelte +++ b/packages/svelte-ux/src/lib/components/TreeList.svelte @@ -14,6 +14,9 @@ li?: HTMLAttributes | ((node: TreeNode) => HTMLAttributes); } = {}; + let className: string | undefined = undefined; + export { className as class }; + export let classes: { ul?: string | ((nodes: TreeNode[]) => string); li?: string | ((node: TreeNode) => string); @@ -27,7 +30,7 @@ 'TreeList', typeof settingsClasses.ul === 'string' ? settingsClasses.ul : settingsClasses.ul?.(nodes), typeof classes.ul === 'string' ? classes.ul : classes.ul?.(nodes), - $$props.class + className )} > {#each nodes ?? [] as node} diff --git a/packages/svelte-ux/src/lib/components/settings.ts b/packages/svelte-ux/src/lib/components/settings.ts index b58b49e1a..5ef7e2bf1 100644 --- a/packages/svelte-ux/src/lib/components/settings.ts +++ b/packages/svelte-ux/src/lib/components/settings.ts @@ -22,11 +22,91 @@ import { } from './theme.js'; import type { LabelPlacement } from '../types/index.js'; +import LucideArrowUp from '~icons/lucide/arrow-up'; +import LucideArrowDown from '~icons/lucide/arrow-down'; +import LucideArrowLeft from '~icons/lucide/arrow-left'; +import LucideArrowRight from '~icons/lucide/arrow-right'; +import LucideBraces from '~icons/lucide/braces'; +import LucideCalendar from '~icons/lucide/calendar.svelte'; +import LucideCheck from '~icons/lucide/check'; +import LucideChevronLeft from '~icons/lucide/chevron-left.svelte'; +import LucideChevronRight from '~icons/lucide/chevron-right.svelte'; +import LucideChevronDown from '~icons/lucide/chevron-down.svelte'; +import LucideChevronFirst from '~icons/lucide/chevron-first.svelte'; +import LucideChevronLast from '~icons/lucide/chevron-last.svelte'; +import LucideClipboardPaste from '~icons/lucide/clipboard-paste'; +import LucideCode from '~icons/lucide/code'; +import LucideCopy from '~icons/lucide/copy'; +import LucideDollarSign from '~icons/lucide/dollar-sign'; +import LucideEllipsis from '~icons/lucide/ellipsis'; +import LucideEllipsisVertical from '~icons/lucide/ellipsis-vertical'; +import LucideEye from '~icons/lucide/eye'; +import LucideGripHorizontal from '~icons/lucide/grip-horizontal'; +import LucideHouse from '~icons/lucide/house'; +import LucideInfo from '~icons/lucide/info'; +import LucideCircleAlert from '~icons/lucide/circle-alert'; +import LucideMenu from '~icons/lucide/menu'; +import LucideMinus from '~icons/lucide/minus'; +import LucidePercent from '~icons/lucide/percent'; +import LucidePencil from '~icons/lucide/pencil'; +import LucidePlus from '~icons/lucide/plus'; +import LucideRefreshCw from '~icons/lucide/refresh-cw'; +import LucideScissors from '~icons/lucide/scissors'; +import LucideSearch from '~icons/lucide/search'; +import LucideTrash2 from '~icons/lucide/trash-2'; +import LucideUndo2 from '~icons/lucide/undo-2'; +import LucideX from '~icons/lucide/x'; + +import LucideSun from '~icons/lucide/sun'; +import LucideMoon from '~icons/lucide/moon'; +import LucideMonitor from '~icons/lucide/monitor'; + +export const DEFAULT_ICONS = { + alert: LucideCircleAlert, + arrowUp: LucideArrowUp, + arrowDown: LucideArrowDown, + arrowLeft: LucideArrowLeft, + arrowRight: LucideArrowRight, + calendar: LucideCalendar, + check: LucideCheck, + chevronLeft: LucideChevronLeft, + chevronRight: LucideChevronRight, + chevronDown: LucideChevronDown, + chevronFirst: LucideChevronFirst, + chevronLast: LucideChevronLast, + close: LucideX, + cut: LucideScissors, + code: LucideCode, + codeBraces: LucideBraces, + copy: LucideCopy, + currency: LucideDollarSign, + edit: LucidePencil, + ellipsis: LucideEllipsis, + ellipsisVertical: LucideEllipsisVertical, + gripHorizontal: LucideGripHorizontal, + home: LucideHouse, + info: LucideInfo, + menu: LucideMenu, + minus: LucideMinus, + paste: LucideClipboardPaste, + percent: LucidePercent, + plus: LucidePlus, + refresh: LucideRefreshCw, + reveal: LucideEye, + search: LucideSearch, + trash: LucideTrash2, + undo: LucideUndo2, + + lightMode: LucideSun, + darkMode: LucideMoon, + monitor: LucideMonitor, +}; + export interface DefaultProps { labelPlacement: LabelPlacement; } -export type SettingsInput = { +export type SettingsOptions = { /** Force a specific locale setting. */ forceLocale?: string; /** Use this locale in case we don't have locale info for the user's current locale as returned from Intl. @@ -35,7 +115,9 @@ export type SettingsInput = { /** Format information for additional locales that are not built-in to svelte-ux. */ localeFormats?: Record; + /** Component settings including defaults props and classes */ components?: ComponentSettings; + /** A list of the available themes */ themes?: { light?: string[]; @@ -43,6 +125,8 @@ export type SettingsInput = { }; currentTheme?: ThemeStore; + icons?: typeof DEFAULT_ICONS; + /** The existing locale store, if calling settings when there is already an existing `Settings` object */ locale?: LocaleStore; /** The existing locale store, if calling settings when there is already an existing `Settings` object */ @@ -51,7 +135,7 @@ export type SettingsInput = { format?: Readable; }; -export interface Settings extends Omit { +export interface Settings extends Omit { /** The currently selected locale */ locale: LocaleStore; /** The settings for the currently selected locale */ @@ -60,13 +144,14 @@ export interface Settings extends Omit format: Readable; currentTheme: ThemeStore; showDrawer: Writable; + icons: typeof DEFAULT_ICONS; componentSettingsCache: Partial>>; } const settingsKey = Symbol(); -function createLocaleStores(settings: SettingsInput) { +function createLocaleStores(settings: SettingsOptions) { if (settings.locale && settings.localeSettings && settings.format) { return { locale: settings.locale, @@ -101,7 +186,7 @@ function createShowDrawer() { return writable(BROWSER ? window.innerWidth >= breakpoints.md : true); } -export function settings(settings: SettingsInput = {}): Settings { +export function settings(settings: SettingsOptions = {}): Settings { const lightThemes = settings.themes ? (settings.themes.light ?? []) : ['light']; const darkThemes = settings.themes ? (settings.themes.dark ?? []) : ['dark']; @@ -126,6 +211,7 @@ export function settings(settings: SettingsInput = {}): Settings { currentTheme, componentSettingsCache: {}, showDrawer, + icons: settings.icons ?? DEFAULT_ICONS, ...localeStores, }); } @@ -137,6 +223,7 @@ function getFallbackSettings() { currentTheme: createThemeStore({ light: [], dark: [] }), componentSettingsCache: {}, showDrawer: createShowDrawer(), + icons: DEFAULT_ICONS, ...createLocaleStores({}), }; return FALLBACK_SETTINGS; diff --git a/packages/svelte-ux/src/lib/types/index.ts b/packages/svelte-ux/src/lib/types/index.ts index a85dfd0e8..9219288c8 100644 --- a/packages/svelte-ux/src/lib/types/index.ts +++ b/packages/svelte-ux/src/lib/types/index.ts @@ -1,3 +1,5 @@ +import type { Component, ComponentProps } from 'svelte'; +import type { SvelteHTMLElements } from 'svelte/elements'; import type { FlyParams, SlideParams, @@ -7,10 +9,13 @@ import type { } from 'svelte/transition'; import type { ThemeColors } from '@layerstack/tailwind'; +import Icon from '$lib/components/Icon.svelte'; +import MenuItem from '$lib/components/MenuItem.svelte'; + export type MenuOption = { label: string; value: T; - icon?: string; + icon?: ComponentProps['icon']; group?: string; disabled?: boolean; } & Record; @@ -32,3 +37,7 @@ export type ButtonSize = 'sm' | 'md' | 'lg'; export type ButtonRounded = boolean | 'full'; export type TransitionParams = BlurParams | FadeParams | FlyParams | SlideParams | ScaleParams; + +export type IconData = ComponentProps['data']; +export type IconComponent = Component; +export type IconProp = IconComponent | IconData; diff --git a/packages/svelte-ux/src/lib/utils/icons.ts b/packages/svelte-ux/src/lib/utils/icons.ts index db351b8c3..bedb17cd9 100644 --- a/packages/svelte-ux/src/lib/utils/icons.ts +++ b/packages/svelte-ux/src/lib/utils/icons.ts @@ -2,16 +2,13 @@ import type { ComponentProps } from 'svelte'; import { isLiteralObject } from '@layerstack/utils/object'; import type { default as Icon } from '../components/Icon.svelte'; +import type { IconData } from '$lib/types/index.js'; -export type IconInput = ComponentProps['data'] | ComponentProps; -export type IconData = ComponentProps['data']; - -export function asIconData(v: IconInput): IconData { +export function asIconData(v: IconData | ComponentProps): IconData { return isIconComponentProps(v) ? v.data : v; } -function isIconComponentProps(v: IconInput): v is ComponentProps { +function isIconComponentProps(v: IconData | ComponentProps): v is ComponentProps { // `iconName` is a required property of `IconDefinition`, the only other object that `IconInput` supports. - // If it is undefined, then only ComponentProps is viable. - return isLiteralObject(v) && typeof v['iconName'] === 'undefined'; + return isLiteralObject(v) && !('iconName' in v); } diff --git a/packages/svelte-ux/src/routes/+layout.svelte b/packages/svelte-ux/src/routes/+layout.svelte index d900782ed..52dd588bd 100644 --- a/packages/svelte-ux/src/routes/+layout.svelte +++ b/packages/svelte-ux/src/routes/+layout.svelte @@ -2,7 +2,12 @@ import { onMount } from 'svelte'; import posthog from 'posthog-js'; import 'prism-themes/themes/prism-vsc-dark-plus.css'; - import { mdiArrowTopRight, mdiDotsVertical, mdiGithub, mdiTwitter } from '@mdi/js'; + + import LucideArrowUpRight from '~icons/lucide/arrow-up-right'; + import LucideEllipsisVertical from '~icons/lucide/ellipsis-vertical'; + import LucideGithub from '~icons/lucide/github'; + import CustomBluesky from '~icons/custom-brands/bluesky'; + import CustomDiscord from '~icons/custom-brands/discord'; import { AppBar, @@ -165,23 +170,23 @@ // Posthog analytics if (!DEV) { - const unsubscribePage = page.subscribe(($page) => { - if (currentPath && currentPath !== $page.url.pathname) { + const unsubscribePage = page.subscribe((page) => { + if (currentPath && currentPath !== page.url.pathname) { // Page navigated away // @ts-expect-error - .capture() exists - posthog.capture('$pageleave'); + posthog.capture('pageleave'); } // Page entered - currentPath = $page.url.pathname; + currentPath = page.url.pathname; // @ts-expect-error - .capture() exists - posthog.capture('$pageview'); + posthog.capture('pageview'); }); const handleBeforeUnload = () => { // Hard reloads or browser exit // @ts-expect-error - .capture() exists - posthog.capture('$pageleave'); + posthog.capture('pageleave'); }; window.addEventListener('beforeunload', handleBeforeUnload); @@ -224,7 +229,7 @@
+ ``` Internally, each component uses the `cls()` util which leverages [tailwind-merge](https://github.com/dcastil/tailwind-merge) for easy style overriding (see `class precedence` below). diff --git a/packages/svelte-ux/src/routes/docs/+layout.svelte b/packages/svelte-ux/src/routes/docs/+layout.svelte index 8d618f9f4..63f3da0f1 100644 --- a/packages/svelte-ux/src/routes/docs/+layout.svelte +++ b/packages/svelte-ux/src/routes/docs/+layout.svelte @@ -1,22 +1,13 @@
Docs
- +
{type}
@@ -128,7 +121,7 @@ href={sourceUrl ? `https://github.com/techniq/svelte-ux/blob/main/packages/svelte-ux/${sourceUrl}` : ''} - icon={mdiCodeTags} + icon={icons.code} /> {#if !hideTableOfContents}
@@ -59,7 +61,7 @@

menuIcon prop

- +

menuIcon slot

@@ -68,7 +70,7 @@
@@ -70,7 +70,7 @@ A
-
diff --git a/packages/svelte-ux/src/routes/docs/components/Collapse/+page.svelte b/packages/svelte-ux/src/routes/docs/components/Collapse/+page.svelte index c6e4bd577..2169542d7 100644 --- a/packages/svelte-ux/src/routes/docs/components/Collapse/+page.svelte +++ b/packages/svelte-ux/src/routes/docs/components/Collapse/+page.svelte @@ -1,9 +1,11 @@

Examples

@@ -65,7 +65,7 @@ slot="trigger" title="Item {i + 1}" subheading="List Item" - icon={mdiAccount} + icon={LucideUserRound} avatar={{ class: 'bg-surface-content/50 text-surface-100/90' }} class="flex-1" noShadow @@ -89,7 +89,7 @@ slot="trigger" title="Item 1" subheading="Expansion Panel" - icon={mdiAccount} + icon={LucideUserRound} avatar={{ class: 'bg-surface-content/50 text-surface-100/90' }} class="flex-1" noShadow @@ -103,7 +103,7 @@ @@ -111,7 +111,7 @@ slot="trigger" title="Item 3" subheading="Expansion Panel" - icon={mdiAccount} + icon={LucideUserRound} avatar={{ class: 'bg-surface-content/50 text-surface-100/90' }} class="flex-1" noShadow @@ -132,7 +132,7 @@ @@ -140,7 +140,7 @@ slot="trigger" title="Item 2" subheading="Expansion Panel" - icon={mdiAccount} + icon={LucideUserRound} avatar={{ class: 'bg-surface-content/50 text-surface-100/90' }} class="flex-1" noShadow @@ -154,7 +154,7 @@ diff --git a/packages/svelte-ux/src/routes/docs/components/Field/+page.svelte b/packages/svelte-ux/src/routes/docs/components/Field/+page.svelte index 492684464..9e2bf5f0a 100644 --- a/packages/svelte-ux/src/routes/docs/components/Field/+page.svelte +++ b/packages/svelte-ux/src/routes/docs/components/Field/+page.svelte @@ -1,15 +1,13 @@ @@ -130,7 +130,7 @@
- + Yes No All @@ -144,15 +144,15 @@
with icons
- + - + - + - + @@ -213,7 +213,7 @@ - +
diff --git a/packages/svelte-ux/src/routes/docs/components/Icon/+page.svelte b/packages/svelte-ux/src/routes/docs/components/Icon/+page.svelte index 5ee3fb0a1..9d91bb57e 100644 --- a/packages/svelte-ux/src/routes/docs/components/Icon/+page.svelte +++ b/packages/svelte-ux/src/routes/docs/components/Icon/+page.svelte @@ -1,21 +1,29 @@ @@ -27,37 +35,42 @@

Examples

-
-

Material Design icons

+

Path data

- - - -
+ + + + + +

Svg string

- - '} - /> - - - - - - + + + +

Svg url

+ + + + + +

Svg element

+ + + {@html LucideUserSvg} +

Component

+ + + + + +
Typically you will use the component directly, but it can be helpful when passing as a prop or + part of a structure, such as a menu item
+

Font Awesome icons

@@ -65,7 +78,7 @@ href="https://fontawesome.com/icons" target="_blank" class="flex-row-reverse" - icon={mdiOpenInNew} + icon={LucideArrowUpRight} > Icons @@ -84,7 +97,7 @@ href="https://fonts.google.com/icons" target="_blank" class="flex-row-reverse" - icon={mdiOpenInNew} + icon={LucideArrowUpRight} > Icons @@ -92,7 +105,7 @@ href="https://developers.google.com/fonts/docs/material_symbols" target="_blank" class="flex-row-reverse" - icon={mdiOpenInNew} + icon={LucideArrowUpRight} > Docs @@ -113,44 +126,37 @@

Sizes

- - - - - - - - + + + + + + + +

Color

- - - - - - '} - class="fill-primary" - /> - - + + + + + + +

Multiple paths

@@ -158,44 +164,35 @@

Rotate / Scale / Flip

- - - - - + + + + +

Animation

- - - - + + + +

with Tooltip

- - + + - - + + - - '} - /> + + - - - - - - + + {@html LucideUserSvg} diff --git a/packages/svelte-ux/src/routes/docs/components/ListItem/+page.svelte b/packages/svelte-ux/src/routes/docs/components/ListItem/+page.svelte index 1a4667f53..b93a5bae3 100644 --- a/packages/svelte-ux/src/routes/docs/components/ListItem/+page.svelte +++ b/packages/svelte-ux/src/routes/docs/components/ListItem/+page.svelte @@ -1,11 +1,13 @@

Examples

@@ -34,7 +43,7 @@
- import { - mdiContentCopy, - mdiContentCut, - mdiContentPaste, - mdiMagnify, - mdiChevronDown, - } from '@mdi/js'; - - import { Icon, MenuButton, MenuItem, TextField } from 'svelte-ux'; + import { getSettings, Icon, MenuButton, MenuItem, TextField } from 'svelte-ux'; import Preview from '$lib/components/Preview.svelte'; + import { asIconData } from '$lib/utils/icons.js'; + + const { icons } = getSettings(); const options = [ { label: 'Cut', value: 'cut' }, @@ -17,9 +12,9 @@ ]; const optionsWithIcons = [ - { label: 'Cut', value: 'cut', icon: mdiContentCut }, - { label: 'Copy', value: 'copy', icon: mdiContentCopy }, - { label: 'Paste', value: 'paste', icon: mdiContentPaste }, + { label: 'Cut', value: 'cut', icon: icons.cut }, + { label: 'Copy', value: 'copy', icon: icons.copy }, + { label: 'Paste', value: 'paste', icon: icons.paste }, ]; @@ -46,7 +41,7 @@

Icon

- +

Option icons

@@ -61,7 +56,7 @@ {#if value} - {value.label} + {value.label} {:else} No selection {/if} @@ -74,7 +69,7 @@ - + @@ -114,7 +109,7 @@ let:close >
- +
{#each options as option} diff --git a/packages/svelte-ux/src/routes/docs/components/MenuField/+page.svelte b/packages/svelte-ux/src/routes/docs/components/MenuField/+page.svelte index 9777be234..f9935a990 100644 --- a/packages/svelte-ux/src/routes/docs/components/MenuField/+page.svelte +++ b/packages/svelte-ux/src/routes/docs/components/MenuField/+page.svelte @@ -1,9 +1,9 @@ @@ -61,12 +59,7 @@
- +
@@ -77,7 +70,7 @@ @@ -99,7 +92,7 @@ alert('Undo'), Dismiss: () => {} }} actionsPlacement="below" closeIcon diff --git a/packages/svelte-ux/src/routes/docs/components/Overflow/+page.svelte b/packages/svelte-ux/src/routes/docs/components/Overflow/+page.svelte index ce4db0f39..e4750df68 100644 --- a/packages/svelte-ux/src/routes/docs/components/Overflow/+page.svelte +++ b/packages/svelte-ux/src/routes/docs/components/Overflow/+page.svelte @@ -39,12 +39,12 @@ -
@@ -152,11 +151,11 @@
-
-
@@ -166,7 +165,7 @@
- - - - -
@@ -221,9 +220,9 @@ {@const startOfMonth = intervalOffset('month', firstOfMonth, value)}
-
{@const startOfMonth = intervalOffset('month', firstOfMonth, value)} diff --git a/packages/svelte-ux/src/routes/docs/components/SelectField/+page.svelte b/packages/svelte-ux/src/routes/docs/components/SelectField/+page.svelte index 69ca41660..d97e3b887 100644 --- a/packages/svelte-ux/src/routes/docs/components/SelectField/+page.svelte +++ b/packages/svelte-ux/src/routes/docs/components/SelectField/+page.svelte @@ -1,11 +1,10 @@ @@ -78,10 +78,10 @@ - Register - Choose plan - Purchase - Receive product + Register + Choose plan + Purchase + Receive product diff --git a/packages/svelte-ux/src/routes/docs/components/Switch/+page.svelte b/packages/svelte-ux/src/routes/docs/components/Switch/+page.svelte index 3f10abab7..27a15189c 100644 --- a/packages/svelte-ux/src/routes/docs/components/Switch/+page.svelte +++ b/packages/svelte-ux/src/routes/docs/components/Switch/+page.svelte @@ -1,9 +1,9 @@ @@ -40,14 +40,14 @@
{#if checked} - + {/if} {#if checked} - + {:else} - + {/if}
@@ -60,7 +60,7 @@ - +
diff --git a/packages/svelte-ux/src/routes/docs/components/Table/+page.svelte b/packages/svelte-ux/src/routes/docs/components/Table/+page.svelte index 1dd85cf24..76813ba79 100644 --- a/packages/svelte-ux/src/routes/docs/components/Table/+page.svelte +++ b/packages/svelte-ux/src/routes/docs/components/Table/+page.svelte @@ -1,8 +1,7 @@ diff --git a/packages/svelte-ux/src/routes/docs/components/Tabs/+page.svelte b/packages/svelte-ux/src/routes/docs/components/Tabs/+page.svelte index 4afac01d6..7da351a5d 100644 --- a/packages/svelte-ux/src/routes/docs/components/Tabs/+page.svelte +++ b/packages/svelte-ux/src/routes/docs/components/Tabs/+page.svelte @@ -1,10 +1,11 @@

Examples

@@ -83,7 +93,7 @@ - import { mdiTrashCan } from '@mdi/js'; - - import { Button, Tooltip } from 'svelte-ux'; + import { Button, getSettings, Tooltip } from 'svelte-ux'; import Preview from '$lib/components/Preview.svelte'; + + const { icons } = getSettings();

Examples

@@ -19,7 +19,7 @@ -