This commit is contained in:
OfficialDakari 2025-03-26 21:58:05 +05:00
parent c32b784f8b
commit d516397d3e
17 changed files with 3304 additions and 0 deletions

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"editor.detectIndentation": false,
"editor.insertSpaces": false,
"eslint.format.enable": true
}

4
build.config.ts Normal file
View File

@ -0,0 +1,4 @@
export default {
base: '/',
};

18
index.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root">
</div>
<script type="module" src="./src/index.tsx"></script>
</body>
</html>

2751
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

34
package.json Normal file
View File

@ -0,0 +1,34 @@
{
"name": "materialnext",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@emotion/styled": "^11.13.5",
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
"@lit/react": "^1.0.7",
"@material/material-color-utilities": "^0.3.0",
"@material/web": "^2.2.0",
"@mui/material": "^6.1.6",
"@vitejs/plugin-react": "^4.3.3",
"beercss": "^3.7.12",
"classnames": "^2.5.1",
"material-dynamic-colors": "^1.1.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"uuid": "^11.1.0"
},
"devDependencies": {
"@rollup/plugin-inject": "^5.0.5",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"typescript": "^5.6.3",
"vite": "^5.4.11",
"vite-plugin-top-level-await": "^1.4.4"
}
}

48
src/components/Box.tsx Normal file
View File

@ -0,0 +1,48 @@
import React, { CSSProperties, ReactNode } from 'react';
type BoxProps = {
style?: CSSProperties;
children: ReactNode;
display?: 'flex' | 'block' | 'inline-block' | 'inline' | 'grid' | 'inline-grid' | 'contents' | 'list-item' | 'hidden' | 'initial' | 'inherit';
flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse';
flexWrap?: 'nowrap' | 'wrap' | 'wrap-reverse';
justifyContent?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly';
alignItems?: 'flex-start' | 'flex-end' | 'center' | 'baseline' | 'stretch';
alignContent?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly' | 'stretch' | 'baseline' | 'first baseline' | 'last baseline' | 'safe center' | 'unsafe center' | 'left' | 'right' | 'safe left' | 'safe right' | 'unsafe left' | 'unsafe right';
gap?: number;
rowGap?: number;
columnGap?: number;
flex?: number;
flexGrow?: number;
flexShrink?: number;
flexBasis?: 'auto' | 'content' | 'fit-content' | 'max-content' | 'min-content' | 'fill' | 'fit-content' | 'max-content' | 'min-content' | 'fill' | 'fit-content' | 'max-content' | 'min-content' | 'fill';
flexFlow?: 'row' | 'row-reverse' | 'column' | 'column-reverse';
alignSelf?: 'auto' | 'flex-start' | 'flex-end' | 'center' | 'baseline' | 'stretch';
order?: number;
};
export const Box = ({ style, children, alignContent, alignSelf, justifyContent, alignItems, display, flex, flexBasis, flexDirection, flexFlow, flexGrow, flexShrink, flexWrap, gap, rowGap, columnGap, order }: BoxProps) => {
return (
<div style={{
...style,
display,
flexDirection,
flexWrap,
justifyContent,
alignItems,
alignContent,
gap,
rowGap,
columnGap,
flex,
flexGrow,
flexShrink,
flexBasis,
flexFlow,
alignSelf,
order,
}}>
{children}
</div>
);
};

75
src/components/Button.tsx Normal file
View File

