@loidolt/theme-svelte 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
  import { Tabs as TabsPrimitive } from 'bits-ui';
3
3
  import type { TabsRootProps } from 'bits-ui';
4
4
  import type { Snippet } from 'svelte';
5
- import type { NavItem, Orientation } from '../types.js';
5
+ import type { Orientation, TabItem } from '../types.js';
6
6
  import { cx } from '../utils.js';
7
7
 
8
8
  interface Props extends Omit<
@@ -11,14 +11,27 @@
11
11
  > {
12
12
  /** Defaults to the first tab, so a panel is always rendered. */
13
13
  value?: T;
14
- tabs: NavItem<T>[];
14
+ tabs: TabItem<T>[];
15
15
  /** Accessible name of the tab list. */
16
16
  label?: string;
17
17
  orientation?: Orientation;
18
+ /**
19
+ * `line` underlines the selected tab. `rail` is an icon rail for a settings sidebar: short
20
+ * labels under icons, the list fixed beside (or above) panels that scroll on their own.
21
+ */
22
+ variant?: 'line' | 'rail';
23
+ /**
24
+ * Every panel stays mounted, hidden while unselected, so a panel keeps its state and its
25
+ * controls can be found while another one shows.
26
+ */
18
27
  children: Snippet<[{ value: T }]>;
28
+ /** Shared content above the panels, such as a filter or search; pinned while the rail's panels scroll. */
29
+ panelHeader?: Snippet;
19
30
  class?: string;
20
31
  listClass?: string;
21
32
  contentClass?: string;
33
+ /** The element around the panels, present in the rail variant or with a `panelHeader`. */
34
+ panelsClass?: string;
22
35
  onValueChange?: (value: T) => void;
23
36
  ref?: HTMLElement | null;
24
37
  }
@@ -30,10 +43,13 @@
30
43
  value = $bindable(tabs[0]?.value),
31
44
  label,
32
45
  orientation = 'horizontal',
46
+ variant = 'line',
33
47
  children,
48
+ panelHeader,
34
49
  class: className,
35
50
  listClass,
36
51
  contentClass,
52
+ panelsClass,
37
53
  onValueChange,
38
54
  ref = $bindable(null),
39
55
  ...rest
@@ -54,8 +70,17 @@
54
70
  onValueChange?.(active);
55
71
  }
56
72
  });
73
+
74
+ const wrapped = $derived(variant === 'rail' || panelHeader !== undefined);
57
75
  </script>
58
76
 
77
+ {#snippet panels()}
78
+ {#each tabs as tab (tab.value)}<TabsPrimitive.Content
79
+ class={cx('ldt-tabs__content', contentClass)}
80
+ value={tab.value}>{@render children({ value: tab.value })}</TabsPrimitive.Content
81
+ >{/each}
82
+ {/snippet}
83
+
59
84
  <TabsPrimitive.Root
60
85
  bind:ref
61
86
  value={active}
@@ -64,18 +89,27 @@
64
89
  onValueChange?.(next as T);
65
90
  }}
66
91
  {orientation}
67
- class={cx('ldt-tabs', className)}
92
+ data-variant={variant}
93
+ class={cx('ldt-tabs', variant === 'rail' && 'ldt-tabs--rail', className)}
68
94
  {...rest}
69
95
  >
70
96
  <TabsPrimitive.List class={cx('ldt-tabs__list', listClass)} aria-label={label}
