├── .changeset
├── README.md
└── config.json
├── .github
└── workflows
│ └── release.yml
├── .gitignore
├── .npmrc
├── .vscode
└── settings.json
├── LICENSE.md
├── README.md
├── WHY.md
├── apps
└── demo
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── next.config.mjs
│ ├── package.json
│ ├── pnpm-lock.yaml
│ ├── postcss.config.mjs
│ ├── public
│ ├── next.svg
│ └── vercel.svg
│ ├── src
│ ├── app
│ │ ├── favicon.ico
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ ├── opengraph-image.jpg
│ │ ├── page.tsx
│ │ └── providers.tsx
│ └── components
│ │ └── GitHubButton
│ │ └── index.tsx
│ ├── tailwind.config.ts
│ └── tsconfig.json
├── package.json
├── packages
├── eslint-config
│ ├── README.md
│ ├── library.js
│ ├── next.js
│ ├── package.json
│ └── react-internal.js
├── typescript-config
│ ├── base.json
│ ├── nextjs.json
│ ├── package.json
│ └── react-library.json
└── ui
│ ├── .eslintrc.cjs
│ ├── CHANGELOG.md
│ ├── LICENSE.md
│ ├── README.md
│ ├── WHY.md
│ ├── package.json
│ ├── src
│ ├── app-bar
│ │ └── index.tsx
│ ├── badge
│ │ └── index.tsx
│ ├── box
│ │ └── index.tsx
│ ├── button
│ │ └── index.tsx
│ ├── card
│ │ └── index.tsx
│ ├── checkbox
│ │ └── index.tsx
│ ├── chip
│ │ └── index.tsx
│ ├── container
│ │ └── index.tsx
│ ├── dialog
│ │ └── index.tsx
│ ├── divider
│ │ └── index.tsx
│ ├── elevation
│ │ └── index.tsx
│ ├── fab
│ │ └── index.tsx
│ ├── focus-ring
│ │ └── index.tsx
│ ├── icon-button
│ │ └── index.tsx
│ ├── icon
│ │ └── index.tsx
│ ├── internal
│ │ ├── aria
│ │ │ ├── aria.js
│ │ │ └── delegate.js
│ │ ├── controller
│ │ │ ├── attachable-controller.js
│ │ │ ├── form-submitter.js
│ │ │ ├── is-rtl.js
│ │ │ └── string-converter.js
│ │ ├── events
│ │ │ ├── dispatch-hooks.js
│ │ │ ├── form-label-activation.js
│ │ │ └── redispatch-event.js
│ │ └── motion
│ │ │ └── animation.js
│ ├── item
│ │ └── index.tsx
│ ├── list
│ │ └── index.tsx
│ ├── menu
│ │ └── index.tsx
│ ├── navigation-bar
│ │ └── index.tsx
│ ├── navigation-drawer
│ │ └── index.tsx
│ ├── navigation-rail
│ │ ├── index.tsx
│ │ └── web-component
│ │ │ ├── harness.js
│ │ │ ├── internal
│ │ │ ├── constants.js
│ │ │ ├── navigation-rail-styles.js
│ │ │ ├── navigation-rail.js
│ │ │ └── state.js
│ │ │ └── navigation-rail.js
│ ├── navigation-tab
│ │ └── index.tsx
│ ├── progress
│ │ └── index.tsx
│ ├── radio
│ │ └── index.tsx
│ ├── ripple
│ │ └── index.tsx
│ ├── segmented-button
│ │ └── index.tsx
│ ├── select
│ │ └── index.tsx
│ ├── sheet
│ │ └── index.tsx
│ ├── slider
│ │ └── index.tsx
│ ├── snackbar
│ │ └── index.tsx
│ ├── stack
│ │ └── index.tsx
│ ├── switch
│ │ └── index.tsx
│ ├── tabs
│ │ └── index.tsx
│ ├── text-field
│ │ └── index.tsx
│ ├── typography
│ │ └── index.tsx
│ └── utils
│ │ └── index.ts
│ ├── tsconfig.json
│ ├── tsconfig.lint.json
│ ├── tsup.config.ts
│ └── turbo
│ └── generators
│ ├── config.ts
│ └── templates
│ └── component.hbs
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── turbo.json
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": false,
5 | "fixed": [],
6 | "linked": [],
7 | "access": "restricted",
8 | "baseBranch": "main",
9 | "updateInternalDependencies": "patch",
10 | "ignore": []
11 | }
12 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | concurrency: ${{ github.workflow }}-${{ github.ref }}
9 |
10 | jobs:
11 | release:
12 | name: Release
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout Repo
16 | uses: actions/checkout@v4
17 |
18 | - name: Setup pnpm 8
19 | uses: pnpm/action-setup@v3
20 | with:
21 | version: 8
22 |
23 | - name: Setup Node.js 20.x
24 | uses: actions/setup-node@v4
25 | with:
26 | node-version: 20.x
27 |
28 | - name: Install Dependencies
29 | run: pnpm i
30 |
31 | - name: Create Release Pull Request or Publish to npm
32 | id: changesets
33 | uses: changesets/action@v1
34 | with:
35 | # This expects you to have a script called release which does a build for your packages and calls changeset publish
36 | publish: pnpm publish-packages
37 | env:
38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
40 |
41 | # - name: Send a Slack notification if a publish happens
42 | # if: steps.changesets.outputs.published == 'true'
43 | # # You can do something when a publish happens.
44 | # run: my-slack-bot send-notification --message "A new version of ${GITHUB_REPOSITORY} was published!"
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # Dependencies
4 | node_modules
5 | .pnp
6 | .pnp.js
7 |
8 | # Local env files
9 | .env
10 | .env.local
11 | .env.development.local
12 | .env.test.local
13 | .env.production.local
14 |
15 | # Testing
16 | coverage
17 |
18 | # Turbo
19 | .turbo
20 |
21 | # Vercel
22 | .vercel
23 |
24 | # Build Outputs
25 | .next/
26 | out/
27 | build
28 | dist
29 |
30 |
31 | # Debug
32 | npm-debug.log*
33 | yarn-debug.log*
34 | yarn-error.log*
35 |
36 | # Misc
37 | .DS_Store
38 | *.pem
39 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grayhatdevelopers/material-web-components-react/979eb8d5adaa27bc4af47f4867c5c0d45873349e/.npmrc
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "eslint.workingDirectories": [
3 | {
4 | "mode": "auto"
5 | }
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Grayhat Developers Private Limited
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Material Web Components for React
2 |
3 | [](https://www.npmjs.com/package/material-web-components-react)
4 | 
5 | [](https://material-web.dev)
6 | [](https://material-web-components-react.grayhat.studio)
7 |
8 | A thin React wrapper over [@material/web](https://github.com/material-components/material-web/). Aims to be a locally-installable, accessible and customizable Material standard for React. Recommended to use with [MUI](https://mui.com/). Free. Open Source. **Looking for maintainers**.
9 |
10 | 
11 |
12 | ## Installation
13 |
14 | To use Material Web Components for React as a **library** in your project, run:
15 |
16 | ```sh
17 | npm install material-web-components-react
18 | ```
19 |
20 | ## Usage
21 |
22 | Here's a general example of how the components can be used:
23 |
24 | ```tsx
25 | import React from 'react';
26 | import Button from 'material-web-components-react/button';
27 |
28 | function Example() {
29 | return (
30 |
31 |
32 |
33 | );
34 | }
35 | ```
36 |
37 | For a detailed reference on usage, you might want to check out the source code of the [NextJS demo](https://github.com/grayhatdevelopers/material-web-components-react/blob/main/apps/demo/src/app/page.tsx). It's simple!
38 |
39 | Under the hood, this library simply uses the official [@material/web](https://github.com/material-components/material-web/) components. Visit [the official Material Web Components docs](https://github.com/material-components/material-web/blob/main/docs/intro.md) to learn how to use those components. The props remain the same!
40 |
41 | ## Contributing
42 |
43 | ### Local Development Setup 💻
44 |
45 | 1. Fork this repository
46 |
47 | 2. Clone your forked copy of the project
48 |
49 | 3. Change to the project directory
50 |
51 | 4. To get started, ensure you have pnpm installed globally, run the following command:
52 |
53 | ```
54 | npm install -g pnpm
55 | ```
56 |
57 | 5. To install all project dependencies, run the following command:
58 |
59 | ```
60 | pnpm i
61 | ```
62 |
63 | 6. To build the project, run the following commands (these needs to be done only once):
64 |
65 | ```
66 | cd packages/ui
67 | ```
68 |
69 | ```
70 | pnpm build
71 | ```
72 |
73 | 7. Return to the monorepo root:
74 |
75 | ```
76 | cd ../../
77 | ```
78 |
79 | 8. To run the code locally, run the following code:
80 |
81 | ```
82 | pnpm dev
83 | ```
84 |
85 | This will run the demo app on http://localhost:3000, and whenever you update the library, the app should rebuild
86 |
87 | ### Roadmap 🚀
88 |
89 | - [ ] Make sure all native Web Components are properly working
90 | - [x] Demo all components
91 | - [x] Add all missing events
92 | - [ ] [Need help] Add theming (design tokens) through Tailwind (i.e. remove all ts-ignores) (https://github.com/grayhatdevelopers/material-web-components-react/pull/2)
93 | - [x] Resolve SSR/SSG issues, make compatible with NextJS (i.e. remove all dynamic imports)
94 | - [x] Separate the demo from the actual UI code
95 | - [x] Make installable as a package.
96 | - [ ] [Need help] Make installable as code-in-project, like shadcn/ui, so developers have more control (https://github.com/grayhatdevelopers/material-web-components-react/pull/11)
97 | - [ ] [Need help] Add better TypeScript support (https://github.com/grayhatdevelopers/material-web-components-react/issues/12)
98 | - [ ] Sync with upstream (i.e. https://github.com/material-components/material-web/blob/main/docs/intro.md) through webhooks and automations
99 | - [ ] Use [Copybara](https://github.com/google/copybara) (or good ol' GitHub webhooks) to automate syncing with upstream
100 | - [ ] Use [lit-analyzer](https://www.npmjs.com/package/lit-analyzer) to see which Web Components changed. Perhaps mix with an LLM to compare existing and newly autogenerated code.
101 | - [ ] Create a PR with the new Component code.
102 | - [ ] Mix this library with Tailwind and BaseUI in order to complete missing Components which may prove useful for building production applications
103 | - [x] App Bars
104 | - [x] Top App Bar
105 | - [x] Bottom App Bar
106 | - [x] Stack
107 | - [x] Box
108 | - [x] Navigation Rail
109 | - [x] Container
110 | - [x] Typography
111 |
112 | ### Credits
113 |
114 | Huge shout out to Elizabeth Mitchell ([asyncLiz](https://github.com/asyncliz/)) and the rest of the Material Design team for their awesome Web Components implementation.
115 |
116 | Thank you [Travis Reeder](https://github.com/treeder) for your Web Component implementation of Navigation Rail. I had to copy it to this project. I couldn't use yours directly because it would import `@material/web` again and bring conflicts.
117 |
118 | Thanks for making the crappy, brain-dead wrapper components:
119 |
120 | - [ChatGPT](https://chatgpt.com/share/574a9601-8927-4992-884e-16c58f24a982)
121 |
122 | Thanks for improving the demo:
123 |
124 | - [TalhaHere12](https://github.com/TalhaHere12)
125 |
126 | Thanks for building BottomSheet and Snackbar:
127 |
128 | - [Aroonaongithhub](https://github.com/Aroonaongithhub/)
129 |
--------------------------------------------------------------------------------
/WHY.md:
--------------------------------------------------------------------------------
1 | # Why another React Material library?
2 |
3 | Let's evaluate our options.
4 |
5 | ## Libraries which implement Material 2
6 |
7 | These are libraries which were built upon an older specification of Google's official Material Web Components, but those followed the Material 2 specification. Such libraries are either discontinued, or slowly dying out. Those include, but are not limited to:
8 |
9 | - material-tailwind (currently looks like the best!): https://github.com/creativetimofficial/material-tailwind
10 | - material-web-components-react (Official): https://github.com/material-components/material-components-web-react
11 | - react-mdc: https://react-mdc.github.io/#/
12 | - muicss: https://www.muicss.com/
13 |
14 | Check out more libraries listed at https://m2.material.io/develop/web/guides/framework-wrappers .
15 |
16 | ## Libraries which implement Material 3
17 |
18 | These are libraries which implement Material 3, the latest specification (so far) from the Material Design team. Some look great, others... not really. These include, but are not limited to:
19 |
20 | - beercss (insane work, totally framework independent. I should consider using this): https://www.beercss.com/
21 | - actifyjs (currently looks like the best React-only implementation!): https://actifyjs.com/
22 |
23 | I'd love to add more. Send in a Pull Request if you'd like to contribute more.
24 |
25 | ## Popular, but slow
26 |
27 | [MUI](https://mui.com/) is the library which fits this category. While I love their work, [they are slow](https://github.com/mui/material-ui/issues/29345) at keeping up with latest design philosophies and practices (And rightly so! A lot of software depends on their stability). MUI has announced better Material 3 support in Q4 of 2024. Too far away, at least for when this project started!
28 |
29 | For MUI, and similar libraries, I recommend using them alongside this library to "fill in" the missing parts.
30 |
31 | ## The future: Web Components!
32 |
33 | The library we use under the hood, [@material/web](https://github.com/material-components/material-web) fits here. Web Components are the future, we must accept it. But while we build towards that future, it's obvious that current applications must be maintained. Hence the existence of this library. Our aim would be to keep this library synced with this underlying new technology as much as possible, so when we transition, it's seamless!
34 |
35 | Another great library to plug in here would be: https://www.mdui.org/en/docs/2/
36 |
--------------------------------------------------------------------------------
/apps/demo/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/apps/demo/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/apps/demo/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # index
2 |
3 | ## 0.1.22
4 |
5 | ### Patch Changes
6 |
7 | - Updated dependencies
8 | - material-web-components-react@0.3.17
9 |
10 | ## 0.1.21
11 |
12 | ### Patch Changes
13 |
14 | - Updated dependencies
15 | - material-web-components-react@0.3.16
16 |
17 | ## 0.1.20
18 |
19 | ### Patch Changes
20 |
21 | - Updated dependencies
22 | - material-web-components-react@0.3.15
23 |
24 | ## 0.1.19
25 |
26 | ### Patch Changes
27 |
28 | - Updated dependencies
29 | - material-web-components-react@0.3.14
30 |
31 | ## 0.1.18
32 |
33 | ### Patch Changes
34 |
35 | - Updated dependencies
36 | - material-web-components-react@0.3.13
37 |
38 | ## 0.1.17
39 |
40 | ### Patch Changes
41 |
42 | - Updated dependencies
43 | - material-web-components-react@0.3.12
44 |
45 | ## 0.1.16
46 |
47 | ### Patch Changes
48 |
49 | - Updated dependencies
50 | - material-web-components-react@0.3.11
51 |
52 | ## 0.1.15
53 |
54 | ### Patch Changes
55 |
56 | - Updated dependencies
57 | - material-web-components-react@0.3.10
58 |
59 | ## 0.1.14
60 |
61 | ### Patch Changes
62 |
63 | - Updated dependencies
64 | - material-web-components-react@0.3.9
65 |
66 | ## 0.1.13
67 |
68 | ### Patch Changes
69 |
70 | - Updated dependencies
71 | - material-web-components-react@0.3.8
72 |
73 | ## 0.1.12
74 |
75 | ### Patch Changes
76 |
77 | - Updated dependencies
78 | - material-web-components-react@0.3.7
79 |
80 | ## 0.1.11
81 |
82 | ### Patch Changes
83 |
84 | - Updated dependencies
85 | - material-web-components-react@0.3.6
86 |
87 | ## 0.1.10
88 |
89 | ### Patch Changes
90 |
91 | - Updated dependencies
92 | - material-web-components-react@0.3.5
93 |
94 | ## 0.1.9
95 |
96 | ### Patch Changes
97 |
98 | - Updated dependencies [d84f889]
99 | - material-web-components-react@0.3.4
100 |
101 | ## 0.1.8
102 |
103 | ### Patch Changes
104 |
105 | - Updated dependencies
106 | - material-web-components-react@0.3.3
107 |
108 | ## 0.1.7
109 |
110 | ### Patch Changes
111 |
112 | - Updated dependencies [b0c82e6]
113 | - material-web-components-react@0.3.2
114 |
115 | ## 0.1.6
116 |
117 | ### Patch Changes
118 |
119 | - Updated dependencies
120 | - material-web-components-react@0.3.1
121 |
122 | ## 0.1.5
123 |
124 | ### Patch Changes
125 |
126 | - Updated dependencies
127 | - material-web-components-react@0.3.0
128 |
129 | ## 0.1.4
130 |
131 | ### Patch Changes
132 |
133 | - Updated dependencies
134 | - material-web-components-react@0.2.8
135 |
136 | ## 0.1.3
137 |
138 | ### Patch Changes
139 |
140 | - Updated dependencies [1b43630]
141 | - Updated dependencies [f895d71]
142 | - material-web-components-react@0.2.7
143 |
144 | ## 0.1.2
145 |
146 | ### Patch Changes
147 |
148 | - Updated dependencies [20b989b]
149 | - material-web-components-react@0.2.6
150 |
151 | ## 0.1.1
152 |
153 | ### Patch Changes
154 |
155 | - Updated dependencies
156 | - material-web-components-react@0.2.5
157 |
--------------------------------------------------------------------------------
/apps/demo/README.md:
--------------------------------------------------------------------------------
1 | # Material Web Components for React
2 |
3 | This demo shows how native Material Web Components can be used in NextJS/React, TypeScript and Tailwind, with minimal configuration.
4 |
5 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
6 |
7 | ## Getting Started
8 |
9 | First, run the development server:
10 |
11 | ```bash
12 | npm run dev
13 | # or
14 | yarn dev
15 | # or
16 | pnpm dev
17 | # or
18 | bun dev
19 | ```
20 |
21 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
22 |
23 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
24 |
25 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
26 |
27 | ## Learn More
28 |
29 | To learn more about Next.js, take a look at the following resources:
30 |
31 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
32 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
33 |
34 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
35 |
36 | ## Deploy on Vercel
37 |
38 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
39 |
40 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
41 |
--------------------------------------------------------------------------------
/apps/demo/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 |
3 | import withLitSSR from "@lit-labs/nextjs";
4 |
5 | const nextConfig = {
6 | reactStrictMode: true,
7 | swcMinify: true,
8 | };
9 |
10 | export default withLitSSR()(nextConfig);
11 |
--------------------------------------------------------------------------------
/apps/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "index",
3 | "version": "0.1.22",
4 | "private": true,
5 | "packageManager": "pnpm@9.1.0",
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start",
10 | "lint": "next lint"
11 | },
12 | "dependencies": {
13 | "@lit-labs/nextjs": "^0.2.0",
14 | "@lit-labs/ssr-react": "^0.3.0",
15 | "material-web-components-react": "workspace:*",
16 | "material-symbols": "^0.21.2",
17 | "next": "^14.2.4",
18 | "react": "^18",
19 | "react-dom": "^18",
20 | "react-element-to-jsx-string": "^15.0.0",
21 | "react-github-btn": "latest",
22 | "tailwindcss": "^3.4.1"
23 | },
24 | "devDependencies": {
25 | "@types/node": "^20",
26 | "@types/react": "^18",
27 | "@types/react-dom": "^18",
28 | "eslint": "^8",
29 | "eslint-config-next": "14.2.4",
30 | "postcss": "^8",
31 | "typescript": "^5"
32 | },
33 | "description": "This demo shows how native Material Web Components can be used in NextJS/React, TypeScript and Tailwind, with minimal configuration.",
34 | "main": "index.js",
35 | "author": "",
36 | "license": "ISC"
37 | }
38 |
--------------------------------------------------------------------------------
/apps/demo/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/apps/demo/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/demo/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/demo/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grayhatdevelopers/material-web-components-react/979eb8d5adaa27bc4af47f4867c5c0d45873349e/apps/demo/src/app/favicon.ico
--------------------------------------------------------------------------------
/apps/demo/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 247, 242, 250;
8 | --background-end-rgb: 247, 242, 250;
9 | }
10 |
11 | body {
12 | color: rgb(var(--foreground-rgb));
13 | background: linear-gradient(
14 | to bottom,
15 | transparent,
16 | rgb(var(--background-end-rgb))
17 | )
18 | rgb(var(--background-start-rgb));
19 | }
20 |
21 | @layer utilities {
22 | .text-balance {
23 | text-wrap: balance;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/apps/demo/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import "material-symbols";
2 |
3 | import type { Metadata } from "next";
4 | import { Roboto } from "next/font/google";
5 | import "./globals.css";
6 | import SnackbarClientProvider from "./providers";
7 |
8 | // @TODO: Get static fonts to work somehow, to prevent FOUC
9 | const roboto = Roboto({
10 | weight: ["400", "500", "700"],
11 | style: ["normal", "italic"],
12 | subsets: ["latin"],
13 | display: "swap",
14 | variable: "--font-roboto",
15 | });
16 |
17 | export const metadata: Metadata = {
18 | title: "Demo - Material Web Components for React",
19 | description:
20 | "Fast, performant, Material 3 compliant. The lightest Material library out there for ReactJS.",
21 | };
22 |
23 | export default function RootLayout({
24 | children,
25 | }: Readonly<{
26 | children: React.ReactNode;
27 | }>) {
28 | return (
29 |
30 |
31 |
35 | {/* From BaseUI: https://mui.com/base-ui/getting-started/usage/#responsive-meta-tag */}
36 |
37 |
38 |
39 |
40 |
41 |
42 | {children}
43 |
44 |
45 |
46 |
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/apps/demo/src/app/opengraph-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grayhatdevelopers/material-web-components-react/979eb8d5adaa27bc4af47f4867c5c0d45873349e/apps/demo/src/app/opengraph-image.jpg
--------------------------------------------------------------------------------
/apps/demo/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import AppBar from "material-web-components-react/app-bar";
4 | import Badge from "material-web-components-react/badge";
5 | import Button from "material-web-components-react/button";
6 | import Card from "material-web-components-react/card";
7 | import Checkbox from "material-web-components-react/checkbox";
8 | import Chip, { ChipSet } from "material-web-components-react/chip";
9 | import Dialog from "material-web-components-react/dialog";
10 | import Divider from "material-web-components-react/divider";
11 | import Elevation from "material-web-components-react/elevation";
12 | import FAB from "material-web-components-react/fab";
13 | import FocusRing from "material-web-components-react/focus-ring";
14 | import Icon from "material-web-components-react/icon";
15 | import IconButton from "material-web-components-react/icon-button";
16 | import Item from "material-web-components-react/item";
17 | import List, { ListItem } from "material-web-components-react/list";
18 | import Menu, { MenuItem } from "material-web-components-react/menu";
19 | import NavigationBar from "material-web-components-react/navigation-bar";
20 | import { NavigationDrawerModal } from "material-web-components-react/navigation-drawer";
21 | import NavigationTab from "material-web-components-react/navigation-tab";
22 | import NavigationRail from "material-web-components-react/navigation-rail";
23 | import Progress from "material-web-components-react/progress";
24 | import Radio from "material-web-components-react/radio";
25 | import Ripple from "material-web-components-react/ripple";
26 | import SegmentedButton, {
27 | SegmentedButtonSet,
28 | } from "material-web-components-react/segmented-button";
29 | import Select, { SelectOption } from "material-web-components-react/select";
30 | import { BottomSheet } from "material-web-components-react/sheet";
31 | import Slider from "material-web-components-react/slider";
32 | import Switch from "material-web-components-react/switch";
33 | import Tabs, { PrimaryTab } from "material-web-components-react/tabs";
34 | import TextField from "material-web-components-react/text-field";
35 | import {snackbar} from "material-web-components-react/snackbar";
36 |
37 | import Stack from "material-web-components-react/stack";
38 |
39 | // @ts-expect-error
40 | import pkgJson from "material-web-components-react/package.json?module=json";
41 |
42 | import React, { useEffect, useState } from "react";
43 | import { renderToString } from "react-dom/server";
44 | import Link from "next/link";
45 |
46 | const Column = ({ children, ...props }: { children: any; id: string }) => {
47 | return (
48 |
52 | {children}
53 |
54 | );
55 | };
56 |
57 | const DemoSection = ({ title, children }: { title: any; children: any }) => {
58 | return (
59 |
60 |
{title}
61 | {children}
62 |
63 | );
64 | };
65 |
66 | const NavigationContent = ({
67 | setShowNavigationModal,
68 | }: {
69 | setShowNavigationModal: (value: boolean) => void;
70 | }) => {
71 | return (
72 | <>
73 |
74 | - setShowNavigationModal?.(false)}
76 | className="mt-4 rounded-r-full w-full flex flex-row justify-between pr-2 select-none"
77 | >
78 |
79 |
80 | stacked_inbox
81 | All inboxes
82 |
83 |
84 |
85 |
86 |
87 | - setShowNavigationModal?.(false)}
89 | className="mt-4 rounded-r-full w-full flex flex-row justify-between pr-2 select-none"
90 | >
91 |
92 |
93 | inbox
94 | Primary
95 |
96 |
97 |
98 |
99 | - setShowNavigationModal?.(false)}
101 | className="mt-4 rounded-r-full w-full flex flex-row justify-between pr-2 select-none"
102 | >
103 |
104 |
105 | group
106 | Social
107 |
108 |
109 |
110 |
111 | - setShowNavigationModal?.(false)}
113 | className="mt-4 rounded-r-full w-full flex flex-row justify-between pr-2 select-none"
114 | >
115 |
116 |
117 | info
118 | Updates
119 |
120 |
121 |
122 |
123 | - setShowNavigationModal?.(false)}
125 | className="mt-4 rounded-r-full w-full flex flex-row justify-between pr-2 select-none"
126 | >
127 |
128 |
129 | forum
130 | Forums
131 |
132 |
133 |
134 | All labels
135 |
136 | - setShowNavigationModal?.(false)}
138 | className="mt-4 rounded-r-full w-full flex flex-row justify-between pr-2 select-none"
139 | >
140 |
141 |
142 | star
143 | Starred
144 |
145 |
146 |
147 | >
148 | );
149 | };
150 |
151 | const ComponentDemo = ({
152 | title,
153 | docsLink,
154 | children,
155 | codeContainerProps,
156 | }: {
157 | title: any;
158 | docsLink?: any;
159 | children: any;
160 | codeContainerProps?: any;
161 | }) => {
162 | const [showCode, setShowCode] = useState(false);
163 |
164 | return (
165 |
166 |
167 |
{title}
168 |
169 | {/*
*/}
177 |
178 |
182 | {showCode
183 | ? renderToString(children)
184 | .replaceAll("", "\n")
185 | .replaceAll("", "\n")
186 | : children}
187 |
188 |
189 | );
190 | };
191 |
192 | export default function Home() {
193 | const [showDialog, setShowDialog] = useState(false);
194 | const [showBottomSheet, setShowBottomSheet] = useState(false);
195 | const [showModalBottomSheet, setShowModalBottomSheet] = useState(false);
196 | const [showMenu, setShowMenu] = useState(false);
197 | const [isPlayingProgressIndicators, setIsPlayingProgressIndicators] =
198 | useState(false);
199 |
200 | const [showNavigationModal, setShowNavigationModal] = useState(false);
201 | const [extendRail, setExtendRail] = useState(false);
202 |
203 | const [isExpanded, setIsExpanded] = useState(false);
204 |
205 | useEffect(() => {
206 | if (typeof window !== "undefined") {
207 | if (showNavigationModal) {
208 | document.body.style.position = "fixed";
209 | document.body.style.top = `-${window.scrollY}px`;
210 | } else {
211 | const scrollY = document.body.style.top;
212 | document.body.style.position = "";
213 | document.body.style.top = "";
214 | document.body.style.position = "";
215 | document.body.style.top = "";
216 | window.scrollTo(0, parseInt(scrollY || "0") * -1);
217 | }
218 | }
219 | }, [showNavigationModal]);
220 |
221 | return (
222 | <>
223 |
247 |
322 |
323 |
324 |
325 | {/* @ts-ignore */}
326 |
327 | {/* @ts-ignore */}
328 |
329 |
332 |
335 |
338 |
341 |
344 |
345 | {/* @ts-ignore */}
346 |
347 |
351 |
355 |
359 |
363 |
367 |
368 | {/* @ts-ignore */}
369 |
370 |
373 |
376 |
379 |
382 |
385 |
386 |
387 |
388 |
389 |
390 | {/* @ts-ignore */}
391 |
392 |
393 | add
394 |
395 |
396 | add
397 |
398 |
399 | add
400 |
401 |
402 | add
403 |
404 |
405 |
406 |
407 |
408 | {/* @ts-ignore */}
409 |
410 |
411 | Settings
412 |
413 |
414 |
415 | Settings
416 |
417 |
418 |
419 | Settings
420 |
421 |
422 |
423 | Settings
424 |
425 |
426 | {/* @ts-ignore */}
427 |
428 |
429 | Settings
430 |
431 |
432 |
433 | Settings
434 |
435 |
436 |
437 | Settings
438 |
439 |
440 |
441 | Settings
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 | calendar_view_day
450 |
451 |
452 | calendar_view_week
453 |
454 |
455 | calendar_view_month
456 |
457 |
458 | calendar_today
459 |
460 |
461 |
462 |
463 |
464 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 | {/* @ts-ignore */}
478 |
479 |
480 |
485 |
486 | notifications
487 |
488 |
489 |
490 |
495 |
496 | mail
497 |
498 |
499 |
500 |
501 |
502 |
503 | {/* @ts-ignore */}
504 |
505 |
507 | setIsPlayingProgressIndicators((oldState) => !oldState)
508 | }
509 | >
510 |
515 | {isPlayingProgressIndicators ? "pause" : "play_arrow"}
516 |
517 |
518 |
522 |
527 |
528 |
529 |
530 |
531 |
538 |
539 |
540 |
547 |
548 |
549 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
{
563 | // @ts-expect-error
564 | const snackbarId = snackbar.show("Tapped on an element.", {
565 | actionText: "Close me",
566 | onAction: () => snackbar.dismiss(snackbarId),
567 | className: 'bg-[#313033] text-[#F4EFF4]'
568 | })
569 | }}>
570 | Tap me for ripple effect (also for a snackbar!)
571 |
572 |
573 |
574 |
575 |
576 |
577 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
594 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 | more_vert
608 |
609 |
610 |
611 | Elevated
612 |
613 |
614 |
618 |
619 |
620 | more_vert
621 |
622 |
623 |
624 | Filled
625 |
626 |
627 |
631 |
632 |
633 | more_vert
634 |
635 |
636 |
637 | Outlined
638 |
639 |
640 |
641 |
642 |
650 |
651 |
652 |
653 |
654 |
655 |
661 |
672 |
673 |
674 |
675 |
676 |
681 | Fruits
682 |
683 | Apple
684 | Banana
685 |
686 | Cucumber
687 |
688 | Cucumbers are long green fruits that are just as long as this
689 | multi-line description
690 |
691 |
692 |
697 | Shop for Kiwis
698 |
699 | This will link you out in a new tab
700 |
701 | open_in_new
702 |
703 |
704 |
705 |
706 |
707 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
732 |
736 |
740 |
744 |
745 |
746 |
747 |
748 |
749 |
750 | event
751 | Assist
752 |
753 |
754 | Filter
755 |
756 |
757 | Input
758 |
759 |
760 | Suggestion
761 |
762 |
763 |
764 |
765 | event
766 | Assist
767 |
768 |
769 | Filter
770 |
771 |
772 | Input
773 |
774 |
775 | Suggestion
776 |
777 |
778 |
779 |
780 |
781 |
811 |
812 |
813 |
814 |
815 |
816 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
831 |
832 |
833 |
834 |
835 |
842 |
850 |
851 |
852 |
853 |
857 |
858 |
859 |
860 |
861 |
862 |
863 |
864 | videocam
865 | Video
866 |
867 |
868 | image
869 | Photos
870 |
871 |
872 | music_note
873 | Audio
874 |
875 |
876 |
877 |
878 |
879 |
880 |
881 |
887 | home
888 |
889 | home
890 |
891 |
892 |
898 | group
899 |
900 | group
901 |
902 | {
907 | alert("clicked!");
908 | }}
909 | >
910 |
916 | notifications
917 |
918 | notifications
919 |
920 |
921 |
922 |
923 |
924 |
925 |
931 |
932 |
933 |
934 |
935 |
936 |
937 |
938 | arrow_back
939 |
940 |
941 |
942 | Center-aligned
943 |
944 |
945 |
946 | more_vert
947 |
948 |
949 |
950 |
951 |
952 |
953 | arrow_back
954 |
955 |
956 | Small
957 |
958 |
959 | attach_file
960 |
961 |
962 | calendar_today
963 |
964 |
965 | more_vert
966 |
967 |
968 |
969 |
970 |
974 | setIsExpanded(expanded)
975 | }
976 | style={{
977 | // @ts-ignore
978 | "--md-elevation-level": 1,
979 | }}
980 | >
981 | {isExpanded && }
982 |
983 |
984 | arrow_back
985 |
986 |
987 |
988 | Medium (with Elevation)
989 |
990 | Medium (with Elevation)
991 |
992 |
993 | attach_file
994 |
995 |
996 | calendar_today
997 |
998 |
999 | more_vert
1000 |
1001 |
1002 |
1003 |
1004 |
1005 |
1006 |
1010 |
1011 | arrow_back
1012 |
1013 |
1014 | Large
1015 | Large
1016 |
1017 |
1018 | attach_file
1019 |
1020 |
1021 | calendar_today
1022 |
1023 |
1024 | more_vert
1025 |
1026 |
1027 |
1028 |
1029 |
1030 |
1031 |
1032 |
1033 |
1034 |
1035 |
1036 |
1037 | search
1038 |
1039 |
1040 | close
1041 |
1042 |
1043 |
1044 |
1045 | search
1046 |
1047 |
1048 | close
1049 |
1050 |
1051 |
1052 |
1053 |
1054 | search
1055 |
1056 |
1057 | close
1058 |
1059 |
1060 |
1061 |
1062 | search
1063 |
1064 |
1065 | close
1066 |
1067 |
1068 |
1069 |
1070 |
1071 |
1072 | setShowBottomSheet(open)}
1075 | className="z-50"
1076 | >
1077 |
1078 | Actions
1079 |
1080 |
1081 |
1082 |
1083 |
1084 | share
1085 |
1086 |
1087 | Share
1088 |
1089 |
1090 |
1091 |
1092 | add
1093 |
1094 |
1095 | Add to
1096 |
1097 |
1098 |
1099 |
1100 | delete
1101 |
1102 |
1103 | Trash
1104 |
1105 |
1106 |
1107 |
1108 | archive
1109 |
1110 |
1111 | Archive
1112 |
1113 |
1114 |
1115 |
1116 | settings
1117 |
1118 |
1119 | Settings
1120 |
1121 |
1122 |
1123 |
1124 | favorite
1125 |
1126 |
1127 | Favorite
1128 |
1129 |
1130 |
1131 | setShowModalBottomSheet(open)}
1135 | className="z-50"
1136 | >
1137 |
1138 | Actions
1139 |
1140 |
1141 |
1142 |
1143 |
1144 | share
1145 |
1146 |
1147 | Share
1148 |
1149 |
1150 |
1151 |
1152 | add
1153 |
1154 |
1155 | Add to
1156 |
1157 |
1158 |
1159 |
1160 | delete
1161 |
1162 |
1163 | Trash
1164 |
1165 |
1166 |
1167 |
1168 | archive
1169 |
1170 |
1171 | Archive
1172 |
1173 |
1174 |
1175 |
1176 | settings
1177 |
1178 |
1179 | Settings
1180 |
1181 |
1182 |
1183 |
1184 | favorite
1185 |
1186 |
1187 | Favorite
1188 |
1189 |
1190 |
1191 |
1192 | >
1193 | );
1194 | }
1195 |
--------------------------------------------------------------------------------
/apps/demo/src/app/providers.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {SnackbarProvider} from "material-web-components-react/snackbar";
4 |
5 | const SnackbarClientProvider = (props: any) =>
6 |
7 | export default SnackbarClientProvider
--------------------------------------------------------------------------------
/apps/demo/src/components/GitHubButton/index.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import GHButton from "react-github-btn";
3 |
4 | const GitHubButton = (props: any) => (
5 | {props.children}
6 | );
7 |
8 | export default GitHubButton;
9 |
--------------------------------------------------------------------------------
/apps/demo/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | content: [
5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | safelist: [
10 | {
11 | pattern: /opacity-(.)/,
12 | },
13 | {
14 | pattern: /text-(xs|sm|md|lg|xl|2xl|3xl)/,
15 | },
16 | {
17 | pattern: /rounded-(xs|sm|md|lg|xl|2xl|3xl)/,
18 | },
19 | {
20 | pattern: /p(y|t|x|b|l|r)-(.)/,
21 | },
22 | {
23 | pattern: /gap-(y|t|x|b)-(.)/,
24 | },
25 | {
26 | pattern: /flex-(.)/,
27 | },
28 | {
29 | pattern: /bg-(.)/,
30 | },
31 | {
32 | pattern: /bg-[#313033]/,
33 | },
34 | {
35 | pattern: /min-(w|h)-(.)/,
36 | },
37 | {
38 | pattern: /max-(w|h)-(.)/,
39 | },
40 | {
41 | pattern: /([a-zA-Z]+)-./,
42 | },
43 | ],
44 | theme: {
45 | extend: {
46 | backgroundImage: {
47 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
48 | "gradient-conic":
49 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
50 | },
51 | },
52 | },
53 | plugins: [],
54 | };
55 | export default config;
56 |
--------------------------------------------------------------------------------
/apps/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@packages/*": ["../../packages/*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "material-web-components-react-monorepo",
3 | "private": true,
4 | "scripts": {
5 | "build": "turbo build",
6 | "dev": "turbo dev",
7 | "lint": "turbo lint",
8 | "format": "prettier --write \"**/*.{ts,tsx,md}\"",
9 | "publish-packages": "turbo run build lint --filter=material-web-components-react && changeset version && changeset publish"
10 | },
11 | "devDependencies": {
12 | "@changesets/cli": "^2.27.7",
13 | "prettier": "^3.2.5",
14 | "turbo": "^2.0.6",
15 | "typescript": "^5.4.5"
16 | },
17 | "packageManager": "pnpm@8.15.6",
18 | "engines": {
19 | "node": ">=18"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/eslint-config/README.md:
--------------------------------------------------------------------------------
1 | # `@turbo/eslint-config`
2 |
3 | Collection of internal eslint configurations.
4 |
--------------------------------------------------------------------------------
/packages/eslint-config/library.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require("node:path");
2 |
3 | const project = resolve(process.cwd(), "tsconfig.json");
4 |
5 | /** @type {import("eslint").Linter.Config} */
6 | module.exports = {
7 | extends: ["eslint:recommended", "prettier", "turbo"],
8 | plugins: ["only-warn"],
9 | globals: {
10 | React: true,
11 | JSX: true,
12 | },
13 | env: {
14 | node: true,
15 | },
16 | settings: {
17 | "import/resolver": {
18 | typescript: {
19 | project,
20 | },
21 | },
22 | },
23 | ignorePatterns: [
24 | // Ignore dotfiles
25 | ".*.js",
26 | "node_modules/",
27 | "dist/",
28 | ],
29 | overrides: [
30 | {
31 | files: ["*.js?(x)", "*.ts?(x)"],
32 | },
33 | ],
34 | };
35 |
--------------------------------------------------------------------------------
/packages/eslint-config/next.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require("node:path");
2 |
3 | const project = resolve(process.cwd(), "tsconfig.json");
4 |
5 | /** @type {import("eslint").Linter.Config} */
6 | module.exports = {
7 | extends: [
8 | "eslint:recommended",
9 | "prettier",
10 | require.resolve("@vercel/style-guide/eslint/next"),
11 | "turbo",
12 | ],
13 | globals: {
14 | React: true,
15 | JSX: true,
16 | },
17 | env: {
18 | node: true,
19 | browser: true,
20 | },
21 | plugins: ["only-warn"],
22 | settings: {
23 | "import/resolver": {
24 | typescript: {
25 | project,
26 | },
27 | },
28 | },
29 | ignorePatterns: [
30 | // Ignore dotfiles
31 | ".*.js",
32 | "node_modules/",
33 | ],
34 | overrides: [{ files: ["*.js?(x)", "*.ts?(x)"] }],
35 | };
36 |
--------------------------------------------------------------------------------
/packages/eslint-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/eslint-config",
3 | "version": "0.0.0",
4 | "private": true,
5 | "files": [
6 | "library.js",
7 | "next.js",
8 | "react-internal.js"
9 | ],
10 | "devDependencies": {
11 | "@vercel/style-guide": "^5.2.0",
12 | "eslint-config-turbo": "^2.0.0",
13 | "eslint-config-prettier": "^9.1.0",
14 | "eslint-plugin-only-warn": "^1.1.0",
15 | "@typescript-eslint/parser": "^7.1.0",
16 | "@typescript-eslint/eslint-plugin": "^7.1.0",
17 | "typescript": "^5.3.3"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/eslint-config/react-internal.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require("node:path");
2 |
3 | const project = resolve(process.cwd(), "tsconfig.json");
4 |
5 | /*
6 | * This is a custom ESLint configuration for use with
7 | * internal (bundled by their consumer) libraries
8 | * that utilize React.
9 | */
10 |
11 | /** @type {import("eslint").Linter.Config} */
12 | module.exports = {
13 | extends: ["eslint:recommended", "prettier", "turbo"],
14 | plugins: ["only-warn"],
15 | globals: {
16 | React: true,
17 | JSX: true,
18 | },
19 | env: {
20 | browser: true,
21 | },
22 | settings: {
23 | "import/resolver": {
24 | typescript: {
25 | project,
26 | },
27 | },
28 | },
29 | ignorePatterns: [
30 | // Ignore dotfiles
31 | ".*.js",
32 | "node_modules/",
33 | "dist/",
34 | ],
35 | overrides: [
36 | // Force ESLint to detect .tsx files
37 | { files: ["*.js?(x)", "*.ts?(x)"] },
38 | ],
39 | };
40 |
--------------------------------------------------------------------------------
/packages/typescript-config/base.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Default",
4 | "compilerOptions": {
5 | "declaration": true,
6 | "declarationMap": true,
7 | "esModuleInterop": true,
8 | "incremental": false,
9 | "isolatedModules": true,
10 | "lib": ["es2022", "DOM", "DOM.Iterable"],
11 | "module": "NodeNext",
12 | "moduleDetection": "force",
13 | "moduleResolution": "NodeNext",
14 | "noUncheckedIndexedAccess": true,
15 | "resolveJsonModule": true,
16 | "skipLibCheck": true,
17 | "strict": false,
18 | "target": "ES2022"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/typescript-config/nextjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Next.js",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "plugins": [{ "name": "next" }],
7 | "module": "ESNext",
8 | "moduleResolution": "Bundler",
9 | "allowJs": true,
10 | "jsx": "preserve",
11 | "noEmit": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/typescript-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/typescript-config",
3 | "version": "0.0.0",
4 | "private": true,
5 | "license": "MIT",
6 | "publishConfig": {
7 | "access": "public"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/typescript-config/react-library.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "React Library",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "jsx": "react-jsx"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/ui/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import("eslint").Linter.Config} */
2 | module.exports = {
3 | root: true,
4 | extends: ["@repo/eslint-config/react-internal.js"],
5 | parser: "@typescript-eslint/parser",
6 | parserOptions: {
7 | project: "./tsconfig.lint.json",
8 | tsconfigRootDir: __dirname,
9 | },
10 | rules: {
11 | "no-redeclare": 0,
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/packages/ui/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # material-web-react
2 |
3 | ## 0.3.17
4 |
5 | ### Patch Changes
6 |
7 | - chore: update base lib
8 |
9 | ## 0.3.16
10 |
11 | ### Patch Changes
12 |
13 | - use latest material version
14 |
15 | ## 0.3.15
16 |
17 | ### Patch Changes
18 |
19 | - add custom classname option to snackbar
20 |
21 | ## 0.3.14
22 |
23 | ### Patch Changes
24 |
25 | - fix: appbar
26 |
27 | ## 0.3.13
28 |
29 | ### Patch Changes
30 |
31 | - ttomsheet content to top
32 |
33 | ## 0.3.12
34 |
35 | ### Patch Changes
36 |
37 | - Make bottom sheet take highest precedence
38 |
39 | ## 0.3.11
40 |
41 | ### Patch Changes
42 |
43 | - add snackbar component
44 |
45 | ## 0.3.10
46 |
47 | ### Patch Changes
48 |
49 | - feat: add snackbar component
50 |
51 | ## 0.3.9
52 |
53 | ### Patch Changes
54 |
55 | - chore: clean typescript
56 |
57 | ## 0.3.8
58 |
59 | ### Patch Changes
60 |
61 | - fix: appbar center-aligned balancing
62 |
63 | ## 0.3.7
64 |
65 | ### Patch Changes
66 |
67 | - Add two new components for MUI junkies; Container and Typography
68 |
69 | ## 0.3.6
70 |
71 | ### Patch Changes
72 |
73 | - add navigation rail
74 |
75 | ## 0.3.5
76 |
77 | ### Patch Changes
78 |
79 | - Add bottom sheet
80 |
81 | ## 0.3.4
82 |
83 | ### Patch Changes
84 |
85 | - d84f889: add latest material/web, make SSG compatible
86 |
87 | ## 0.3.3
88 |
89 | ### Patch Changes
90 |
91 | - Keep animations in tailwind classes only
92 |
93 | ## 0.3.2
94 |
95 | ### Patch Changes
96 |
97 | - b0c82e6: Add app bars implementation v1
98 |
99 | ## 0.3.1
100 |
101 | ### Patch Changes
102 |
103 | - add navigation drawer and an example of its usage
104 |
105 | ## 0.3.0
106 |
107 | ### Minor Changes
108 |
109 | - feat: add new components
110 |
111 | ## 0.2.8
112 |
113 | ### Patch Changes
114 |
115 | - add package json metadata
116 |
117 | ## 0.2.7
118 |
119 | ### Patch Changes
120 |
121 | - 1b43630: add postbuild
122 | - f895d71: add publish directory
123 |
124 | ## 0.2.6
125 |
126 | ### Patch Changes
127 |
128 | - 20b989b: feat: add minification in dist
129 |
130 | ## 0.2.5
131 |
132 | ### Patch Changes
133 |
134 | - docs: add shields
135 |
136 | ## 0.2.4
137 |
138 | ### Patch Changes
139 |
140 | - add readme to ui folder
141 |
142 | ## 0.2.3
143 |
144 | ### Patch Changes
145 |
146 | - Regenerate dist
147 |
148 | ## 0.2.2
149 |
150 | ### Patch Changes
151 |
152 | - Testing readme yet again...
153 |
154 | ## 0.2.1
155 |
156 | ### Patch Changes
157 |
158 | - Test readme in package
159 |
160 | ## 0.2.0
161 |
162 | ### Minor Changes
163 |
164 | - Testing again...
165 | - Testing once more...
166 |
167 | ### Patch Changes
168 |
169 | - docs: Add README file to build
170 | - Testing again to see if README is visisble
171 |
172 | ## 0.1.0
173 |
174 | ### Minor Changes
175 |
176 | - Testing to see if this works in a fresh NextJS project
177 |
178 | ## 1.0.0
179 |
180 | ### Major Changes
181 |
182 | - c6b4f60: First push to npm
183 |
--------------------------------------------------------------------------------
/packages/ui/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Grayhat Developers Private Limited
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/ui/README.md:
--------------------------------------------------------------------------------
1 | # Material Web Components for React
2 |
3 | [](https://www.npmjs.com/package/material-web-components-react)
4 | 
5 | [](https://material-web.dev)
6 | [](https://material-web-components-react.grayhat.studio)
7 |
8 | A thin React wrapper over [@material/web](https://github.com/material-components/material-web/). Aims to be a locally-installable, accessible and customizable Material standard for React. Recommended to use with [MUI](https://mui.com/). Free. Open Source. **Looking for maintainers**.
9 |
10 | 
11 |
12 | ## Installation
13 |
14 | To use Material Web Components for React as a **library** in your project, run:
15 |
16 | ```sh
17 | npm install material-web-components-react
18 | ```
19 |
20 | ## Usage
21 |
22 | Here's a general example of how the components can be used:
23 |
24 | ```tsx
25 | import React from 'react';
26 | import Button from 'material-web-components-react/button';
27 |
28 | function Example() {
29 | return (
30 |
31 |
32 |
33 | );
34 | }
35 | ```
36 |
37 | For a detailed reference on usage, you might want to check out the source code of the [NextJS demo](https://github.com/grayhatdevelopers/material-web-components-react/blob/main/apps/demo/src/app/page.tsx). It's simple!
38 |
39 | Under the hood, this library simply uses the official [@material/web](https://github.com/material-components/material-web/) components. Visit [the official Material Web Components docs](https://github.com/material-components/material-web/blob/main/docs/intro.md) to learn how to use those components. The props remain the same!
40 |
41 | ## Contributing
42 |
43 | We're looking for maintainers and contributors!
44 |
45 | ### Roadmap 🚀
46 |
47 | - [ ] Make sure all native Web Components are properly working
48 | - [x] Demo all components
49 | - [x] Add all missing events
50 | - [ ] [Need help] Add theming (design tokens) through Tailwind (i.e. remove all ts-ignores) (https://github.com/grayhatdevelopers/material-web-components-react/pull/2)
51 | - [x] Resolve SSR/SSG issues, make compatible with NextJS (i.e. remove all dynamic imports)
52 | - [x] Separate the demo from the actual UI code
53 | - [x] Make installable as a package.
54 | - [ ] [Need help] Make installable as code-in-project, like shadcn/ui, so developers have more control (https://github.com/grayhatdevelopers/material-web-components-react/pull/11)
55 | - [ ] [Need help] Add better TypeScript support (https://github.com/grayhatdevelopers/material-web-components-react/issues/12)
56 | - [ ] Sync with upstream (i.e. https://github.com/material-components/material-web/blob/main/docs/intro.md) through webhooks and automations
57 | - [ ] Use [Copybara](https://github.com/google/copybara) (or good ol' GitHub webhooks) to automate syncing with upstream
58 | - [ ] Use [lit-analyzer](https://www.npmjs.com/package/lit-analyzer) to see which Web Components changed. Perhaps mix with an LLM to compare existing and newly autogenerated code.
59 | - [ ] Create a PR with the new Component code.
60 | - [ ] Mix this library with Tailwind and BaseUI in order to complete missing Components which may prove useful for building production applications
61 | - [x] App Bars
62 | - [x] Top App Bar
63 | - [x] Bottom App Bar
64 | - [x] Stack
65 | - [x] Box
66 | - [x] Navigation Rail
67 | - [x] Container
68 | - [x] Typography
69 |
70 | ### Credits
71 |
72 | Huge shout out to Elizabeth Mitchell ([asyncLiz](https://github.com/asyncliz/)) and the rest of the Material Design team for their awesome Web Components implementation.
73 |
74 | Thank you [Travis Reeder](https://github.com/treeder) for your Web Component implementation of Navigation Rail. I had to copy it to this project. I couldn't use yours directly because it would import `@material/web` again and bring conflicts.
75 |
76 | Thanks for making the crappy, brain-dead wrapper components:
77 |
78 | - [ChatGPT](https://chatgpt.com/share/574a9601-8927-4992-884e-16c58f24a982)
79 |
80 | Thanks for improving the demo:
81 |
82 | - [TalhaHere12](https://github.com/TalhaHere12)
83 |
84 | Thanks for building BottomSheet and Snackbar:
85 |
86 | - [Aroonaongithhub](https://github.com/Aroonaongithhub/)
87 |
--------------------------------------------------------------------------------
/packages/ui/WHY.md:
--------------------------------------------------------------------------------
1 | # Why another React Material library?
2 |
3 | Let's evaluate our options.
4 |
5 | ## Libraries which implement Material 2
6 |
7 | These are libraries which were built upon an older specification of Google's official Material Web Components, but those followed the Material 2 specification. Such libraries are either discontinued, or slowly dying out. Those include, but are not limited to:
8 |
9 | - material-tailwind (currently looks like the best!): https://github.com/creativetimofficial/material-tailwind
10 | - material-web-components-react (Official): https://github.com/material-components/material-components-web-react
11 | - react-mdc: https://react-mdc.github.io/#/
12 | - muicss: https://www.muicss.com/
13 |
14 | Check out more libraries listed at https://m2.material.io/develop/web/guides/framework-wrappers .
15 |
16 | ## Libraries which implement Material 3
17 |
18 | These are libraries which implement Material 3, the latest specification (so far) from the Material Design team. Some look great, others... not really. These include, but are not limited to:
19 |
20 | - beercss (insane work, totally framework independent. I should consider using this): https://www.beercss.com/
21 | - actifyjs (currently looks like the best React-only implementation!): https://actifyjs.com/
22 |
23 | I'd love to add more. Send in a Pull Request if you'd like to contribute more.
24 |
25 | ## Popular, but slow
26 |
27 | [MUI](https://mui.com/) is the library which fits this category. While I love their work, [they are slow](https://github.com/mui/material-ui/issues/29345) at keeping up with latest design philosophies and practices (And rightly so! A lot of software depends on their stability). MUI has announced better Material 3 support in Q4 of 2024. Too far away, at least for when this project started!
28 |
29 | For MUI, and similar libraries, I recommend using them alongside this library to "fill in" the missing parts.
30 |
31 | ## The future: Web Components!
32 |
33 | The library we use under the hood, [@material/web](https://github.com/material-components/material-web) fits here. Web Components are the future, we must accept it. But while we build towards that future, it's obvious that current applications must be maintained. Hence the existence of this library. Our aim would be to keep this library synced with this underlying new technology as much as possible, so when we transition, it's seamless!
34 |
35 | Another great library to plug in here would be: https://www.mdui.org/en/docs/2/
36 |
--------------------------------------------------------------------------------
/packages/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "material-web-components-react",
3 | "version": "0.3.17",
4 | "author": "Grayhat Team",
5 | "type": "module",
6 | "keywords": [
7 | "react",
8 | "react-component",
9 | "web-components",
10 | "material",
11 | "material-ui",
12 | "material design"
13 | ],
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/grayhatdevelopers/material-web-components-react",
17 | "directory": "packages/ui"
18 | },
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/grayhatdevelopers/material-web-components-react/issues"
22 | },
23 | "homepage": "https://material-web-components-react.grayhat.studio",
24 | "publishConfig": {
25 | "access": "public",
26 | "directory": "./dist"
27 | },
28 | "scripts": {
29 | "lint": "eslint . --max-warnings 0",
30 | "generate:component": "turbo gen react-component",
31 | "build": "tsup",
32 | "postbuild": "cp ../../*.md . && cp ./*.md ./dist && cp package.json ./dist",
33 | "dev": "tsup --watch",
34 | "release": "build && cd dist && npx changeset publish"
35 | },
36 | "devDependencies": {
37 | "@repo/eslint-config": "workspace:*",
38 | "@repo/typescript-config": "workspace:*",
39 | "@swc/core": "^1.7.0",
40 | "@turbo/gen": "^1.12.4",
41 | "@types/eslint": "^8.56.5",
42 | "@types/node": "^20.11.24",
43 | "@types/react": "^18.2.61",
44 | "@types/react-dom": "^18.2.19",
45 | "eslint": "^8.57.0",
46 | "postcss": "^8",
47 | "tsup": "^8.2.0",
48 | "typescript": "^5.3.3",
49 | "fast-glob": "^3.3.2"
50 | },
51 | "dependencies": {
52 | "@lit/react": "^1.0.5",
53 | "@material/web": "^2.2.0",
54 | "autoprefixer": "^10.4.19",
55 | "lit": "^3.2.0",
56 | "react": "^18.2.0",
57 | "react-hot-toast": "^2.4.1",
58 | "react-swipeable": "^7.0.1",
59 | "tailwind-merge": "^2.4.0",
60 | "tailwindcss": "^3.4.1",
61 | "tslib": "^2.6.3",
62 | "vaul": "^0.9.1"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/packages/ui/src/app-bar/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react";
2 | import { twMerge } from "tailwind-merge";
3 | import { findSlotProp, removeSlotProps } from "../utils/index.js";
4 |
5 | const AppBar = ({
6 | className,
7 | variant = null,
8 | leadingElements,
9 | headlineElement,
10 | headlineExpandedElement,
11 | trailingElements,
12 | children,
13 | onExpansionChange,
14 | ...props
15 | }: {
16 | className?: string;
17 | variant?: null | "center-aligned" | "small" | "medium" | "large";
18 | leadingElements?: any;
19 | headlineElement?: any;
20 | headlineExpandedElement?: any;
21 | trailingElements?: any;
22 | children?: React.ReactNode;
23 | // eslint-disable-next-line no-unused-vars
24 | onExpansionChange?: (expanded: boolean) => void;
25 | }) => {
26 | const _leadingElements = leadingElements
27 | ? leadingElements
28 | : children
29 | ? findSlotProp(children, "leading", true)
30 | : null;
31 | const _headlineElement = headlineElement
32 | ? headlineElement
33 | : children
34 | ? findSlotProp(children, "headline")
35 | : null;
36 |
37 | const hasHeadlineExpandedElement = findSlotProp(
38 | children,
39 | "headline-expanded"
40 | );
41 | const _headlineExpandedElement = headlineExpandedElement
42 | ? headlineExpandedElement
43 | : children
44 | ? hasHeadlineExpandedElement
45 | : _headlineElement;
46 | const _trailingElements = trailingElements
47 | ? trailingElements
48 | : children
49 | ? findSlotProp(children, "trailing", true)
50 | : null;
51 |
52 | const remainingElements = removeSlotProps(children, [
53 | "leading",
54 | "headline",
55 | "headline-expanded",
56 | "trailing",
57 | ]);
58 |
59 | const _variant = variant
60 | ? variant
61 | : _trailingElements?.length > 1 || _leadingElements?.length === 0
62 | ? hasHeadlineExpandedElement
63 | ? "medium"
64 | : "small"
65 | : "center-aligned";
66 |
67 | const showExpandedHeadline = _variant === "medium" || _variant === "large";
68 |
69 | const headlineExpandedRef = useRef();
70 |
71 | const [isHeadlineExpandedVisible, setIsHeadlineExpandedVisible] =
72 | useState(showExpandedHeadline);
73 |
74 | useEffect(() => {
75 | let observer: IntersectionObserver;
76 | if (headlineExpandedRef?.current) {
77 | observer = new IntersectionObserver((entries) => {
78 | entries.forEach((entry) => {
79 | setIsHeadlineExpandedVisible(entry.isIntersecting);
80 | onExpansionChange?.(!entry.isIntersecting);
81 | });
82 | },
83 | {
84 | rootMargin: "-20px 0px",
85 | threshold: 0,
86 | }
87 | );
88 | observer.observe(headlineExpandedRef.current);
89 | }
90 | return () => {
91 | observer?.disconnect();
92 | };
93 | }, [headlineExpandedRef]);
94 |
95 | return (
96 | <>
97 |
107 |
114 | {_leadingElements}
115 |
116 |
117 |
131 | {_headlineElement}
132 |
133 |
134 |
141 | {_trailingElements}
142 |
143 |
144 | {remainingElements}
145 |
146 | {showExpandedHeadline && (
147 |
157 | {_headlineExpandedElement}
158 |
159 | )}
160 | >
161 | );
162 | };
163 |
164 | export default AppBar;
165 |
--------------------------------------------------------------------------------
/packages/ui/src/badge/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { MdBadge } from "@material/web/labs/badge/badge";
4 | import { createComponent } from "@lit/react";
5 |
6 | export type { MdBadge as MdBadgeType } from "@material/web/labs/badge/badge";
7 |
8 | // const events = {
9 | // 'onOpen': 'open',
10 | // 'onOpened': 'opened',
11 | // 'onClose': 'close',
12 | // 'onClosed': 'closed',
13 | // 'onCancel': 'cancel',
14 | // }
15 |
16 | export const Badge = createComponent({
17 | tagName: "md-badge",
18 | elementClass: MdBadge,
19 | react: React,
20 | // events: events,
21 | });
22 |
23 | export default Badge;
24 |
--------------------------------------------------------------------------------
/packages/ui/src/box/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Ref } from "react";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | const Box = React.forwardRef(
5 | (
6 | {
7 | component: Component = "div",
8 | className,
9 | ...props
10 | }: { component: string } & HTMLElement,
11 | ref: Ref,
12 | ) => {
13 | return (
14 |
20 | );
21 | },
22 | );
23 |
24 | export default Box;
25 |
--------------------------------------------------------------------------------
/packages/ui/src/button/index.tsx:
--------------------------------------------------------------------------------
1 | // file: Fab.react.tsx
2 | import { MdElevatedButton } from "@material/web/button/elevated-button";
3 | import { MdFilledButton } from "@material/web/button/filled-button";
4 | import { MdFilledTonalButton } from "@material/web/button/filled-tonal-button";
5 | import { MdOutlinedButton } from "@material/web/button/outlined-button";
6 | import { MdTextButton } from "@material/web/button/text-button";
7 | import React from "react";
8 | import { createComponent } from "@lit/react";
9 | import { matchVariant } from "../utils";
10 |
11 | export type { MdElevatedButton } from "@material/web/button/elevated-button.js";
12 | export type { MdFilledButton } from "@material/web/button/filled-button.js";
13 | export type { MdFilledTonalButton } from "@material/web/button/filled-tonal-button.js";
14 | export type { MdTextButton } from "@material/web/button/text-button";
15 |
16 | export const FilledButton = createComponent({
17 | tagName: "md-filled-button",
18 | elementClass: MdFilledButton,
19 | react: React,
20 | });
21 | export const FilledTonalButton = createComponent({
22 | tagName: "md-filled-tonal-button",
23 | elementClass: MdFilledTonalButton,
24 | react: React,
25 | });
26 | export const ElevatedButton = createComponent({
27 | tagName: "md-elevated-button",
28 | elementClass: MdElevatedButton,
29 | react: React,
30 | });
31 | export const OutlinedButton = createComponent({
32 | tagName: "md-outlined-button",
33 | elementClass: MdOutlinedButton,
34 | react: React,
35 | });
36 | export const TextButton = createComponent({
37 | tagName: "md-text-button",
38 | elementClass: MdTextButton,
39 | react: React,
40 | });
41 |
42 | const Button = (props: any) => {
43 | if (matchVariant(props.variant, "md-filled-button"))
44 | return {props.children};
45 | if (matchVariant(props.variant, "md-filled-tonal-button"))
46 | return {props.children};
47 | if (matchVariant(props.variant, "md-outlined-button"))
48 | return {props.children};
49 | if (matchVariant(props.variant, "md-text-button"))
50 | return {props.children};
51 | return {props.children};
52 | };
53 |
54 | export default Button;
55 |
--------------------------------------------------------------------------------
/packages/ui/src/card/index.tsx:
--------------------------------------------------------------------------------
1 | // file: Fab.react.tsx
2 | import { MdElevatedCard } from "@material/web/labs/card/elevated-card";
3 | import { MdFilledCard } from "@material/web/labs/card/filled-card";
4 | import { MdOutlinedCard } from "@material/web/labs/card/outlined-card";
5 | import React from "react";
6 | import { createComponent } from "@lit/react";
7 | import { matchVariant } from "../utils";
8 |
9 | export type { MdElevatedCard } from "@material/web/labs/card/elevated-card.js";
10 | export type { MdFilledCard } from "@material/web/labs/card/filled-card.js";
11 | export type { MdOutlinedCard } from "@material/web/labs/card/outlined-card.js";
12 |
13 | export const FilledCard = createComponent({
14 | tagName: "md-filled-card",
15 | elementClass: MdElevatedCard,
16 | react: React,
17 | });
18 | export const ElevatedCard = createComponent({
19 | tagName: "md-elevated-card",
20 | elementClass: MdFilledCard,
21 | react: React,
22 | });
23 | export const OutlinedCard = createComponent({
24 | tagName: "md-outlined-card",
25 | elementClass: MdOutlinedCard,
26 | react: React,
27 | });
28 |
29 | const Card = (props: any) => {
30 | if (matchVariant(props.variant, "md-filled-card"))
31 | return {props.children};
32 | if (matchVariant(props.variant, "md-outlined-card"))
33 | return {props.children};
34 | return {props.children};
35 | };
36 |
37 | export default Card;
38 |
--------------------------------------------------------------------------------
/packages/ui/src/checkbox/index.tsx:
--------------------------------------------------------------------------------
1 | // file: Checkbox.react.tsx
2 | import { MdCheckbox } from "@material/web/checkbox/checkbox";
3 | import React from "react";
4 | import { createComponent } from "@lit/react";
5 |
6 | export type { MdCheckbox } from "@material/web/checkbox/checkbox.js";
7 |
8 | const events = {
9 | onChange: "change",
10 | onInput: "input",
11 | };
12 |
13 | export default createComponent({
14 | tagName: "md-checkbox",
15 | elementClass: MdCheckbox,
16 | react: React,
17 | events,
18 | });
19 |
--------------------------------------------------------------------------------
/packages/ui/src/chip/index.tsx:
--------------------------------------------------------------------------------
1 | // file: Icon.react.tsx
2 | import { MdChipSet } from "@material/web/chips/chip-set";
3 | import { MdAssistChip } from "@material/web/chips/assist-chip";
4 | import { MdFilterChip } from "@material/web/chips/filter-chip";
5 | import { MdInputChip } from "@material/web/chips/input-chip";
6 | import { MdSuggestionChip } from "@material/web/chips/suggestion-chip";
7 |
8 | import React from "react";
9 | import { createComponent } from "@lit/react";
10 | import { matchVariant } from "../utils";
11 |
12 | export type { FabSize, FabVariant } from "@material/web/fab/fab.js";
13 |
14 | const events = {
15 | onUpdateFocus: "update-focus",
16 | };
17 |
18 | export const ChipSet = createComponent({
19 | tagName: "md-chip-set",
20 | elementClass: MdChipSet,
21 | react: React,
22 | });
23 |
24 | export const AssistChip = createComponent({
25 | tagName: "md-assist-chip",
26 | elementClass: MdAssistChip,
27 | react: React,
28 | events,
29 | });
30 | export const FilterChip = createComponent({
31 | tagName: "md-filter-chip",
32 | elementClass: MdFilterChip,
33 | react: React,
34 | events: {
35 | ...events,
36 | onRemove: "remove",
37 | },
38 | });
39 | export const InputChip = createComponent({
40 | tagName: "md-input-chip",
41 | elementClass: MdInputChip,
42 | react: React,
43 | events: {
44 | ...events,
45 | onRemove: "remove",
46 | },
47 | });
48 | export const SuggestionChip = createComponent({
49 | tagName: "md-suggestion-chip",
50 | elementClass: MdSuggestionChip,
51 | react: React,
52 | events,
53 | });
54 |
55 | const Chip = (props: any) => {
56 | if (matchVariant(props.variant, "md-filter-chip"))
57 | return {props.children};
58 | if (matchVariant(props.variant, "md-input-chip"))
59 | return {props.children};
60 | if (matchVariant(props.variant, "md-suggestion-chip"))
61 | return {props.children};
62 | return {props.children};
63 | };
64 |
65 | export default Chip;
66 |
--------------------------------------------------------------------------------
/packages/ui/src/container/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { twMerge } from "tailwind-merge";
3 | const Container = React.forwardRef(
4 | (
5 | {
6 | component: Component = "div",
7 | disableGutters = false,
8 | fixed = false,
9 | maxWidth = "lg",
10 | className,
11 | ...props
12 | }: any,
13 | ref
14 | ) => {
15 | const maxWidthClasses = {
16 | xs: "max-w-xs",
17 | sm: "max-w-sm",
18 | md: "max-w-md",
19 | lg: "max-w-lg",
20 | xl: "max-w-xl",
21 | false: "",
22 | };
23 |
24 | return (
25 |
37 | {props.children}
38 |
39 | );
40 | }
41 | );
42 |
43 | export default Container;
44 |
--------------------------------------------------------------------------------
/packages/ui/src/dialog/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { MdDialog } from "@material/web/dialog/dialog.js";
4 | import { createComponent } from "@lit/react";
5 |
6 | export type { MdDialog as MdDialogType } from "@material/web/dialog/dialog";
7 |
8 | const events = {
9 | onOpen: "open",
10 | onOpened: "opened",
11 | onClose: "close",
12 | onClosed: "closed",
13 | onCancel: "cancel",
14 | };
15 |
16 | export const Dialog = createComponent({
17 | tagName: "md-dialog",
18 | elementClass: MdDialog,
19 | react: React,
20 | events: events,
21 | });
22 |
23 | export default Dialog;
24 |
--------------------------------------------------------------------------------
/packages/ui/src/divider/index.tsx:
--------------------------------------------------------------------------------
1 | // file: Divider.react.tsx
2 | import { MdDivider } from "@material/web/divider/divider";
3 | import React from "react";
4 | import { createComponent } from "@lit/react";
5 |
6 | export type { MdDivider } from "@material/web/divider/divider.js";
7 |
8 | export default createComponent({
9 | tagName: "md-divider",
10 | elementClass: MdDivider,
11 | react: React,
12 | });
13 |
--------------------------------------------------------------------------------
/packages/ui/src/elevation/index.tsx:
--------------------------------------------------------------------------------
1 | // file: Elevation.react.tsx
2 | import { MdElevation } from "@material/web/elevation/elevation";
3 | import React from "react";
4 | import { createComponent } from "@lit/react";
5 |
6 | export type { MdElevation } from "@material/web/elevation/elevation.js";
7 |
8 | export default createComponent({
9 | tagName: "md-elevation",
10 | elementClass: MdElevation,
11 | react: React,
12 | });
13 |
--------------------------------------------------------------------------------
/packages/ui/src/fab/index.tsx:
--------------------------------------------------------------------------------
1 | // file: Fab.react.tsx
2 | import { MdFab } from "@material/web/fab/fab.js";
3 | import { MdBrandedFab } from "@material/web/fab/branded-fab";
4 | import React from "react";
5 | import { createComponent } from "@lit/react";
6 | import { matchVariant } from "../utils";
7 |
8 | export type { FabSize, FabVariant } from "@material/web/fab/fab.js";
9 |
10 | export const DefaultFab = createComponent({
11 | tagName: "md-fab",
12 | elementClass: MdFab,
13 | react: React,
14 | });
15 | export const BrandedFab = createComponent({
16 | tagName: "md-text-button",
17 | elementClass: MdBrandedFab,
18 | react: React,
19 | });
20 |
21 | const Fab = (props: any) => {
22 | if (matchVariant(props.variant, "md-branded-fab"))
23 | return {props.children};
24 | return {props.children};
25 | };
26 |
27 | export default Fab;
28 |
--------------------------------------------------------------------------------
/packages/ui/src/focus-ring/index.tsx:
--------------------------------------------------------------------------------
1 | // file: FocusRing.react.tsx
2 | import { MdFocusRing } from "@material/web/focus/md-focus-ring";
3 | import React from "react";
4 | import { createComponent } from "@lit/react";
5 |
6 | export type { MdFocusRing } from "@material/web/focus/md-focus-ring.js";
7 |
8 | const events = {
9 | onVisibilityChanged: "visibility-changed",
10 | };
11 | export default createComponent({
12 | tagName: "md-focus-ring",
13 | elementClass: MdFocusRing,
14 | react: React,
15 | events,
16 | });
17 |
--------------------------------------------------------------------------------
/packages/ui/src/icon-button/index.tsx:
--------------------------------------------------------------------------------
1 | // file: IconButton.react.tsx
2 | import { MdIconButton } from "@material/web/iconbutton/icon-button";
3 | import { MdFilledIconButton } from "@material/web/iconbutton/filled-icon-button";
4 | import { MdFilledTonalIconButton } from "@material/web/iconbutton/filled-tonal-icon-button";
5 | import { MdOutlinedIconButton } from "@material/web/iconbutton/outlined-icon-button";
6 | import React from "react";
7 | import { createComponent } from "@lit/react";
8 | import { matchVariant } from "../utils";
9 |
10 | export type { MdIconButton } from "@material/web/iconbutton/icon-button.js";
11 | export type { MdFilledIconButton } from "@material/web/iconbutton/filled-icon-button.js";
12 | export type { MdFilledTonalIconButton } from "@material/web/iconbutton/filled-tonal-icon-button.js";
13 | export type { MdOutlinedIconButton } from "@material/web/iconbutton/outlined-icon-button.js";
14 |
15 | const events = {
16 | onInput: "input",
17 | onChange: "change",
18 | };
19 |
20 | export const DefaultIconButton = createComponent({
21 | tagName: "md-icon-button",
22 | elementClass: MdIconButton,
23 | react: React,
24 | events,
25 | });
26 |
27 | export const FilledIconButton = createComponent({
28 | tagName: "md-filled-icon-button",
29 | elementClass: MdFilledIconButton,
30 | react: React,
31 | events,
32 | });
33 |
34 | export const FilledTonalIconButton = createComponent({
35 | tagName: "md-filled-tonal-icon-button",
36 | elementClass: MdFilledTonalIconButton,
37 | react: React,
38 | events,
39 | });
40 |
41 | export const OutlinedIconButton = createComponent({
42 | tagName: "md-outlined-icon-button",
43 | elementClass: MdOutlinedIconButton,
44 | react: React,
45 | events,
46 | });
47 |
48 | const IconButton = (props: any) => {
49 | if (matchVariant(props.variant, "md-filled-icon-button", "icon-button"))
50 | return {props.children};
51 | if (matchVariant(props.variant, "md-filled-tonal-icon-button", "icon-button"))
52 | return (
53 | {props.children}
54 | );
55 | if (matchVariant(props.variant, "md-outlined-icon-button", "icon-button"))
56 | return {props.children};
57 | return {props.children};
58 | };
59 |
60 | export default IconButton;
61 |
--------------------------------------------------------------------------------
/packages/ui/src/icon/index.tsx:
--------------------------------------------------------------------------------
1 | // file: Icon.react.tsx
2 | import { MdIcon } from "@material/web/icon/icon.js";
3 | import React from "react";
4 | import { createComponent } from "@lit/react";
5 |
6 | export type { FabSize, FabVariant } from "@material/web/fab/fab.js";
7 |
8 | export default createComponent({
9 | tagName: "md-icon",
10 | elementClass: MdIcon,
11 | react: React,
12 | events: {
13 | onClick: "click",
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/packages/ui/src/internal/aria/aria.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2023 Google LLC
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 | /**
7 | * Accessibility Object Model reflective aria properties.
8 | */
9 | export const ARIA_PROPERTIES = [
10 | "ariaAtomic",
11 | "ariaAutoComplete",
12 | "ariaBusy",
13 | "ariaChecked",
14 | "ariaColCount",
15 | "ariaColIndex",
16 | "ariaColSpan",
17 | "ariaCurrent",
18 | "ariaDisabled",
19 | "ariaExpanded",
20 | "ariaHasPopup",
21 | "ariaHidden",
22 | "ariaInvalid",
23 | "ariaKeyShortcuts",
24 | "ariaLabel",
25 | "ariaLevel",
26 | "ariaLive",
27 | "ariaModal",
28 | "ariaMultiLine",
29 | "ariaMultiSelectable",
30 | "ariaOrientation",
31 | "ariaPlaceholder",
32 | "ariaPosInSet",
33 | "ariaPressed",
34 | "ariaReadOnly",
35 | "ariaRequired",
36 | "ariaRoleDescription",
37 | "ariaRowCount",
38 | "ariaRowIndex",
39 | "ariaRowSpan",
40 | "ariaSelected",
41 | "ariaSetSize",
42 | "ariaSort",
43 | "ariaValueMax",
44 | "ariaValueMin",
45 | "ariaValueNow",
46 | "ariaValueText",
47 | ];
48 | /**
49 | * Accessibility Object Model aria attributes.
50 | */
51 | export const ARIA_ATTRIBUTES = ARIA_PROPERTIES.map(ariaPropertyToAttribute);
52 | /**
53 | * Checks if an attribute is one of the AOM aria attributes.
54 | *
55 | * @example
56 | * isAriaAttribute('aria-label'); // true
57 | *
58 | * @param attribute The attribute to check.
59 | * @return True if the attribute is an aria attribute, or false if not.
60 | */
61 | export function isAriaAttribute(attribute) {
62 | return attribute.startsWith("aria-");
63 | }
64 | /**
65 | * Converts an AOM aria property into its corresponding attribute.
66 | *
67 | * @example
68 | * ariaPropertyToAttribute('ariaLabel'); // 'aria-label'
69 | *
70 | * @param property The aria property.
71 | * @return The aria attribute.
72 | */
73 | export function ariaPropertyToAttribute(property) {
74 | return (
75 | property
76 | .replace("aria", "aria-")
77 | // IDREF attributes also include an "Element" or "Elements" suffix
78 | .replace(/Elements?/g, "")
79 | .toLowerCase()
80 | );
81 | }
82 | //# sourceMappingURL=aria.js.map
83 |
--------------------------------------------------------------------------------
/packages/ui/src/internal/aria/delegate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2023 Google LLC
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 | import { ARIA_PROPERTIES, ariaPropertyToAttribute } from "./aria.js";
7 | /**
8 | * Sets up a `ReactiveElement` constructor to enable updates when delegating
9 | * aria attributes. Elements may bind `this.aria*` properties to `aria-*`
10 | * attributes in their render functions.
11 | *
12 | * This function will:
13 | * - Call `requestUpdate()` when an aria attribute changes.
14 | * - Add `role="presentation"` to the host.
15 | *
16 | * NOTE: The following features are not currently supported:
17 | * - Delegating IDREF attributes (ex: `aria-labelledby`, `aria-controls`)
18 | * - Delegating the `role` attribute
19 | *
20 | * @example
21 | * class XButton extends LitElement {
22 | * static {
23 | * requestUpdateOnAriaChange(XButton);
24 | * }
25 | *
26 | * protected override render() {
27 | * return html`
28 | *
31 | * `;
32 | * }
33 | * }
34 | *
35 | * @param ctor The `ReactiveElement` constructor to patch.
36 | */
37 | export function requestUpdateOnAriaChange(ctor) {
38 | for (const ariaProperty of ARIA_PROPERTIES) {
39 | ctor.createProperty(ariaProperty, {
40 | attribute: ariaPropertyToAttribute(ariaProperty),
41 | reflect: true,
42 | });
43 | }
44 | ctor.addInitializer((element) => {
45 | const controller = {
46 | hostConnected() {
47 | element.setAttribute("role", "presentation");
48 | },
49 | };
50 | element.addController(controller);
51 | });
52 | }
53 | //# sourceMappingURL=delegate.js.map
54 |
--------------------------------------------------------------------------------
/packages/ui/src/internal/controller/attachable-controller.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2023 Google LLC
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 | import { isServer } from "lit";
7 | /**
8 | * A key to retrieve an `Attachable` element's `AttachableController` from a
9 | * global `MutationObserver`.
10 | */
11 | const ATTACHABLE_CONTROLLER = Symbol("attachableController");
12 | let FOR_ATTRIBUTE_OBSERVER;
13 | if (!isServer) {
14 | /**
15 | * A global `MutationObserver` that reacts to `for` attribute changes on
16 | * `Attachable` elements. If the `for` attribute changes, the controller will
17 | * re-attach to the new referenced element.
18 | */
19 | FOR_ATTRIBUTE_OBSERVER = new MutationObserver((records) => {
20 | for (const record of records) {
21 | // When a control's `for` attribute changes, inform its
22 | // `AttachableController` to update to a new control.
23 | record.target[ATTACHABLE_CONTROLLER]?.hostConnected();
24 | }
25 | });
26 | }
27 | /**
28 | * A controller that provides an implementation for `Attachable` elements.
29 | *
30 | * @example
31 | * ```ts
32 | * class MyElement extends LitElement implements Attachable {
33 | * get control() { return this.attachableController.control; }
34 | *
35 | * private readonly attachableController = new AttachableController(
36 | * this,
37 | * (previousControl, newControl) => {
38 | * previousControl?.removeEventListener('click', this.handleClick);
39 | * newControl?.addEventListener('click', this.handleClick);
40 | * }
41 | * );
42 | *
43 | * // Implement remaining `Attachable` properties/methods that call the
44 | * // controller's properties/methods.
45 | * }
46 | * ```
47 | */
48 | export class AttachableController {
49 | get htmlFor() {
50 | return this.host.getAttribute("for");
51 | }
52 | set htmlFor(htmlFor) {
53 | if (htmlFor === null) {
54 | this.host.removeAttribute("for");
55 | } else {
56 | this.host.setAttribute("for", htmlFor);
57 | }
58 | }
59 | get control() {
60 | if (this.host.hasAttribute("for")) {
61 | if (!this.htmlFor || !this.host.isConnected) {
62 | return null;
63 | }
64 | return this.host.getRootNode().querySelector(`#${this.htmlFor}`);
65 | }
66 | return this.currentControl || this.host.parentElement;
67 | }
68 | set control(control) {
69 | if (control) {
70 | this.attach(control);
71 | } else {
72 | this.detach();
73 | }
74 | }
75 | /**
76 | * Creates a new controller for an `Attachable` element.
77 | *
78 | * @param host The `Attachable` element.
79 | * @param onControlChange A callback with two parameters for the previous and
80 | * next control. An `Attachable` element may perform setup or teardown
81 | * logic whenever the control changes.
82 | */
83 | constructor(host, onControlChange) {
84 | this.host = host;
85 | this.onControlChange = onControlChange;
86 | this.currentControl = null;
87 | host.addController(this);
88 | host[ATTACHABLE_CONTROLLER] = this;
89 | FOR_ATTRIBUTE_OBSERVER?.observe(host, { attributeFilter: ["for"] });
90 | }
91 | attach(control) {
92 | if (control === this.currentControl) {
93 | return;
94 | }
95 | this.setCurrentControl(control);
96 | // When imperatively attaching, remove the `for` attribute so
97 | // that the attached control is used instead of a referenced one.
98 | this.host.removeAttribute("for");
99 | }
100 | detach() {
101 | this.setCurrentControl(null);
102 | // When imperatively detaching, add an empty `for=""` attribute. This will
103 | // ensure the control is `null` rather than the `parentElement`.
104 | this.host.setAttribute("for", "");
105 | }
106 | /** @private */
107 | hostConnected() {
108 | this.setCurrentControl(this.control);
109 | }
110 | /** @private */
111 | hostDisconnected() {
112 | this.setCurrentControl(null);
113 | }
114 | setCurrentControl(control) {
115 | this.onControlChange(this.currentControl, control);
116 | this.currentControl = control;
117 | }
118 | }
119 | //# sourceMappingURL=attachable-controller.js.map
120 |
--------------------------------------------------------------------------------
/packages/ui/src/internal/controller/form-submitter.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2023 Google LLC
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 | import { isServer } from "lit";
7 | import { internals } from "../../labs/behaviors/element-internals.js";
8 | /**
9 | * Sets up an element's constructor to enable form submission. The element
10 | * instance should be form associated and have a `type` property.
11 | *
12 | * A click listener is added to each element instance. If the click is not
13 | * default prevented, it will submit the element's form, if any.
14 | *
15 | * @example
16 | * ```ts
17 | * class MyElement extends mixinElementInternals(LitElement) {
18 | * static {
19 | * setupFormSubmitter(MyElement);
20 | * }
21 | *
22 | * static formAssociated = true;
23 | *
24 | * type: FormSubmitterType = 'submit';
25 | * }
26 | * ```
27 | *
28 | * @param ctor The form submitter element's constructor.
29 | */
30 | export function setupFormSubmitter(ctor) {
31 | if (isServer) {
32 | return;
33 | }
34 | ctor.addInitializer((instance) => {
35 | const submitter = instance;
36 | submitter.addEventListener("click", async (event) => {
37 | const { type, [internals]: elementInternals } = submitter;
38 | const { form } = elementInternals;
39 | if (!form || type === "button") {
40 | return;
41 | }
42 | // Wait a full task for event bubbling to complete.
43 | await new Promise((resolve) => {
44 | setTimeout(resolve);
45 | });
46 | if (event.defaultPrevented) {
47 | return;
48 | }
49 | if (type === "reset") {
50 | form.reset();
51 | return;
52 | }
53 | // form.requestSubmit(submitter) does not work with form associated custom
54 | // elements. This patches the dispatched submit event to add the correct
55 | // `submitter`.
56 | // See https://github.com/WICG/webcomponents/issues/814
57 | form.addEventListener(
58 | "submit",
59 | (submitEvent) => {
60 | Object.defineProperty(submitEvent, "submitter", {
61 | configurable: true,
62 | enumerable: true,
63 | get: () => submitter,
64 | });
65 | },
66 | { capture: true, once: true },
67 | );
68 | elementInternals.setFormValue(submitter.value);
69 | form.requestSubmit();
70 | });
71 | });
72 | }
73 | //# sourceMappingURL=form-submitter.js.map
74 |
--------------------------------------------------------------------------------
/packages/ui/src/internal/controller/is-rtl.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2022 Google LLC
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 | /**
7 | * Returns `true` if the given element is in a right-to-left direction.
8 | *
9 | * @param el Element to determine direction from
10 | * @param shouldCheck Optional. If `false`, return `false` without checking
11 | * direction. Determining the direction of `el` is somewhat expensive, so
12 | * this parameter can be used as a conditional guard. Defaults to `true`.
13 | */
14 | export function isRtl(el, shouldCheck = true) {
15 | return (
16 | shouldCheck &&
17 | getComputedStyle(el).getPropertyValue("direction").trim() === "rtl"
18 | );
19 | }
20 | //# sourceMappingURL=is-rtl.js.map
21 |
--------------------------------------------------------------------------------
/packages/ui/src/internal/controller/string-converter.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2022 Google LLC
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 | export const stringConverter = {
7 | fromAttribute(value) {
8 | return value ?? "";
9 | },
10 | toAttribute(value) {
11 | return value || null;
12 | },
13 | };
14 | //# sourceMappingURL=string-converter.js.map
15 |
--------------------------------------------------------------------------------
/packages/ui/src/internal/events/dispatch-hooks.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2023 Google LLC
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 | /**
7 | * A symbol used to access dispatch hooks on an event.
8 | */
9 | const dispatchHooks = Symbol("dispatchHooks");
10 | /**
11 | * Add a hook for an event that is called after the event is dispatched and
12 | * propagates to other event listeners.
13 | *
14 | * This is useful for behaviors that need to check if an event is canceled.
15 | *
16 | * The callback is invoked synchronously, which allows for better integration
17 | * with synchronous platform APIs (like `