@ -0,0 +1,75 @@
import { createComponent } from '@lit/react';
import { MdFilledButton as MdFilledButtonWebComponent } from '@material/web/button/filled-button';
import { MdFilledTonalButton as MdFilledTonalButtonWebComponent } from '@material/web/button/filled-tonal-button';
import { MdElevatedButton as MdElevatedButtonWebComponent } from '@material/web/button/elevated-button';
import { MdOutlinedButton as MdOutlinedButtonWebComponent } from '@material/web/button/outlined-button';
import { MdTextButton as MdTextButtonWebComponent } from '@material/web/button/text-button';
import React from 'react';
const MdFilledButton = createComponent({
tagName: 'md-filled-button',
elementClass: MdFilledButtonWebComponent,
react: React,
});
const MdFilledTonalButton = createComponent({
tagName: 'md-filled-tonal-button',
elementClass: MdFilledTonalButtonWebComponent,
react: React,
});
const MdElevatedButton = createComponent({
tagName: 'md-elevated-button',
elementClass: MdElevatedButtonWebComponent,
react: React,
});
const MdOutlinedButton = createComponent({
tagName: 'md-outlined-button',
elementClass: MdOutlinedButtonWebComponent,
react: React,
});
const MdTextButton = createComponent({
tagName: 'md-text-button',
elementClass: MdTextButtonWebComponent,
react: React,
});
type ButtonProps = {
variant?: 'filled' | 'filled-tonal' | 'elevated' | 'outlined' | 'text';
children: React.ReactNode;
disabled?: boolean;
softDisabled?: boolean;
href?: string;
target?: '_blank' | '_parent' | '_self' | '_top';
type?: 'button' | 'submit' | 'reset';
value?: string;
onClick?: (e: React.MouseEvent) => void;
onMouseDown?: (e: React.MouseEvent) => void;
onMouseUp?: (e: React.MouseEvent) => void;
onMouseEnter?: (e: React.MouseEvent) => void;
onMouseLeave?: (e: React.MouseEvent) => void;
onFocus?: (e: React.FocusEvent) => void;
onBlur?: (e: React.FocusEvent) => void;
className?: string;
style?: React.CSSProperties;
id?: string;
};
export const Button = ({ variant, children, disabled, softDisabled, href, target, type, value, ...props }: ButtonProps) => {
switch (variant) {
case 'filled':
return <MdFilledButton {...props} disabled={disabled} softDisabled={softDisabled} href={href} target={target} type={type} value={value}>{children}</MdFilledButton>;
case 'filled-tonal':
return <MdFilledTonalButton {...props} disabled={disabled} softDisabled={softDisabled} href={href} target={target} type={type} value={value}>{children}</MdFilledTonalButton>;
case 'elevated':
return <MdElevatedButton {...props} disabled={disabled} softDisabled={softDisabled} href={href} target={target} type={type} value={value}>{children}</MdElevatedButton>;
case 'outlined':
return <MdOutlinedButton {...props} disabled={disabled} softDisabled={softDisabled} href={href} target={target} type={type} value={value}>{children}</MdOutlinedButton>;
case 'text':
return <MdTextButton {...props} disabled={disabled} softDisabled={softDisabled} href={href} target={target} type={type} value={value}>{children}</MdTextButton>;
default:
return <MdFilledButton {...props} disabled={disabled} softDisabled={softDisabled} href={href} target={target} type={type} value={value}>{children}</MdFilledButton>;
}
};

View File

@ -0,0 +1,65 @@
import React, { FormEvent, MouseEvent, FocusEvent } from "react";
import { createComponent } from "@lit/react";
import { MdCheckbox as MdCheckboxWebComponent } from "@material/web/checkbox/checkbox";
const MdCheckbox = createComponent({
tagName: "md-checkbox",
react: React,
elementClass: MdCheckboxWebComponent,
});
type CheckboxProps = {
checked?: boolean;
indeterminate?: boolean;
id?: string;
disabled?: boolean;
hidden?: boolean;
ariaLabel?: string;
ariaLabelledBy?: string;
ariaDescribedBy?: string;
ariaControls?: string;
required?: boolean;
onInput?: (e: FormEvent<MdCheckboxWebComponent>) => void;
onChange?: (e: FormEvent<MdCheckboxWebComponent>) => void;
onClick?: (e: MouseEvent<MdCheckboxWebComponent>) => void;
onMouseDown?: (e: MouseEvent<MdCheckboxWebComponent>) => void;
onMouseUp?: (e: MouseEvent<MdCheckboxWebComponent>) => void;
onMouseEnter?: (e: MouseEvent<MdCheckboxWebComponent>) => void;
onFocus?: (e: FocusEvent<MdCheckboxWebComponent>) => void;
onBlur?: (e: FocusEvent<MdCheckboxWebComponent>) => void;
className?: string;
style?: React.CSSProperties;
tabIndex?: number;
name?: string;
};
export const Checkbox = ({ checked, indeterminate, id, disabled, hidden, ariaLabel, ariaLabelledBy, ariaDescribedBy, ariaControls, required, onInput, onChange, onClick, onMouseDown, onMouseUp, onMouseEnter, onFocus, onBlur, className, style, tabIndex, name, ...props }: CheckboxProps) => {
return <MdCheckbox
{...props}
id={id}
value={checked ? 'on' : 'off'}
indeterminate={indeterminate}
disabled={disabled || false}
required={required}
checked={checked}
hidden={hidden}
touch-target='wrapper'
aria-label={ariaLabel}
aria-labelledby={ariaLabelledBy}
aria-describedby={ariaDescribedBy}
aria-controls={ariaControls}
aria-required={required}
onInput={onInput}
onChange={onChange}
onClick={onClick}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
onMouseEnter={onMouseEnter}
onFocus={onFocus}
onBlur={onBlur}
className={className}
style={style}
tabIndex={tabIndex}
name={name || undefined}
/>;
};

