+# Solid Material
+
+> Material 3 Expressive components built for SolidJS
+
Solid Material is a component library implementing [Material 3 Expressive][url-m3] using [SolidJS][url-solid].
+It provides components closely following the guidelines of Material 3 Expressive.
+
+
+
+
+
+
+## ✨ Features
+
+- **Material 3 Expressive:** Components implementing the M3 Expressive spec
+
+- **Layout Components:** Responsive components for navigation (rail and bar) and content in multiple panes
+
+- **Dynamic Theming:** Baseline and dynamic color schemes based on a source color
+
+- **Design Tokens:** Includes tokens for easings, springs, and static palette color tokens for 11 colors
+
+- **View Transitions:** Animated transitions between pages using the View Transitions API
+
+- **Few dependencies**: Avoids external dependencies as much as possible by making use of modern CSS features like
+ scroll-driven animations and anchor positioning
+
+## 🚀 Quick Start
+
+Add the components and icons to your SolidJS project:
+
+```bash
+pnpm add @solidmaterial/material @solidmaterial/icons
+```
+
+Add the Vite plugin to be able to import SVG files as SolidJS components:
+
+```bash
+pnpm add -D @solidmaterial/vite-plugin-solid-svg
+```
+
+vite.config.ts:
+
+```ts
+import solidSvg from '@solidmaterial/vite-plugin-solid-svg';
+import { defineConfig } from 'vite';
+import solidPlugin from 'vite-plugin-solid';
-## Packages
+export default defineConfig({
+ plugins: [solidPlugin(), solidSvg()]
+});
+```
-| Package |
-| --------------------------------------------------- |
-| [@solidmaterial/material][url-sm-material] |
-| [@solidmaterial/icons][url-sm-icons] |
-| [@solidmaterial/vite-plugin-solid-svg][url-sm-vite] |
+src/index.tsx:
-## License
+```tsx
+import { MaterialButton } from '@solidmaterial/material/components/button';
+import { MaterialCard, MaterialCardBody } from '@solidmaterial/material/components/card';
+import { H1 } from '@solidmaterial/material/components/typography';
+import { MaterialTheme } from '@solidmaterial/material/styling';
+import { render } from 'solid-js/web';
+
+import TotalDissolvedSolidsIcon from '@solidmaterial/icons/400/outlined/total_dissolved_solids.svg';
+
+const App = () => {
+ return (
+
+
+
+
+
+
+
+ Material 3 Expressive with Solid Material
+
+
+ Get started
+
+
+
+
+
+
+ );
+};
+
+render(() => , document.querySelector('#root')!);
+```
+
+## 📖 Documentation
+
+You can read more detailed instructions on how to display navigation components (like the navigation rail and bar)
+or how to put your content in flexible or fixed panes on the [Get Started][url-get-started] page.
+
+To view all the components you can use, visit the [Storybook][url-storybook] site.
+
+## 📦 Packages
+
+| Package | Description |
+| --------------------------------------------------- | --------------------------------------------------------------------------------- |
+| [@solidmaterial/material][url-sm-material] | Core and layout components for M3 Expressive, design tokens, and view transitions |
+| [@solidmaterial/icons][url-sm-icons] | Icons from Material Symbols as SVG files |
+| [@solidmaterial/vite-plugin-solid-svg][url-sm-vite] | Vite plugin to import SVG files as SolidJS components |
+
+## 🛠️ Development
+
+```bash
+pnpm install # Install dependencies
+pnpm dev # Run development environment of the landing page
+
+pnpm build # Create a production build of the packages
+pnpm preview # View the production build of the landing page
+
+pnpm lint # Run oxlint on all packages
+pnpm format:check # Check formatting using oxfmt
+pnpm format # Format all files using oxfmt
+
+pnpm install:webkit # Install the WebKit browser needed to run the tests
+pnpm test # Run the tests of all packages
+```
+
+## ❤️ Credits
+
+This project uses the web components of [@material/web][url-material-web] for
+the ripple, focus ring, and elevation effects, and for some of the components
+often used in forms (like the checkbox, text field, etc.), these are mere bindings
+around the respective web components.
+See the [README.md][url-sm-material] file for the exact list.
+
+The [Material Symbols][url-google-mdi] in [@solidmaterial/icons][url-sm-icons] were made by Google.
+
+Many of the SolidJS components make use of the primitives from [Solid Primitives][url-solid-primitives].
+Much thanks to their contributors.
+
+## 🤖 AI?
+
+The components (TypeScript and CSS) in [@solidmaterial/material][url-sm-material] were handcrafted,
+but files which don't end up in its production build (like the calculator demo app) may
+or may not have been partially assisted by small LLMs.
+
+## 📜 License
This project is licensed under the Apache 2.0 license.
+[url-get-started]: https://solid-material.sites.deploybase.eu/get-started
+[url-google-mdi]: https://github.com/google/material-design-icons
+[url-material-web]: https://material-web.dev/
[url-m3]: https://m3.material.io/
-[url-solid]: https://www.solidjs.com/
[url-sm-material]: https://github.com/mprovok/solid-material/tree/main/packages/material#readme
[url-sm-icons]: https://github.com/mprovok/solid-material/tree/main/packages/icons#readme
[url-sm-vite]: https://github.com/mprovok/solid-material/tree/main/packages/vite-plugin-solid-svg#readme
+[url-solid]: https://www.solidjs.com/
+[url-solid-primitives]: https://primitives.solidjs.community/
+[url-storybook]: https://solid-material.sites.deploybase.eu/storybook/
diff --git a/apps/hero/README.md b/apps/hero/README.md
new file mode 100644
index 00000000..85b19224
--- /dev/null
+++ b/apps/hero/README.md
@@ -0,0 +1,3 @@
+# Example app using Solid Material for hero section of README.md
+
+A small app used for a visual hero in the README.md in the root of the repository.
diff --git a/apps/hero/index.html b/apps/hero/index.html
new file mode 100644
index 00000000..7bf9f625
--- /dev/null
+++ b/apps/hero/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/hero/package.json b/apps/hero/package.json
new file mode 100644
index 00000000..12df2b71
--- /dev/null
+++ b/apps/hero/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@solidmaterial/hero",
+ "version": "1.0.0",
+ "private": true,
+ "description": "Example app using Solid Material for hero section of README.md",
+ "bugs": "https://github.com/mprovok/solid-material/issues",
+ "license": "Apache-2.0",
+ "author": "Mark Provo Kluit",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/mprovok/solid-material.git",
+ "directory": "apps/calculator"
+ },
+ "type": "module",
+ "scripts": {
+ "dev": "vite --host",
+ "lint": "oxlint --report-unused-disable-directives",
+ "lint:fix": "oxlint --fix --report-unused-disable-directives"
+ },
+ "dependencies": {
+ "@fontsource/google-sans-flex": "5.2.3",
+ "@solidjs/router": "catalog:solid",
+ "@solidmaterial/icons": "workspace:*",
+ "@solidmaterial/material": "workspace:*",
+ "solid-js": "catalog:solid"
+ },
+ "devDependencies": {
+ "@solidmaterial/vite-plugin-solid-svg": "workspace:*",
+ "vite": "catalog:vite",
+ "vite-plugin-solid": "catalog:vite"
+ }
+}
diff --git a/apps/hero/src/NavigationLayout.tsx b/apps/hero/src/NavigationLayout.tsx
new file mode 100644
index 00000000..6b29d346
--- /dev/null
+++ b/apps/hero/src/NavigationLayout.tsx
@@ -0,0 +1,56 @@
+import type { MaterialNavigationItemType } from '@solidmaterial/material/components/navigation-item';
+import type { FlowComponent } from 'solid-js';
+
+import { MaterialNavigationLayout } from '@solidmaterial/material/layouts';
+
+import HomeFillIcon from '@solidmaterial/icons/400/outlined/home-fill.svg';
+import HomeIcon from '@solidmaterial/icons/400/outlined/home.svg';
+import SettingsFillIcon from '@solidmaterial/icons/400/outlined/settings-fill.svg';
+import SettingsIcon from '@solidmaterial/icons/400/outlined/settings.svg';
+import ImageFillIcon from '@solidmaterial/icons/400/rounded/image-fill.svg';
+import ImageIcon from '@solidmaterial/icons/400/rounded/image.svg';
+import VideocamFillIcon from '@solidmaterial/icons/400/rounded/videocam-fill.svg';
+import VideocamIcon from '@solidmaterial/icons/400/rounded/videocam.svg';
+
+export const NavigationLayout: FlowComponent = props => {
+ const items: MaterialNavigationItemType[] = [
+ {
+ label: 'Label 1',
+ icon: HomeIcon,
+ activeIcon: HomeFillIcon,
+ href: '/',
+ end: true
+ },
+ {
+ label: 'Label 2',
+ icon: ImageIcon,
+ activeIcon: ImageFillIcon,
+ href: '/label-2'
+ },
+ {
+ label: 'Label 3',
+ icon: VideocamIcon,
+ activeIcon: VideocamFillIcon,
+ href: '/label-3'
+ },
+ {
+ label: 'Label 4',
+ icon: SettingsIcon,
+ activeIcon: SettingsFillIcon,
+ href: '/label-4'
+ }
+ ];
+
+ return (
+
+ {props.children}
+
+ );
+};
diff --git a/apps/hero/src/components/examples/ExampleConnectedButtonGroup.tsx b/apps/hero/src/components/examples/ExampleConnectedButtonGroup.tsx
new file mode 100644
index 00000000..8877e7c7
--- /dev/null
+++ b/apps/hero/src/components/examples/ExampleConnectedButtonGroup.tsx
@@ -0,0 +1,33 @@
+import type { VoidComponent } from 'solid-js';
+
+import { MaterialButton } from '@solidmaterial/material/components/button';
+import { MaterialButtonGroup } from '@solidmaterial/material/components/button-group';
+import { Breakpoints } from '@solidmaterial/material/utils';
+import { Index, createSignal } from 'solid-js';
+
+const LABELS = ['One', 'Two', 'Three'];
+const LABELS_MOBILE = ['A', 'B', 'C'];
+
+export const ExampleConnectedButtonGroup: VoidComponent = () => {
+ const [toggled, setToggled] = createSignal(0);
+
+ const isMobile = () => Breakpoints.isCompactWidth();
+
+ return (
+
+
+ {(_, index) => (
+ setToggled(index)}
+ >
+ {isMobile() ? LABELS_MOBILE[index] : LABELS[index]}
+
+ )}
+
+
+ );
+};
diff --git a/apps/hero/src/components/examples/ExampleList.tsx b/apps/hero/src/components/examples/ExampleList.tsx
new file mode 100644
index 00000000..ebc81031
--- /dev/null
+++ b/apps/hero/src/components/examples/ExampleList.tsx
@@ -0,0 +1,27 @@
+import type { VoidComponent } from 'solid-js';
+
+import { MaterialIcon } from '@solidmaterial/material/components/icon';
+import { MaterialList, MaterialListItem } from '@solidmaterial/material/components/list';
+import { Index } from 'solid-js';
+
+import StarIcon from '@solidmaterial/icons/400/outlined/star.svg';
+
+const LABELS = ['Segmented', 'Item', 'List'];
+
+export const ExampleList: VoidComponent = () => (
+
+
+ {(_, index) => (
+
+
+
+ }
+ >
+ {LABELS[index]}
+
+ )}
+
+
+);
diff --git a/apps/hero/src/components/examples/ExampleSearchBar.tsx b/apps/hero/src/components/examples/ExampleSearchBar.tsx
new file mode 100644
index 00000000..55c8cfa4
--- /dev/null
+++ b/apps/hero/src/components/examples/ExampleSearchBar.tsx
@@ -0,0 +1,36 @@
+import type { VoidComponent } from 'solid-js';
+
+import { MaterialIconButton } from '@solidmaterial/material/components/icon-button';
+import { MaterialSearch, MaterialSearchBar } from '@solidmaterial/material/components/search';
+import { Match, Switch, createSignal } from 'solid-js';
+
+import FavoriteIcon from '@solidmaterial/icons/400/outlined/favorite.svg';
+import MapIcon from '@solidmaterial/icons/400/outlined/map.svg';
+import StarIcon from '@solidmaterial/icons/400/outlined/star.svg';
+
+export const ExampleSearchBar: VoidComponent = () => {
+ const [searchInput, setSearchInput] = createSignal('');
+
+ return (
+
+ (
+
+
+ } />
+
+
+ } />
+ } />
+
+
+ )}
+ />
+
+ );
+};
diff --git a/apps/hero/src/components/examples/ExampleSplitButtonMenu.tsx b/apps/hero/src/components/examples/ExampleSplitButtonMenu.tsx
new file mode 100644
index 00000000..c387d9c6
--- /dev/null
+++ b/apps/hero/src/components/examples/ExampleSplitButtonMenu.tsx
@@ -0,0 +1,31 @@
+import type { VoidComponent } from 'solid-js';
+
+import { showSnack } from '@solidmaterial/material/components/snackbar';
+import { MaterialSplitButtonMenu } from '@solidmaterial/material/components/split-button';
+import { createSignal } from 'solid-js';
+
+import EditIcon from '@solidmaterial/icons/400/outlined/edit.svg';
+
+const LABELS = ['One', 'Two', 'Three'];
+
+export const ExampleSplitButtonMenu: VoidComponent = () => {
+ const [label, setLabel] = createSignal('Split button');
+
+ const onClick = () => {
+ showSnack({
+ text: `Clicked on ${label()}`,
+ dismissable: false
+ });
+ };
+
+ return (
+ }
+ menuItems={LABELS.map(text => ({ label: text, onClick: () => setLabel(text) }))}
+ onClick={onClick}
+ >
+ {label()}
+
+ );
+};
diff --git a/apps/hero/src/index.css b/apps/hero/src/index.css
new file mode 100644
index 00000000..5ba86ce4
--- /dev/null
+++ b/apps/hero/src/index.css
@@ -0,0 +1,7 @@
+:root {
+ --md-ref-typeface-brand: Google Sans Flex, system-ui;
+}
+
+body {
+ background-color: var(--md-sys-color-background);
+}
diff --git a/apps/hero/src/index.tsx b/apps/hero/src/index.tsx
new file mode 100644
index 00000000..dfd9dab7
--- /dev/null
+++ b/apps/hero/src/index.tsx
@@ -0,0 +1,31 @@
+import '@fontsource/google-sans-flex/latin-400.css';
+import '@fontsource/google-sans-flex/latin-500.css';
+import type { ParentComponent } from 'solid-js';
+
+import { Route, Router } from '@solidjs/router';
+import { MaterialTheme } from '@solidmaterial/material/styling';
+import { render } from 'solid-js/web';
+
+import './index.css';
+
+import { NavigationLayout } from './NavigationLayout';
+import RouteHome from './routes';
+
+const RootLayout: ParentComponent = props => {
+ return (
+
+ {props.children}
+
+ );
+};
+
+const root = document.querySelector('#root');
+
+render(
+ () => (
+
+
+
+ ),
+ root!
+);
diff --git a/apps/hero/src/logo/Logo.module.css b/apps/hero/src/logo/Logo.module.css
new file mode 100644
index 00000000..d10131d8
--- /dev/null
+++ b/apps/hero/src/logo/Logo.module.css
@@ -0,0 +1,31 @@
+:root {
+ --logo-color: var(--md-sys-color-on-primary);
+ --logo-background-color: var(--md-sys-color-primary);
+
+ --logo-size: 32rem;
+ --text-size: 18rem;
+}
+
+.logo {
+ width: var(--logo-size);
+ height: var(--logo-size);
+ background-color: var(--logo-background-color);
+ border-radius: 50%;
+ mask-image: url(@solidmaterial/icons/400/outlined/total_dissolved_solids.svg), linear-gradient(#000, #000);
+ mask-size: 100% 100%;
+ mask-composite: exclude;
+}
+
+.container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+
+ h1 {
+ font-size: var(--text-size);
+ line-height: var(--text-size);
+ font-weight: 500;
+ color: var(--logo-background-color);
+ }
+}
diff --git a/apps/hero/src/logo/Logo.tsx b/apps/hero/src/logo/Logo.tsx
new file mode 100644
index 00000000..25d07649
--- /dev/null
+++ b/apps/hero/src/logo/Logo.tsx
@@ -0,0 +1,33 @@
+import type { VoidComponent } from 'solid-js';
+
+import { H1 } from '@solidmaterial/material/components/typography';
+
+import styles from './Logo.module.css';
+
+/**
+ * Renders the logo of Solid Material
+ *
+ * In Chrome Devtools, right click on the element and then choose "Capture node screenshot"
+ * to export it to a PNG file.
+ *
+ * 1. Open the PNG file in Inkscape
+ * 2. Select the image with the select tool (S) and then choose Path > Trace Bitmap...
+ * 3. On tab Multicolor:
+ * - Detection mode: Colors
+ * - Enable "Remove background"
+ * - Speckles: 1000
+ * - Smooth corners: 1.0
+ * - Optimize: 0.0
+ * 4. Delete the image element and save it to an SVG file
+ * 5. Edit the SVG file, add fill and stroke attributes and clean up the XML
+ */
+export const Logo: VoidComponent = () => {
+ return (
+