Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
30b2a6f
new navigation rail component, minor website redesign, and more tweaks
mrsproutt Aug 22, 2025
a2043f2
Merge branch 'KTibow:main' into main
mrsproutt Aug 22, 2025
fed6e85
ripple effect, demos.md, llms.txt
mrsproutt Aug 23, 2025
18cbee6
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Aug 23, 2025
49b12f2
actually export the components
mrsproutt Aug 23, 2025
57e0844
non spec colors for better contrast
mrsproutt Aug 23, 2025
96fbe02
navrail modal and sidesheet shadow, update demos.md
mrsproutt Aug 23, 2025
b4ea580
z-index organization
mrsproutt Aug 23, 2025
ca67ebe
more small tweaks
mrsproutt Aug 23, 2025
7121a41
ungoof css
mrsproutt Aug 23, 2025
15acfba
ripples
mrsproutt Aug 24, 2025
08de5d9
rename icon imports
mrsproutt Aug 24, 2025
3ca17e8
pointer events auto
mrsproutt Aug 24, 2025
4e9eefc
fix icon imports, generate demos, combine attributes
mrsproutt Aug 24, 2025
8166446
generating messes stuff up
mrsproutt Aug 24, 2025
ccbcb04
remove themed scrollbars
mrsproutt Aug 24, 2025
315d695
update demo
mrsproutt Aug 24, 2025
16ca778
fighting eslint, font stuff
mrsproutt Aug 24, 2025
74ee5fd
rtl support
mrsproutt Aug 24, 2025
56fa079
badge updates
mrsproutt Aug 24, 2025
3f28df7
fix <a> nesting, update demo
mrsproutt Aug 24, 2025
93833dd
font troubles, aria
mrsproutt Aug 24, 2025
6e88087
type button
mrsproutt Aug 24, 2025
e536ad3
types
mrsproutt Aug 24, 2025
0bb09d2
remove onclick
mrsproutt Aug 24, 2025
5332c79
remove onclick
mrsproutt Aug 24, 2025
99a5cb7
remove onclick
mrsproutt Aug 24, 2025
ced001f
color mixing
mrsproutt Aug 24, 2025
3d0a97c
color mixing
mrsproutt Aug 24, 2025
dc03e6f
color mixing
mrsproutt Aug 24, 2025
536885e
color mixing
mrsproutt Aug 24, 2025
6e87846
use util-easing
mrsproutt Aug 24, 2025
805b234
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Aug 24, 2025
037098e
Merge branch 'KTibow:main' into main
mrsproutt Aug 24, 2025
1cd0dd9
new alignments
mrsproutt Aug 24, 2025
815ff87
demos and small tweaks
mrsproutt Aug 24, 2025
ae72d61
wip: some animations, progress on menutoggle
mrsproutt Aug 27, 2025
024a9b7
finish navigationtoggle
mrsproutt Aug 27, 2025
ef6df3f
Merge branch 'KTibow:main' into main
mrsproutt Aug 27, 2025
79e215c
color fixes based off figma design
mrsproutt Aug 27, 2025
15cb131
small tweaks
mrsproutt Aug 27, 2025
10a84bb
make the links span the full width
mrsproutt Aug 27, 2025
39fc5b5
more tweaks
mrsproutt Aug 27, 2025
abfca0a
fix mobile support and centering
mrsproutt Aug 28, 2025
38046d1
figma saves the day (again)
mrsproutt Aug 28, 2025
fdcf17b
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Aug 28, 2025
975453e
wraping and centering text
mrsproutt Aug 28, 2025
79d17a7
a11y is horrible
mrsproutt Aug 30, 2025
46c0e73
Merge branch 'main' into main
mrsproutt Aug 30, 2025
c3b36f9
use addBadge
mrsproutt Aug 30, 2025
9e8da13
Merge branch 'KTibow:main' into main
mrsproutt Sep 2, 2025
14cd33c
Merge branch 'KTibow:main' into main
mrsproutt Sep 4, 2025
67dfaf6
prevent duplicate navigationtoggle ids
mrsproutt Sep 4, 2025
94d280d
wip: animations
mrsproutt Sep 6, 2025
d12663f
fix stuff caused by animations
mrsproutt Sep 6, 2025
ebb4f92
remove layout changes per request
mrsproutt Nov 4, 2025
8368060
how was I supposed to know that button did
mrsproutt Nov 4, 2025
c7020f8
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Nov 4, 2025
f46eead
Revert "how was I supposed to know that button did"
mrsproutt Nov 4, 2025
59e6085
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Nov 4, 2025
ab2be7f
ok, I genuinely have no fucking clue what is going on
mrsproutt Nov 4, 2025
d70b359
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Nov 4, 2025
13b0997
im digging my own grave
mrsproutt Nov 4, 2025
3565cae
please god
mrsproutt Nov 4, 2025
ea3b9c1
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Nov 4, 2025
027e1b3
holdon now
mrsproutt Nov 4, 2025
e63f720
Merge branch 'KTibow:main' into main
mrsproutt Nov 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ export default ts.config(
"svelte/prefer-writable-derived": "off",
},
},
{
files: ["src/routes/*.svelte"],
rules: {
"svelte/prefer-writable-derived": "off",
},
},
{
files: ["src/routes/*.svelte"],
rules: {
Expand Down
5 changes: 1 addition & 4 deletions src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
<meta charset="utf-8" />
<meta property="og:image" content="%sveltekit.assets%/og.png" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap"
/>
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
Expand Down
70 changes: 70 additions & 0 deletions src/demos.md
Original file line number Diff line number Diff line change
Expand Up @@ -1195,3 +1195,73 @@ let errored = $state(false);
{/if}
{/snippet}
```

## Navigation Rail

Minimal demo:

```svelte
<NavigationRail>
{#snippet fab(open)}
<FAB color="primary-container" icon={iconEdit} text={open ? "Label" : ""} onclick={() => alert("!")} />
{/snippet}

<NavigationRailItem label="Label" icon={iconStars} active />

<NavigationRailItem label="Label" icon={iconStarsOutline} />

<NavigationRailItem label="Label" icon={addBadge(iconStarsOutline, 3)} />

<NavigationRailItem label="Label" icon={addBadge(iconStarsOutline)} />
</NavigationRail>
```

Full demo:

```use
DateField
DateFieldOutlined
```

```ts
let collapse = $state<'full' | 'normal' | 'no'>('normal');
let alignment = $state<'top' | 'center'>('center');
let modal = $state<boolean>(false);
```

```svelte
<label>
<Switch bind:checked={modal} />
{modal ? "Modal" : "Normal"}
</label>
<label>
<Arrows list={['top', 'center']} bind:value={alignment} />
{alignment == "top"
? "Top"
: "Center"}
</label>
<label>
<Arrows list={['normal', 'full', 'no']} bind:value={collapse} />
{collapse == "normal"
? "Collapse"
: collapse == "full"
? "Fully Collapse"
: "Don't Collapse"}
</label>

{#snippet demo()}
<NavigationRail {collapse} {alignment} {modal}>
{#snippet fab(open)}
<FAB color="primary-container" icon={iconEdit} text={open ? "Label" : ""} onclick={() => {}} />
{/snippet}

<NavigationRailItem label="Label" icon={iconStar} active />

<NavigationRailItem label="Label" icon={iconStarsOutline} />

<NavigationRailItem label="Label" icon={addBadge(iconStarsOutline, 3)} />

<NavigationRailItem label="Label" icon={addBadge(iconStarsOutline)} />
</NavigationRail>
{/snippet}
```
49 changes: 41 additions & 8 deletions src/lib/buttons/FAB.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
let {
color = "primary",
elevation = "normal",
showLabel = "auto",
size = "normal",
icon,
text,
Expand All @@ -32,22 +33,24 @@
| "secondary"
| "tertiary";
elevation?: "normal" | "lowered" | "none";
showLabel?: boolean | "auto";
} & ContentProps &
ButtonAttrs = $props();
</script>

<button
type="button"
class="m3-container m3-font-label-large color-{color} size-{size} elevation-{elevation}"
class:label={showLabel === null ? !!text : showLabel}
{...extra}
>
<Layer />
{#if icon}
<Icon {icon} />
{/if}
{#if text}
{text}
<div class="icon">
<Icon {icon} />
</div>
{/if}
<span>{text}</span>
</button>

<style>
Expand All @@ -61,10 +64,31 @@
border: none;
position: relative;
overflow: hidden;
flex-direction: row;

align-items: center;
justify-content: center;
cursor: pointer;
transition: width var(--m3-util-easing);
}

.m3-container > span {
transition: opacity var(--m3-util-easing-fast);
}

.m3-container.label {
width: max-content;
}

.m3-container:has(span:empty) {
gap: 0;
}

.m3-container.label > span {
opacity: 1;
}

.m3-container:not(.label) > span {
opacity: 0;
}

.elevation-normal {
Expand All @@ -76,28 +100,37 @@

.size-small {
height: 2.5rem;
width: 2.5rem;
padding: 0.5rem;
gap: 0.5rem;
border-radius: var(--m3-fab-small-shape);
}

.size-normal {
height: 3.5rem;
width: 3.5rem;
padding: 1rem;
gap: 0.75rem;
border-radius: var(--m3-fab-normal-shape);
}

.size-large {
height: 6rem;
width: 6rem;
padding: 1.875rem;
gap: 1.875rem;
border-radius: var(--m3-fab-large-shape);
}
.size-small > :global(svg),
.size-normal > :global(svg) {

.size-small > .icon > :global(svg),
.size-normal > .icon > :global(svg),
.size-small > .icon,
.size-normal > .icon {
width: 1.5rem;
height: 1.5rem;
}
.size-large > :global(svg) {
.size-large > .icon > :global(svg),
.size-large > .icon {
width: 2.25rem;
height: 2.25rem;
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export { default as WavyLinearProgressEstimate } from "./forms/WavyLinearProgres

export { default as NavCMLX } from "./nav/NavCMLX.svelte";
export { default as NavCMLXItem } from "./nav/NavCMLXItem.svelte";
export { default as NavigationRail } from "./nav/NavigationRail.svelte";
export { default as NavigationRailItem } from "./nav/NavigationRailItem.svelte";
export { default as Tabs } from "./nav/Tabs.svelte";
export { default as TabsLink } from "./nav/TabsLink.svelte";
export { default as VariableTabs } from "./nav/VariableTabs.svelte";
Expand Down
2 changes: 1 addition & 1 deletion src/lib/misc/recommended-styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@
::selection {
background-color: rgb(var(--m3-scheme-tertiary-container));
color: rgb(var(--m3-scheme-on-tertiary-container));
}
}
1 change: 1 addition & 0 deletions src/lib/misc/styles.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* Needed for elevation to work */
:root {
interpolate-size: allow-keywords;
--m3-util-elevation-0: none;
--m3-util-elevation-1:
/* Spot */
Expand Down
156 changes: 156 additions & 0 deletions src/lib/nav/NavigationRail.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<script lang="ts">
import type { Snippet } from "svelte";

import NavigationToggle from "./NavigationToggle.svelte";

let {
open = $bindable(false),
collapse = 'normal',
modal = false,
alignment = 'top',
fab,
children,
} = $props<{
open?: boolean;
collapse?: 'normal' | 'full' | 'no' | boolean;
modal?: boolean;
alignment?: 'top' | 'center';
fab?: Snippet<[open: boolean]>;
children: Snippet;
}>();

const onkeydown = (e: KeyboardEvent) => {
if (modal && open && e.key === 'Escape') {
e.preventDefault();

open = false;
}
}
</script>

<svelte:window {onkeydown} />

<div class="m3-container">
<div class="rail" class:open={open && (collapse !== 'no' && collapse !== false)} class:centered={alignment === 'center'} class:fullyCollapse={collapse === 'full'} class:modal>
{#if (collapse !== 'no' && collapse !== false) || fab}
<div class="top">
{#if collapse !== 'no' && collapse !== false}
<NavigationToggle mode={collapse === 'full' ? 'inline-detached' : 'inline'} bind:active={open} />
{/if}

{#if fab}
<div>
{@render fab(open && (collapse !== 'no' && collapse !== false))}
</div>
{/if}
</div>
{/if}

<div class="items" role="menu" aria-labelledby="m3-navigationtoggle">
{@render children()}
</div>
</div>

{#if modal}
<!--svelte-ignore a11y_no_static_element_interactions--><!--svelte-ignore a11y_click_events_have_key_events-->
<div class="shadow" onclick={() => open = false}></div>
{/if}
</div>

<style>
.m3-container {
width: 96px;
height: 100%;
transition: width var(--m3-util-easing-spatial);
}

.m3-container:has(>.rail.fullyCollapse) {
width: 0;
}

.rail.open,
.m3-container:has(>.rail.open:not(.modal)),
.rail.fullyCollapse {
width: 220px;
}

.rail {
display: flex;
flex-direction: column;
gap: 40px;
width: 96px;
height: 100%;
padding: 44px 0px 56px 0px;
transition: all var(--m3-util-easing-spatial);
overflow: hidden;
overflow-y: auto;
}

.rail:not(.open).fullyCollapse {
pointer-events: none;
width: 0px;
}

.rail:not(.open).fullyCollapse > .top > :not(:global(.toggle)),
.rail:not(.open).fullyCollapse > .items {
opacity: 0;
}

.rail.modal {
border-start-end-radius: var(--m3-util-rounding-large);
border-end-end-radius: var(--m3-util-rounding-large);
}

.rail.modal.open,
.rail:not(.modal) {
background: rgb(var(--m3-scheme-surface-container));
}

.top {
margin-inline: 20px;
display: flex;
flex-direction: column;
z-index: 1;
}

.items {
display: flex;
flex-direction: column;
gap: 16px;
width: 96px;
height: 100%;
align-self: stretch;
transition: gap var(--m3-util-easing-fast), width 0.2s;
height: 100%;
}

.rail.open > .items,
.rail.fullyCollapse > .items {
gap: 0px;
width: 220px;
}

.rail.centered > .items {
justify-content: center;
}

.shadow {
position: fixed;
inset: 0;
z-index: -1;
background: rgb(var(--m3-scheme-scrim) / 0.5);
transition: opacity var(--m3-util-easing);
}

.rail:not(.open) + .shadow {
opacity: 0;
pointer-events: none;
}

@media (width <= 300px) {
.rail.open {
border-radius: 0px !important;
width: 100vw;
}
}
</style>
Loading
Loading