8
src/components/Forms.tsx Normal file
View File

@ -0,0 +1,8 @@
import React from 'react';
import { createComponent } from '@lit/react';
export const FormLabel = createComponent({
tagName: 'label',
react: React,
elementClass: HTMLLabelElement,
});

62
src/components/Switch.tsx Normal file
View File

@ -0,0 +1,62 @@
import React, { FormEvent, MouseEvent, FocusEvent } from "react";
import { createComponent } from "@lit/react";
import { MdSwitch as MdSwitchWebComponent } from "@material/web/switch/switch";
const MdSwitch = createComponent({
tagName: "md-switch",
react: React,
elementClass: MdSwitchWebComponent,
});
type SwitchProps = {
checked?: boolean;
id?: string;
disabled?: boolean;
hidden?: boolean;
ariaLabel?: string;
ariaLabelledBy?: string;
ariaDescribedBy?: string;
ariaControls?: string;
required?: boolean;
onInput?: (e: FormEvent<MdSwitchWebComponent>) => void;
onChange?: (e: FormEvent<MdSwitchWebComponent>) => void;
onClick?: (e: MouseEvent<MdSwitchWebComponent>) => void;
onMouseDown?: (e: MouseEvent<MdSwitchWebComponent>) => void;
onMouseUp?: (e: MouseEvent<MdSwitchWebComponent>) => void;
onMouseEnter?: (e: MouseEvent<MdSwitchWebComponent>) => void;
onFocus?: (e: FocusEvent<MdSwitchWebComponent>) => void;
onBlur?: (e: FocusEvent<MdSwitchWebComponent>) => void;
className?: string;
style?: React.CSSProperties;
tabIndex?: number;
name?: string;
};
export const Switch = ({ checked, id, disabled, hidden, ariaLabel, ariaLabelledBy, ariaDescribedBy, ariaControls, required, onInput, onChange, onClick, onMouseDown, onMouseUp, onMouseEnter, onFocus, onBlur, className, style, tabIndex, name, ...props }: SwitchProps) => {
return <MdSwitch
{...props}
id={id}
disabled={disabled || false}
required={required}
selected={checked}
hidden={hidden}
touch-target='wrapper'
aria-label={ariaLabel}
aria-labelledby={ariaLabelledBy}
aria-describedby={ariaDescribedBy}
aria-controls={ariaControls}
aria-required={required}
onInput={onInput}
onChange={onChange}
onClick={onClick}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
onMouseEnter={onMouseEnter}
onFocus={onFocus}
onBlur={onBlur}
className={className}
style={style}
tabIndex={tabIndex}
name={name || undefined}
/>;
};

14
src/hooks/useScheme.tsx Normal file
View File

@ -0,0 +1,14 @@
import React, { createContext, useContext } from "react";
const SchemeContext = createContext<'light' | 'dark'>('light');
export function SchemeProvider({ children, scheme }: { children: React.ReactNode, scheme: 'light' | 'dark' }) {
return (
<SchemeContext.Provider value={scheme}>{children}</SchemeContext.Provider>
);
}
export function useScheme() {
const scheme = useContext(SchemeContext);
return scheme;
}

