├── .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 | [![npm version](https://badge.fury.io/js/material-web-components-react.svg)](https://www.npmjs.com/package/material-web-components-react) 4 | ![release](https://img.shields.io/badge/release-beta-blue) 5 | [![docs](https://img.shields.io/badge/read%20the%20docs-8A2BE2)](https://material-web.dev) 6 | [![docs](https://img.shields.io/badge/live%20demo-FFA500)](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 | ![hero](https://material-web-components-react.grayhat.studio/opengraph-image.jpg) 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 | {/*
170 | setShowCode(oldState => !oldState)}> 171 | code 172 | 173 | {docsLink && 174 | open_in_new 175 | } 176 |
*/} 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 | setShowDialog(false)} open={showDialog}> 662 |
Dialog title
663 |
664 | A simple dialog with free-form content. 665 |
666 |
667 | 670 |
671 |
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 |
    708 | Fruits 709 | 710 | Apple 711 | Banana 712 | 713 |
    Cucumber
    714 |
    715 | Cucumbers are long green fruits that are just as long as this 716 | multi-line description 717 |
    718 |
    719 |
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 |
782 |
787 | 791 | 792 | 796 | 797 | 809 |
810 |
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 | setShowMenu(false)} 844 | open={showMenu} 845 | id="usage-menu" 846 | anchor="usage-anchor" 847 | > 848 | Menu Item 1 849 | 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 | [![npm version](https://badge.fury.io/js/material-web-components-react.svg)](https://www.npmjs.com/package/material-web-components-react) 4 | ![release](https://img.shields.io/badge/release-beta-blue) 5 | [![docs](https://img.shields.io/badge/read%20the%20docs-8A2BE2)](https://material-web.dev) 6 | [![docs](https://img.shields.io/badge/live%20demo-FFA500)](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 | ![hero](https://material-web-components-react.grayhat.studio/opengraph-image.jpg) 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 `
` or `