@noxlovette/material 0.2.2 → 0.3.1

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.
Files changed (76) hide show
  1. package/claude-skill/material-design/SKILL.md +3 -1
  2. package/claude-skill/material-design/references/component-patterns.md +15 -0
  3. package/dist/components/badge/Badge.stories.svelte +58 -0
  4. package/dist/components/badge/Badge.stories.svelte.d.ts +19 -0
  5. package/dist/components/buttons/Button.stories.svelte +50 -0
  6. package/dist/components/buttons/Button.stories.svelte.d.ts +19 -0
  7. package/dist/components/buttons/ButtonIcon.stories.svelte +74 -0
  8. package/dist/components/buttons/ButtonIcon.stories.svelte.d.ts +19 -0
  9. package/dist/components/buttons/FAB.stories.svelte +59 -0
  10. package/dist/components/buttons/FAB.stories.svelte.d.ts +19 -0
  11. package/dist/components/buttons/Toggle.stories.svelte +46 -0
  12. package/dist/components/buttons/Toggle.stories.svelte.d.ts +19 -0
  13. package/dist/components/cards/Card.stories.svelte +72 -0
  14. package/dist/components/cards/Card.stories.svelte.d.ts +19 -0
  15. package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte +35 -0
  16. package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte.d.ts +4 -0
  17. package/dist/components/containers/dialogue/Dialogue.stories.svelte +86 -0
  18. package/dist/components/containers/dialogue/Dialogue.stories.svelte.d.ts +4 -0
  19. package/dist/components/containers/menu/Menu.stories.svelte +62 -0
  20. package/dist/components/containers/menu/Menu.stories.svelte.d.ts +19 -0
  21. package/dist/components/containers/panes/SinglePane.stories.svelte +67 -0
  22. package/dist/components/containers/panes/SinglePane.stories.svelte.d.ts +19 -0
  23. package/dist/components/containers/panes/SplitPane.stories.svelte +124 -0
  24. package/dist/components/containers/panes/SplitPane.stories.svelte.d.ts +19 -0
  25. package/dist/components/containers/panes/SplitPane.svelte +25 -21
  26. package/dist/components/containers/panes/SplitPane.svelte.d.ts +4 -1
  27. package/dist/components/containers/panes/SupportingPane.stories.svelte +113 -0
  28. package/dist/components/containers/panes/SupportingPane.stories.svelte.d.ts +19 -0
  29. package/dist/components/containers/panes/theme.d.ts +18 -0
  30. package/dist/components/containers/panes/theme.js +11 -0
  31. package/dist/components/containers/panes/types.d.ts +17 -1
  32. package/dist/components/containers/popover/Popover.stories.svelte +65 -0
  33. package/dist/components/containers/popover/Popover.stories.svelte.d.ts +19 -0
  34. package/dist/components/containers/side-sheet/SideSheet.stories.svelte +54 -0
  35. package/dist/components/containers/side-sheet/SideSheet.stories.svelte.d.ts +4 -0
  36. package/dist/components/forms/checkbox/Checkbox.stories.svelte +42 -0
  37. package/dist/components/forms/checkbox/Checkbox.stories.svelte.d.ts +19 -0
  38. package/dist/components/forms/search/Search.stories.svelte +41 -0
  39. package/dist/components/forms/search/Search.stories.svelte.d.ts +19 -0
  40. package/dist/components/forms/select/Select.stories.svelte +89 -0
  41. package/dist/components/forms/select/Select.stories.svelte.d.ts +19 -0
  42. package/dist/components/forms/select/Select.svelte +1 -1
  43. package/dist/components/forms/slider/Slider.stories.svelte +76 -0
  44. package/dist/components/forms/slider/Slider.stories.svelte.d.ts +19 -0
  45. package/dist/components/forms/switch/Switch.stories.svelte +47 -0
  46. package/dist/components/forms/switch/Switch.stories.svelte.d.ts +19 -0
  47. package/dist/components/forms/textfield/Textfield.stories.svelte +74 -0
  48. package/dist/components/forms/textfield/Textfield.stories.svelte.d.ts +19 -0
  49. package/dist/components/misc/Avatar.svelte +2 -2
  50. package/dist/components/misc/types.d.ts +4 -0
  51. package/dist/components/nav/appbar/AppBar.stories.svelte +49 -0
  52. package/dist/components/nav/appbar/AppBar.stories.svelte.d.ts +19 -0
  53. package/dist/components/nav/navbar/Navbar.stories.svelte +33 -0
  54. package/dist/components/nav/navbar/Navbar.stories.svelte.d.ts +19 -0
  55. package/dist/components/nav/rail/Rail.stories.svelte +73 -0
  56. package/dist/components/nav/rail/Rail.stories.svelte.d.ts +19 -0
  57. package/dist/components/nav/tabs/Tabs.stories.svelte +55 -0
  58. package/dist/components/nav/tabs/Tabs.stories.svelte.d.ts +18 -0
  59. package/dist/components/pill/Pill.stories.svelte +48 -0
  60. package/dist/components/pill/Pill.stories.svelte.d.ts +19 -0
  61. package/dist/components/progress/Progress.stories.svelte +64 -0
  62. package/dist/components/progress/Progress.stories.svelte.d.ts +18 -0
  63. package/dist/components/snackbar/Snackbar.stories.svelte +76 -0
  64. package/dist/components/snackbar/Snackbar.stories.svelte.d.ts +4 -0
  65. package/dist/components/typography/body/Body.stories.svelte +30 -0
  66. package/dist/components/typography/body/Body.stories.svelte.d.ts +19 -0
  67. package/dist/components/typography/display/Display.stories.svelte +30 -0
  68. package/dist/components/typography/display/Display.stories.svelte.d.ts +19 -0
  69. package/dist/components/typography/headline/Headline.stories.svelte +30 -0
  70. package/dist/components/typography/headline/Headline.stories.svelte.d.ts +19 -0
  71. package/dist/components/typography/label/Label.stories.svelte +30 -0
  72. package/dist/components/typography/label/Label.stories.svelte.d.ts +19 -0
  73. package/dist/components/typography/title/Title.stories.svelte +30 -0
  74. package/dist/components/typography/title/Title.stories.svelte.d.ts +19 -0
  75. package/dist/utils/Layer.svelte +4 -3
  76. package/package.json +12 -4