76
src/hooks/useTheme.tsx Normal file
View File

@ -0,0 +1,76 @@
import { hexFromArgb, Theme } from "@material/material-color-utilities";
import React from "react";
import { createContext, useContext } from "react";
import { useScheme } from "./useScheme";
const ThemeContext = createContext<Theme | undefined>(undefined);
export function ThemeProvider({ children, theme, scheme }: { children: React.ReactNode, theme: Theme, scheme: 'dark' | 'light' }) {
return (
<ThemeContext.Provider value={theme}>
<style>
{`
body {
--md-ref-typeface-brand: 'Open Sans';
--md-ref-typeface-plain: system-ui;
--md-sys-color-primary: ${hexFromArgb(theme.schemes[scheme].primary)};
--md-sys-color-primary-container: ${hexFromArgb(theme.schemes[scheme].primaryContainer)};
--md-sys-color-secondary: ${hexFromArgb(theme.schemes[scheme].secondary)};
--md-sys-color-secondary-container: ${hexFromArgb(theme.schemes[scheme].secondaryContainer)};
--md-sys-color-tertiary: ${hexFromArgb(theme.schemes[scheme].tertiary)};
--md-sys-color-tertiary-container: ${hexFromArgb(theme.schemes[scheme].tertiaryContainer)};
--md-sys-color-error: ${hexFromArgb(theme.schemes[scheme].error)};
--md-sys-color-error-container: ${hexFromArgb(theme.schemes[scheme].errorContainer)};
--md-sys-color-background: ${hexFromArgb(theme.schemes[scheme].background)};
--md-sys-color-surface: ${hexFromArgb(theme.schemes[scheme].surface)};
--md-sys-color-surface-bright: ${hexFromArgb(theme.schemes[scheme].surface)};
--md-sys-color-surface-dim: ${hexFromArgb(theme.schemes[scheme].surface)};
--md-sys-color-surface-container: ${hexFromArgb(theme.schemes[scheme].surface)};
--md-sys-color-surface-container-lowest: ${hexFromArgb(theme.schemes[scheme].surface)};
--md-sys-color-surface-container-low: ${hexFromArgb(theme.schemes[scheme].surface)};
--md-sys-color-surface-container-high: ${hexFromArgb(theme.schemes[scheme].surface)};
--md-sys-color-surface-container-highest: ${hexFromArgb(theme.schemes[scheme].surface)};
--md-sys-color-outline: ${hexFromArgb(theme.schemes[scheme].outline)};
--md-sys-color-outline-variant: ${hexFromArgb(theme.schemes[scheme].outlineVariant)};
--md-sys-color-scrim: ${hexFromArgb(theme.schemes[scheme].scrim)};
--md-sys-color-shadow: ${hexFromArgb(theme.schemes[scheme].shadow)};
--md-sys-color-on-surface: ${hexFromArgb(theme.schemes[scheme].onSurface)};
--md-sys-color-on-surface-variant: ${hexFromArgb(theme.schemes[scheme].onSurfaceVariant)};
--md-sys-color-on-surface-bright: ${hexFromArgb(theme.schemes[scheme].onSurface)};
--md-sys-color-on-surface-dim: ${hexFromArgb(theme.schemes[scheme].onSurface)};
--md-sys-color-background: ${hexFromArgb(theme.schemes[scheme].background)};
--md-sys-color-background-variant: ${hexFromArgb(theme.schemes[scheme].surface)};
--md-sys-color-on-primary: ${hexFromArgb(theme.schemes[scheme].onPrimary)};
--md-sys-color-on-primary-container: ${hexFromArgb(theme.schemes[scheme].onPrimaryContainer)};
--md-sys-color-on-secondary: ${hexFromArgb(theme.schemes[scheme].onSecondary)};
--md-sys-color-on-secondary-container: ${hexFromArgb(theme.schemes[scheme].onSecondaryContainer)};
--md-sys-color-on-tertiary: ${hexFromArgb(theme.schemes[scheme].onTertiary)};
--md-sys-color-on-tertiary-container: ${hexFromArgb(theme.schemes[scheme].onTertiaryContainer)};
--md-sys-color-on-error: ${hexFromArgb(theme.schemes[scheme].onError)};
--md-sys-color-on-error-container: ${hexFromArgb(theme.schemes[scheme].onErrorContainer)};
background-color: var(--md-sys-color-background);
}
`}
</style>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return context;
}
export function usePalette() {
const theme = useTheme();
const scheme = useScheme();
return theme.schemes[scheme];
}

34
src/index.tsx Normal file
View File

@ -0,0 +1,34 @@
import React, { useState } from "react";
import { createRoot } from 'react-dom/client';
import { createTheme } from './utils/theme';
import { ThemeProvider } from './hooks/useTheme';
import { Button } from './components/Button';
import { Box } from "./components/Box";
import { Checkbox } from "./components/Checkbox";
import { Switch } from "./components/Switch";
const mountApp = async () => {
const rootContainer = document.getElementById('root');
if (rootContainer === null) {
console.error('Root container element not found!');
return;
}
const theme = createTheme('#e22216');
const root = createRoot(rootContainer);
root.render(
<ThemeProvider theme={theme} scheme='dark'>
<Box style={{
width: '100%',
height: '100vh',
backgroundColor: 'var(--md-sys-color-background)',
}}>
<Button onClick={() => alert('Hello')} variant='filled'>Hello</Button>
<Checkbox />
<Switch />
</Box>
</ThemeProvider>
);
};
mountApp();

37
src/types/material-dynamic-colors.d.ts vendored Normal file
View File

@ -0,0 +1,37 @@
declare module 'material-dynamic-colors' {
interface IMaterialDynamicColorsTheme {
primary: string;
onPrimary: string;
primaryContainer: string;
onPrimaryContainer: string;
secondary: string;
onSecondary: string;
secondaryContainer: string;
onSecondaryContainer: string;
tertiary: string;
onTertiary: string;
tertiaryContainer: string;
onTertiaryContainer: string;
error: string;
onError: string;
errorContainer: string;
onErrorContainer: string;
background: string;
onBackground: string;
surface: string;
onSurface: string;
surfaceVariant: string;
onSurfaceVariant: string;
outline: string;
shadow: string;
scrim: string;
}
interface IMaterialDynamicColors {
light: IMaterialDynamicColorsTheme;
dark: IMaterialDynamicColorsTheme;
}
function MaterialDynamicColors(accent: string): Promise<IMaterialDynamicColors>;
export default MaterialDynamicColors;
}

7
src/utils/theme.ts Normal file
View File

@ -0,0 +1,7 @@
import { argbFromHex, themeFromSourceColor, applyTheme } from '@material/material-color-utilities';
export function createTheme(accent: string) {
const sourceColor = argbFromHex(accent);
const theme = themeFromSourceColor(sourceColor);
return theme;
}

26
tsconfig.json Normal file
View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
"sourceMap": true,
"jsx": "react",
"target": "ES2016",
"module": "ES2020",
"lib": [
"ES2021",
"DOM"
],
"allowJs": true,
"strict": true,
"esModuleInterop": true,
"moduleResolution": "Node",
"resolveJsonModule": true,
"outDir": "dist",
"skipLibCheck": true
},
"exclude": [
"node_modules",
"dist"
],
"include": [
"src"
]
}

40
vite.config.js Normal file
View File

@ -0,0 +1,40 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill';
import inject from '@rollup/plugin-inject';
import buildConfig from './build.config';
export default defineConfig({
appType: 'spa',
publicDir: false,
base: buildConfig.base,
server: {
port: 8080,
host: '127.0.0.1',
},
plugins: [
react()
],
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis',
},
plugins: [
// Enable esbuild polyfill plugins
NodeGlobalsPolyfillPlugin({
process: false,
buffer: true
}),
],
},
},
build: {
outDir: 'dist',
sourcemap: true,
copyPublicDir: false,
rollupOptions: {
plugins: [inject({ Buffer: ['buffer', 'Buffer'] })],
},
},
});