71
97
  >{#each tabs as tab (tab.value)}<TabsPrimitive.Trigger
72
98
  class="ldt-tabs__trigger"
73
99
  value={tab.value}
74
- disabled={tab.disabled}>{tab.label}</TabsPrimitive.Trigger
100
+ disabled={tab.disabled}
101
+ title={tab.description}
102
+ >{#if tab.icon}<span class="ldt-tabs__icon" aria-hidden="true">{@render tab.icon()}</span
103
+ ><span class="ldt-tabs__label">{tab.label}</span
104
+ >{:else}{tab.label}{/if}</TabsPrimitive.Trigger
75
105
  >{/each}</TabsPrimitive.List
76
106
  >
77
- {#each tabs as tab (tab.value)}<TabsPrimitive.Content
78
- class={cx('ldt-tabs__content', contentClass)}
79
- value={tab.value}>{@render children({ value: tab.value })}</TabsPrimitive.Content
80
- >{/each}
107
+ {#if wrapped}
108
+ <div class={cx('ldt-tabs__panels', panelsClass)}>
109
+ {#if panelHeader}<div class="ldt-tabs__panel-header">{@render panelHeader()}</div>{/if}
110
+ {@render panels()}
111
+ </div>
112
+ {:else}
113
+ {@render panels()}
114
+ {/if}
81
115
  </TabsPrimitive.Root>
@@ -1,20 +1,33 @@
1
1
  import { Tabs as TabsPrimitive } from 'bits-ui';
2
2
  import type { Snippet } from 'svelte';
3
- import type { NavItem, Orientation } from '../types.js';
3
+ import type { Orientation, TabItem } from '../types.js';
4
4
  declare function $$render<T extends string = string>(): {
5
5
  props: Omit<TabsPrimitive.RootProps, "orientation" | "value" | "children" | "class" | "onValueChange" | "ref" | "child"> & {
6
6
  /** Defaults to the first tab, so a panel is always rendered. */
7
7
  value?: T;
8
- tabs: NavItem<T>[];
8
+ tabs: TabItem<T>[];
9
9
  /** Accessible name of the tab list. */
10
10
  label?: string;
11
11
  orientation?: Orientation;
12
+ /**
13
+ * `line` underlines the selected tab. `rail` is an icon rail for a settings sidebar: short
14
+ * labels under icons, the list fixed beside (or above) panels that scroll on their own.
15
+ */
16
+ variant?: "line" | "rail";
17
+ /**
18
+ * Every panel stays mounted, hidden while unselected, so a panel keeps its state and its
19
+ * controls can be found while another one shows.
20
+ */
12
21
  children: Snippet<[{
13
22
  value: T;
14
23
  }]>;
24
+ /** Shared content above the panels, such as a filter or search; pinned while the rail's panels scroll. */
25
+ panelHeader?: Snippet;
15
26
  class?: string;
16
27
  listClass?: string;
17
28
  contentClass?: string;
29
+ /** The element around the panels, present in the rail variant or with a `panelHeader`. */
30
+ panelsClass?: string;
18
31
  onValueChange?: (value: T) => void;
19
32
  ref?: HTMLElement | null;
20
33
  };
package/dist/index.d.ts CHANGED
@@ -116,4 +116,4 @@ export { createTheme, themeScript } from './theme.svelte.js';
116
116
  export type { ColorScheme, Theme, ThemeOptions, ThemePreference, ThemeScriptOptions, } from './theme.svelte.js';
117
117
  export { breakpointQuery, createMediaQuery } from './media.svelte.js';
118
118
  export type { BreakpointName, MediaQuery, MediaQueryOptions } from './media.svelte.js';
119
- export type { ActionVariant, Alignment, BadgeSize, BadgeVariant, ChoiceOption, ColumnAlign, Crumb, Disclosure, ControlSize, DialogSize, HeadingLevel, MenuCheckboxItem, MenuEntry, MenuGroup, MenuItem, MenuRadioGroup, MenuSub, NavItem, Option, OptionGroup, Orientation, Placement, SortDirection, StatusVariant, TriggerChildProps, } from './types.js';
119
+ export type { ActionVariant, Alignment, BadgeSize, BadgeVariant, ChoiceOption, ColumnAlign, Crumb, Disclosure, ControlSize, DialogSize, HeadingLevel, MenuCheckboxItem, MenuEntry, MenuGroup, MenuItem, MenuRadioGroup, MenuSub, NavItem, Option, OptionGroup, Orientation, Placement, SortDirection, StatusVariant, TabItem, TriggerChildProps, } from './types.js';
package/dist/types.d.ts CHANGED
@@ -42,6 +42,14 @@ export interface ChoiceOption<T extends string = string> extends Option<T> {
42
42
  }
43
43
  /** `Option` is the single vocabulary for choice lists; `NavItem` is kept as a readable alias. */
44
44
  export type NavItem<T extends string = string> = Option<T>;
45
+ /**
46
+ * One `Tabs` tab. The icon leads the label, and sits above it in the `rail` variant, where the
47
+ * label is short; `description` says more on hover and to assistive technology.
48
+ */
49
+ export interface TabItem<T extends string = string> extends Option<T> {
50
+ icon?: Snippet;
51
+ description?: string;
52
+ }
45
53
  /** One step in a breadcrumb trail. The final crumb is the current page and takes no `href`. */
46
54
  export interface Crumb {
47
55
  label: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loidolt/theme-svelte",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Accessible Svelte 5 components for Loidolt applications",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,8 +31,8 @@
31
31
  "clean": "rimraf dist"
32
32
  },
33
33
  "dependencies": {
34
- "@loidolt/theme-styles": "^0.9.0",
35
- "@loidolt/theme-tokens": "^0.9.0",
34
+ "@loidolt/theme-styles": "^0.10.0",
35
+ "@loidolt/theme-tokens": "^0.10.0",
36
36
  "bits-ui": "^2.19.0"
37
37
  },
38
38
  "peerDependencies": {