@@ -0,0 +1,62 @@
1
+ <!--
2
+ Menu owns its own open/close state internally (bits-ui DropdownMenu.Root bind:open, defaulting
3
+ to closed) and renders a default filled Button trigger when no `trigger` snippet is supplied —
4
+ so unlike Dialogue/Popover, no external open state is needed to demo it: clicking the default
5
+ trigger is enough.
6
+ -->
7
+ <script module lang="ts">
8
+ import { defineMeta } from '@storybook/addon-svelte-csf';
9
+ import Menu from './Menu.svelte';
10
+ import MenuItem from '../menu-item/MenuItem.svelte';
11
+ import ButtonIcon from '../../buttons/ButtonIcon.svelte';
12
+ import { Divider } from '../../../index.js';
13
+
14
+ const { Story } = defineMeta({
15
+ title: 'Containers/Menu',
16
+ component: Menu,
17
+ argTypes: {
18
+ label: { control: 'text' },
19
+ align: { control: 'select', options: ['start', 'center', 'end'] }
20
+ },
21
+ args: {
22
+ label: 'Open menu',
23
+ align: 'start'
24
+ }
25
+ });
26
+ </script>
27
+
28
+ <Story name="Playground">
29
+ {#snippet template(args)}
30
+ <div class="p-6">
31
+ <Menu label={args.label} align={args.align}>
32
+ <MenuItem iconProps={{ name: 'content_cut' }}>Cut</MenuItem>
33
+ <MenuItem iconProps={{ name: 'content_copy' }}>Copy</MenuItem>
34
+ <MenuItem iconProps={{ name: 'content_paste' }}>Paste</MenuItem>
35
+ </Menu>
36
+ </div>
37
+ {/snippet}
38
+ </Story>
39
+
40
+ <Story name="With Custom Trigger" asChild>
41
+ <div class="p-6">
42
+ <Menu align="end">
43
+ {#snippet trigger()}
44
+ <ButtonIcon iconProps={{ name: 'more_vert' }} variant="text" />
45
+ {/snippet}
46
+ <MenuItem iconProps={{ name: 'edit' }}>Edit</MenuItem>
47
+ <MenuItem iconProps={{ name: 'archive' }} helper="Move to archive folder">Archive</MenuItem>
48
+ <Divider class="my-2" />
49
+ <MenuItem iconProps={{ name: 'delete' }} disabled>Delete</MenuItem>
50
+ </Menu>
51
+ </div>
52
+ </Story>
53
+
54
+ <Story name="With Selected Item" asChild>
55
+ <div class="p-6">
56
+ <Menu label="Sort by">
57
+ <MenuItem selected>Name</MenuItem>
58
+ <MenuItem>Date modified</MenuItem>
59
+ <MenuItem>Size</MenuItem>
60
+ </Menu>
61
+ </div>
62
+ </Story>
@@ -0,0 +1,19 @@
1
+ import Menu from './Menu.svelte';
2
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
+ $$bindings?: Bindings;
5
+ } & Exports;
6
+ (internal: unknown, props: {
7
+ $$events?: Events;
8
+ $$slots?: Slots;
9
+ }): Exports & {
10
+ $set?: any;
11
+ $on?: any;
12
+ };
13
+ z_$$bindings?: Bindings;
14
+ }
15
+ declare const Menu: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type Menu = InstanceType<typeof Menu>;
19
+ export default Menu;
@@ -0,0 +1,67 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import SinglePane from './SinglePane.svelte';
4
+
5
+ const { Story } = defineMeta({
6
+ title: 'Containers/Single Pane',
7
+ component: SinglePane,
8
+ argTypes: {
9
+ centered: {
10
+ control: 'select',
11
+ options: ['none', 'narrow', 'medium']
12
+ },
13
+ padding: {
14
+ control: 'select',
15
+ options: ['none', 'sm', 'md', 'lg']
16
+ },
17
+ gap: {
18
+ control: 'select',
19
+ options: ['none', 'sm', 'md', 'lg', 'xl']
20
+ },
21
+ background: { control: 'boolean' },
22
+ full: { control: 'boolean' },
23
+ rounded: { control: 'boolean' }
24
+ },
25
+ args: {
26
+ centered: 'medium',
27
+ padding: 'md',
28
+ gap: 'md',
29
+ background: true,
30
+ full: false,
31
+ rounded: true
32
+ }
33
+ });
34
+ </script>
35
+
36
+ <Story name="Playground">
37
+ {#snippet template(args)}
38
+ <SinglePane
39
+ centered={args.centered}
40
+ padding={args.padding}
41
+ gap={args.gap}
42
+ background={args.background}
43
+ full={args.full}
44
+ rounded={args.rounded}
45
+ >
46
+ <div class="bg-md-sys-color-surface-container-high rounded-lg p-6">Block one</div>
47
+ <div class="bg-md-sys-color-surface-container-high rounded-lg p-6">Block two</div>
48
+ <div class="bg-md-sys-color-surface-container-high rounded-lg p-6">Block three</div>
49
+ </SinglePane>
50
+ {/snippet}
51
+ </Story>
52
+
53
+ <Story name="Centered Widths" asChild>
54
+ <div class="flex flex-col gap-6">
55
+ <SinglePane centered="narrow" background={false}>
56
+ <div class="bg-md-sys-color-secondary-container rounded-lg p-4 text-sm">narrow</div>
57
+ </SinglePane>
58
+ <SinglePane centered="medium" background={false}>
59
+ <div class="bg-md-sys-color-secondary-container rounded-lg p-4 text-sm">medium</div>
60
+ </SinglePane>
61
+ <SinglePane centered="none" background={false}>
62
+ <div class="bg-md-sys-color-secondary-container rounded-lg p-4 text-sm">
63
+ none (full width)
64
+ </div>
65
+ </SinglePane>
66
+ </div>
67
+ </Story>
@@ -0,0 +1,19 @@
1
+ import SinglePane from './SinglePane.svelte';
2
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
+ $$bindings?: Bindings;
5
+ } & Exports;
6
+ (internal: unknown, props: {
7
+ $$events?: Events;
8
+ $$slots?: Slots;
9
+ }): Exports & {
10
+ $set?: any;
11
+ $on?: any;
12
+ };
13
+ z_$$bindings?: Bindings;
14
+ }
15
+ declare const SinglePane: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type SinglePane = InstanceType<typeof SinglePane>;
19
+ export default SinglePane;
@@ -0,0 +1,124 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import SplitPane from './SplitPane.svelte';
4
+
5
+ const { Story } = defineMeta({
6
+ title: 'Containers/Split Pane',
7
+ component: SplitPane,
8
+ argTypes: {
9
+ anchor: {
10
+ control: 'select',
11
+ options: ['viewport', 'parent', 'sticky']
12
+ },
13
+ resizable: { control: 'boolean' },
14
+ leftWidth: { control: 'number' },
15
+ minLeft: { control: 'number' },
16
+ maxLeft: { control: 'number' },
17
+ full: { control: 'boolean' },
18
+ rounded: { control: 'boolean' },
19
+ mobilePane: {
20
+ control: 'select',
21
+ options: ['left', 'right']
22
+ }
23
+ },
24
+ args: {
25
+ anchor: 'parent',
26
+ resizable: true,
27
+ leftWidth: 320,
28
+ minLeft: 240,
29
+ maxLeft: 560,
30
+ full: false,
31
+ rounded: true,
32
+ mobilePane: 'right'
33
+ }
34
+ });
35
+ </script>
36
+
37
+ <!--
38
+ Playground uses anchor="parent" (bounded/relative container) since it's the
39
+ only mode that demos meaningfully inside Storybook's iframe canvas — "viewport"
40
+ fixes to the true browser edge and "sticky" needs a real scrolling page.
41
+ -->
42
+ <Story name="Playground">
43
+ {#snippet template(args)}
44
+ <div
45
+ class="relative h-[420px] overflow-hidden rounded-lg border border-md-sys-color-outline-variant"
46
+ >
47
+ <SplitPane {...args}>
48
+ {#snippet left()}
49
+ <div class="p-4">
50
+ <p class="md-sys-typescale-title-small mb-2">Left pane</p>
51
+ <p class="text-md-sys-color-on-surface-variant text-sm">
52
+ Drag the handle to resize. Persisted to localStorage when resizable.
53
+ </p>
54
+ </div>
55
+ {/snippet}
56
+ {#snippet right()}
57
+ <div class="p-4">
58
+ <p class="md-sys-typescale-title-small mb-2">Right pane</p>
59
+ <p class="text-md-sys-color-on-surface-variant text-sm">Main content area.</p>
60
+ </div>
61
+ {/snippet}
62
+ </SplitPane>
63
+ </div>
64
+ {/snippet}
65
+ </Story>
66
+
67
+ <!--
68
+ anchor="viewport" — left pane fixes to the true browser edge. Meant for a
69
+ full-page app layout where SplitPane is the outermost positioned element
70
+ (e.g. the root Rail). Shown here at a reduced height so the fixed left pane
71
+ is still visible inside the story canvas.
72
+ -->
73
+ <Story name="Anchor Viewport" asChild>
74
+ <div
75
+ class="relative h-[420px] overflow-hidden rounded-lg border border-md-sys-color-outline-variant"
76
+ >
77
+ <SplitPane anchor="viewport" resizable={false} leftWidth={280}>
78
+ {#snippet left()}
79
+ <div class="p-4">
80
+ <p class="md-sys-typescale-title-small mb-2">Fixed to viewport edge</p>
81
+ <p class="text-md-sys-color-on-surface-variant text-sm">
82
+ In a real page this pane is position: fixed to the true left edge of the browser.
83
+ </p>
84
+ </div>
85
+ {/snippet}
86
+ {#snippet right()}
87
+ <div class="p-4">
88
+ <p class="text-md-sys-color-on-surface-variant text-sm">Right / main content.</p>
89
+ </div>
90
+ {/snippet}
91
+ </SplitPane>
92
+ </div>
93
+ </Story>
94
+
95
+ <!--
96
+ anchor="sticky" — left/right sit in normal flex flow and the left pane
97
+ sticks (position: sticky) as the page scrolls. Used for a sidebar nested
98
+ inside other already-offset content, e.g. the docs secondary nav. Paired
99
+ with resizable={false} for a static, non-draggable sidebar. Needs a tall
100
+ scrollable right pane to demonstrate the sticky behaviour.
101
+ -->
102
+ <Story name="Anchor Sticky" asChild>
103
+ <div class="h-[420px] overflow-y-auto rounded-lg border border-md-sys-color-outline-variant">
104
+ <SplitPane anchor="sticky" resizable={false} leftWidth={240} rounded={false}>
105
+ {#snippet left()}
106
+ <div class="p-4">
107
+ <p class="md-sys-typescale-title-small mb-2">Sticky nav</p>
108
+ <p class="text-md-sys-color-on-surface-variant text-sm">
109
+ Stays pinned to the top of the viewport while the right pane scrolls past it.
110
+ </p>
111
+ </div>
112
+ {/snippet}
113
+ {#snippet right()}
114
+ <div class="flex flex-col gap-4 p-4">
115
+ {#each Array(12) as _, i}
116
+ <div class="bg-md-sys-color-surface-container-high rounded-lg p-6">
117
+ Scrollable content block {i + 1}
118
+ </div>
119
+ {/each}
120
+ </div>
121
+ {/snippet}
122
+ </SplitPane>
123
+ </div>
124
+ </Story>
@@ -0,0 +1,19 @@
1
+ import SplitPane from './SplitPane.svelte';
2
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
+ $$bindings?: Bindings;
5
+ } & Exports;
6
+ (internal: unknown, props: {
7
+ $$events?: Events;
8
+ $$slots?: Slots;
9
+ }): Exports & {
10
+ $set?: any;
11
+ $on?: any;
12
+ };
13
+ z_$$bindings?: Bindings;
14
+ }
15
+ declare const SplitPane: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type SplitPane = InstanceType<typeof SplitPane>;
19
+ export default SplitPane;
@@ -1,6 +1,7 @@
1
1
  <!--
2
2
  @component
3
- SplitPane provides a resizable two-column layout.
3
+ SplitPane provides a two-column layout. Set `resizable={false}` for a static,
4
+ non-draggable sidebar (e.g. a docs nav) instead of a user-resizable split.
4
5
  -->
5
6
  <script lang="ts">
6
7
  import type { SplitPaneProps } from './types.js';
@@ -16,6 +17,7 @@ SplitPane provides a resizable two-column layout.
16
17
  leftWidth = $bindable(396),
17
18
  full = true,
18
19
  rounded = true,
20
+ resizable = true,
19
21
  minLeft = 280,
20
22
  maxLeft = 720,
21
23
  storageKey = 'splitpane:leftWidth',
@@ -76,7 +78,7 @@ SplitPane provides a resizable two-column layout.
76
78
  onMount(() => {
77
79
  mounted = true;
78
80
 
79
- if (persist && typeof localStorage !== 'undefined') {
81
+ if (resizable && persist && typeof localStorage !== 'undefined') {
80
82
  const stored = Number(localStorage.getItem(storageKey));
81
83
  if (!Number.isNaN(stored)) {
82
84
  leftWidth = clampWidth(stored);
@@ -96,7 +98,7 @@ SplitPane provides a resizable two-column layout.
96
98
  });
97
99
 
98
100
  $effect(() => {
99
- if (!persist || typeof localStorage === 'undefined') return;
101
+ if (!resizable || !persist || typeof localStorage === 'undefined') return;
100
102
  localStorage.setItem(storageKey, String(leftWidth));
101
103
  });
102
104
  </script>
@@ -113,25 +115,27 @@ SplitPane provides a resizable two-column layout.
113
115
  </div>
114
116
 
115
117
  <!-- DRAG HANDLE -->
116
- <div
117
- class={hCls()}
118
- role="separator"
119
- aria-orientation="vertical"
120
- aria-valuenow={leftWidth}
121
- aria-valuemin={minLeft}
122
- aria-valuemax={maxLeft}
123
- style={`left: ${handleOffset};`}
124
- onpointerdown={startDrag}
125
- onpointermove={moveDrag}
126
- onpointerup={endDrag}
127
- onpointercancel={endDrag}
128
- >
118
+ {#if resizable}
129
119
  <div
130
- class={handleGrip({
131
- class: clsx(dragging && 'w-0.5')
132
- })}
133
- ></div>
134
- </div>
120
+ class={hCls()}
121
+ role="separator"
122
+ aria-orientation="vertical"
123
+ aria-valuenow={leftWidth}
124
+ aria-valuemin={minLeft}
125
+ aria-valuemax={maxLeft}
126
+ style={anchor === 'sticky' ? undefined : `left: ${handleOffset};`}
127
+ onpointerdown={startDrag}
128
+ onpointermove={moveDrag}
129
+ onpointerup={endDrag}
130
+ onpointercancel={endDrag}
131
+ >
132
+ <div
133
+ class={handleGrip({
134
+ class: clsx(dragging && 'w-0.5')
135
+ })}
136
+ ></div>
137
+ </div>
138
+ {/if}
135
139
 
136
140
  <!-- RIGHT PANE -->
137
141
  <div class={rCls({ class: clsx(rightClass, rightMobileClass) })}>
@@ -1,5 +1,8 @@
1
1
  import type { SplitPaneProps } from './types.js';
2
- /** SplitPane provides a resizable two-column layout. */
2
+ /**
3
+ * SplitPane provides a two-column layout. Set `resizable={false}` for a static,
4
+ * non-draggable sidebar (e.g. a docs nav) instead of a user-resizable split.
5
+ */
3
6
  declare const SplitPane: import("svelte").Component<SplitPaneProps, {}, "leftWidth">;
4
7
  type SplitPane = ReturnType<typeof SplitPane>;
5
8
  export default SplitPane;
@@ -0,0 +1,113 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import SupportingPane from './SupportingPane.svelte';
4
+
5
+ const { Story } = defineMeta({
6
+ title: 'Containers/Supporting Pane',
7
+ component: SupportingPane,
8
+ argTypes: {
9
+ anchor: {
10
+ control: 'select',
11
+ options: ['viewport', 'parent']
12
+ },
13
+ position: {
14
+ control: 'select',
15
+ options: ['left', 'right']
16
+ },
17
+ centered: { control: 'boolean' },
18
+ rounded: { control: 'boolean' },
19
+ full: { control: 'boolean' },
20
+ gap: {
21
+ control: 'select',
22
+ options: ['none', 'sm', 'md', 'lg']
23
+ },
24
+ collapsible: { control: 'boolean' },
25
+ supportingWidth: { control: 'number' },
26
+ mobileSheet: { control: 'boolean' }
27
+ },
28
+ args: {
29
+ anchor: 'parent',
30
+ position: 'right',
31
+ centered: false,
32
+ rounded: false,
33
+ full: false,
34
+ gap: 'md',
35
+ collapsible: false,
36
+ supportingWidth: 320,
37
+ mobileSheet: false
38
+ }
39
+ });
40
+ </script>
41
+
42
+ <!--
43
+ anchor="parent" demos meaningfully in a bounded box (classic sticky-while-
44
+ scrolling-the-page behaviour). anchor="viewport" — used by the docs pages'
45
+ own "On this page" TOC — fixes the supporting pane to the true browser edge,
46
+ which only reads correctly on a real scrolling page, not inside Storybook's
47
+ iframe canvas.
48
+ -->
49
+ <Story name="Playground">
50
+ {#snippet template(args)}
51
+ <SupportingPane {...args}>
52
+ {#snippet main()}
53
+ <div class="flex flex-col gap-4 p-4">
54
+ {#each Array(6) as _, i}
55
+ <div class="bg-md-sys-color-surface-container-high rounded-lg p-6">
56
+ Main content block {i + 1}
57
+ </div>
58
+ {/each}
59
+ </div>
60
+ {/snippet}
61
+ {#snippet supporting()}
62
+ <div class="bg-md-sys-color-surface-container rounded-lg p-4">
63
+ <p class="md-sys-typescale-title-small mb-2">Supporting content</p>
64
+ <p class="text-md-sys-color-on-surface-variant text-sm">
65
+ Fixed-width side panel — e.g. a table of contents or contextual actions.
66
+ </p>
67
+ </div>
68
+ {/snippet}
69
+ </SupportingPane>
70
+ {/snippet}
71
+ </Story>
72
+
73
+ <Story name="Collapsible" asChild>
74
+ <SupportingPane anchor="parent" position="right" collapsible>
75
+ {#snippet main()}
76
+ <div class="flex flex-col gap-4 p-4">
77
+ {#each Array(4) as _, i}
78
+ <div class="bg-md-sys-color-surface-container-high rounded-lg p-6">
79
+ Main content block {i + 1}
80
+ </div>
81
+ {/each}
82
+ </div>
83
+ {/snippet}
84
+ {#snippet supporting()}
85
+ <div class="bg-md-sys-color-surface-container rounded-lg p-4">
86
+ <p class="md-sys-typescale-title-small mb-2">Collapsible panel</p>
87
+ <p class="text-md-sys-color-on-surface-variant text-sm">
88
+ A toggle button appears over the main pane to hide/show this panel (desktop only).
89
+ </p>
90
+ </div>
91
+ {/snippet}
92
+ </SupportingPane>
93
+ </Story>
94
+
95
+ <Story name="Position Left" asChild>
96
+ <SupportingPane anchor="parent" position="left">
97
+ {#snippet main()}
98
+ <div class="flex flex-col gap-4 p-4">
99
+ {#each Array(4) as _, i}
100
+ <div class="bg-md-sys-color-surface-container-high rounded-lg p-6">
101
+ Main content block {i + 1}
102
+ </div>
103
+ {/each}
104
+ </div>
105
+ {/snippet}
106
+ {#snippet supporting()}
107
+ <div class="bg-md-sys-color-surface-container rounded-lg p-4">
108
+ <p class="md-sys-typescale-title-small mb-2">Supporting content</p>
109
+ <p class="text-md-sys-color-on-surface-variant text-sm">Panel rendered on the left.</p>
110
+ </div>
111
+ {/snippet}
112
+ </SupportingPane>
113
+ </Story>
@@ -0,0 +1,19 @@
1
+ import SupportingPane from './SupportingPane.svelte';
2
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
+ $$bindings?: Bindings;
5
+ } & Exports;
6
+ (internal: unknown, props: {
7
+ $$events?: Events;
8
+ $$slots?: Slots;
9
+ }): Exports & {
10
+ $set?: any;
11
+ $on?: any;
12
+ };
13
+ z_$$bindings?: Bindings;
14
+ }
15
+ declare const SupportingPane: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type SupportingPane = InstanceType<typeof SupportingPane>;
19
+ export default SupportingPane;
@@ -13,6 +13,12 @@ export declare const splitPane: import("tailwind-variants").TVReturnType<{
13
13
  left: string;
14
14
  handle: string;
15
15
  };
16
+ sticky: {
17
+ base: string;
18
+ left: string;
19
+ handle: string;
20
+ right: string;
21
+ };
16
22
  };
17
23
  full: {
18
24
  true: {
@@ -49,6 +55,12 @@ export declare const splitPane: import("tailwind-variants").TVReturnType<{
49
55
  left: string;
50
56
  handle: string;
51
57
  };
58
+ sticky: {
59
+ base: string;
60
+ left: string;
61
+ handle: string;
62
+ right: string;
63
+ };
52
64
  };
53
65
  full: {
54
66
  true: {
@@ -85,6 +97,12 @@ export declare const splitPane: import("tailwind-variants").TVReturnType<{
85
97
  left: string;
86
98
  handle: string;
87
99
  };
100
+ sticky: {
101
+ base: string;
102
+ left: string;
103
+ handle: string;
104
+ right: string;
105
+ };
88
106
  };
89
107
  full: {
90
108
  true: {
@@ -18,6 +18,17 @@ export const splitPane = tv({
18
18
  base: 'relative',
19
19
  left: 'md:absolute',
20
20
  handle: 'absolute'
21
+ },
22
+ // In-flow flex row: left/right sit side by side and the left pane sticks
23
+ // as the page scrolls, instead of being fixed/absolute. Use for nested
24
+ // sidebars (e.g. a secondary docs nav) that live inside an already
25
+ // width-offset content column, where "viewport" would fix to the true
26
+ // left edge and "parent" would lose sticky-while-scrolling behaviour.
27
+ sticky: {
28
+ base: 'md:flex md:flex-row',
29
+ left: 'shrink-0 md:sticky md:top-0 md:self-start md:max-h-dvh',
30
+ handle: 'md:sticky md:top-0 md:ml-0 md:h-dvh md:translate-x-0 md:self-start',
31
+ right: 'md:ml-0 md:grow md:min-w-0'
21
32
  }
22
33
  },
23
34
  full: {
@@ -36,8 +36,24 @@ export type SplitPaneProps = SplitPaneVariants & DivAttrs & {
36
36
  persist?: boolean;
37
37
  /** Called when the component wants to update external footer padding. */
38
38
  onPaddingChange?: (padding: number) => void;
39
- /** The anchor mode for the panes. */
39
+ /**
40
+ * The anchor mode for the panes.
41
+ * - `viewport` — left pane fixes to the true browser edge (full-page app layouts).
42
+ * - `parent` — left pane is absolutely positioned within a `relative` ancestor
43
+ * (embedded/contained demos, does not stick while scrolling).
44
+ * - `sticky` — left/right sit in normal flex flow and the left pane sticks as
45
+ * the page scrolls. Use for a sidebar nested inside other offset content
46
+ * (e.g. a secondary docs nav inside an already Rail-offset column), where
47
+ * `viewport` would fix to the wrong edge and `parent` would lose sticky
48
+ * behaviour. Typically paired with `resizable={false}`.
49
+ */
40
50
  anchor?: SplitPaneVariants['anchor'];
51
+ /**
52
+ * Whether the split is user-draggable. Set to `false` for a static sidebar —
53
+ * hides the drag handle and skips width persistence.
54
+ * @default true
55
+ */
56
+ resizable?: boolean;
41
57
  /**
42
58
  * Which pane to show on mobile. On desktop both panes are always visible.
43
59
  * @default 'right'