├── .prettierignore ├── .eslintignore ├── examples ├── with-cra │ ├── .env.development │ ├── src │ │ ├── components │ │ │ ├── SonnatSvgLogo │ │ │ │ ├── index.js │ │ │ │ └── SonnatSvgLogo.js │ │ │ └── index.js │ │ ├── theme.js │ │ └── index.js │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── .gitignore │ ├── README.md │ └── package.json ├── with-nextjs │ ├── components │ │ ├── SonnatSvgLogo │ │ │ ├── index.js │ │ │ └── SonnatSvgLogo.js │ │ └── index.js │ ├── public │ │ └── favicon.ico │ ├── theme.js │ ├── package.json │ ├── README.md │ ├── .gitignore │ └── pages │ │ ├── _document.js │ │ └── _app.js └── with-basic-ssr │ ├── components │ ├── SonnatSvgLogo │ │ ├── index.js │ │ └── SonnatSvgLogo.js │ └── index.js │ ├── .babelrc │ ├── .editorconfig │ ├── theme.js │ ├── .prettierrc │ ├── .gitignore │ ├── README.md │ ├── webpack.config.js │ ├── client.js │ ├── package.json │ └── server.js ├── lib ├── Code │ ├── index.ts │ ├── styles.ts │ └── Code.tsx ├── Icon │ ├── index.ts │ └── styles.ts ├── Row │ ├── index.ts │ ├── styles.ts │ └── Row.tsx ├── Tag │ └── index.ts ├── Text │ └── index.ts ├── Badge │ └── index.ts ├── Card │ ├── Body │ │ ├── index.ts │ │ ├── styles.ts │ │ └── Body.tsx │ ├── Media │ │ ├── index.ts │ │ ├── styles.ts │ │ └── Media.tsx │ ├── Header │ │ ├── index.ts │ │ ├── styles.ts │ │ └── Header.tsx │ ├── ActionBar │ │ ├── index.ts │ │ ├── styles.ts │ │ └── ActionBar.tsx │ ├── ActionableArea │ │ ├── index.ts │ │ └── styles.ts │ ├── index.ts │ ├── styles.ts │ └── Card.tsx ├── Flex │ ├── Item │ │ └── index.ts │ ├── index.ts │ └── context.ts ├── Image │ └── index.ts ├── Menu │ ├── Item │ │ ├── index.ts │ │ └── styles.ts │ ├── ItemGroup │ │ ├── index.ts │ │ └── styles.ts │ ├── index.ts │ └── context.ts ├── NoSsr │ ├── index.ts │ └── NoSsr.tsx ├── Radio │ └── index.ts ├── TabBar │ ├── Tab │ │ └── index.ts │ ├── index.ts │ └── context.ts ├── Table │ ├── Row │ │ ├── index.ts │ │ ├── context.ts │ │ ├── styles.ts │ │ └── Row.tsx │ ├── Body │ │ ├── index.ts │ │ └── Body.tsx │ ├── Cell │ │ ├── index.ts │ │ └── styles.ts │ ├── Footer │ │ ├── index.ts │ │ └── Footer.tsx │ ├── Header │ │ ├── index.ts │ │ └── Header.tsx │ ├── context.ts │ ├── innerContext.ts │ ├── index.ts │ └── styles.ts ├── Button │ └── index.ts ├── Column │ ├── index.ts │ └── styles.ts ├── Dialog │ ├── Body │ │ ├── index.ts │ │ ├── styles.ts │ │ └── Body.tsx │ ├── Header │ │ ├── index.ts │ │ ├── styles.ts │ │ └── Header.tsx │ ├── ActionBar │ │ ├── index.ts │ │ ├── styles.ts │ │ └── ActionBar.tsx │ ├── index.ts │ ├── context.ts │ └── styles.ts ├── Divider │ ├── index.ts │ ├── styles.ts │ └── Divider.tsx ├── Popper │ ├── index.ts │ └── styles.ts ├── Portal │ ├── index.ts │ └── Portal.tsx ├── Spinner │ ├── Clip │ │ ├── index.ts │ │ └── styles.ts │ ├── Moon │ │ ├── index.ts │ │ └── styles.ts │ └── index.ts ├── Switch │ └── index.ts ├── Tooltip │ └── index.ts ├── Breadcrumb │ ├── Item │ │ ├── index.ts │ │ ├── styles.ts │ │ └── Item.tsx │ ├── index.ts │ └── styles.ts ├── Checkbox │ └── index.ts ├── Container │ ├── index.ts │ ├── styles.ts │ └── Container.tsx ├── FormControl │ ├── Label │ │ ├── index.ts │ │ ├── styles.ts │ │ └── Label.tsx │ ├── Feedback │ │ ├── index.ts │ │ ├── styles.ts │ │ └── Feedback.tsx │ ├── Description │ │ ├── index.ts │ │ ├── styles.ts │ │ └── Description.tsx │ ├── useFormControl.ts │ ├── index.ts │ ├── context.ts │ └── styles.ts ├── IconButton │ ├── styles.ts │ └── index.ts ├── InputBase │ ├── index.ts │ ├── useInputBase.ts │ └── context.ts ├── Select │ ├── Option │ │ ├── index.ts │ │ └── Option.tsx │ ├── OptionGroup │ │ ├── index.ts │ │ └── OptionGroup.tsx │ ├── index.ts │ └── context.ts ├── Skeleton │ ├── index.ts │ └── styles.ts ├── Snackbar │ └── index.ts ├── TextArea │ └── index.ts ├── TextField │ ├── index.ts │ └── context.ts ├── ActionChip │ └── index.ts ├── CheckGroup │ ├── index.ts │ ├── useCheckGroup.ts │ ├── context.ts │ └── styles.ts ├── ChoiceChip │ └── index.ts ├── PageLoader │ ├── index.ts │ └── styles.ts ├── RadioGroup │ ├── index.ts │ ├── useRadioGroup.ts │ ├── context.ts │ └── styles.ts ├── CssBaseline │ ├── index.ts │ └── CssBaseline.tsx ├── InputSlider │ └── index.ts ├── InputStepper │ ├── index.ts │ ├── reducer.ts │ └── actions.ts ├── InputAdornment │ └── index.ts ├── RemovableChip │ └── index.ts ├── PortalDestination │ ├── index.ts │ ├── styles.ts │ └── PortalDestination.tsx ├── styles │ ├── ServerStyleSheets │ │ ├── index.ts │ │ ├── Provider.tsx │ │ └── ServerStyleSheets.tsx │ ├── defaultTheme.ts │ ├── useTheme.ts │ ├── createRadius.ts │ ├── createZIndexes.ts │ ├── useDarkMode.ts │ ├── makeStyles.ts │ ├── swatches.ts │ ├── jssPreset.ts │ ├── ThemeProvider.tsx │ ├── index.ts │ ├── createMixins.ts │ ├── generateColorSwatch.ts │ ├── createSpacings.ts │ └── createBreakpoints.ts ├── utils │ ├── camelCase.ts │ ├── isUndef.ts │ ├── clamp.ts │ ├── generateUniqueString.ts │ ├── useIsomorphicLayoutEffect.ts │ ├── getVar.ts │ ├── onNextFrame.ts │ ├── useLatest.ts │ ├── usePreviousValue.ts │ ├── setRef.ts │ ├── useGetLatest.ts │ ├── useIsMounted.ts │ ├── map.ts │ ├── useForkedRefs.ts │ ├── getOffsetFromWindow.ts │ ├── useOnChange.ts │ ├── detectScrollBarWidth.ts │ ├── createSvgIcon.tsx │ ├── useSyncEffect.ts │ ├── useId.ts │ ├── useEventCallback.ts │ ├── HTMLElementType.ts │ ├── deepMerge.ts │ ├── useConstantProp.ts │ ├── getSingleChild.tsx │ ├── index.ts │ ├── is.ts │ └── scrollLeft.ts └── internals │ └── icons │ ├── Minus.tsx │ ├── Plus.tsx │ ├── ChevronDown.tsx │ ├── Check.tsx │ ├── ChevronLeft.tsx │ ├── ChevronRight.tsx │ ├── ChevronRightLarge.tsx │ ├── ChevronLeftLarge.tsx │ ├── index.ts │ ├── Close.tsx │ ├── CloseLarge.tsx │ └── Magnifier.tsx ├── tsconfig.cjs.json ├── .editorconfig ├── next.config.js ├── next-env.d.ts ├── .prettierrc ├── tsconfig.lint.json ├── pages ├── index.tsx ├── _document.tsx └── _app.tsx ├── scripts ├── minify.js ├── build-packages.js └── build-npm-files.js ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── pull_request_template.md ├── tsconfig.esm.json ├── tsconfig.json ├── LICENSE ├── .eslintrc ├── sonnat.svg ├── README.md └── .gitignore /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | examples/ 4 | -------------------------------------------------------------------------------- /examples/with-cra/.env.development: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | -------------------------------------------------------------------------------- /lib/Code/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Code"; 2 | export * from "./Code"; 3 | -------------------------------------------------------------------------------- /lib/Icon/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Icon"; 2 | export * from "./Icon"; 3 | -------------------------------------------------------------------------------- /lib/Row/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Row"; 2 | export * from "./Row"; 3 | -------------------------------------------------------------------------------- /lib/Tag/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Tag"; 2 | export * from "./Tag"; 3 | -------------------------------------------------------------------------------- /lib/Text/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Text"; 2 | export * from "./Text"; 3 | -------------------------------------------------------------------------------- /lib/Badge/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Badge"; 2 | export * from "./Badge"; 3 | -------------------------------------------------------------------------------- /lib/Card/Body/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Body"; 2 | export * from "./Body"; 3 | -------------------------------------------------------------------------------- /lib/Flex/Item/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Item"; 2 | export * from "./Item"; 3 | -------------------------------------------------------------------------------- /lib/Image/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Image"; 2 | export * from "./Image"; 3 | -------------------------------------------------------------------------------- /lib/Menu/Item/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Item"; 2 | export * from "./Item"; 3 | -------------------------------------------------------------------------------- /lib/NoSsr/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./NoSsr"; 2 | export * from "./NoSsr"; 3 | -------------------------------------------------------------------------------- /lib/Radio/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Radio"; 2 | export * from "./Radio"; 3 | -------------------------------------------------------------------------------- /lib/TabBar/Tab/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Tab"; 2 | export * from "./Tab"; 3 | -------------------------------------------------------------------------------- /lib/Table/Row/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Row"; 2 | export * from "./Row"; 3 | -------------------------------------------------------------------------------- /lib/Button/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Button"; 2 | export * from "./Button"; 3 | -------------------------------------------------------------------------------- /lib/Card/Media/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Media"; 2 | export * from "./Media"; 3 | -------------------------------------------------------------------------------- /lib/Column/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Column"; 2 | export * from "./Column"; 3 | -------------------------------------------------------------------------------- /lib/Dialog/Body/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Body"; 2 | export * from "./Body"; 3 | -------------------------------------------------------------------------------- /lib/Divider/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Divider"; 2 | export * from "./Divider"; 3 | -------------------------------------------------------------------------------- /lib/Popper/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Popper"; 2 | export * from "./Popper"; 3 | -------------------------------------------------------------------------------- /lib/Portal/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Portal"; 2 | export * from "./Portal"; 3 | -------------------------------------------------------------------------------- /lib/Spinner/Clip/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Clip"; 2 | export * from "./Clip"; 3 | -------------------------------------------------------------------------------- /lib/Spinner/Moon/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Moon"; 2 | export * from "./Moon"; 3 | -------------------------------------------------------------------------------- /lib/Switch/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Switch"; 2 | export * from "./Switch"; 3 | -------------------------------------------------------------------------------- /lib/Table/Body/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Body"; 2 | export * from "./Body"; 3 | -------------------------------------------------------------------------------- /lib/Table/Cell/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Cell"; 2 | export * from "./Cell"; 3 | -------------------------------------------------------------------------------- /lib/Tooltip/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Tooltip"; 2 | export * from "./Tooltip"; 3 | -------------------------------------------------------------------------------- /lib/Breadcrumb/Item/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Item"; 2 | export * from "./Item"; 3 | -------------------------------------------------------------------------------- /lib/Card/Header/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Header"; 2 | export * from "./Header"; 3 | -------------------------------------------------------------------------------- /lib/Checkbox/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Checkbox"; 2 | export * from "./Checkbox"; 3 | -------------------------------------------------------------------------------- /lib/Container/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Container"; 2 | export * from "./Container"; 3 | -------------------------------------------------------------------------------- /lib/Dialog/Header/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Header"; 2 | export * from "./Header"; 3 | -------------------------------------------------------------------------------- /lib/FormControl/Label/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Label"; 2 | export * from "./Label"; 3 | -------------------------------------------------------------------------------- /lib/IconButton/styles.ts: -------------------------------------------------------------------------------- 1 | export { default, type VariantColorCombo } from "../Button/styles"; 2 | -------------------------------------------------------------------------------- /lib/InputBase/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./InputBase"; 2 | export * from "./InputBase"; 3 | -------------------------------------------------------------------------------- /lib/Select/Option/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Option"; 2 | export * from "./Option"; 3 | -------------------------------------------------------------------------------- /lib/Skeleton/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Skeleton"; 2 | export * from "./Skeleton"; 3 | -------------------------------------------------------------------------------- /lib/Snackbar/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Snackbar"; 2 | export * from "./Snackbar"; 3 | -------------------------------------------------------------------------------- /lib/Table/Footer/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Footer"; 2 | export * from "./Footer"; 3 | -------------------------------------------------------------------------------- /lib/Table/Header/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Header"; 2 | export * from "./Header"; 3 | -------------------------------------------------------------------------------- /lib/TextArea/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./TextArea"; 2 | export * from "./TextArea"; 3 | -------------------------------------------------------------------------------- /lib/TextField/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./TextField"; 2 | export * from "./TextField"; 3 | -------------------------------------------------------------------------------- /examples/with-cra/src/components/SonnatSvgLogo/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./SonnatSvgLogo"; 2 | -------------------------------------------------------------------------------- /examples/with-nextjs/components/SonnatSvgLogo/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./SonnatSvgLogo"; 2 | -------------------------------------------------------------------------------- /lib/ActionChip/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./ActionChip"; 2 | export * from "./ActionChip"; 3 | -------------------------------------------------------------------------------- /lib/Card/ActionBar/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./ActionBar"; 2 | export * from "./ActionBar"; 3 | -------------------------------------------------------------------------------- /lib/CheckGroup/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./CheckGroup"; 2 | export { default } from "./CheckGroup"; 3 | -------------------------------------------------------------------------------- /lib/ChoiceChip/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./ChoiceChip"; 2 | export * from "./ChoiceChip"; 3 | -------------------------------------------------------------------------------- /lib/IconButton/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./IconButton"; 2 | export * from "./IconButton"; 3 | -------------------------------------------------------------------------------- /lib/Menu/ItemGroup/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./ItemGroup"; 2 | export * from "./ItemGroup"; 3 | -------------------------------------------------------------------------------- /lib/PageLoader/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./PageLoader"; 2 | export * from "./PageLoader"; 3 | -------------------------------------------------------------------------------- /lib/RadioGroup/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./RadioGroup"; 2 | export * from "./RadioGroup"; 3 | -------------------------------------------------------------------------------- /examples/with-basic-ssr/components/SonnatSvgLogo/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./SonnatSvgLogo"; 2 | -------------------------------------------------------------------------------- /examples/with-basic-ssr/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as SonnatSvgLogo } from "./SonnatSvgLogo"; 2 | -------------------------------------------------------------------------------- /examples/with-cra/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as SonnatSvgLogo } from "./SonnatSvgLogo"; 2 | -------------------------------------------------------------------------------- /examples/with-nextjs/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as SonnatSvgLogo } from "./SonnatSvgLogo"; 2 | -------------------------------------------------------------------------------- /lib/CssBaseline/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./CssBaseline"; 2 | export * from "./CssBaseline"; 3 | -------------------------------------------------------------------------------- /lib/Dialog/ActionBar/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./ActionBar"; 2 | export * from "./ActionBar"; 3 | -------------------------------------------------------------------------------- /lib/FormControl/Feedback/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Feedback"; 2 | export * from "./Feedback"; 3 | -------------------------------------------------------------------------------- /lib/InputSlider/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./InputSlider"; 2 | export * from "./InputSlider"; 3 | -------------------------------------------------------------------------------- /lib/InputStepper/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./InputStepper"; 2 | export * from "./InputStepper"; 3 | -------------------------------------------------------------------------------- /lib/InputAdornment/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./InputAdornment"; 2 | export * from "./InputAdornment"; 3 | -------------------------------------------------------------------------------- /lib/RemovableChip/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./RemovableChip"; 2 | export * from "./RemovableChip"; 3 | -------------------------------------------------------------------------------- /lib/Select/OptionGroup/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./OptionGroup"; 2 | export * from "./OptionGroup"; 3 | -------------------------------------------------------------------------------- /lib/Card/ActionableArea/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./ActionableArea"; 2 | export * from "./ActionableArea"; 3 | -------------------------------------------------------------------------------- /lib/FormControl/Description/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Description"; 2 | export * from "./Description"; 3 | -------------------------------------------------------------------------------- /lib/PortalDestination/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./PortalDestination"; 2 | export * from "./PortalDestination"; 3 | -------------------------------------------------------------------------------- /examples/with-cra/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonnat/sonnat-ui/HEAD/examples/with-cra/public/favicon.ico -------------------------------------------------------------------------------- /examples/with-nextjs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonnat/sonnat-ui/HEAD/examples/with-nextjs/public/favicon.ico -------------------------------------------------------------------------------- /lib/styles/ServerStyleSheets/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./ServerStyleSheets"; 2 | export * from "./ServerStyleSheets"; 3 | -------------------------------------------------------------------------------- /lib/utils/camelCase.ts: -------------------------------------------------------------------------------- 1 | const camelCase = (s: string): string => 2 | s.replace(/-./g, x => x.toUpperCase()[1]); 3 | 4 | export default camelCase; 5 | -------------------------------------------------------------------------------- /lib/utils/isUndef.ts: -------------------------------------------------------------------------------- 1 | const isUndef = (value: T | undefined): value is undefined => 2 | typeof value === "undefined"; 3 | 4 | export default isUndef; 5 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.esm.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "outDir": "./dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/with-basic-ssr/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": ["@babel/plugin-proposal-class-properties"] 4 | } 5 | -------------------------------------------------------------------------------- /lib/Flex/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Flex"; 2 | export * from "./Flex"; 3 | 4 | export { default as FlexItem } from "./Item"; 5 | export * from "./Item"; 6 | -------------------------------------------------------------------------------- /lib/utils/clamp.ts: -------------------------------------------------------------------------------- 1 | const clamp = (number: number, min: number, max: number): number => 2 | Math.max(Math.min(number, max), min); 3 | 4 | export default clamp; 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = false 8 | insert_final_newline = false -------------------------------------------------------------------------------- /lib/utils/generateUniqueString.ts: -------------------------------------------------------------------------------- 1 | const generateUniqueString = (): string => 2 | "_" + Math.random().toString(36).substr(2, 9); 3 | 4 | export default generateUniqueString; 5 | -------------------------------------------------------------------------------- /lib/Breadcrumb/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Breadcrumb"; 2 | export * from "./Breadcrumb"; 3 | 4 | export { default as BreadcrumbItem } from "./Item"; 5 | export * from "./Item"; 6 | -------------------------------------------------------------------------------- /examples/with-basic-ssr/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = false 8 | insert_final_newline = false -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next/dist/next-server/server/config').NextConfig} */ 2 | const nextConfig = { reactStrictMode: true, trailingSlash: false }; 3 | 4 | module.exports = nextConfig; 5 | -------------------------------------------------------------------------------- /lib/TabBar/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/export */ 2 | export { default } from "./TabBar"; 3 | export * from "./TabBar"; 4 | 5 | export { default as Tab } from "./Tab"; 6 | export * from "./Tab"; 7 | -------------------------------------------------------------------------------- /lib/styles/defaultTheme.ts: -------------------------------------------------------------------------------- 1 | import createTheme from "./createTheme"; 2 | 3 | const defaultTheme = createTheme(); 4 | 5 | export type DefaultTheme = typeof defaultTheme; 6 | 7 | export default defaultTheme; 8 | -------------------------------------------------------------------------------- /lib/Spinner/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/export */ 2 | export { default as ClipSpinner } from "./Clip"; 3 | export * from "./Clip"; 4 | 5 | export { default as MoonSpinner } from "./Moon"; 6 | export * from "./Moon"; 7 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /lib/styles/useTheme.ts: -------------------------------------------------------------------------------- 1 | import { useTheme as jssUseTheme } from "react-jss"; 2 | import type { DefaultTheme } from "./defaultTheme"; 3 | 4 | const useTheme = (): T => jssUseTheme(); 5 | 6 | export default useTheme; 7 | -------------------------------------------------------------------------------- /lib/utils/useIsomorphicLayoutEffect.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const useIsomorphicLayoutEffect = 4 | typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect; 5 | 6 | export default useIsomorphicLayoutEffect; 7 | -------------------------------------------------------------------------------- /lib/Card/Media/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | { root: { width: "100%", position: "relative" } }, 5 | { name: "SonnatCardMedia" } 6 | ); 7 | 8 | export default useStyles; 9 | -------------------------------------------------------------------------------- /lib/utils/getVar.ts: -------------------------------------------------------------------------------- 1 | const getVar = ( 2 | variable: T, 3 | fallback: NonNullable, 4 | condition = true 5 | ): NonNullable => 6 | variable == null || condition ? fallback : (variable as NonNullable); 7 | 8 | export default getVar; 9 | -------------------------------------------------------------------------------- /lib/Menu/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Item"; 2 | export { default as MenuItem } from "./Item"; 3 | export * from "./ItemGroup"; 4 | export { default as MenuItemGroup } from "./ItemGroup"; 5 | export * from "./Menu"; 6 | export { default } from "./Menu"; 7 | -------------------------------------------------------------------------------- /examples/with-cra/src/theme.js: -------------------------------------------------------------------------------- 1 | import createTheme from "@sonnat/ui/styles/createTheme"; 2 | 3 | const theme = createTheme({ 4 | typography: { 5 | ltrFontFamily: "Roboto" 6 | }, 7 | direction: "ltr" 8 | }); 9 | 10 | export default theme; 11 | -------------------------------------------------------------------------------- /examples/with-nextjs/theme.js: -------------------------------------------------------------------------------- 1 | import createTheme from "@sonnat/ui/styles/createTheme"; 2 | 3 | const theme = createTheme({ 4 | typography: { 5 | ltrFontFamily: "Roboto" 6 | }, 7 | direction: "ltr" 8 | }); 9 | 10 | export default theme; 11 | -------------------------------------------------------------------------------- /lib/InputBase/useInputBase.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import InputBaseContext, { IContext } from "./context"; 3 | 4 | const useInputBase = (): IContext | undefined => 5 | React.useContext(InputBaseContext); 6 | 7 | export default useInputBase; 8 | -------------------------------------------------------------------------------- /examples/with-basic-ssr/theme.js: -------------------------------------------------------------------------------- 1 | import createTheme from "@sonnat/ui/styles/createTheme"; 2 | 3 | const theme = createTheme({ 4 | typography: { 5 | ltrFontFamily: "Roboto" 6 | }, 7 | direction: "ltr" 8 | }); 9 | 10 | export default theme; 11 | -------------------------------------------------------------------------------- /lib/CheckGroup/useCheckGroup.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import CheckGroupContext, { IContext } from "./context"; 3 | 4 | const useCheckGroup = (): IContext | undefined => 5 | React.useContext(CheckGroupContext); 6 | 7 | export default useCheckGroup; 8 | -------------------------------------------------------------------------------- /lib/RadioGroup/useRadioGroup.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import RadioGroupContext, { IContext } from "./context"; 3 | 4 | const useRadioGroup = (): IContext | undefined => 5 | React.useContext(RadioGroupContext); 6 | 7 | export default useRadioGroup; 8 | -------------------------------------------------------------------------------- /lib/FormControl/useFormControl.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import FormControlContext, { IContext } from "./context"; 3 | 4 | const useFormControl = (): IContext | undefined => 5 | React.useContext(FormControlContext); 6 | 7 | export default useFormControl; 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "packageManager": "yarn", 4 | "printWidth": 80, 5 | "semi": true, 6 | "singleQuote": false, 7 | "tabWidth": 2, 8 | "useEditorConfig": true, 9 | "trailingComma": "none", 10 | "arrowParens": "avoid" 11 | } 12 | -------------------------------------------------------------------------------- /lib/utils/onNextFrame.ts: -------------------------------------------------------------------------------- 1 | type Callback = () => void; 2 | 3 | const onNextFrame = (callback: Callback): void => { 4 | if (typeof window === "undefined") return; 5 | setTimeout(() => requestAnimationFrame(() => void callback())); 6 | }; 7 | 8 | export default onNextFrame; 9 | -------------------------------------------------------------------------------- /lib/Select/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Select"; 2 | export * from "./Select"; 3 | 4 | export { default as SelectOption } from "./Option"; 5 | export * from "./Option"; 6 | 7 | export { default as SelectOptionGroup } from "./OptionGroup"; 8 | export * from "./OptionGroup"; 9 | -------------------------------------------------------------------------------- /lib/utils/useLatest.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const useLatest = (value: T) => { 4 | const ref = React.useRef(value); 5 | 6 | React.useEffect(() => void (ref.current = value)); 7 | 8 | return ref; 9 | }; 10 | 11 | export default useLatest; 12 | -------------------------------------------------------------------------------- /examples/with-basic-ssr/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "packageManager": "yarn", 4 | "printWidth": 80, 5 | "semi": true, 6 | "singleQuote": false, 7 | "tabWidth": 2, 8 | "useEditorConfig": true, 9 | "trailingComma": "none", 10 | "arrowParens": "avoid" 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { "noEmit": true, "jsx": "react-jsx" }, 4 | "exclude": [ 5 | "node_modules", 6 | "dist", 7 | "examples", 8 | "next-env.d.ts", 9 | "pages/**/*.ts", 10 | "pages/**/*.tsx" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /lib/internals/icons/Minus.tsx: -------------------------------------------------------------------------------- 1 | import createSvgIcon from "../../utils/createSvgIcon"; 2 | 3 | const Minus = createSvgIcon( 4 | , 5 | "Minus" 6 | ); 7 | 8 | export default Minus; 9 | -------------------------------------------------------------------------------- /lib/utils/usePreviousValue.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const usePreviousValue = (value: T): T | undefined => { 4 | const ref = React.useRef(); 5 | 6 | React.useEffect(() => void (ref.current = value), [value]); 7 | 8 | return ref.current; 9 | }; 10 | 11 | export default usePreviousValue; 12 | -------------------------------------------------------------------------------- /lib/Flex/context.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface IContext { 4 | isColumn: boolean; 5 | } 6 | 7 | const FlexContext = React.createContext({ isColumn: false }); 8 | 9 | if (process.env.NODE_ENV !== "production") 10 | FlexContext.displayName = "FlexContext"; 11 | 12 | export default FlexContext; 13 | -------------------------------------------------------------------------------- /lib/utils/setRef.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const setRef = (ref: React.Ref, value: T): void => { 4 | if (typeof ref === "function") ref(value); 5 | else if (ref && typeof ref === "object" && "current" in ref) 6 | (ref as React.MutableRefObject).current = value; 7 | }; 8 | 9 | export default setRef; 10 | -------------------------------------------------------------------------------- /lib/Dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ActionBar"; 2 | export { default as DialogActionBar } from "./ActionBar"; 3 | export * from "./Body"; 4 | export { default as DialogBody } from "./Body"; 5 | export * from "./Dialog"; 6 | export { default } from "./Dialog"; 7 | export * from "./Header"; 8 | export { default as DialogHeader } from "./Header"; 9 | -------------------------------------------------------------------------------- /lib/Table/context.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface IContext { 4 | isDense: boolean; 5 | } 6 | 7 | const TableContext = React.createContext(undefined); 8 | 9 | if (process.env.NODE_ENV !== "production") 10 | TableContext.displayName = "TableContext"; 11 | 12 | export default TableContext; 13 | -------------------------------------------------------------------------------- /lib/Card/Body/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | spacings: { spaces } 7 | } = theme; 8 | 9 | return { root: { padding: spaces[7].rem } }; 10 | }, 11 | { name: "SonnatCardBody" } 12 | ); 13 | 14 | export default useStyles; 15 | -------------------------------------------------------------------------------- /lib/PortalDestination/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | { 5 | root: { 6 | position: "absolute", 7 | top: 0, 8 | left: 0, 9 | width: "100%" 10 | } 11 | }, 12 | { name: "SonnatPortalDestination" } 13 | ); 14 | 15 | export default useStyles; 16 | -------------------------------------------------------------------------------- /lib/Select/context.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface IContext { 4 | isMultiple: boolean; 5 | } 6 | 7 | const SelectContext = React.createContext(undefined); 8 | 9 | if (process.env.NODE_ENV !== "production") 10 | SelectContext.displayName = "SelectContext"; 11 | 12 | export default SelectContext; 13 | -------------------------------------------------------------------------------- /lib/Table/Row/context.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface IContext { 4 | isSelected: boolean; 5 | } 6 | 7 | const TableRowContext = React.createContext(undefined); 8 | 9 | if (process.env.NODE_ENV !== "production") 10 | TableRowContext.displayName = "TableRowContext"; 11 | 12 | export default TableRowContext; 13 | -------------------------------------------------------------------------------- /lib/utils/useGetLatest.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import useLatest from "./useLatest"; 3 | 4 | const useGetLatest = (value: T): (() => T) => { 5 | const ref = useLatest(value); 6 | 7 | // eslint-disable-next-line react-hooks/exhaustive-deps 8 | return React.useCallback(() => ref.current, []); 9 | }; 10 | 11 | export default useGetLatest; 12 | -------------------------------------------------------------------------------- /lib/TextField/context.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface IContext { 4 | isEmpty: boolean; 5 | } 6 | 7 | const TextFieldContext = React.createContext(undefined); 8 | 9 | if (process.env.NODE_ENV !== "production") { 10 | TextFieldContext.displayName = "TextFieldContext"; 11 | } 12 | 13 | export default TextFieldContext; 14 | -------------------------------------------------------------------------------- /examples/with-basic-ssr/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # production 7 | /build 8 | 9 | # misc 10 | .DS_Store 11 | .npmrc 12 | .env.local 13 | .env.development.local 14 | .env.test.local 15 | .env.production.local 16 | 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* -------------------------------------------------------------------------------- /lib/FormControl/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Description"; 2 | export { default as FormControlDescription } from "./Description"; 3 | export * from "./Feedback"; 4 | export { default as FormControlFeedback } from "./Feedback"; 5 | export * from "./FormControl"; 6 | export { default } from "./FormControl"; 7 | export * from "./Label"; 8 | export { default as FormControlLabel } from "./Label"; 9 | -------------------------------------------------------------------------------- /lib/Table/innerContext.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface IContext { 4 | cellVariant: "body" | "header" | "footer"; 5 | } 6 | 7 | const TableInnerContext = React.createContext(undefined); 8 | 9 | if (process.env.NODE_ENV !== "production") 10 | TableInnerContext.displayName = "TableInnerContext"; 11 | 12 | export default TableInnerContext; 13 | -------------------------------------------------------------------------------- /lib/internals/icons/Plus.tsx: -------------------------------------------------------------------------------- 1 | import createSvgIcon from "../../utils/createSvgIcon"; 2 | 3 | const Plus = createSvgIcon( 4 | , 5 | "Plus" 6 | ); 7 | 8 | export default Plus; 9 | -------------------------------------------------------------------------------- /lib/RadioGroup/context.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export interface IContext { 4 | value: string; 5 | onChange: (value?: string) => void; 6 | } 7 | 8 | const RadioGroupContext = React.createContext(undefined); 9 | 10 | if (process.env.NODE_ENV !== "production") { 11 | RadioGroupContext.displayName = "RadioGroupContext"; 12 | } 13 | 14 | export default RadioGroupContext; 15 | -------------------------------------------------------------------------------- /lib/utils/useIsMounted.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const useIsMounted = (): (() => boolean) => { 4 | const isMountedRef = React.useRef(false); 5 | 6 | React.useEffect(() => { 7 | isMountedRef.current = true; 8 | return () => void (isMountedRef.current = false); 9 | }, []); 10 | 11 | return React.useCallback(() => isMountedRef.current, []); 12 | }; 13 | 14 | export default useIsMounted; 15 | -------------------------------------------------------------------------------- /examples/with-nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-nextjs", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "@sonnat/ui": "latest", 12 | "@sonnat/icons": "latest", 13 | "next": "latest", 14 | "react": "latest", 15 | "react-dom": "latest" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/with-basic-ssr/README.md: -------------------------------------------------------------------------------- 1 | # Sonnat With Basic SSR 2 | 3 | This is a [React](https://reactjs.org/) project created from scratch. 4 | 5 | ## Getting Started 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `App.js`. The page auto-updates as you edit the file. 16 | -------------------------------------------------------------------------------- /examples/with-cra/.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 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /lib/CheckGroup/context.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export interface IContext { 4 | value: string[]; 5 | onChange: (isChecked: boolean, value?: string) => void; 6 | } 7 | 8 | const CheckGroupContext = React.createContext(undefined); 9 | 10 | if (process.env.NODE_ENV !== "production") { 11 | CheckGroupContext.displayName = "CheckGroupContext"; 12 | } 13 | 14 | export default CheckGroupContext; 15 | -------------------------------------------------------------------------------- /lib/InputBase/context.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export interface IContext { 4 | size: "large" | "medium" | "small"; 5 | disabled: boolean; 6 | hasError: boolean; 7 | } 8 | 9 | const InputBaseContext = React.createContext(undefined); 10 | 11 | if (process.env.NODE_ENV !== "production") { 12 | InputBaseContext.displayName = "InputBaseContext"; 13 | } 14 | 15 | export default InputBaseContext; 16 | -------------------------------------------------------------------------------- /lib/Menu/context.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface IContext { 4 | registerNode: ( 5 | index: number, 6 | node: HTMLDivElement & { disabled?: boolean } 7 | ) => void; 8 | dense: boolean; 9 | } 10 | 11 | const MenuContext = React.createContext(undefined); 12 | 13 | if (process.env.NODE_ENV !== "production") 14 | MenuContext.displayName = "MenuContext"; 15 | 16 | export default MenuContext; 17 | -------------------------------------------------------------------------------- /lib/CheckGroup/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => ({ 5 | root: { 6 | direction: theme.direction, 7 | fontFamily: theme.typography.fontFamily[theme.direction], 8 | display: "flex", 9 | flexWrap: "wrap" 10 | }, 11 | column: { flexDirection: "column" }, 12 | row: { flexDirection: "row" } 13 | }), 14 | { name: "SonnatCheckGroup" } 15 | ); 16 | 17 | export default useStyles; 18 | -------------------------------------------------------------------------------- /lib/RadioGroup/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => ({ 5 | root: { 6 | direction: theme.direction, 7 | fontFamily: theme.typography.fontFamily[theme.direction], 8 | display: "flex", 9 | flexWrap: "wrap" 10 | }, 11 | column: { flexDirection: "column" }, 12 | row: { flexDirection: "row" } 13 | }), 14 | { name: "SonnatRadioGroup" } 15 | ); 16 | 17 | export default useStyles; 18 | -------------------------------------------------------------------------------- /examples/with-basic-ssr/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./client.js", 5 | mode: process.env.NODE_ENV || "development", 6 | output: { 7 | path: path.resolve(__dirname, "build"), 8 | filename: "bundle.js", 9 | publicPath: "/" 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.js$/, 15 | exclude: /node_modules/, 16 | loader: "babel-loader" 17 | } 18 | ] 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /lib/utils/map.ts: -------------------------------------------------------------------------------- 1 | import clamp from "./clamp"; 2 | 3 | const map = ( 4 | number: number, 5 | min: number, 6 | max: number, 7 | rMin: number, 8 | rMax: number, 9 | withinBounds = false 10 | ): number => { 11 | const mappedVal = ((number - min) * (rMax - rMin)) / (max - min) + rMin; 12 | 13 | if (!withinBounds) return mappedVal; 14 | else if (rMax > rMin) return clamp(mappedVal, rMin, rMax); 15 | else return clamp(mappedVal, rMax, rMin); 16 | }; 17 | 18 | export default map; 19 | -------------------------------------------------------------------------------- /examples/with-cra/README.md: -------------------------------------------------------------------------------- 1 | # Sonnat With CRA(create-react-app) 2 | 3 | This is a [React](https://reactjs.org/) project bootstrapped with [`create-react-app`](https://github.com/facebook/create-react-app). 4 | 5 | ## Getting Started 6 | 7 | ```bash 8 | npm run start 9 | # or 10 | yarn start 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `src/App.js`. The page auto-updates as you edit the file. 16 | -------------------------------------------------------------------------------- /lib/Table/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Table"; 2 | export * from "./Table"; 3 | 4 | export { default as TableHeader } from "./Header"; 5 | export * from "./Header"; 6 | 7 | export { default as TableFooter } from "./Footer"; 8 | export * from "./Footer"; 9 | 10 | export { default as TableRow } from "./Row"; 11 | export * from "./Row"; 12 | 13 | export { default as TableBody } from "./Body"; 14 | export * from "./Body"; 15 | 16 | export { default as TableCell } from "./Cell"; 17 | export * from "./Cell"; 18 | -------------------------------------------------------------------------------- /examples/with-cra/src/index.js: -------------------------------------------------------------------------------- 1 | import CssBaseline from "@sonnat/ui/CssBaseline"; 2 | import SonnatInitializer from "@sonnat/ui/styles/SonnatInitializer"; 3 | import React from "react"; 4 | import ReactDOM from "react-dom"; 5 | import App from "./App"; 6 | import theme from "./theme"; 7 | 8 | ReactDOM.render( 9 | 10 |
11 | 12 | 13 |
14 |
, 15 | document.getElementById("root") 16 | ); 17 | -------------------------------------------------------------------------------- /lib/Card/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ActionableArea"; 2 | export { default as CardActionableArea } from "./ActionableArea"; 3 | export * from "./ActionBar"; 4 | export { default as CardActionBar } from "./ActionBar"; 5 | export * from "./Body"; 6 | export { default as CardBody } from "./Body"; 7 | export * from "./Card"; 8 | export { default } from "./Card"; 9 | export * from "./Header"; 10 | export { default as CardHeader } from "./Header"; 11 | export * from "./Media"; 12 | export { default as CardMedia } from "./Media"; 13 | -------------------------------------------------------------------------------- /lib/utils/useForkedRefs.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import setRef from "./setRef"; 3 | 4 | /** 5 | * Cherry-picked from https://github.com/mimshins/utilityjs/tree/main/src/hook/useForkedRefs 6 | */ 7 | const useForkedRefs = (...refs: React.Ref[]): React.RefCallback => 8 | React.useCallback( 9 | (instance: T) => void refs.forEach(ref => void setRef(ref, instance)), 10 | // eslint-disable-next-line react-hooks/exhaustive-deps 11 | [refs] 12 | ); 13 | 14 | export default useForkedRefs; 15 | -------------------------------------------------------------------------------- /examples/with-nextjs/README.md: -------------------------------------------------------------------------------- 1 | # Sonnat With NextJS 2 | 3 | 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). 4 | 5 | ## Getting Started 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | -------------------------------------------------------------------------------- /lib/internals/icons/ChevronDown.tsx: -------------------------------------------------------------------------------- 1 | import createSvgIcon from "../../utils/createSvgIcon"; 2 | 3 | const ChevronDown = createSvgIcon( 4 | , 5 | "ChevronDown" 6 | ); 7 | 8 | export default ChevronDown; 9 | -------------------------------------------------------------------------------- /examples/with-basic-ssr/client.js: -------------------------------------------------------------------------------- 1 | import CssBaseline from "@sonnat/ui/CssBaseline"; 2 | import SonnatInitializer from "@sonnat/ui/styles/SonnatInitializer"; 3 | import React from "react"; 4 | import ReactDOM from "react-dom"; 5 | import App from "./App"; 6 | import theme from "./theme"; 7 | 8 | const Main = () => { 9 | return ( 10 | 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | ReactDOM.hydrate(
, document.getElementById("root")); 18 | -------------------------------------------------------------------------------- /lib/internals/icons/Check.tsx: -------------------------------------------------------------------------------- 1 | import createSvgIcon from "../../utils/createSvgIcon"; 2 | 3 | const Check = createSvgIcon( 4 | , 5 | "Check" 6 | ); 7 | 8 | export default Check; 9 | -------------------------------------------------------------------------------- /lib/FormControl/context.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export interface IContext { 4 | fluid: boolean; 5 | disabled: boolean; 6 | hasError: boolean; 7 | required: boolean; 8 | focusedState: boolean; 9 | onFocus: () => void; 10 | onBlur: () => void; 11 | } 12 | 13 | const FormControlContext = React.createContext(undefined); 14 | 15 | if (process.env.NODE_ENV !== "production") { 16 | FormControlContext.displayName = "FormControlContext"; 17 | } 18 | 19 | export default FormControlContext; 20 | -------------------------------------------------------------------------------- /lib/Popper/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | ({ zIndexes }) => ({ 5 | root: { 6 | opacity: 0, 7 | visibility: "hidden", 8 | zIndex: zIndexes.popover, 9 | transition: ["opacity 240ms ease", "visibility 240ms ease"].join(",") 10 | }, 11 | open: { opacity: 1, visibility: "visible" }, 12 | absolute: { position: "absolute" }, 13 | fixed: { position: "fixed" } 14 | }), 15 | { name: "SonnatPopper" } 16 | ); 17 | 18 | export default useStyles; 19 | -------------------------------------------------------------------------------- /lib/FormControl/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => ({ 5 | root: { 6 | direction: theme.direction, 7 | fontFamily: theme.typography.fontFamily[theme.direction], 8 | display: "inline-flex", 9 | flexDirection: "column", 10 | position: "relative", 11 | verticalAlign: "top", 12 | alignItems: "flex-start" 13 | }, 14 | fluid: { width: "100%" } 15 | }), 16 | { name: "SonnatFormControl" } 17 | ); 18 | 19 | export default useStyles; 20 | -------------------------------------------------------------------------------- /examples/with-nextjs/.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 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /lib/internals/icons/ChevronLeft.tsx: -------------------------------------------------------------------------------- 1 | import createSvgIcon from "../../utils/createSvgIcon"; 2 | 3 | const ChevronLeft = createSvgIcon( 4 | , 5 | "ChevronLeft" 6 | ); 7 | 8 | export default ChevronLeft; 9 | -------------------------------------------------------------------------------- /lib/internals/icons/ChevronRight.tsx: -------------------------------------------------------------------------------- 1 | import createSvgIcon from "../../utils/createSvgIcon"; 2 | 3 | const ChevronRight = createSvgIcon( 4 | , 5 | "ChevronRight" 6 | ); 7 | 8 | export default ChevronRight; 9 | -------------------------------------------------------------------------------- /lib/utils/getOffsetFromWindow.ts: -------------------------------------------------------------------------------- 1 | const getOffsetFromWindow = ( 2 | element: T 3 | ): { top: number; left: number } => { 4 | let topDistance = 0; 5 | let leftDistance = 0; 6 | 7 | do { 8 | topDistance += element.offsetTop; 9 | leftDistance += element.offsetLeft; 10 | 11 | element = element.offsetParent as T; 12 | } while (element); 13 | 14 | return { 15 | top: topDistance < 0 ? 0 : topDistance, 16 | left: leftDistance < 0 ? 0 : leftDistance 17 | }; 18 | }; 19 | 20 | export default getOffsetFromWindow; 21 | -------------------------------------------------------------------------------- /lib/utils/useOnChange.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import useLatest from "./useLatest"; 3 | import usePreviousValue from "./usePreviousValue"; 4 | 5 | const useOnChange = (value: T, onChange: (current: T) => void): void => { 6 | const cachedOnChange = useLatest(onChange); 7 | const prevValue = usePreviousValue(value); 8 | 9 | React.useEffect(() => { 10 | if (value !== prevValue) cachedOnChange.current(value); 11 | // eslint-disable-next-line react-hooks/exhaustive-deps 12 | }, [value, prevValue]); 13 | }; 14 | 15 | export default useOnChange; 16 | -------------------------------------------------------------------------------- /lib/Dialog/context.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface IContext { 4 | id?: string; 5 | bodyHeight: number; 6 | hasOverflow: boolean; 7 | registerHeader: (node: HTMLDivElement) => void; 8 | registerBody: (node: HTMLDivElement) => void; 9 | registerActionBar: (node: HTMLDivElement) => void; 10 | nodesMap: Map; 11 | } 12 | 13 | const DialogContext = React.createContext(undefined); 14 | 15 | if (process.env.NODE_ENV !== "production") 16 | DialogContext.displayName = "DialogContext"; 17 | 18 | export default DialogContext; 19 | -------------------------------------------------------------------------------- /lib/utils/detectScrollBarWidth.ts: -------------------------------------------------------------------------------- 1 | const detectScrollBarWidth = (): number => { 2 | const scrollDiv = document.createElement("div"); 3 | 4 | scrollDiv.style.width = "100px"; 5 | scrollDiv.style.height = "100px"; 6 | scrollDiv.style.overflow = "scroll"; 7 | scrollDiv.style.position = "absolute"; 8 | scrollDiv.style.top = "-9999px"; 9 | 10 | document.body.appendChild(scrollDiv); 11 | const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; 12 | document.body.removeChild(scrollDiv); 13 | 14 | return scrollbarWidth; 15 | }; 16 | 17 | export default detectScrollBarWidth; 18 | -------------------------------------------------------------------------------- /lib/Dialog/Body/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | darkMode, 7 | colors: { background }, 8 | spacings: { spaces } 9 | } = theme; 10 | 11 | return { 12 | root: { padding: spaces[7].rem }, 13 | withOverflow: { 14 | overflowX: "hidden", 15 | overflowY: "scroll", 16 | backgroundColor: !darkMode ? "inherit" : background.dark.accents[2] 17 | } 18 | }; 19 | }, 20 | { name: "SonnatDialogBody" } 21 | ); 22 | 23 | export default useStyles; 24 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Divider from "../lib/Divider"; 3 | import Switch from "../lib/Switch"; 4 | import { AppContext } from "./_app"; 5 | 6 | const Page = () => { 7 | const context = React.useContext(AppContext); 8 | 9 | return ( 10 |
11 | void context?.setIsDarkMode(isChecked)} 15 | /> 16 |
17 | 18 |
19 | ); 20 | }; 21 | 22 | export default Page; 23 | -------------------------------------------------------------------------------- /lib/Dialog/Header/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | spacings: { spaces } 7 | } = theme; 8 | 9 | return { 10 | root: { 11 | position: "relative", 12 | flex: [[1, 1, "auto"]], 13 | display: "flex", 14 | padding: spaces[7].rem, 15 | alignItems: "center" 16 | }, 17 | withOverflow: { 18 | boxShadow: "0 1px 2px 0 rgba(0, 0, 0, 0.12)" 19 | } 20 | }; 21 | }, 22 | { name: "SonnatHeader" } 23 | ); 24 | 25 | export default useStyles; 26 | -------------------------------------------------------------------------------- /lib/utils/createSvgIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import SonnatIcon, { IconProps } from "../Icon"; 3 | 4 | const createSvgIcon = ( 5 | children: React.ReactNode, 6 | name: string 7 | ): React.FC => { 8 | const IconBase = (props: IconProps, ref: IconProps["ref"]) => ( 9 | 10 | {children} 11 | 12 | ); 13 | 14 | const Icon = React.forwardRef(IconBase) as React.FC; 15 | 16 | Icon.displayName = `Sonnat${name}Icon`; 17 | 18 | return Icon; 19 | }; 20 | 21 | export default createSvgIcon; 22 | -------------------------------------------------------------------------------- /examples/with-cra/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | React App 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /lib/internals/icons/ChevronRightLarge.tsx: -------------------------------------------------------------------------------- 1 | import createSvgIcon from "../../utils/createSvgIcon"; 2 | 3 | const ChevronRightLarge = createSvgIcon( 4 | , 5 | "ChevronRightLarge" 6 | ); 7 | 8 | export default ChevronRightLarge; 9 | -------------------------------------------------------------------------------- /lib/internals/icons/ChevronLeftLarge.tsx: -------------------------------------------------------------------------------- 1 | import createSvgIcon from "../../utils/createSvgIcon"; 2 | 3 | const ChevronLeftLarge = createSvgIcon( 4 | , 5 | "ChevronLeftLarge" 6 | ); 7 | 8 | export default ChevronLeftLarge; 9 | -------------------------------------------------------------------------------- /lib/FormControl/Description/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => ({ 5 | root: { 6 | ...theme.typography.variants.bodySmall, 7 | color: !theme.darkMode 8 | ? theme.colors.text.dark.primary 9 | : theme.colors.text.light.primary, 10 | direction: theme.direction, 11 | fontFamily: theme.typography.fontFamily[theme.direction], 12 | marginBottom: theme.spacings.spaces[3].rem 13 | }, 14 | disabled: {} 15 | }), 16 | { name: "SonnatFormControlDescription" } 17 | ); 18 | 19 | export default useStyles; 20 | -------------------------------------------------------------------------------- /lib/Row/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | direction, 7 | spacings: { spaces }, 8 | typography: { pxToRem, fontFamily } 9 | } = theme; 10 | 11 | return { 12 | root: { 13 | direction, 14 | fontFamily: fontFamily[direction], 15 | display: "flex", 16 | flexWrap: "wrap", 17 | marginRight: pxToRem(-spaces[3].px), 18 | marginLeft: pxToRem(-spaces[3].px) 19 | } 20 | }; 21 | }, 22 | { name: "SonnatRow" } 23 | ); 24 | 25 | export default useStyles; 26 | -------------------------------------------------------------------------------- /lib/TabBar/context.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface IContext { 4 | fluid: boolean; 5 | scrollable: boolean; 6 | isRtl: boolean; 7 | size: "large" | "medium" | "small"; 8 | focusLeftAdjacentTab: (identifier: number | string) => void; 9 | focusRightAdjacentTab: (identifier: number | string) => void; 10 | onChange: (identifier: number | string) => void; 11 | } 12 | 13 | const TabBarContext = React.createContext(undefined); 14 | 15 | if (process.env.NODE_ENV !== "production") { 16 | TabBarContext.displayName = "TabBarContext"; 17 | } 18 | 19 | export default TabBarContext; 20 | -------------------------------------------------------------------------------- /lib/internals/icons/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Check } from "./Check"; 2 | export { default as ChevronDown } from "./ChevronDown"; 3 | export { default as ChevronLeftLarge } from "./ChevronLeftLarge"; 4 | export { default as ChevronRightLarge } from "./ChevronRightLarge"; 5 | export { default as Close } from "./Close"; 6 | export { default as CloseLarge } from "./CloseLarge"; 7 | export { default as ChevronLeft } from "./ChevronLeft"; 8 | export { default as ChevronRight } from "./ChevronRight"; 9 | export { default as Minus } from "./Minus"; 10 | export { default as Plus } from "./Plus"; 11 | export { default as Magnifier } from "./Magnifier"; 12 | -------------------------------------------------------------------------------- /lib/Card/ActionBar/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | direction, 7 | spacings: { spaces } 8 | } = theme; 9 | 10 | return { 11 | root: { 12 | display: "flex", 13 | alignItems: "center", 14 | padding: spaces[3].rem, 15 | "& > * + *": { 16 | ...{ 17 | ltr: { marginLeft: spaces[3].rem }, 18 | rtl: { marginRight: spaces[3].rem } 19 | }[direction] 20 | } 21 | } 22 | }; 23 | }, 24 | { name: "SonnatCardActionBar" } 25 | ); 26 | 27 | export default useStyles; 28 | -------------------------------------------------------------------------------- /scripts/minify.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fse = require("fs-extra"); 3 | const { minify } = require("terser"); 4 | const glob = require("fast-glob"); 5 | 6 | const packagePath = process.cwd(); 7 | 8 | const buildPath = path.join(packagePath, "./dist"); 9 | 10 | /** @param {string[]} files */ 11 | const runMinify = async files => { 12 | for (const file of files) { 13 | const source = await fse.readFile(file, { encoding: "utf8" }); 14 | const result = await minify(source); 15 | 16 | await fse.writeFile(file, result.code); 17 | } 18 | }; 19 | 20 | void (async () => { 21 | await runMinify(await glob(path.join(buildPath, "**/*/*.js"))); 22 | })(); 23 | -------------------------------------------------------------------------------- /lib/InputStepper/reducer.ts: -------------------------------------------------------------------------------- 1 | import { types, Action } from "./actions"; 2 | 3 | type Reducer = ( 4 | state: { addition: boolean; subtraction: boolean }, 5 | action: Action 6 | ) => { addition: boolean; subtraction: boolean }; 7 | 8 | const reducer: Reducer = (state, action) => { 9 | switch (action.type) { 10 | case types.PREVENT_ADDITION: 11 | return { ...state, addition: false }; 12 | case types.PREVENT_SUBTRACTION: 13 | return { ...state, subtraction: false }; 14 | case types.ALLOW_ADDITION_AND_SUBTRACTION: 15 | return { subtraction: true, addition: true }; 16 | default: 17 | return state; 18 | } 19 | }; 20 | 21 | export default reducer; 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /lib/styles/createRadius.ts: -------------------------------------------------------------------------------- 1 | import { type Typography } from "./createTypography"; 2 | 3 | const createRadius = (options?: { pxToRem: Typography["pxToRem"] }) => { 4 | const { 5 | pxToRem = (size: number) => 6 | typeof size === "number" && !isNaN(size) ? `${size / 16}rem` : "" 7 | } = options || {}; 8 | 9 | return { 10 | /** Equivalent to `2px`. */ 11 | xSmall: pxToRem(2), 12 | /** Equivalent to `4px`. */ 13 | small: pxToRem(4), 14 | /** Equivalent to `8px`. */ 15 | medium: pxToRem(8), 16 | /** Equivalent to `16px`. */ 17 | large: pxToRem(16), 18 | rounded: 1024 19 | }; 20 | }; 21 | 22 | export type Radius = ReturnType; 23 | 24 | export default createRadius; 25 | -------------------------------------------------------------------------------- /lib/FormControl/Feedback/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => ({ 5 | root: { 6 | ...theme.typography.variants.caption, 7 | color: !theme.darkMode 8 | ? theme.colors.text.dark.secondary 9 | : theme.colors.text.light.secondary, 10 | direction: theme.direction, 11 | fontFamily: theme.typography.fontFamily[theme.direction], 12 | marginTop: theme.spacings.spaces[1].rem 13 | }, 14 | errored: { 15 | color: !theme.darkMode 16 | ? theme.colors.error.origin 17 | : theme.colors.error.light 18 | } 19 | }), 20 | { name: "SonnatFormControlFeedback" } 21 | ); 22 | 23 | export default useStyles; 24 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ## Bug 8 | 9 | - [ ] Related issues linked using `fixes #number` 10 | 11 | ## Feature 12 | 13 | - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. 14 | - [ ] Related issues linked using `fixes #number` 15 | - [ ] Documentation added 16 | - [ ] Telemetry added. In case of a feature if it's used or not. 17 | 18 | ## Examples 19 | 20 | - [ ] Make sure the linting passes 21 | -------------------------------------------------------------------------------- /lib/Card/Header/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | direction, 7 | spacings: { spaces } 8 | } = theme; 9 | 10 | return { 11 | root: { 12 | display: "flex", 13 | padding: spaces[7].rem, 14 | alignItems: "center" 15 | }, 16 | body: { flex: [[1, 1, "auto"]] }, 17 | action: { 18 | flex: [[0, 0, "auto"]], 19 | alignSelf: "flex-start", 20 | ...{ 21 | ltr: { marginLeft: spaces[3].rem }, 22 | rtl: { marginRight: spaces[3].rem } 23 | }[direction] 24 | } 25 | }; 26 | }, 27 | { name: "SonnatCardHeader" } 28 | ); 29 | 30 | export default useStyles; 31 | -------------------------------------------------------------------------------- /lib/styles/createZIndexes.ts: -------------------------------------------------------------------------------- 1 | export interface ZIndexes extends Record { 2 | sticky: number; 3 | header: number; 4 | drawer: number; 5 | backdrop: number; 6 | modal: number; 7 | popover: number; 8 | } 9 | 10 | const createZIndexes = >( 11 | zIndexesInput?: T 12 | ): T & ZIndexes => { 13 | const { 14 | sticky = 1000, 15 | header = 1010, 16 | drawer = 1020, 17 | backdrop = 1030, 18 | modal = 1040, 19 | popover = 1050, 20 | ...others 21 | } = zIndexesInput || {}; 22 | 23 | return { 24 | sticky, 25 | header, 26 | drawer, 27 | backdrop, 28 | popover, 29 | modal, 30 | ...others 31 | } as T & ZIndexes; 32 | }; 33 | 34 | export default createZIndexes; 35 | -------------------------------------------------------------------------------- /lib/utils/useSyncEffect.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const useSyncEffect = ( 4 | effectCallback: React.EffectCallback, 5 | dependencyList?: React.DependencyList 6 | ): void => { 7 | const key = React.useRef({}); 8 | const cleanupRef = React.useRef>(); 9 | 10 | // eslint-disable-next-line react-hooks/exhaustive-deps 11 | const currentKey = React.useMemo(() => ({}), dependencyList); 12 | 13 | if (key !== currentKey) { 14 | key.current = currentKey; 15 | cleanupRef.current = effectCallback(); 16 | } 17 | 18 | React.useEffect( 19 | () => () => { 20 | if (cleanupRef.current) cleanupRef.current(); 21 | }, 22 | [currentKey] 23 | ); 24 | }; 25 | 26 | export default useSyncEffect; 27 | -------------------------------------------------------------------------------- /examples/with-cra/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-cra", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@sonnat/ui": "latest", 7 | "@sonnat/icons": "latest", 8 | "react": "latest", 9 | "react-dom": "latest", 10 | "react-scripts": "latest" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test", 16 | "eject": "react-scripts eject" 17 | }, 18 | "browserslist": { 19 | "production": [ 20 | ">0.2%", 21 | "not dead", 22 | "not op_mini all" 23 | ], 24 | "development": [ 25 | "last 1 chrome version", 26 | "last 1 firefox version", 27 | "last 1 safari version" 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/internals/icons/Close.tsx: -------------------------------------------------------------------------------- 1 | import createSvgIcon from "../../utils/createSvgIcon"; 2 | 3 | const Close = createSvgIcon( 4 | , 5 | "Close" 6 | ); 7 | 8 | export default Close; 9 | -------------------------------------------------------------------------------- /lib/NoSsr/NoSsr.tsx: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import * as React from "react"; 3 | 4 | export interface NoSsrProps { 5 | /** The content of the component. (in CSR) */ 6 | children?: React.ReactNode; 7 | /** The content of the component. (in SSR) */ 8 | fallback?: React.ReactNode; 9 | } 10 | 11 | const NoSsr = (props: NoSsrProps): JSX.Element => { 12 | const { children, fallback = null } = props; 13 | 14 | const [isMounted, setMounted] = React.useState(false); 15 | 16 | React.useEffect(() => { 17 | setMounted(true); 18 | }, []); 19 | 20 | return {isMounted ? children : fallback}; 21 | }; 22 | 23 | NoSsr.propTypes = { 24 | children: PropTypes.node, 25 | fallback: PropTypes.node 26 | }; 27 | 28 | export default NoSsr; 29 | -------------------------------------------------------------------------------- /lib/CssBaseline/CssBaseline.tsx: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import * as React from "react"; 3 | import useTheme from "../styles/useTheme"; 4 | import useIsomorphicLayoutEffect from "../utils/useIsomorphicLayoutEffect"; 5 | import useStyles from "./styles"; 6 | 7 | export interface CssBaselineProps { 8 | children?: React.ReactNode; 9 | } 10 | 11 | const CssBaseline = (props: CssBaselineProps): JSX.Element => { 12 | useStyles(); 13 | 14 | const theme = useTheme(); 15 | 16 | useIsomorphicLayoutEffect(() => { 17 | document.body.dir = theme.direction; 18 | }, [theme.direction]); 19 | 20 | return {props.children}; 21 | }; 22 | 23 | CssBaseline.propTypes = { 24 | children: PropTypes.node 25 | }; 26 | 27 | export default CssBaseline; 28 | -------------------------------------------------------------------------------- /lib/internals/icons/CloseLarge.tsx: -------------------------------------------------------------------------------- 1 | import createSvgIcon from "../../utils/createSvgIcon"; 2 | 3 | const CloseLarge = createSvgIcon( 4 | , 5 | "CloseLarge" 6 | ); 7 | 8 | export default CloseLarge; 9 | -------------------------------------------------------------------------------- /lib/utils/useId.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const DEFAULT_PREFIX = "SONNAT-GEN-ID"; 4 | 5 | let globalId = 0; 6 | const useId = ( 7 | idOverride?: string, 8 | prefix = DEFAULT_PREFIX 9 | ): string | undefined => { 10 | const [defaultId, setDefaultId] = React.useState(idOverride); 11 | 12 | const id = idOverride || defaultId; 13 | 14 | React.useEffect(() => { 15 | if (defaultId == null) { 16 | // Fallback to this default id when possible. 17 | // Use the incrementing value for client-side rendering only. 18 | // We can't use it server-side. 19 | globalId += 1; 20 | setDefaultId(`${prefix.length ? prefix : DEFAULT_PREFIX}-${globalId}`); 21 | } 22 | }, [defaultId, prefix]); 23 | 24 | return id; 25 | }; 26 | 27 | export default useId; 28 | -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "target": "ES5", 5 | "outDir": "./dist/esm", 6 | "lib": ["DOM.Iterable", "DOM", "ES2017"], 7 | "jsx": "react-jsx", 8 | "strictNullChecks": true, 9 | "esModuleInterop": true, 10 | "moduleResolution": "Node", 11 | "noUnusedParameters": true, 12 | "noUnusedLocals": true, 13 | "noImplicitAny": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "declaration": true, 17 | "skipLibCheck": true, 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "allowJs": true, 21 | "types": ["node", "react"] 22 | }, 23 | "exclude": ["dist", "node_modules", "examples", "develop"], 24 | "include": ["lib/**/*.ts", "lib/**/*.tsx"] 25 | } 26 | -------------------------------------------------------------------------------- /lib/internals/icons/Magnifier.tsx: -------------------------------------------------------------------------------- 1 | import createSvgIcon from "../../utils/createSvgIcon"; 2 | 3 | const Magnifier = createSvgIcon( 4 | , 5 | "Magnifier" 6 | ); 7 | 8 | export default Magnifier; 9 | -------------------------------------------------------------------------------- /lib/utils/useEventCallback.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import * as React from "react"; 3 | import useIsomorphicLayoutEffect from "./useIsomorphicLayoutEffect"; 4 | 5 | /** 6 | * A community-wide workaround for `useCallback()`. 7 | * Because the `useCallback()` hook invalidates too often in practice. 8 | * 9 | * https://github.com/facebook/react/issues/14099#issuecomment-440013892 10 | */ 11 | const useEventCallback = any>( 12 | fn: T 13 | ): ((...args: unknown[]) => void) => { 14 | const ref = React.useRef(fn); 15 | 16 | useIsomorphicLayoutEffect(() => void (ref.current = fn)); 17 | 18 | return React.useCallback( 19 | (...args: unknown[]) => void ref.current(...args), 20 | [] 21 | ); 22 | }; 23 | 24 | export default useEventCallback; 25 | -------------------------------------------------------------------------------- /lib/styles/useDarkMode.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import usePreviousValue from "../utils/usePreviousValue"; 3 | import defaultTheme, { type DefaultTheme } from "./defaultTheme"; 4 | import type { Theme } from "./createTheme"; 5 | 6 | const useDarkMode = ( 7 | isDarkMode = false, 8 | theme: T = defaultTheme as T 9 | ): T => { 10 | const cachedTheme = React.useRef(theme); 11 | const prevState = usePreviousValue(isDarkMode); 12 | 13 | const newTheme = React.useMemo(() => { 14 | if (isDarkMode !== prevState) { 15 | return { ...cachedTheme.current, darkMode: isDarkMode }; 16 | } else return cachedTheme.current; 17 | }, [prevState, isDarkMode]); 18 | 19 | cachedTheme.current = newTheme; 20 | 21 | return newTheme; 22 | }; 23 | 24 | export default useDarkMode; 25 | -------------------------------------------------------------------------------- /lib/InputStepper/actions.ts: -------------------------------------------------------------------------------- 1 | export type Action = 2 | | { type: "PREVENT_SUBTRACTION" } 3 | | { type: "PREVENT_ADDITION" } 4 | | { type: "ALLOW_ADDITION_AND_SUBTRACTION" }; 5 | 6 | export const types: Record = { 7 | PREVENT_SUBTRACTION: "PREVENT_SUBTRACTION", 8 | PREVENT_ADDITION: "PREVENT_ADDITION", 9 | ALLOW_ADDITION_AND_SUBTRACTION: "ALLOW_ADDITION_AND_SUBTRACTION" 10 | }; 11 | 12 | export const preventSubtraction = (): { type: "PREVENT_SUBTRACTION" } => { 13 | return { type: "PREVENT_SUBTRACTION" }; 14 | }; 15 | 16 | export const preventAddition = (): { type: "PREVENT_ADDITION" } => { 17 | return { type: "PREVENT_ADDITION" }; 18 | }; 19 | 20 | export const allowAdditionAndSubtraction = (): { 21 | type: "ALLOW_ADDITION_AND_SUBTRACTION"; 22 | } => { 23 | return { type: "ALLOW_ADDITION_AND_SUBTRACTION" }; 24 | }; 25 | -------------------------------------------------------------------------------- /lib/styles/makeStyles.ts: -------------------------------------------------------------------------------- 1 | import { createUseStyles } from "react-jss"; 2 | import type { Classes, MakeStylesOptions, Styles } from "../typings"; 3 | import type { DefaultTheme } from "./defaultTheme"; 4 | 5 | const makeStyles = ( 6 | styles: Styles, 7 | options: MakeStylesOptions = {} 8 | ): ((data?: P & { theme?: T }) => Classes) => { 9 | const { 10 | name, 11 | classNamePrefix: classNamePrefixOption, 12 | meta: metaOption, 13 | ...sheetOptions 14 | } = options; 15 | 16 | const classNamePrefix = name || classNamePrefixOption || "Sonnat"; 17 | const meta = metaOption || classNamePrefix; 18 | 19 | return createUseStyles(styles, { 20 | classNamePrefix, 21 | name, 22 | meta, 23 | ...sheetOptions 24 | }); 25 | }; 26 | 27 | export default makeStyles; 28 | -------------------------------------------------------------------------------- /lib/utils/HTMLElementType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Cherry-picked from https://github.com/mui-org/material-ui/blob/master/packages/mui-utils/src/HTMLElementType.ts 3 | */ 4 | const HTMLElementType = ( 5 | props: Record, 6 | propName: string, 7 | componentName: string, 8 | location: string, 9 | propFullName: string 10 | ): Error | null => { 11 | if (process.env.NODE_ENV === "production") return null; 12 | 13 | const propValue = props[propName]; 14 | const safePropName = propFullName || propName; 15 | 16 | if (propValue == null) return null; 17 | 18 | if (propValue && (propValue as HTMLElement).nodeType !== 1) { 19 | return new Error( 20 | `Invalid ${location} \`${safePropName}\` supplied to \`${componentName}\`. ` + 21 | `Expected an HTMLElement.` 22 | ); 23 | } 24 | 25 | return null; 26 | }; 27 | 28 | export default HTMLElementType; 29 | -------------------------------------------------------------------------------- /lib/Dialog/ActionBar/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | direction, 7 | spacings: { spaces } 8 | } = theme; 9 | 10 | return { 11 | root: { 12 | position: "relative", 13 | display: "flex", 14 | alignItems: "center", 15 | justifyContent: "flex-end", 16 | padding: spaces[7].rem, 17 | "& > * + *": { 18 | ...{ 19 | ltr: { marginLeft: spaces[3].rem }, 20 | rtl: { marginRight: spaces[3].rem } 21 | }[direction] 22 | } 23 | }, 24 | withOverflow: { 25 | padding: [[spaces[5].rem, spaces[7].rem]], 26 | boxShadow: "0 -1px 2px 0 rgba(0, 0, 0, 0.12)" 27 | } 28 | }; 29 | }, 30 | { name: "SonnatActionBar" } 31 | ); 32 | 33 | export default useStyles; 34 | -------------------------------------------------------------------------------- /lib/FormControl/Label/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => ({ 5 | root: { 6 | ...theme.typography.variants.subtitle, 7 | color: !theme.darkMode 8 | ? theme.colors.text.dark.primary 9 | : theme.colors.text.light.primary, 10 | direction: theme.direction, 11 | fontFamily: theme.typography.fontFamily[theme.direction], 12 | paddingBottom: theme.spacings.spaces[3].rem 13 | }, 14 | requiredIndicator: { 15 | color: theme.darkMode 16 | ? theme.colors.error.origin 17 | : theme.colors.error.light, 18 | ...(theme.direction === "rtl" 19 | ? { marginRight: theme.spacings.spaces[1].rem } 20 | : { marginLeft: theme.spacings.spaces[1].rem }) 21 | }, 22 | disabled: {} 23 | }), 24 | { name: "SonnatFormControlLabel" } 25 | ); 26 | 27 | export default useStyles; 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "alwaysStrict": true, 5 | "strictNullChecks": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "isolatedModules": true, 9 | "jsx": "preserve", 10 | "lib": ["dom", "es2017"], 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "incremental": true, 14 | "noEmit": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "noUnusedLocals": true, 17 | "noImplicitAny": true, 18 | "noUnusedParameters": true, 19 | "resolveJsonModule": true, 20 | "skipLibCheck": true, 21 | "strict": true, 22 | "target": "es5", 23 | "types": ["node", "react"] 24 | }, 25 | "include": [ 26 | "next-env.d.ts", 27 | "pages/**/*.ts", 28 | "pages/**/*.tsx", 29 | "lib/**/*.ts", 30 | "lib/**/*.tsx" 31 | ], 32 | "exclude": ["node_modules", "dist", "examples"] 33 | } 34 | -------------------------------------------------------------------------------- /lib/utils/deepMerge.ts: -------------------------------------------------------------------------------- 1 | type ObjectType = Record; 2 | 3 | export const isPlainObject = (value: T): boolean => 4 | value && 5 | typeof value === "object" && 6 | (value).constructor === Object; 7 | 8 | const deepMerge = ( 9 | target: ObjectType, 10 | source: ObjectType, 11 | options = { clone: true } 12 | ): ObjectType => { 13 | const output = options.clone ? { ...target } : target; 14 | 15 | if (isPlainObject(target) && isPlainObject(source)) { 16 | Object.keys(source).forEach(key => { 17 | // Avoid prototype pollution 18 | if (key === "__proto__") return; 19 | 20 | if (isPlainObject(source[key]) && key in target) { 21 | output[key] = deepMerge( 22 | target[key], 23 | source[key], 24 | options 25 | ); 26 | } else output[key] = source[key]; 27 | }); 28 | } 29 | 30 | return output; 31 | }; 32 | 33 | export default deepMerge; 34 | -------------------------------------------------------------------------------- /lib/Card/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | darkMode, 7 | direction, 8 | radius, 9 | colors: { divider, background }, 10 | typography: { fontFamily } 11 | } = theme; 12 | 13 | return { 14 | root: { 15 | direction, 16 | fontFamily: fontFamily[direction], 17 | borderRadius: radius.small, 18 | backgroundColor: !darkMode 19 | ? background.light.origin 20 | : background.dark.accents[1] 21 | }, 22 | outlined: { 23 | border: `1px solid ${!darkMode ? divider.dark : divider.light}` 24 | }, 25 | elevated: { 26 | boxShadow: [ 27 | "0 0 4px 0 rgba(0, 0, 0, 0.04)", 28 | "0 4px 12px 0 rgba(0, 0, 0, 0.04)", 29 | "0 2px 8px 0 rgba(0, 0, 0, 0.08)" 30 | ].join(", ") 31 | } 32 | }; 33 | }, 34 | { name: "SonnatCard" } 35 | ); 36 | 37 | export default useStyles; 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /lib/Menu/ItemGroup/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | darkMode, 7 | colors: { text }, 8 | spacings: { spaces }, 9 | typography: { pxToRem, variants } 10 | } = theme; 11 | 12 | return { 13 | root: {}, 14 | title: { 15 | ...variants.bodySmall, 16 | color: !darkMode ? text.dark.primary : text.light.primary, 17 | paddingRight: spaces[7].rem, 18 | paddingLeft: spaces[7].rem, 19 | height: pxToRem(40), 20 | flexGrow: "1", 21 | display: "flex", 22 | alignItems: "center" 23 | }, 24 | dense: { 25 | "& $title": { 26 | height: pxToRem(32), 27 | fontSize: variants.caption.fontSize, 28 | lineHeight: variants.caption.lineHeight 29 | } 30 | }, 31 | hide: { display: "none" } 32 | }; 33 | }, 34 | { name: "SonnatMenuItemGroup" } 35 | ); 36 | 37 | export default useStyles; 38 | -------------------------------------------------------------------------------- /examples/with-basic-ssr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-basic-ssr", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "start": "npm-run-all -p build serve", 7 | "build": "webpack -w", 8 | "serve": "nodemon --ignore ./build --exec babel-node -- server.js", 9 | "prod": "cross-env NODE_ENV=production npm run start", 10 | "dev": "cross-env NODE_ENV=development npm run start" 11 | }, 12 | "dependencies": { 13 | "@sonnat/icons": "latest", 14 | "@sonnat/ui": "latest", 15 | "express": "latest", 16 | "react": "latest", 17 | "react-dom": "latest" 18 | }, 19 | "devDependencies": { 20 | "@babel/cli": "^7.16.7", 21 | "@babel/core": "^7.16.7", 22 | "@babel/node": "^7.16.7", 23 | "@babel/plugin-proposal-class-properties": "^7.16.7", 24 | "@babel/preset-env": "^7.16.7", 25 | "@babel/preset-react": "^7.16.7", 26 | "babel-loader": "^8.2.3", 27 | "cross-env": "^7.0.3", 28 | "nodemon": "^2.0.15", 29 | "npm-run-all": "^4.1.5", 30 | "webpack": "^5.65.0", 31 | "webpack-cli": "^4.9.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/Card/ActionableArea/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => ({ 5 | root: { 6 | position: "relative", 7 | width: "100%", 8 | cursor: "pointer", 9 | "&:after": { 10 | content: '""', 11 | zIndex: 0, 12 | overflow: "hidden", 13 | pointerEvents: "none", 14 | position: "absolute", 15 | top: 0, 16 | right: 0, 17 | bottom: 0, 18 | left: 0, 19 | opacity: 0, 20 | backgroundColor: "currentcolor", 21 | transition: "opacity 180ms ease" 22 | }, 23 | "&:hover:after": { 24 | opacity: 0.04, 25 | "@media (hover: none)": { opacity: 0 } 26 | }, 27 | "&:active:after": { 28 | opacity: 0.08 29 | } 30 | }, 31 | focusVisible: { 32 | outline: `2px solid ${ 33 | theme.darkMode ? theme.swatches.blue[500] : theme.swatches.blue[600] 34 | }`, 35 | outlineOffset: 1 36 | } 37 | }), 38 | { name: "SonnatCardActionableArea" } 39 | ); 40 | 41 | export default useStyles; 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sonnat 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 | -------------------------------------------------------------------------------- /lib/styles/swatches.ts: -------------------------------------------------------------------------------- 1 | import generateColorSwatch from "./generateColorSwatch"; 2 | 3 | export const red = generateColorSwatch("#ec1313"); 4 | export const pink = generateColorSwatch("#ec135f"); 5 | export const purple = generateColorSwatch("#c113ec"); 6 | export const deepPurple = generateColorSwatch("#9656d6"); 7 | export const violet = generateColorSwatch("#4913ec"); 8 | export const navy = generateColorSwatch("#1337ec"); 9 | export const blue = generateColorSwatch("#1387ec"); 10 | export const aqua = generateColorSwatch("#13cfec"); 11 | export const teal = generateColorSwatch("#13ecab"); 12 | export const green = generateColorSwatch("#13ec3b"); 13 | export const lime = generateColorSwatch("#92ec13"); 14 | export const yellow = generateColorSwatch("#eca013"); 15 | export const orange = generateColorSwatch("#ec6a13"); 16 | export const grey = generateColorSwatch("#000000", -100); 17 | 18 | const swatches = { 19 | red, 20 | pink, 21 | purple, 22 | deepPurple, 23 | violet, 24 | navy, 25 | blue, 26 | aqua, 27 | teal, 28 | green, 29 | lime, 30 | yellow, 31 | orange, 32 | grey 33 | }; 34 | 35 | export type Swatches = typeof swatches; 36 | 37 | export default swatches; 38 | -------------------------------------------------------------------------------- /lib/styles/jssPreset.ts: -------------------------------------------------------------------------------- 1 | import type { JssOptions } from "jss"; 2 | import camelCase from "jss-plugin-camel-case"; 3 | import defaultUnit from "jss-plugin-default-unit"; 4 | import extend from "jss-plugin-extend"; 5 | import global from "jss-plugin-global"; 6 | import nested from "jss-plugin-nested"; 7 | import propsSort from "jss-plugin-props-sort"; 8 | import functions from "jss-plugin-rule-value-function"; 9 | import vendorPrefixer from "jss-plugin-vendor-prefixer"; 10 | 11 | const basePlugins = [ 12 | functions(), 13 | global(), 14 | extend(), 15 | nested(), 16 | camelCase(), 17 | defaultUnit(), 18 | propsSort() 19 | ]; 20 | 21 | /** Subset of jss-preset-default with only the plugins the Sonnat components are using. */ 22 | const jssPreset = (): Pick => { 23 | return { 24 | plugins: 25 | typeof window === "undefined" 26 | ? basePlugins 27 | : // Disable the vendor prefixer on server-side, it does nothing. 28 | // This way, we can get a performance boost. 29 | // Instead, we can use `autoprefixer` on the server. 30 | [...basePlugins, vendorPrefixer()] 31 | }; 32 | }; 33 | 34 | export default jssPreset; 35 | -------------------------------------------------------------------------------- /lib/Row/Row.tsx: -------------------------------------------------------------------------------- 1 | import c from "classnames"; 2 | import PropTypes from "prop-types"; 3 | import * as React from "react"; 4 | import type { MergeElementProps } from "../typings"; 5 | import useStyles from "./styles"; 6 | 7 | interface RowBaseProps { 8 | /** The content of the component. */ 9 | children?: React.ReactNode; 10 | /** 11 | * Append to the classNames applied to the component so you can override or 12 | * extend the styles. 13 | */ 14 | className?: string; 15 | } 16 | 17 | export type RowProps = MergeElementProps<"div", RowBaseProps>; 18 | 19 | type Component = { 20 | (props: RowProps): React.ReactElement | null; 21 | propTypes?: React.WeakValidationMap | undefined; 22 | displayName?: string | undefined; 23 | }; 24 | 25 | const RowBase = (props: RowProps, ref: React.Ref) => { 26 | const { children, className, ...otherProps } = props; 27 | 28 | const classes = useStyles(); 29 | 30 | return ( 31 |
32 | {children} 33 |
34 | ); 35 | }; 36 | 37 | const Row = React.forwardRef(RowBase) as Component; 38 | 39 | Row.propTypes = { 40 | className: PropTypes.string, 41 | children: PropTypes.node 42 | }; 43 | 44 | export default Row; 45 | -------------------------------------------------------------------------------- /lib/Table/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | direction, 7 | radius, 8 | darkMode, 9 | colors: { text, divider }, 10 | spacings: { spaces }, 11 | typography: { variants, fontFamily, fontWeight } 12 | } = theme; 13 | 14 | return { 15 | root: { 16 | direction, 17 | fontFamily: fontFamily[direction], 18 | width: "100%", 19 | overflowX: "auto", 20 | borderRadius: radius.small, 21 | border: `1px solid ${!darkMode ? divider.dark : divider.light}` 22 | }, 23 | table: { 24 | display: "table", 25 | width: "100%", 26 | borderCollapse: "collapse", 27 | borderSpacing: 0, 28 | captionSide: "bottom" 29 | }, 30 | caption: { 31 | ...variants.caption, 32 | fontWeight: fontWeight.medium, 33 | textAlign: "inherit", 34 | padding: spaces[7].rem, 35 | color: !darkMode ? text.dark.secondary : text.light.secondary 36 | }, 37 | dense: { "& $caption": { padding: spaces[3].rem } }, 38 | borderLess: { border: "none" } 39 | }; 40 | }, 41 | { name: "SonnatTable" } 42 | ); 43 | 44 | export default useStyles; 45 | -------------------------------------------------------------------------------- /lib/Card/Body/Body.tsx: -------------------------------------------------------------------------------- 1 | import c from "classnames"; 2 | import PropTypes from "prop-types"; 3 | import * as React from "react"; 4 | import type { MergeElementProps } from "../../typings"; 5 | import useStyles from "./styles"; 6 | 7 | interface CardBodyBaseProps { 8 | /** The content of the component. */ 9 | children?: React.ReactNode; 10 | /** 11 | * Append to the classNames applied to the component so you can override or 12 | * extend the styles. 13 | */ 14 | className?: string; 15 | } 16 | 17 | export type CardBodyProps = MergeElementProps<"div", CardBodyBaseProps>; 18 | 19 | type Component = { 20 | (props: CardBodyProps): React.ReactElement | null; 21 | propTypes?: React.WeakValidationMap | undefined; 22 | displayName?: string | undefined; 23 | }; 24 | 25 | const CardBodyBase = (props: CardBodyProps, ref: React.Ref) => { 26 | const { className, children, ...otherProps } = props; 27 | 28 | const classes = useStyles(); 29 | 30 | return ( 31 |
32 | {children} 33 |
34 | ); 35 | }; 36 | 37 | const CardBody = React.forwardRef(CardBodyBase) as Component; 38 | 39 | CardBody.propTypes = { 40 | children: PropTypes.node, 41 | className: PropTypes.string 42 | }; 43 | 44 | export default CardBody; 45 | -------------------------------------------------------------------------------- /lib/PortalDestination/PortalDestination.tsx: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import * as React from "react"; 3 | import Portal from "../Portal"; 4 | import type { MergeElementProps } from "../typings"; 5 | import useStyles from "./styles"; 6 | 7 | interface BaseProps { 8 | /** The content of the component. */ 9 | children?: React.ReactNode; 10 | } 11 | 12 | export type PortalDestinationProps = MergeElementProps<"div", BaseProps>; 13 | 14 | type Component = { 15 | (props: PortalDestinationProps): React.ReactElement | null; 16 | propTypes?: React.WeakValidationMap | undefined; 17 | displayName?: string | undefined; 18 | }; 19 | 20 | const PortalDestinationBase = ( 21 | props: PortalDestinationProps, 22 | ref: React.Ref 23 | ) => { 24 | const { children, ...otherProps } = props; 25 | 26 | const classes = useStyles(); 27 | 28 | return ( 29 | 30 |
36 | {children} 37 |
38 |
39 | ); 40 | }; 41 | 42 | const PortalDestination = React.forwardRef(PortalDestinationBase) as Component; 43 | 44 | PortalDestination.propTypes = { 45 | children: PropTypes.node 46 | }; 47 | 48 | export default PortalDestination; 49 | -------------------------------------------------------------------------------- /lib/PageLoader/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | colors, 7 | darkMode, 8 | zIndexes, 9 | typography: { pxToRem } 10 | } = theme; 11 | 12 | return { 13 | root: { 14 | position: "fixed", 15 | left: 0, 16 | right: 0, 17 | bottom: 0, 18 | zIndex: zIndexes.header, 19 | opacity: 0, 20 | visibility: "hidden", 21 | transition: "opacity 360ms ease, visibility 360ms ease" 22 | }, 23 | progress: { 24 | position: "absolute", 25 | zIndex: 2, 26 | top: 0, 27 | left: 0, 28 | height: pxToRem(4), 29 | backgroundColor: !darkMode 30 | ? colors.primary.origin 31 | : colors.primary.light, 32 | transition: "width 360ms ease" 33 | }, 34 | overlay: { 35 | position: "absolute", 36 | zIndex: 1, 37 | top: 0, 38 | left: 0, 39 | width: "100%", 40 | height: "100%", 41 | backgroundColor: darkMode 42 | ? "rgba(0, 0, 0, 0.56)" 43 | : "rgba(255, 255, 255, 0.7)" 44 | }, 45 | visible: { opacity: 1, visibility: "visible" } 46 | }; 47 | }, 48 | { name: "SonnatPageLoader" } 49 | ); 50 | 51 | export default useStyles; 52 | -------------------------------------------------------------------------------- /scripts/build-packages.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fse = require("fs-extra"); 3 | const glob = require("fast-glob"); 4 | 5 | const packagePath = process.cwd(); 6 | 7 | const buildPath = path.join(packagePath, "./dist"); 8 | 9 | void (async () => { 10 | const moduleDirectories = ( 11 | await glob(path.join(buildPath, "**/*/index.js"), { ignore: ["**/esm/**"] }) 12 | ).map(path.dirname); 13 | 14 | for (const moduleDirectory of moduleDirectories) { 15 | const typingsPath = path.join(moduleDirectory, "index.d.ts"); 16 | const typingsExist = await fse.pathExists(typingsPath); 17 | 18 | const relativePath = path.relative(buildPath, moduleDirectory); 19 | 20 | const packageJson = { 21 | sideEffects: false, 22 | module: path.join( 23 | (depth => { 24 | let path = ""; 25 | for (let i = 0; i < depth; i++) path += i < depth - 1 ? "../" : ".."; 26 | return path; 27 | })(relativePath.split("/").length), 28 | "esm", 29 | relativePath, 30 | "index.js" 31 | ), 32 | main: "./index.js" 33 | }; 34 | 35 | if (typingsExist) packageJson.types = "./index.d.ts"; 36 | 37 | const packageJsonPath = path.join(moduleDirectory, "package.json"); 38 | 39 | await fse.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); 40 | } 41 | })(); 42 | -------------------------------------------------------------------------------- /lib/Card/Media/Media.tsx: -------------------------------------------------------------------------------- 1 | import c from "classnames"; 2 | import PropTypes from "prop-types"; 3 | import * as React from "react"; 4 | import type { MergeElementProps } from "../../typings"; 5 | import useStyles from "./styles"; 6 | 7 | interface CardMediaBaseProps { 8 | /** The content of the component. */ 9 | children?: React.ReactNode; 10 | /** 11 | * Append to the classNames applied to the component so you can override or 12 | * extend the styles. 13 | */ 14 | className?: string; 15 | } 16 | 17 | export type CardMediaProps = MergeElementProps<"div", CardMediaBaseProps>; 18 | 19 | type Component = { 20 | (props: CardMediaProps): React.ReactElement | null; 21 | propTypes?: React.WeakValidationMap | undefined; 22 | displayName?: string | undefined; 23 | }; 24 | 25 | const CardMediaBase = ( 26 | props: CardMediaProps, 27 | ref: React.Ref 28 | ) => { 29 | const { className, children, ...otherProps } = props; 30 | 31 | const classes = useStyles(); 32 | 33 | return ( 34 |
35 | {children} 36 |
37 | ); 38 | }; 39 | 40 | const CardMedia = React.forwardRef(CardMediaBase) as Component; 41 | 42 | CardMedia.propTypes = { 43 | children: PropTypes.node, 44 | className: PropTypes.string 45 | }; 46 | 47 | export default CardMedia; 48 | -------------------------------------------------------------------------------- /lib/styles/ServerStyleSheets/Provider.tsx: -------------------------------------------------------------------------------- 1 | import { SheetsRegistry } from "jss"; 2 | import PropTypes from "prop-types"; 3 | import React from "react"; 4 | import type { GenerateClassName } from "../../typings"; 5 | 6 | interface ServerContextValue { 7 | sheetsRegistry: SheetsRegistry | undefined; 8 | generateServerClassName: GenerateClassName | undefined; 9 | } 10 | 11 | interface ServerProviderProps { 12 | children: React.ReactNode; 13 | sheetsRegistry?: SheetsRegistry; 14 | generateServerClassName?: GenerateClassName; 15 | } 16 | 17 | export const ServerContext = React.createContext({ 18 | sheetsRegistry: undefined, 19 | generateServerClassName: undefined 20 | }); 21 | 22 | if (process.env.NODE_ENV !== "production") { 23 | ServerContext.displayName = "ServerContext"; 24 | } 25 | 26 | const ServerProvider = (props: ServerProviderProps): JSX.Element => { 27 | const { children, sheetsRegistry, generateServerClassName } = props; 28 | 29 | return ( 30 | 31 | {children} 32 | 33 | ); 34 | }; 35 | 36 | ServerProvider.propTypes = { 37 | children: PropTypes.node.isRequired, 38 | sheetsRegistry: PropTypes.instanceOf(SheetsRegistry), 39 | generateServerClassName: PropTypes.func 40 | }; 41 | 42 | export default ServerProvider; 43 | -------------------------------------------------------------------------------- /lib/Code/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | darkMode, 7 | direction, 8 | radius, 9 | colors: { divider, text, transparent }, 10 | spacings: { spaces }, 11 | swatches: { grey }, 12 | typography: { fontWeight, setText, fontFamily } 13 | } = theme; 14 | 15 | return { 16 | root: { 17 | ...setText({ 18 | fontSize: "0.875em", 19 | fontWeight: fontWeight.regular, 20 | lineHeight: 1.5714285714, 21 | color: !darkMode ? text.dark.primary : text.light.primary 22 | }), 23 | direction, 24 | fontFamily: fontFamily.monospace, 25 | borderRadius: radius.xSmall, 26 | backgroundColor: !darkMode ? grey[50] : grey[900], 27 | display: "inline-block", 28 | padding: `0 ${spaces[1].rem}` 29 | }, 30 | codeBlock: { 31 | overflow: "auto", 32 | display: "block", 33 | textAlign: "left", 34 | direction: "ltr", 35 | fontSize: "0.875em", 36 | padding: spaces[7].rem, 37 | backgroundColor: transparent, 38 | border: `1px solid ${!darkMode ? divider.dark : divider.light}`, 39 | borderRadius: radius.small 40 | } 41 | }; 42 | }, 43 | { name: "SonnatCode" } 44 | ); 45 | 46 | export default useStyles; 47 | -------------------------------------------------------------------------------- /lib/Select/OptionGroup/OptionGroup.tsx: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import * as React from "react"; 3 | import type { MergeElementProps } from "../../typings"; 4 | 5 | interface OptionGroupBaseProps { 6 | /** The content of the group. */ 7 | children?: React.ReactNode; 8 | /** 9 | * Append to the classNames applied to the component so you can override or 10 | * extend the styles. 11 | */ 12 | className?: string; 13 | /** 14 | * Append to the classNames applied to the title so you can override or 15 | * extend the styles. 16 | */ 17 | titleClassName?: string; 18 | /** The title of the group. */ 19 | title?: string; 20 | } 21 | 22 | export type SelectOptionGroupProps = MergeElementProps< 23 | "div", 24 | OptionGroupBaseProps 25 | >; 26 | 27 | type Component = { 28 | (props: SelectOptionGroupProps): React.ReactElement | null; 29 | propTypes?: React.WeakValidationMap | undefined; 30 | displayName?: string | undefined; 31 | }; 32 | 33 | const SelectOptionGroupBase = (props: SelectOptionGroupProps) => ( 34 | <>{props.children} 35 | ); 36 | 37 | const SelectOptionGroup = SelectOptionGroupBase as Component; 38 | 39 | SelectOptionGroup.propTypes = { 40 | children: PropTypes.node, 41 | title: PropTypes.string, 42 | className: PropTypes.string, 43 | titleClassName: PropTypes.string 44 | }; 45 | 46 | export default SelectOptionGroup; 47 | -------------------------------------------------------------------------------- /lib/styles/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React from "react"; 3 | import { ThemeProvider as JssThemeProvider } from "react-jss"; 4 | import type { DefaultTheme } from "./defaultTheme"; 5 | 6 | interface ThemeProviderProps> { 7 | children: React.ReactNode; 8 | theme: NonNullable | ((outerTheme: T) => NonNullable); 9 | } 10 | 11 | interface IThemeProvider> { 12 | (props: ThemeProviderProps): JSX.Element; 13 | propTypes?: React.WeakValidationMap> | undefined; 14 | } 15 | 16 | /** 17 | * This component takes a `theme` prop. 18 | * It makes the `theme` available down the React tree thanks to React context. 19 | * This component should preferably be used at **the root of your component tree**. 20 | */ 21 | const ThemeProvider: IThemeProvider = props => { 22 | const { children, theme } = props; 23 | 24 | if (theme == null) 25 | throw new Error( 26 | "[Sonnat]: `theme` prop is missing from the !" 27 | ); 28 | 29 | return {children}; 30 | }; 31 | 32 | ThemeProvider.propTypes = { 33 | children: PropTypes.node.isRequired, 34 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 35 | // @ts-ignore 36 | theme: PropTypes.oneOfType([PropTypes.object, PropTypes.func]).isRequired 37 | }; 38 | 39 | export default ThemeProvider; 40 | -------------------------------------------------------------------------------- /lib/Card/ActionBar/ActionBar.tsx: -------------------------------------------------------------------------------- 1 | import c from "classnames"; 2 | import PropTypes from "prop-types"; 3 | import * as React from "react"; 4 | import type { MergeElementProps } from "../../typings"; 5 | import useStyles from "./styles"; 6 | 7 | interface CardActionBarBaseProps { 8 | /** The content of the component. */ 9 | children?: React.ReactNode; 10 | /** 11 | * Append to the classNames applied to the component so you can override or 12 | * extend the styles. 13 | */ 14 | className?: string; 15 | } 16 | 17 | export type CardActionBarProps = MergeElementProps< 18 | "div", 19 | CardActionBarBaseProps 20 | >; 21 | 22 | type Component = { 23 | (props: CardActionBarProps): React.ReactElement | null; 24 | propTypes?: React.WeakValidationMap | undefined; 25 | displayName?: string | undefined; 26 | }; 27 | 28 | const CardActionBarBase = ( 29 | props: CardActionBarProps, 30 | ref: React.Ref 31 | ) => { 32 | const { className, children, ...otherProps } = props; 33 | 34 | const classes = useStyles(); 35 | 36 | return ( 37 |
38 | {children} 39 |
40 | ); 41 | }; 42 | 43 | const CardActionBar = React.forwardRef(CardActionBarBase) as Component; 44 | 45 | CardActionBar.propTypes = { 46 | children: PropTypes.node, 47 | className: PropTypes.string 48 | }; 49 | 50 | export default CardActionBar; 51 | -------------------------------------------------------------------------------- /scripts/build-npm-files.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fse = require("fs-extra"); 3 | 4 | const packagePath = process.cwd(); 5 | 6 | const buildPath = path.join(packagePath, "./dist"); 7 | 8 | void (async () => { 9 | const rootPackageJson = path.join(packagePath, "package.json"); 10 | const readme = path.join(packagePath, "README.md"); 11 | const license = path.join(packagePath, "LICENSE"); 12 | 13 | const rootPackageJsonData = JSON.parse( 14 | await fse.readFile(rootPackageJson, { 15 | encoding: "utf8" 16 | }) 17 | ); 18 | 19 | const npmPackageJson = { 20 | sideEffects: false, 21 | main: "index.js", 22 | types: "index.d.ts", 23 | module: "esm/index.js", 24 | name: rootPackageJsonData.name, 25 | version: rootPackageJsonData.version, 26 | description: rootPackageJsonData.description, 27 | license: rootPackageJsonData.license, 28 | homepage: rootPackageJsonData.homepage, 29 | repository: rootPackageJsonData.repository, 30 | keywords: rootPackageJsonData.keywords, 31 | peerDependencies: rootPackageJsonData.peerDependencies, 32 | dependencies: rootPackageJsonData.dependencies 33 | }; 34 | 35 | await fse.copyFile(readme, path.join(buildPath, "README.md")); 36 | await fse.copyFile(license, path.join(buildPath, "LICENSE")); 37 | await fse.writeFile( 38 | path.join(buildPath, "package.json"), 39 | JSON.stringify(npmPackageJson, null, 2) 40 | ); 41 | })(); 42 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:react/recommended", 5 | "plugin:import/recommended", 6 | "plugin:import/typescript", 7 | "prettier" 8 | ], 9 | "env": { 10 | "browser": true, 11 | "es6": true, 12 | "node": true, 13 | "commonjs": true 14 | }, 15 | "globals": { 16 | "Atomics": "readonly", 17 | "SharedArrayBuffer": "readonly", 18 | "JSX": true 19 | }, 20 | "plugins": [ 21 | "eslint-plugin-import", 22 | "eslint-plugin-react", 23 | "eslint-plugin-react-hooks", 24 | "@typescript-eslint/eslint-plugin" 25 | ], 26 | "parser": "@typescript-eslint/parser", 27 | "parserOptions": { 28 | "sourceType": "module" 29 | }, 30 | "rules": { 31 | "no-alert": "error", 32 | "no-console": "warn", 33 | "prefer-const": "error", 34 | "default-case": "warn", 35 | "react-hooks/rules-of-hooks": "error", 36 | "react-hooks/exhaustive-deps": "warn", 37 | "react/react-in-jsx-scope": "off" 38 | }, 39 | "overrides": [ 40 | { 41 | "files": ["*.ts", "*.tsx", "*.d.ts"], 42 | "extends": [ 43 | "plugin:@typescript-eslint/recommended", 44 | "plugin:@typescript-eslint/recommended-requiring-type-checking" 45 | ], 46 | "parserOptions": { 47 | "sourceType": "module", 48 | "project": ["tsconfig.json"] 49 | } 50 | } 51 | ], 52 | "settings": { "react": { "version": "detect" } } 53 | } 54 | -------------------------------------------------------------------------------- /lib/Spinner/Moon/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | direction, 7 | radius, 8 | typography: { pxToRem, fontFamily } 9 | } = theme; 10 | 11 | return { 12 | root: { 13 | direction, 14 | fontFamily: fontFamily[direction], 15 | display: "flex", 16 | alignItems: "center", 17 | justifyContent: "center", 18 | position: "relative" 19 | }, 20 | base: { 21 | display: "flex", 22 | alignItems: "center", 23 | justifyContent: "center", 24 | position: "relative", 25 | width: "100%", 26 | height: "100%", 27 | borderRadius: radius.rounded, 28 | animationName: "$rotateAnimation", 29 | animationDuration: "600ms", 30 | animationTimingFunction: "linear", 31 | animationIterationCount: "infinite" 32 | }, 33 | movingParticle: { 34 | position: "absolute", 35 | top: "0", 36 | width: pxToRem(4), 37 | height: pxToRem(4), 38 | transform: "translateY(-100%)", 39 | borderRadius: radius.rounded 40 | }, 41 | "@keyframes rotateAnimation": { 42 | "0%": { transform: "rotate(0)" }, 43 | "100%": { transform: "rotate(360deg)" } 44 | } 45 | }; 46 | }, 47 | { name: "SonnatMoonSpinner" } 48 | ); 49 | 50 | export default useStyles; 51 | -------------------------------------------------------------------------------- /lib/Table/Row/styles.ts: -------------------------------------------------------------------------------- 1 | import { lighten } from "../../styles/colorUtils"; 2 | import makeStyles from "../../styles/makeStyles"; 3 | 4 | export type AlignCombo = 5 | | "verticalAlignMiddle" 6 | | "verticalAlignTop" 7 | | "verticalAlignBottom"; 8 | 9 | const useStyles = makeStyles( 10 | theme => { 11 | const { colors, darkMode } = theme; 12 | 13 | const selectedBgColor = !darkMode 14 | ? colors.primary.origin 15 | : colors.primary.light; 16 | 17 | return { 18 | root: { 19 | display: "table-row", 20 | outline: "none", 21 | color: "inherit", 22 | transition: "background-color 180ms ease" 23 | }, 24 | verticalAlignMiddle: { verticalAlign: "middle" }, 25 | verticalAlignTop: { verticalAlign: "top" }, 26 | verticalAlignBottom: { verticalAlign: "bottom" }, 27 | selected: { 28 | color: colors.getContrastColorOf(selectedBgColor), 29 | backgroundColor: selectedBgColor, 30 | "&$hoverable:hover": { 31 | backgroundColor: lighten(selectedBgColor, 0.15) 32 | } 33 | }, 34 | hoverable: { 35 | "&:hover": { 36 | backgroundColor: !darkMode 37 | ? colors.createBlackColor({ alpha: 0.04 }, true, darkMode) 38 | : colors.createWhiteColor({ alpha: 0.04 }, true, darkMode) 39 | } 40 | } 41 | }; 42 | }, 43 | { name: "SonnatTableRow" } 44 | ); 45 | 46 | export default useStyles; 47 | -------------------------------------------------------------------------------- /lib/Skeleton/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | colors, 7 | darkMode, 8 | direction, 9 | radius, 10 | typography: { fontFamily } 11 | } = theme; 12 | 13 | return { 14 | root: { 15 | direction, 16 | fontFamily: fontFamily[direction], 17 | display: "block", 18 | backgroundColor: !darkMode 19 | ? colors.createBlackColor({ alpha: 0.12 }, true, darkMode) 20 | : colors.createWhiteColor({ alpha: 0.12 }, true, darkMode), 21 | height: "1.2em", 22 | animation: "$pulse 1.5s ease-in-out 0.5s infinite" 23 | }, 24 | text: { 25 | marginTop: 0, 26 | marginBottom: 0, 27 | height: "auto", 28 | transformOrigin: "0 55%", 29 | transform: "scale(1, 0.60)", 30 | borderRadius: radius.small, 31 | "&:empty:before": { content: '"\\00a0"' } 32 | }, 33 | circular: { borderRadius: radius.rounded }, 34 | rectangular: { borderRadius: radius.small }, 35 | autoHeight: { height: "auto" }, 36 | autoWidth: { maxWidth: "fit-content" }, 37 | hasChildren: { "& > *": { visibility: "hidden" } }, 38 | "@keyframes pulse": { 39 | "0%": { opacity: 1 }, 40 | "50%": { opacity: 0.4 }, 41 | "100%": { opacity: 1 } 42 | } 43 | }; 44 | }, 45 | { name: "SonnatSkeleton" } 46 | ); 47 | 48 | export default useStyles; 49 | -------------------------------------------------------------------------------- /lib/utils/useConstantProp.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { NotUndefined } from "../typings"; 3 | import isUndef from "./isUndef"; 4 | 5 | interface Options { 6 | componentName: string; 7 | propName: string; 8 | errorHandler?: () => string; 9 | notNullable?: boolean; 10 | } 11 | 12 | type R = T2 extends undefined ? T1 | undefined : NotUndefined; 13 | 14 | const useConstantProp = ( 15 | initialValue: T1, 16 | defaultValue: T2, 17 | options: Options 18 | ): R => { 19 | const { 20 | componentName, 21 | propName, 22 | errorHandler, 23 | notNullable = false 24 | } = options; 25 | 26 | const fallbackCondition = notNullable 27 | ? initialValue == null 28 | : isUndef(initialValue); 29 | 30 | const { current: value } = React.useRef( 31 | !fallbackCondition ? initialValue : defaultValue 32 | ); 33 | 34 | React.useEffect(() => { 35 | if (initialValue != null && initialValue !== value) { 36 | if (errorHandler) 37 | throw new Error(`[Sonnat][${componentName}]: ${errorHandler()}`); 38 | else { 39 | throw new Error( 40 | `[Sonnat][${componentName}]: It seems that you are changing the \`${propName}\` prop after being initialized.` + 41 | `This property can't be changed after being initialized.` 42 | ); 43 | } 44 | } 45 | // eslint-disable-next-line react-hooks/exhaustive-deps 46 | }, [initialValue]); 47 | 48 | return value as R; 49 | }; 50 | 51 | export default useConstantProp; 52 | -------------------------------------------------------------------------------- /sonnat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /lib/Spinner/Clip/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | direction, 7 | typography: { pxToRem, fontFamily } 8 | } = theme; 9 | 10 | return { 11 | root: { 12 | direction, 13 | fontFamily: fontFamily[direction] 14 | }, 15 | svg: { 16 | width: "100%", 17 | height: "100%", 18 | animationName: "$rotateAnimation", 19 | animationDuration: "0.8s", 20 | animationTimingFunction: "linear", 21 | animationIterationCount: "infinite" 22 | }, 23 | base: { 24 | transition: "fill 360ms ease" 25 | }, 26 | movingParticle: { 27 | fill: "none", 28 | strokeLinecap: "round", 29 | strokeLinejoin: "round", 30 | strokeWidth: pxToRem(2), 31 | animationName: "$clipAnimation", 32 | animationDuration: "1.6s", 33 | animationTimingFunction: "ease-in", 34 | animationIterationCount: "infinite", 35 | transformOrigin: "center" 36 | }, 37 | "@keyframes clipAnimation": { 38 | "0%": { strokeDasharray: "1, 100", strokeDashoffset: "0" }, 39 | "50%": { strokeDasharray: "80, 100", strokeDashoffset: "46" }, 40 | "100%": { strokeDasharray: "1, 100", strokeDashoffset: "0" } 41 | }, 42 | "@keyframes rotateAnimation": { 43 | "100%": { transform: "rotate(360deg)" } 44 | } 45 | }; 46 | }, 47 | { name: "SonnatClipSpinner" } 48 | ); 49 | 50 | export default useStyles; 51 | -------------------------------------------------------------------------------- /examples/with-nextjs/pages/_document.js: -------------------------------------------------------------------------------- 1 | import ServerStyleSheets from "@sonnat/ui/styles/ServerStyleSheets"; 2 | import Document, { Head, Html, Main, NextScript } from "next/document"; 3 | import * as React from "react"; 4 | 5 | export default class MyDocument extends Document { 6 | static async getInitialProps(ctx) { 7 | const sheets = new ServerStyleSheets(); 8 | 9 | const originalRenderPage = ctx.renderPage; 10 | 11 | ctx.renderPage = () => 12 | originalRenderPage({ 13 | enhanceApp: App => props => sheets.collect() 14 | }); 15 | 16 | const initialProps = await Document.getInitialProps(ctx); 17 | 18 | return { 19 | ...initialProps, 20 | // Styles fragment is rendered after the app and page rendering finish. 21 | styles: [ 22 | ...React.Children.toArray(initialProps.styles), 23 | sheets.getStyleElement() 24 | ] 25 | }; 26 | } 27 | 28 | render() { 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 | 43 | 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 | 10 | [React](https://reactjs.org/) component library using [Sonnat Design System](https://sonnat.design) to build faster, elegant, and accessible web applications. 11 | 12 | [![license](https://img.shields.io/github/license/sonnat/sonnat-ui?color=EE3F7C&style=for-the-badge)](https://github.com/sonnat/sonnat-ui/blob/main/LICENSE) 13 | [![npm latest package](https://img.shields.io/npm/v/@sonnat/ui?color=EE3F7C&style=for-the-badge)](https://www.npmjs.com/package/@sonnat/ui) 14 | [![npm downloads](https://img.shields.io/npm/dt/@sonnat/ui?color=EE3F7C&style=for-the-badge)](https://www.npmjs.com/package/@sonnat/ui) 15 | [![Follow us on Twitter](https://img.shields.io/twitter/follow/sonnatdesign?color=EE3F7C&label=follow%20us%20on%20twitter&style=for-the-badge)](https://twitter.com/sonnatdesign) 16 | 17 |
18 | 19 | ### Getting Started 20 | 21 | Visit [https://sonnat.dev/docs/installation](https://www.sonnat.dev/docs/installation) to get started with Sonnat-UI. 22 | 23 | ### Documentation 24 | 25 | Visit [https://sonnat.dev/docs](https://www.sonnat.dev/docs) to view the full documentation. 26 | 27 | ### Community 28 | 29 | The Sonnat-UI community can be found on [Github Discussions](https://github.com/sonnat/sonnat-ui/discussions) | [Discord](https://discord.gg/h4Dpr4PnXW), where you can ask questions, voice ideas, and share your projects. 30 | -------------------------------------------------------------------------------- /lib/Card/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import c from "classnames"; 2 | import PropTypes from "prop-types"; 3 | import * as React from "react"; 4 | import type { MergeElementProps } from "../../typings"; 5 | import useStyles from "./styles"; 6 | 7 | interface CardHeaderBase { 8 | /** The content of the component. */ 9 | children?: React.ReactNode; 10 | /** The action to display in the header. */ 11 | action?: React.ReactNode; 12 | /** 13 | * Append to the classNames applied to the component so you can override or 14 | * extend the styles. 15 | */ 16 | className?: string; 17 | } 18 | 19 | export type CardHeaderProps = MergeElementProps<"div", CardHeaderBase>; 20 | 21 | type Component = { 22 | (props: CardHeaderProps): React.ReactElement | null; 23 | propTypes?: React.WeakValidationMap | undefined; 24 | displayName?: string | undefined; 25 | }; 26 | 27 | const CardHeaderBase = ( 28 | props: CardHeaderProps, 29 | ref: React.Ref 30 | ) => { 31 | const { className, children, action, ...otherProps } = props; 32 | 33 | const classes = useStyles(); 34 | 35 | return ( 36 |
37 |
{children}
38 | {action &&
{action}
} 39 |
40 | ); 41 | }; 42 | 43 | const CardHeader = React.forwardRef(CardHeaderBase) as Component; 44 | 45 | CardHeader.propTypes = { 46 | children: PropTypes.node, 47 | className: PropTypes.string, 48 | action: PropTypes.node 49 | }; 50 | 51 | export default CardHeader; 52 | -------------------------------------------------------------------------------- /lib/Container/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | breakpoints, 7 | direction, 8 | spacings: { gutter }, 9 | typography: { fontFamily } 10 | } = theme; 11 | 12 | return { 13 | root: { 14 | direction, 15 | fontFamily: fontFamily[direction], 16 | width: "100%", 17 | paddingRight: gutter, 18 | paddingLeft: gutter, 19 | marginRight: "auto", 20 | marginLeft: "auto", 21 | [breakpoints.up("xxs")]: { 22 | maxWidth: "100%", 23 | "&$xxsFluid": { extend: "fluid" } 24 | }, 25 | [breakpoints.up("xs")]: { 26 | "&$xsFluid": { extend: "fluid" } 27 | }, 28 | [breakpoints.up("sm")]: { 29 | maxWidth: breakpoints.values["sm"], 30 | "&$smFluid": { extend: "fluid" } 31 | }, 32 | [breakpoints.up("md")]: { 33 | "&$mdFluid": { extend: "fluid" } 34 | }, 35 | [breakpoints.up("lg")]: { 36 | maxWidth: breakpoints.values["md"], 37 | "&$lgFluid": { extend: "fluid" } 38 | }, 39 | [breakpoints.up("xlg")]: { 40 | maxWidth: 1184, 41 | "&$xlgFluid": { extend: "fluid" } 42 | } 43 | }, 44 | fluid: { maxWidth: "100%" }, 45 | noPadding: { paddingRight: 0, paddingLeft: 0 }, 46 | xxsFluid: {}, 47 | xsFluid: {}, 48 | smFluid: {}, 49 | mdFluid: {}, 50 | lgFluid: {}, 51 | xlgFluid: {} 52 | }; 53 | }, 54 | { name: "SonnatContainer" } 55 | ); 56 | 57 | export default useStyles; 58 | -------------------------------------------------------------------------------- /lib/Table/Cell/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | export type AlignCombo = 4 | | "textAlignCenter" 5 | | "textAlignInherit" 6 | | "textAlignJustify" 7 | | "textAlignLeft" 8 | | "textAlignRight"; 9 | 10 | const useStyles = makeStyles( 11 | theme => { 12 | const { 13 | darkMode, 14 | spacings: { spaces }, 15 | colors: { text, divider }, 16 | typography: { variants } 17 | } = theme; 18 | 19 | return { 20 | root: { 21 | display: "table-cell", 22 | verticalAlign: "inherit", 23 | borderBottom: `1px solid ${!darkMode ? divider.dark : divider.light}`, 24 | padding: spaces[7].rem 25 | }, 26 | bodyCell: { 27 | ...variants.body, 28 | color: !darkMode ? text.dark.primary : text.light.primary 29 | }, 30 | headerCell: { 31 | ...variants.subtitle, 32 | color: !darkMode ? text.dark.primary : text.light.primary, 33 | borderBottomColor: darkMode 34 | ? "rgba(255, 255, 255, 0.24)" 35 | : "rgba(0, 0, 0, 0.24)" 36 | }, 37 | footerCell: { 38 | ...variants.caption, 39 | color: !darkMode ? text.dark.secondary : text.light.secondary 40 | }, 41 | textAlignCenter: { textAlign: "center" }, 42 | textAlignInherit: { textAlign: "inherit" }, 43 | textAlignJustify: { textAlign: "justify" }, 44 | textAlignLeft: { textAlign: "left" }, 45 | textAlignRight: { textAlign: "right" }, 46 | dense: { padding: spaces[3].rem }, 47 | selected: { color: "inherit" } 48 | }; 49 | }, 50 | { name: "SonnatTableCell" } 51 | ); 52 | 53 | export default useStyles; 54 | -------------------------------------------------------------------------------- /lib/Table/Body/Body.tsx: -------------------------------------------------------------------------------- 1 | import c from "classnames"; 2 | import PropTypes from "prop-types"; 3 | import * as React from "react"; 4 | import makeStyles from "../../styles/makeStyles"; 5 | import type { MergeElementProps } from "../../typings"; 6 | import TableInnerContext from "../innerContext"; 7 | 8 | interface TableBodyBaseProps { 9 | /** The content of the component. */ 10 | children?: React.ReactNode; 11 | /** 12 | * Append to the classNames applied to the component so you can override or 13 | * extend the styles. 14 | */ 15 | className?: string; 16 | } 17 | 18 | export type TableBodyProps = MergeElementProps<"tbody", TableBodyBaseProps>; 19 | 20 | type Component = { 21 | (props: TableBodyProps): React.ReactElement | null; 22 | propTypes?: React.WeakValidationMap | undefined; 23 | displayName?: string | undefined; 24 | }; 25 | 26 | const useStyles = makeStyles( 27 | { root: { display: "table-row-group" } }, 28 | { name: "SonnatTableBody" } 29 | ); 30 | 31 | const TableBodyBase = ( 32 | props: TableBodyProps, 33 | ref: React.Ref 34 | ) => { 35 | const { className, children, ...otherProps } = props; 36 | 37 | const classes = useStyles(); 38 | 39 | return ( 40 | 41 | 42 | {children} 43 | 44 | 45 | ); 46 | }; 47 | 48 | const TableBody = React.forwardRef(TableBodyBase) as Component; 49 | 50 | TableBody.propTypes = { 51 | children: PropTypes.node, 52 | className: PropTypes.string 53 | }; 54 | 55 | export default TableBody; 56 | -------------------------------------------------------------------------------- /lib/utils/getSingleChild.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { isFragment } from "react-is"; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint 5 | const getSingleChild =

( 6 | children: React.ReactNode, 7 | componentName?: string 8 | ): React.ReactElement

| null => { 9 | if (!children) { 10 | // eslint-disable-next-line no-console 11 | console.error( 12 | "Sonnat: The `children` prop has to be a single valid element." 13 | ); 14 | 15 | return null; 16 | } 17 | 18 | if (!React.isValidElement(children)) { 19 | if (process.env.NODE_ENV !== "production") { 20 | // eslint-disable-next-line no-console 21 | console.error( 22 | `Sonnat: The \`children\` provided to the ${ 23 | componentName ?? "component" 24 | } isn't a valid children.` 25 | ); 26 | } 27 | 28 | return null; 29 | } 30 | 31 | if (isFragment(children)) { 32 | if (process.env.NODE_ENV !== "production") { 33 | // eslint-disable-next-line no-console 34 | console.error( 35 | `Sonnat: The ${ 36 | componentName ?? "component" 37 | } doesn't accept Fragment as children.` 38 | ); 39 | } 40 | 41 | return null; 42 | } 43 | 44 | try { 45 | return React.Children.only(children) as React.ReactElement; 46 | } catch (err) { 47 | if (process.env.NODE_ENV !== "production") { 48 | // eslint-disable-next-line no-console 49 | console.error( 50 | "Sonnat: The `children` prop has to be a single valid element." 51 | ); 52 | } 53 | 54 | return null; 55 | } 56 | }; 57 | 58 | export default getSingleChild; 59 | -------------------------------------------------------------------------------- /lib/Table/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import c from "classnames"; 2 | import PropTypes from "prop-types"; 3 | import * as React from "react"; 4 | import makeStyles from "../../styles/makeStyles"; 5 | import type { MergeElementProps } from "../../typings"; 6 | import TableInnerContext from "../innerContext"; 7 | 8 | interface TableFooterBaseProps { 9 | /** The content of the component. */ 10 | children?: React.ReactNode; 11 | /** 12 | * Append to the classNames applied to the component so you can override or 13 | * extend the styles. 14 | */ 15 | className?: string; 16 | } 17 | 18 | export type TableFooterProps = MergeElementProps<"tfoot", TableFooterBaseProps>; 19 | 20 | type Component = { 21 | (props: TableFooterProps): React.ReactElement | null; 22 | propTypes?: React.WeakValidationMap | undefined; 23 | displayName?: string | undefined; 24 | }; 25 | 26 | const useStyles = makeStyles( 27 | { root: { display: "table-footer-group" } }, 28 | { name: "SonnatTableFooter" } 29 | ); 30 | 31 | const TableFooterBase = ( 32 | props: TableFooterProps, 33 | ref: React.Ref 34 | ) => { 35 | const { className, children, ...otherProps } = props; 36 | 37 | const classes = useStyles(); 38 | 39 | return ( 40 | 41 | 42 | {children} 43 | 44 | 45 | ); 46 | }; 47 | 48 | const TableFooter = React.forwardRef(TableFooterBase) as Component; 49 | 50 | TableFooter.propTypes = { 51 | children: PropTypes.node, 52 | className: PropTypes.string 53 | }; 54 | 55 | export default TableFooter; 56 | -------------------------------------------------------------------------------- /lib/Table/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import c from "classnames"; 2 | import PropTypes from "prop-types"; 3 | import * as React from "react"; 4 | import makeStyles from "../../styles/makeStyles"; 5 | import type { MergeElementProps } from "../../typings"; 6 | import TableInnerContext from "../innerContext"; 7 | 8 | interface TableHeaderBaseProps { 9 | /** The content of the component. */ 10 | children?: React.ReactNode; 11 | /** 12 | * Append to the classNames applied to the component so you can override or 13 | * extend the styles. 14 | */ 15 | className?: string; 16 | } 17 | 18 | export type TableHeaderProps = MergeElementProps<"thead", TableHeaderBaseProps>; 19 | 20 | type Component = { 21 | (props: TableHeaderProps): React.ReactElement | null; 22 | propTypes?: React.WeakValidationMap | undefined; 23 | displayName?: string | undefined; 24 | }; 25 | 26 | const useStyles = makeStyles( 27 | { root: { display: "table-header-group" } }, 28 | { name: "SonnatTableHeader" } 29 | ); 30 | 31 | const TableHeaderBase = ( 32 | props: TableHeaderProps, 33 | ref: React.Ref 34 | ) => { 35 | const { className, children, ...otherProps } = props; 36 | 37 | const classes = useStyles(); 38 | 39 | return ( 40 | 41 | 42 | {children} 43 | 44 | 45 | ); 46 | }; 47 | 48 | const TableHeader = React.forwardRef(TableHeaderBase) as Component; 49 | 50 | TableHeader.propTypes = { 51 | children: PropTypes.node, 52 | className: PropTypes.string 53 | }; 54 | 55 | export default TableHeader; 56 | -------------------------------------------------------------------------------- /lib/Code/Code.tsx: -------------------------------------------------------------------------------- 1 | import c from "classnames"; 2 | import PropTypes from "prop-types"; 3 | import * as React from "react"; 4 | import type { MergeElementProps } from "../typings"; 5 | import useStyles from "./styles"; 6 | 7 | interface CodeBaseProps { 8 | /** The content of the component. */ 9 | children?: React.ReactNode; 10 | /** 11 | * Append to the classNames applied to the component so you can override or 12 | * extend the styles. 13 | */ 14 | className?: string; 15 | /** 16 | * Determine whether the component is code-block or not. 17 | * 18 | * If `true`, the component will be rendered as a `

` element.
19 |    * otherwise, the component will be rendered as a `` element.
20 |    * @default false
21 |    */
22 |   codeBlock?: boolean;
23 | }
24 | 
25 | export type CodeProps = MergeElementProps<"pre", CodeBaseProps>;
26 | 
27 | type Component = {
28 |   (props: CodeProps): React.ReactElement | null;
29 |   propTypes?: React.WeakValidationMap | undefined;
30 |   displayName?: string | undefined;
31 | };
32 | 
33 | const CodeBase = (props: CodeProps, ref: React.Ref) => {
34 |   const { className, children, codeBlock = false, ...otherProps } = props;
35 | 
36 |   const classes = useStyles();
37 | 
38 |   const RootNode = codeBlock ? "pre" : "code";
39 | 
40 |   return (
41 |     
48 |       {children}
49 |     
50 |   );
51 | };
52 | 
53 | const Code = React.forwardRef(CodeBase) as Component;
54 | 
55 | Code.propTypes = {
56 |   children: PropTypes.node,
57 |   className: PropTypes.string,
58 |   codeBlock: PropTypes.bool
59 | };
60 | 
61 | export default Code;
62 | 


--------------------------------------------------------------------------------
/lib/styles/index.ts:
--------------------------------------------------------------------------------
 1 | export * from "./colorUtils";
 2 | export * from "./createBreakpoints";
 3 | export { default as createBreakpoints } from "./createBreakpoints";
 4 | export * from "./createColors";
 5 | export { default as createColors } from "./createColors";
 6 | export * from "./createGenerateClassName";
 7 | export { default as createGenerateClassName } from "./createGenerateClassName";
 8 | export * from "./createMixins";
 9 | export { default as createMixins } from "./createMixins";
10 | export * from "./createRadius";
11 | export { default as createRadius } from "./createRadius";
12 | export * from "./createSpacings";
13 | export { default as createSpacings } from "./createSpacings";
14 | export * from "./createTheme";
15 | export { default as createTheme } from "./createTheme";
16 | export * from "./createTypography";
17 | export { default as createTypography } from "./createTypography";
18 | export * from "./createZIndexes";
19 | export { default as createZIndexes } from "./createZIndexes";
20 | export * from "./defaultTheme";
21 | export { default as defaultTheme } from "./defaultTheme";
22 | export * from "./generateColorSwatch";
23 | export { default as generateColorSwatch } from "./generateColorSwatch";
24 | export { default as jssPreset } from "./jssPreset";
25 | export { default as makeStyles } from "./makeStyles";
26 | export * from "./ServerStyleSheets";
27 | export { default as ServerStyleSheets } from "./ServerStyleSheets";
28 | export * from "./SonnatInitializer";
29 | export { default as SonnatInitializer } from "./SonnatInitializer";
30 | export * from "./swatches";
31 | export { default as swatches } from "./swatches";
32 | export { default as ThemeProvider } from "./ThemeProvider";
33 | export { default as useDarkMode } from "./useDarkMode";
34 | export { default as useTheme } from "./useTheme";
35 | 


--------------------------------------------------------------------------------
/lib/Breadcrumb/Item/styles.ts:
--------------------------------------------------------------------------------
 1 | import makeStyles from "../../styles/makeStyles";
 2 | 
 3 | const useStyles = makeStyles(
 4 |   theme => {
 5 |     const {
 6 |       darkMode,
 7 |       direction,
 8 |       colors: { text },
 9 |       spacings: { spaces },
10 |       mixins: { asIconWrapper },
11 |       hacks: { backfaceVisibilityFix },
12 |       typography: { pxToRem, variants }
13 |     } = theme;
14 | 
15 |     return {
16 |       root: {
17 |         ...variants.caption,
18 |         color: !darkMode ? text.dark.secondary : text.light.secondary,
19 |         maxWidth: pxToRem(120),
20 |         display: "inline-flex",
21 |         alignItems: "center",
22 |         flexShrink: "0",
23 |         cursor: "pointer",
24 |         transition: "color 360ms ease",
25 |         ...(direction === "rtl"
26 |           ? { marginLeft: spaces[1].rem }
27 |           : { marginRight: spaces[1].rem }),
28 |         "&:hover": {
29 |           color: !darkMode ? text.dark.primary : text.light.primary,
30 |           "& > $separator": {
31 |             color: !darkMode ? text.dark.primary : text.light.primary,
32 |             transform: "rotate(180deg)"
33 |           },
34 |           "& ~ $root > $separator": { transform: "rotate(180deg)" }
35 |         }
36 |       },
37 |       content: {
38 |         whiteSpace: "nowrap",
39 |         textOverflow: "ellipsis",
40 |         overflow: "hidden",
41 |         "& > a": { textDecoration: "none", color: "inherit" }
42 |       },
43 |       separator: {
44 |         ...asIconWrapper(16),
45 |         ...backfaceVisibilityFix,
46 |         color: !darkMode ? text.dark.secondary : text.light.secondary,
47 |         flexShrink: "0",
48 |         transition: "color 360ms ease, transform 360ms ease"
49 |       }
50 |     };
51 |   },
52 |   { name: "SonnatBreadcrumbItem" }
53 | );
54 | 
55 | export default useStyles;
56 | 


--------------------------------------------------------------------------------
/lib/Dialog/ActionBar/ActionBar.tsx:
--------------------------------------------------------------------------------
 1 | import c from "classnames";
 2 | import PropTypes from "prop-types";
 3 | import * as React from "react";
 4 | import type { MergeElementProps } from "../../typings";
 5 | import setRef from "../../utils/setRef";
 6 | import DialogContext from "../context";
 7 | import useStyles from "./styles";
 8 | 
 9 | interface DialogActionBarBaseProps {
10 |   /** The content of the component. */
11 |   children?: React.ReactNode;
12 |   /**
13 |    * Append to the classNames applied to the component so you can override or
14 |    * extend the styles.
15 |    */
16 |   className?: string;
17 | }
18 | 
19 | export type DialogActionBarProps = MergeElementProps<
20 |   "div",
21 |   DialogActionBarBaseProps
22 | >;
23 | 
24 | type Component = {
25 |   (props: DialogActionBarProps): React.ReactElement | null;
26 |   propTypes?: React.WeakValidationMap | undefined;
27 |   displayName?: string | undefined;
28 | };
29 | 
30 | const DialogActionBarBase = (
31 |   props: DialogActionBarProps,
32 |   ref: React.Ref
33 | ) => {
34 |   const { className, children, ...otherProps } = props;
35 | 
36 |   const classes = useStyles();
37 |   const context = React.useContext(DialogContext);
38 | 
39 |   return (
40 |     
{ 42 | if (ref) setRef(ref, node); 43 | if (node) context?.registerActionBar(node); 44 | }} 45 | className={c(classes.root, className, { 46 | [classes.withOverflow]: context?.hasOverflow 47 | })} 48 | {...otherProps} 49 | > 50 | {children} 51 |
52 | ); 53 | }; 54 | 55 | const DialogActionBar = React.forwardRef(DialogActionBarBase) as Component; 56 | 57 | DialogActionBar.propTypes = { 58 | children: PropTypes.node, 59 | className: PropTypes.string 60 | }; 61 | 62 | export default DialogActionBar; 63 | -------------------------------------------------------------------------------- /lib/Breadcrumb/Item/Item.tsx: -------------------------------------------------------------------------------- 1 | import c from "classnames"; 2 | import PropTypes from "prop-types"; 3 | import React from "react"; 4 | import { ChevronLeft, ChevronRight } from "../../internals/icons"; 5 | import useTheme from "../../styles/useTheme"; 6 | import type { MergeElementProps } from "../../typings"; 7 | import useStyles from "./styles"; 8 | 9 | interface BreadcrumbItemBaseProps { 10 | /** 11 | * The content of the breadcrumb item. 12 | */ 13 | children?: React.ReactNode; 14 | /** 15 | * Append to the classNames applied to the component so you can override or 16 | * extend the styles. 17 | */ 18 | className?: string; 19 | } 20 | 21 | export type BreadcrumbItemProps = MergeElementProps< 22 | "li", 23 | BreadcrumbItemBaseProps 24 | >; 25 | 26 | type Component = { 27 | (props: BreadcrumbItemProps): React.ReactElement | null; 28 | propTypes?: React.WeakValidationMap | undefined; 29 | displayName?: string | undefined; 30 | }; 31 | 32 | const BreadcrumbItemBase = ( 33 | props: BreadcrumbItemProps, 34 | ref: React.Ref 35 | ) => { 36 | const { className, children, ...otherProps } = props; 37 | 38 | const classes = useStyles(); 39 | const theme = useTheme(); 40 | 41 | const isRtl = theme.direction === "rtl"; 42 | 43 | return ( 44 |
  • 45 |
    {children}
    46 | 47 | {isRtl ? : } 48 | 49 |
  • 50 | ); 51 | }; 52 | 53 | const BreadcrumbItem = React.forwardRef(BreadcrumbItemBase) as Component; 54 | 55 | BreadcrumbItem.propTypes = { 56 | children: PropTypes.node, 57 | className: PropTypes.string 58 | }; 59 | 60 | export default BreadcrumbItem; 61 | -------------------------------------------------------------------------------- /lib/Card/Card.tsx: -------------------------------------------------------------------------------- 1 | import c from "classnames"; 2 | import PropTypes from "prop-types"; 3 | import * as React from "react"; 4 | import type { MergeElementProps } from "../typings"; 5 | import getVar from "../utils/getVar"; 6 | import useStyles from "./styles"; 7 | 8 | interface CardBaseProps { 9 | /** The content of the component. */ 10 | children?: React.ReactNode; 11 | /** 12 | * Append to the classNames applied to the component so you can override or 13 | * extend the styles. 14 | */ 15 | className?: string; 16 | /** 17 | * The variant of the card. 18 | * @default "elevated" 19 | */ 20 | variant?: "outlined" | "elevated"; 21 | } 22 | 23 | export type CardProps = MergeElementProps<"div", CardBaseProps>; 24 | 25 | type Component = { 26 | (props: CardProps): React.ReactElement | null; 27 | propTypes?: React.WeakValidationMap | undefined; 28 | displayName?: string | undefined; 29 | }; 30 | 31 | const allowedVariants = ["outlined", "elevated"] as const; 32 | 33 | const CardBase = (props: CardProps, ref: React.Ref) => { 34 | const { 35 | className, 36 | children, 37 | variant: variantProp = "elevated", 38 | ...otherProps 39 | } = props; 40 | 41 | const classes = useStyles(); 42 | 43 | const variant = getVar( 44 | variantProp, 45 | "elevated", 46 | !allowedVariants.includes(variantProp) 47 | ); 48 | 49 | return ( 50 |
    55 | {children} 56 |
    57 | ); 58 | }; 59 | 60 | const Card = React.forwardRef(CardBase) as Component; 61 | 62 | Card.propTypes = { 63 | children: PropTypes.node, 64 | className: PropTypes.string, 65 | variant: PropTypes.oneOf(allowedVariants) 66 | }; 67 | 68 | export default Card; 69 | -------------------------------------------------------------------------------- /examples/with-nextjs/pages/_app.js: -------------------------------------------------------------------------------- 1 | import CssBaseline from "@sonnat/ui/CssBaseline"; 2 | import makeStyles from "@sonnat/ui/styles/makeStyles"; 3 | import SonnatInitializer from "@sonnat/ui/styles/SonnatInitializer"; 4 | import Head from "next/head"; 5 | import * as React from "react"; 6 | import theme from "../theme"; 7 | 8 | const googleFontFamily = 9 | "https://fonts.googleapis.com/css2?" + 10 | "family=Roboto+Mono:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&" + 11 | "family=Roboto:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&" + 12 | "display=swap"; 13 | 14 | const useGlobalStyles = makeStyles( 15 | { 16 | "@global": { 17 | "html, body": { scrollBehavior: "smooth" }, 18 | img: { verticalAlign: "middle" } 19 | } 20 | }, 21 | { name: "GlobalStyles" } 22 | ); 23 | 24 | export default function App(props) { 25 | // eslint-disable-next-line react/prop-types 26 | const { Component: Page, pageProps } = props; 27 | 28 | useGlobalStyles(); 29 | 30 | React.useEffect(() => { 31 | const sonnatServerStyles = document.getElementById("sonnat-jss-ssr"); 32 | 33 | if (sonnatServerStyles) 34 | sonnatServerStyles.parentElement?.removeChild(sonnatServerStyles); 35 | }, []); 36 | 37 | return ( 38 | 39 | 40 | Sonnat App 41 | 46 | 47 | 48 | 49 |
    50 | 51 | 52 |
    53 |
    54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /lib/styles/createMixins.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { Typography } from "./createTypography"; 3 | 4 | export interface Mixins extends Record { 5 | disableUserSelect: () => React.CSSProperties; 6 | asIconWrapper: (size: number) => React.CSSProperties; 7 | } 8 | 9 | export interface Options { 10 | pxToRem: Typography["pxToRem"]; 11 | } 12 | 13 | const createMixins = >( 14 | mixinsInput?: T, 15 | options?: Options 16 | ): T & Mixins => { 17 | const { pxToRem } = options || {}; 18 | 19 | const _disableUserSelect: Mixins["disableUserSelect"] = () => ({ 20 | WebkitUserSelect: "none", 21 | MozUserSelect: "none", 22 | MsUserSelect: "none", 23 | KhtmlUserSelect: "none", 24 | userSelect: "none", 25 | WebkitTouchCallout: "none", 26 | MsTouchAction: "pan-y", 27 | touchAction: "pan-y", 28 | WebkitTapHighlightColor: "transparent" 29 | }); 30 | 31 | const _asIconWrapper = (size: number) => { 32 | const sizing = 33 | typeof size !== "undefined" 34 | ? { 35 | width: pxToRem ? pxToRem(size) : size, 36 | height: pxToRem ? pxToRem(size) : size, 37 | minWidth: pxToRem ? pxToRem(size) : size, 38 | minHeight: pxToRem ? pxToRem(size) : size, 39 | fontSize: pxToRem ? pxToRem(size) : size 40 | } 41 | : {}; 42 | 43 | return { 44 | display: "flex", 45 | alignItems: "center", 46 | justifyContent: "center", 47 | ...sizing 48 | }; 49 | }; 50 | 51 | const { 52 | disableUserSelect = _disableUserSelect, 53 | asIconWrapper = _asIconWrapper, 54 | ...other 55 | } = mixinsInput || {}; 56 | 57 | return { 58 | disableUserSelect, 59 | asIconWrapper, 60 | ...other 61 | } as T & Mixins; 62 | }; 63 | 64 | export default createMixins; 65 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { 2 | Head, 3 | Html, 4 | Main, 5 | NextScript, 6 | DocumentContext 7 | } from "next/document"; 8 | import ServerStyleSheets from "../lib/styles/ServerStyleSheets"; 9 | import * as React from "react"; 10 | 11 | export default class MyDocument extends Document { 12 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 13 | static async getInitialProps(ctx: DocumentContext) { 14 | const sheets = new ServerStyleSheets(); 15 | 16 | const originalRenderPage = ctx.renderPage; 17 | 18 | ctx.renderPage = () => 19 | originalRenderPage({ 20 | enhanceApp: App => props => sheets.collect() 21 | }); 22 | 23 | const initialProps = await Document.getInitialProps(ctx); 24 | 25 | const css = sheets.toString(); 26 | const sheetId = sheets.getStyleElementId(); 27 | 28 | return { 29 | ...initialProps, 30 | // Styles fragment is rendered after the app and page rendering finish. 31 | styles: [ 32 | ...React.Children.toArray(initialProps.styles), 33 | 36 | ] 37 | }; 38 | } 39 | 40 | render(): JSX.Element { 41 | return ( 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
    54 | 55 | 56 | 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/styles/generateColorSwatch.ts: -------------------------------------------------------------------------------- 1 | import Color from "color"; 2 | 3 | export type ColorSwatch = Record< 4 | 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900, 5 | string 6 | >; 7 | 8 | export const BASE_LUMINANCE = 50; 9 | export const BASE_LEVEL = 600; 10 | export const BASE_LEVEL_INDEX = 6; 11 | 12 | export const LUMINANCE_OFFSET_FROM_BASE = [ 13 | 45, 40, 36, 27, 18, 9, 0, -9, -18, -27 14 | ] as const; 15 | 16 | const minmax = (value: number) => Math.max(Math.min(value, 0.99), 0.01); 17 | 18 | const calcLevelLuminance = (levelIndex: number) => 19 | minmax((BASE_LUMINANCE + LUMINANCE_OFFSET_FROM_BASE[levelIndex]) / 100); 20 | 21 | const calcSaturation = ( 22 | luminance: number, 23 | factor = -1.8, 24 | adjust = 1.8, 25 | shift = 0.4 26 | ) => minmax(luminance ** 2 * factor + luminance * adjust + shift); 27 | 28 | export const generateColorLevel = ( 29 | color: string | Color, 30 | levelIndex: number, 31 | saturationOffset = 0 32 | ) => { 33 | const colorHsl = new Color(color).hsl(); 34 | 35 | const hue = colorHsl.hue() || 0; 36 | const luminance = calcLevelLuminance(levelIndex); 37 | const saturation = calcSaturation(luminance); 38 | 39 | const newColor = new Color(colorHsl) 40 | .hue(hue) 41 | .saturationl((saturation + saturationOffset) * 100) 42 | .lightness(luminance * 100); 43 | 44 | return newColor.hex().toString(); 45 | }; 46 | 47 | // Based on https://hypejunction.github.io/color-wizard/ 48 | // And https://uxplanet.org/designing-systematic-colors-b5d2605b15c 49 | const generateColorSwatch = (color: string | Color, saturationOffset = 0) => 50 | LUMINANCE_OFFSET_FROM_BASE.reduce( 51 | (swatch, _, index) => ({ 52 | ...swatch, 53 | [index === 0 ? 50 : index * 100]: generateColorLevel( 54 | color, 55 | index, 56 | saturationOffset 57 | ) 58 | }), 59 | {} as ColorSwatch 60 | ); 61 | 62 | export default generateColorSwatch; 63 | -------------------------------------------------------------------------------- /lib/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { default as camelCase } from "./camelCase"; 2 | export { default as clamp } from "./clamp"; 3 | export { default as createSvgIcon } from "./createSvgIcon"; 4 | export { default as deepMerge } from "./deepMerge"; 5 | export { default as detectScrollBarWidth } from "./detectScrollBarWidth"; 6 | export * from "./domUtils"; 7 | export { default as generateUniqueString } from "./generateUniqueString"; 8 | export { default as getOffsetFromWindow } from "./getOffsetFromWindow"; 9 | export { default as getSingleChild } from "./getSingleChild"; 10 | export { default as getVar } from "./getVar"; 11 | export { default as HTMLElementType } from "./HTMLElementType"; 12 | export * from "./is"; 13 | export { default as isUndef } from "./isUndef"; 14 | export { default as map } from "./map"; 15 | export { default as onNextFrame } from "./onNextFrame"; 16 | export * from "./scrollLeft"; 17 | export { default as setRef } from "./setRef"; 18 | export { default as useConstantProp } from "./useConstantProp"; 19 | export { default as useControlledProp } from "./useControlledProp"; 20 | export { default as useEventCallback } from "./useEventCallback"; 21 | export { default as useEventListener } from "./useEventListener"; 22 | export { default as useForkedRefs } from "./useForkedRefs"; 23 | export { default as useGetLatest } from "./useGetLatest"; 24 | export { default as useId } from "./useId"; 25 | export { default as useIntersection } from "./useIntersection"; 26 | export { default as useIsFocusVisible } from "./useIsFocusVisible"; 27 | export { default as useIsMounted } from "./useIsMounted"; 28 | export { default as useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect"; 29 | export { default as useOnChange } from "./useOnChange"; 30 | export { default as usePreviousValue } from "./usePreviousValue"; 31 | export { default as useResizeSensor } from "./useResizeSensor"; 32 | export { default as useSyncEffect } from "./useSyncEffect"; 33 | -------------------------------------------------------------------------------- /lib/styles/createSpacings.ts: -------------------------------------------------------------------------------- 1 | import { type Typography } from "./createTypography"; 2 | 3 | export const DEFAULT_SPACER_PX = 16; 4 | 5 | export interface SpacingsInput { 6 | spacer?: number; 7 | } 8 | 9 | const createSpacings = ( 10 | spacingInput?: SpacingsInput, 11 | options?: { pxToRem: Typography["pxToRem"] } 12 | ) => { 13 | const { spacer = DEFAULT_SPACER_PX } = spacingInput || {}; 14 | 15 | const { 16 | pxToRem = (size: number) => 17 | typeof size === "number" && !isNaN(size) ? `${size / 16}rem` : "" 18 | } = options || {}; 19 | 20 | return { 21 | gutter: pxToRem(spacer), 22 | spacer: { rem: pxToRem(spacer), px: spacer }, 23 | spaces: { 24 | /** Equivalent to `2px`. */ 25 | 0: { rem: pxToRem(spacer * 0.125), px: spacer * 0.125 }, 26 | /** Equivalent to `4px`. */ 27 | 1: { rem: pxToRem(spacer * 0.25), px: spacer * 0.25 }, 28 | /** Equivalent to `6px`. */ 29 | 2: { rem: pxToRem(spacer * 0.375), px: spacer * 0.375 }, 30 | /** Equivalent to `8px`. */ 31 | 3: { rem: pxToRem(spacer * 0.5), px: spacer * 0.5 }, 32 | /** Equivalent to `10px`. */ 33 | 4: { rem: pxToRem(spacer * 0.625), px: spacer * 0.625 }, 34 | /** Equivalent to `12px`. */ 35 | 5: { rem: pxToRem(spacer * 0.75), px: spacer * 0.75 }, 36 | /** Equivalent to `14px`. */ 37 | 6: { rem: pxToRem(spacer * 0.875), px: spacer * 0.875 }, 38 | /** Equivalent to `16px`. */ 39 | 7: { rem: pxToRem(spacer), px: spacer }, 40 | /** Equivalent to `18px`. */ 41 | 8: { rem: pxToRem(spacer * 1.125), px: spacer * 1.125 }, 42 | /** Equivalent to `20px`. */ 43 | 9: { rem: pxToRem(spacer * 1.25), px: spacer * 1.25 }, 44 | /** Equivalent to `24px`. */ 45 | 10: { rem: pxToRem(spacer * 1.5), px: spacer * 1.5 } 46 | } 47 | }; 48 | }; 49 | 50 | export type Spacings = ReturnType; 51 | 52 | export default createSpacings; 53 | -------------------------------------------------------------------------------- /examples/with-basic-ssr/server.js: -------------------------------------------------------------------------------- 1 | import CssBaseline from "@sonnat/ui/CssBaseline"; 2 | import ServerStyleSheets from "@sonnat/ui/styles/ServerStyleSheets"; 3 | import SonnatInitializer from "@sonnat/ui/styles/SonnatInitializer"; 4 | import express from "express"; 5 | import React from "react"; 6 | import ReactDOMServer from "react-dom/server"; 7 | import App from "./App"; 8 | import theme from "./theme"; 9 | 10 | function renderFullPage(html, css, styleElementId) { 11 | return ` 12 | 13 | 14 | Sonnat Dev 15 | 16 | 17 | 18 | 19 |
    ${html}
    20 | 21 | 22 | 23 | `; 24 | } 25 | 26 | async function handleRender(req, res) { 27 | const sheets = new ServerStyleSheets(); 28 | 29 | // Render the component to a string. 30 | const html = ReactDOMServer.renderToString( 31 | sheets.collect( 32 | 33 | 34 | 35 | 36 | ) 37 | ); 38 | 39 | // Grab the CSS from our sheets. 40 | const css = sheets.toString(); 41 | 42 | // Send the rendered page back to the client. 43 | res.send(renderFullPage(html, css, sheets.getStyleElementId())); 44 | } 45 | 46 | const app = express(); 47 | 48 | app.use("/build", express.static("build")); 49 | 50 | // This is fired every time the server-side receives a request. 51 | app.use(handleRender); 52 | 53 | app.listen(3000, () => { 54 | // eslint-disable-next-line no-console 55 | console.log( 56 | [ 57 | `Server is successfully running as ${process.env.NODE_ENV}.`, 58 | `You can now view it in the browser at http://localhost:3000` 59 | ].join("\n") 60 | ); 61 | }); 62 | -------------------------------------------------------------------------------- /lib/Menu/Item/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | darkMode, 7 | spacings: { spaces }, 8 | colors: { text, ...colors }, 9 | mixins: { disableUserSelect }, 10 | typography: { pxToRem, variants } 11 | } = theme; 12 | 13 | return { 14 | root: { 15 | ...variants.bodySmall, 16 | ...disableUserSelect(), 17 | color: !darkMode ? text.dark.secondary : text.light.secondary, 18 | width: "100%", 19 | flexShrink: "0", 20 | paddingRight: spaces[7].rem, 21 | paddingLeft: spaces[7].rem, 22 | display: "flex", 23 | alignItems: "center", 24 | minHeight: pxToRem(40), 25 | cursor: "pointer", 26 | overflow: "hidden", 27 | outline: "none", 28 | transition: "color 240ms ease, background-color 240ms ease", 29 | "&:hover": { 30 | backgroundColor: !darkMode 31 | ? colors.createBlackColor({ alpha: 0.04 }, true, darkMode) 32 | : colors.createWhiteColor({ alpha: 0.04 }, true, darkMode) 33 | }, 34 | "&:active": { 35 | color: !darkMode ? colors.primary.origin : colors.primary.light, 36 | outline: "none" 37 | } 38 | }, 39 | focused: { 40 | backgroundColor: !darkMode 41 | ? colors.createBlackColor({ alpha: 0.04 }, true, darkMode) 42 | : colors.createWhiteColor({ alpha: 0.04 }, true, darkMode) 43 | }, 44 | disabled: { 45 | pointerEvents: "none", 46 | color: !darkMode ? text.dark.disabled : text.light.disabled 47 | }, 48 | hide: { display: "none" }, 49 | dense: { 50 | fontSize: variants.caption.fontSize, 51 | lineHeight: variants.caption.lineHeight, 52 | minHeight: pxToRem(32) 53 | } 54 | }; 55 | }, 56 | { name: "SonnatMenuItem" } 57 | ); 58 | 59 | export default useStyles; 60 | -------------------------------------------------------------------------------- /lib/Breadcrumb/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | direction, 7 | darkMode, 8 | colors: { text }, 9 | spacings: { spaces }, 10 | typography: { pxToRem, fontFamily } 11 | } = theme; 12 | 13 | return { 14 | root: { overflow: "hidden" }, 15 | list: { 16 | direction, 17 | fontFamily: fontFamily[direction], 18 | padding: "0", 19 | margin: "0", 20 | listStyle: "none", 21 | display: "flex", 22 | height: pxToRem(24), 23 | flexWrap: "nowrap", 24 | overflowX: "auto", 25 | overflowY: "hidden", 26 | width: "100%", 27 | position: "relative", 28 | scrollBehavior: "smooth", 29 | WebkitOverflowScrolling: "touch", 30 | "& > $item:last-child": { 31 | margin: 0, 32 | color: !darkMode ? text.dark.hint : text.light.hint, 33 | pointerEvents: "none", 34 | "& > [role='separator']": { display: "none" } 35 | } 36 | }, 37 | onlyPreviousStep: { 38 | "& > $item:not(:nth-last-child(2))": { display: "none" }, 39 | "& > $item:nth-last-child(2)": { 40 | margin: 0, 41 | "&:hover": { 42 | "& > [role='separator']": { 43 | color: !darkMode ? text.dark.primary : text.light.primary, 44 | transform: "rotate(180deg)" 45 | }, 46 | "& ~ $item > [role='separator']": { transform: "rotate(0)" } 47 | }, 48 | "& > [role='separator']": { 49 | order: "-1", 50 | transform: "rotate(180deg)", 51 | ...(direction === "rtl" 52 | ? { marginLeft: spaces[1].rem } 53 | : { marginRight: spaces[1].rem }) 54 | } 55 | } 56 | }, 57 | item: {} 58 | }; 59 | }, 60 | { name: "SonnatBreadcrumb" } 61 | ); 62 | 63 | export default useStyles; 64 | -------------------------------------------------------------------------------- /lib/Dialog/Body/Body.tsx: -------------------------------------------------------------------------------- 1 | import c from "classnames"; 2 | import PropTypes from "prop-types"; 3 | import * as React from "react"; 4 | import type { MergeElementProps } from "../../typings"; 5 | import setRef from "../../utils/setRef"; 6 | import DialogContext from "../context"; 7 | import useStyles from "./styles"; 8 | 9 | interface DialogBodyBaseProps { 10 | /** The content of the component. */ 11 | children?: React.ReactNode; 12 | /** 13 | * Append to the classNames applied to the component so you can override or 14 | * extend the styles. 15 | */ 16 | className?: string; 17 | } 18 | 19 | export type DialogBodyProps = MergeElementProps<"div", DialogBodyBaseProps>; 20 | 21 | type Component = { 22 | (props: DialogBodyProps): React.ReactElement | null; 23 | propTypes?: React.WeakValidationMap | undefined; 24 | displayName?: string | undefined; 25 | }; 26 | 27 | const DialogBodyBase = ( 28 | props: DialogBodyProps, 29 | ref: React.Ref 30 | ) => { 31 | const { className, children, style = {}, ...otherProps } = props; 32 | const { height: heightStyle, ...styles } = style; 33 | 34 | const classes = useStyles(); 35 | const context = React.useContext(DialogContext); 36 | 37 | const height = context?.hasOverflow 38 | ? context?.bodyHeight || heightStyle || "auto" 39 | : "auto"; 40 | 41 | return ( 42 |
    { 44 | if (ref) setRef(ref, node); 45 | if (node) context?.registerBody(node); 46 | }} 47 | style={{ ...styles, height }} 48 | className={c(classes.root, className, { 49 | [classes.withOverflow]: context?.hasOverflow 50 | })} 51 | {...otherProps} 52 | > 53 | {children} 54 |
    55 | ); 56 | }; 57 | const DialogBody = React.forwardRef(DialogBodyBase) as Component; 58 | 59 | DialogBody.propTypes = { 60 | children: PropTypes.node, 61 | className: PropTypes.string, 62 | style: PropTypes.object 63 | }; 64 | 65 | export default DialogBody; 66 | -------------------------------------------------------------------------------- /lib/utils/is.ts: -------------------------------------------------------------------------------- 1 | import { getWindow } from "./domUtils"; 2 | 3 | declare global { 4 | interface Window { 5 | HTMLElement: typeof HTMLElement; 6 | Element: typeof Element; 7 | Node: typeof Node; 8 | ShadowRoot: typeof ShadowRoot; 9 | } 10 | } 11 | 12 | export const isWindow = string }>( 13 | input: unknown 14 | ): input is Window => 15 | !input ? false : (input as T).toString?.() === "[object Window]"; 16 | 17 | export const isElement = (input: unknown): input is Element => 18 | input instanceof getWindow(input as Node).Element; 19 | 20 | export const isHTMLElement = (input: unknown): input is HTMLElement => 21 | input instanceof getWindow(input as Node).HTMLElement; 22 | 23 | export const isNode = (input: unknown): input is Node => 24 | input instanceof getWindow(input as Node).Node; 25 | 26 | export const isShadowRoot = (node: Node): node is ShadowRoot => 27 | node instanceof getWindow(node).ShadowRoot || node instanceof ShadowRoot; 28 | 29 | export const isOverflowElement = (element: HTMLElement): boolean => { 30 | const { overflow, overflowX, overflowY } = 31 | getWindow(element).getComputedStyle(element); 32 | 33 | return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX); 34 | }; 35 | 36 | /** 37 | * @see https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block 38 | */ 39 | export const isContainingBlock = (element: Element): boolean => { 40 | const window = getWindow(element); 41 | 42 | const css = window.getComputedStyle(element); 43 | const isFirefox = window.navigator.userAgent 44 | .toLowerCase() 45 | .includes("firefox"); 46 | 47 | return ( 48 | css.transform !== "none" || 49 | css.perspective !== "none" || 50 | css.contain === "paint" || 51 | ["transform", "perspective"].includes(css.willChange) || 52 | (isFirefox && css.willChange === "filter") || 53 | (isFirefox && (css.filter ? css.filter !== "none" : false)) 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /lib/FormControl/Feedback/Feedback.tsx: -------------------------------------------------------------------------------- 1 | import c from "classnames"; 2 | import PropTypes from "prop-types"; 3 | import * as React from "react"; 4 | import type { MergeElementProps } from "../../typings"; 5 | import useFormControl from "../useFormControl"; 6 | import useStyles from "./styles"; 7 | 8 | interface FormControlFeedbackBaseProps { 9 | /** The content of the component. */ 10 | children?: React.ReactNode; 11 | /** 12 | * Append to the classNames applied to the component so you can override or 13 | * extend the styles. 14 | */ 15 | className?: string; 16 | /** 17 | * If `true`, the feedback will indicate invalid input. 18 | * @default false 19 | */ 20 | hasError?: boolean; 21 | } 22 | 23 | export type FormControlFeedbackProps = MergeElementProps< 24 | "div", 25 | FormControlFeedbackBaseProps 26 | >; 27 | 28 | type Component = { 29 | (props: FormControlFeedbackProps): React.ReactElement | null; 30 | propTypes?: React.WeakValidationMap | undefined; 31 | displayName?: string | undefined; 32 | }; 33 | 34 | const FormControlFeedbackBase = ( 35 | props: FormControlFeedbackProps, 36 | ref: React.Ref 37 | ) => { 38 | const { children, className, hasError, ...otherProps } = props; 39 | 40 | const classes = useStyles(); 41 | const formControl = useFormControl(); 42 | 43 | const controlProps = { 44 | hasError: formControl ? formControl.hasError : hasError 45 | }; 46 | 47 | return ( 48 |
    55 | {children} 56 |
    57 | ); 58 | }; 59 | 60 | const FormControlFeedback = React.forwardRef( 61 | FormControlFeedbackBase 62 | ) as Component; 63 | 64 | FormControlFeedback.propTypes = { 65 | children: PropTypes.node, 66 | className: PropTypes.string, 67 | hasError: PropTypes.bool 68 | }; 69 | 70 | export default FormControlFeedback; 71 | -------------------------------------------------------------------------------- /lib/Divider/styles.ts: -------------------------------------------------------------------------------- 1 | import makeStyles from "../styles/makeStyles"; 2 | 3 | const useStyles = makeStyles( 4 | theme => { 5 | const { 6 | direction, 7 | darkMode, 8 | colors: { text, divider }, 9 | spacings: { spaces }, 10 | typography: { pxToRem, fontWeight, fontFamily } 11 | } = theme; 12 | 13 | return { 14 | root: { 15 | direction, 16 | fontFamily: fontFamily[direction], 17 | position: "relative", 18 | margin: "0", 19 | border: "none", 20 | flexShrink: "0", 21 | "&:not($vertical):not($dotted)": { 22 | width: "100%", 23 | height: 1, 24 | backgroundColor: !darkMode ? divider.dark : divider.light 25 | } 26 | }, 27 | spaced: { 28 | "&:not($vertical)": { 29 | marginTop: spaces[7].rem, 30 | marginBottom: spaces[7].rem 31 | }, 32 | "&$vertical": { 33 | marginLeft: spaces[7].rem, 34 | marginRight: spaces[7].rem 35 | } 36 | }, 37 | dotted: { 38 | boxSizing: "content-box", 39 | height: "0", 40 | textAlign: "center", 41 | fontSize: pxToRem(28), 42 | lineHeight: "1.14285714", 43 | fontWeight: fontWeight.regular, 44 | marginTop: spaces[10].rem, 45 | marginBottom: spaces[3].rem, 46 | border: "none", 47 | "&:after": { 48 | content: '"..."', 49 | display: "inline-block", 50 | marginLeft: "0.6em", 51 | position: "relative", 52 | color: !darkMode ? text.dark.primary : text.light.primary, 53 | top: pxToRem(-spaces[10].px), 54 | letterSpacing: "0.6em" 55 | } 56 | }, 57 | vertical: { 58 | height: "auto", 59 | width: 1, 60 | backgroundColor: !darkMode ? divider.dark : divider.light, 61 | alignSelf: "stretch" 62 | } 63 | }; 64 | }, 65 | { name: "SonnatDivider" } 66 | ); 67 | 68 | export default useStyles; 69 | -------------------------------------------------------------------------------- /lib/Dialog/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import c from "classnames"; 2 | import PropTypes from "prop-types"; 3 | import * as React from "react"; 4 | import type { MergeElementProps } from "../../typings"; 5 | import Text from "../../Text"; 6 | import setRef from "../../utils/setRef"; 7 | import DialogContext from "../context"; 8 | import useStyles from "./styles"; 9 | 10 | interface DialogHeaderBaseProps { 11 | /** The content of the component. */ 12 | children?: React.ReactNode; 13 | /** 14 | * Append to the classNames applied to the component so you can override or 15 | * extend the styles. 16 | */ 17 | className?: string; 18 | /** The title to display in the header. */ 19 | title: string; 20 | } 21 | 22 | export type DialogHeaderProps = MergeElementProps<"div", DialogHeaderBaseProps>; 23 | 24 | type Component = { 25 | (props: DialogHeaderProps): React.ReactElement | null; 26 | propTypes?: React.WeakValidationMap | undefined; 27 | displayName?: string | undefined; 28 | }; 29 | 30 | const DialogHeaderBase = ( 31 | props: DialogHeaderProps, 32 | ref: React.Ref 33 | ) => { 34 | const { className, children, title, ...otherProps } = props; 35 | 36 | const classes = useStyles(); 37 | 38 | const context = React.useContext(DialogContext); 39 | 40 | return ( 41 |
    { 43 | if (ref) setRef(ref, node); 44 | if (node) context?.registerHeader(node); 45 | }} 46 | className={c(classes.root, className, { 47 | [classes.withOverflow]: context?.hasOverflow 48 | })} 49 | {...otherProps} 50 | > 51 | 52 | {title} 53 | 54 | {children} 55 |
    56 | ); 57 | }; 58 | 59 | const DialogHeader = React.forwardRef(DialogHeaderBase) as Component; 60 | 61 | DialogHeader.propTypes = { 62 | children: PropTypes.node, 63 | className: PropTypes.string, 64 | title: PropTypes.string.isRequired 65 | }; 66 | 67 | export default DialogHeader; 68 | -------------------------------------------------------------------------------- /lib/FormControl/Description/Description.tsx: -------------------------------------------------------------------------------- 1 | import c from "classnames"; 2 | import PropTypes from "prop-types"; 3 | import * as React from "react"; 4 | import type { MergeElementProps } from "../../typings"; 5 | import useFormControl from "../useFormControl"; 6 | import useStyles from "./styles"; 7 | 8 | interface FormControlDescriptionBaseProps { 9 | /** The content of the component. */ 10 | children?: React.ReactNode; 11 | /** 12 | * Append to the classNames applied to the component so you can override or 13 | * extend the styles. 14 | */ 15 | className?: string; 16 | /** 17 | * If `true`, the description will appear as disabled. 18 | * @default false 19 | */ 20 | disabled?: boolean; 21 | } 22 | 23 | export type FormControlDescriptionProps = MergeElementProps< 24 | "p", 25 | FormControlDescriptionBaseProps 26 | >; 27 | 28 | type Component = { 29 | (props: FormControlDescriptionProps): React.ReactElement | null; 30 | propTypes?: React.WeakValidationMap | undefined; 31 | displayName?: string | undefined; 32 | }; 33 | 34 | const FormControlDescriptionBase = ( 35 | props: FormControlDescriptionProps, 36 | ref: React.Ref 37 | ) => { 38 | const { children, className, disabled = false, ...otherProps } = props; 39 | 40 | const classes = useStyles(); 41 | const formControl = useFormControl(); 42 | 43 | const controlProps = { 44 | disabled: formControl ? formControl.disabled : disabled 45 | }; 46 | 47 | return ( 48 |

    55 | {children} 56 |

    57 | ); 58 | }; 59 | 60 | const FormControlDescription = React.forwardRef( 61 | FormControlDescriptionBase 62 | ) as Component; 63 | 64 | FormControlDescription.propTypes = { 65 | children: PropTypes.node, 66 | className: PropTypes.string, 67 | disabled: PropTypes.bool 68 | }; 69 | 70 | export default FormControlDescription; 71 | -------------------------------------------------------------------------------- /examples/with-cra/src/components/SonnatSvgLogo/SonnatSvgLogo.js: -------------------------------------------------------------------------------- 1 | import Icon from "@sonnat/ui/Icon"; 2 | import * as React from "react"; 3 | 4 | const componentName = "SonnatSvgLogo"; 5 | 6 | const SonnatSvgLogo = props => { 7 | return ( 8 | 9 | 10 | 15 | 20 | 25 | 26 | ); 27 | }; 28 | 29 | SonnatSvgLogo.displayName = componentName; 30 | 31 | export default SonnatSvgLogo; 32 | -------------------------------------------------------------------------------- /examples/with-nextjs/components/SonnatSvgLogo/SonnatSvgLogo.js: -------------------------------------------------------------------------------- 1 | import Icon from "@sonnat/ui/Icon"; 2 | import * as React from "react"; 3 | 4 | const componentName = "SonnatSvgLogo"; 5 | 6 | const SonnatSvgLogo = props => { 7 | return ( 8 | 9 | 10 | 15 | 20 | 25 | 26 | ); 27 | }; 28 | 29 | SonnatSvgLogo.displayName = componentName; 30 | 31 | export default SonnatSvgLogo; 32 | -------------------------------------------------------------------------------- /examples/with-basic-ssr/components/SonnatSvgLogo/SonnatSvgLogo.js: -------------------------------------------------------------------------------- 1 | import Icon from "@sonnat/ui/Icon"; 2 | import * as React from "react"; 3 | 4 | const componentName = "SonnatSvgLogo"; 5 | 6 | const SonnatSvgLogo = props => { 7 | return ( 8 | 9 | 10 | 15 | 20 | 25 | 26 | ); 27 | }; 28 | 29 | SonnatSvgLogo.displayName = componentName; 30 | 31 | export default SonnatSvgLogo; 32 | -------------------------------------------------------------------------------- /lib/Select/Option/Option.tsx: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import * as React from "react"; 3 | import type { MergeElementProps } from "../../typings"; 4 | 5 | interface OptionBaseProps { 6 | /** The content of the item. */ 7 | children?: React.ReactNode; 8 | /** 9 | * Append to the classNames applied to the component so you can override or 10 | * extend the styles. 11 | */ 12 | className?: string; 13 | /** 14 | * The value of the option. 15 | */ 16 | value: string; 17 | /** 18 | * If a label was provided, 19 | * the select component will use it as the item's display value. 20 | * 21 | * It is mandatory to use it when the `children` prop contains 22 | * not just the label but also some extra HTMLElements as well. 23 | */ 24 | label?: string; 25 | /** 26 | * If `true`, the item will be disabled. 27 | * @default false 28 | */ 29 | disabled?: boolean; 30 | /** 31 | * The Callback fires when the item has been clicked. 32 | */ 33 | onClick?: (event: React.MouseEvent) => void; 34 | /** 35 | * The Callback fires when the item has received focus. 36 | */ 37 | onFocus?: (event: React.FocusEvent) => void; 38 | /** 39 | * The Callback fires when the item has lost focus. 40 | */ 41 | onBlur?: (event: React.FocusEvent) => void; 42 | } 43 | 44 | export type SelectOptionProps = MergeElementProps<"div", OptionBaseProps>; 45 | 46 | type Component = { 47 | (props: SelectOptionProps): React.ReactElement | null; 48 | propTypes?: React.WeakValidationMap | undefined; 49 | displayName?: string | undefined; 50 | }; 51 | 52 | const SelectOptionBase = (props: SelectOptionProps) => <>{props.children}; 53 | 54 | const SelectOption = SelectOptionBase as Component; 55 | 56 | SelectOption.propTypes = { 57 | children: PropTypes.node, 58 | className: PropTypes.string, 59 | label: PropTypes.string, 60 | value: PropTypes.string.isRequired, 61 | onClick: PropTypes.func, 62 | onFocus: PropTypes.func, 63 | onBlur: PropTypes.func, 64 | disabled: PropTypes.bool 65 | }; 66 | 67 | export default SelectOption; 68 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from "next/app"; 2 | import Head from "next/head"; 3 | import * as React from "react"; 4 | import CssBaseline from "../lib/CssBaseline"; 5 | import { makeStyles, SonnatInitializer, useDarkMode } from "../lib/styles"; 6 | 7 | interface IAppContext { 8 | setIsDarkMode: React.Dispatch>; 9 | } 10 | 11 | export const AppContext = React.createContext( 12 | undefined 13 | ); 14 | 15 | const googleFontFamily = 16 | "https://fonts.googleapis.com/css2?" + 17 | "family=Roboto+Mono:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&" + 18 | "family=Roboto:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&" + 19 | "display=swap"; 20 | 21 | const useGlobalStyles = makeStyles( 22 | { 23 | "@global": { 24 | "html, body": { scrollBehavior: "smooth" }, 25 | img: { verticalAlign: "middle" } 26 | } 27 | }, 28 | { name: "GlobalStyles" } 29 | ); 30 | 31 | const App = (props: AppProps) => { 32 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 33 | const { Component: Page, pageProps } = props; 34 | 35 | useGlobalStyles(); 36 | 37 | React.useEffect(() => { 38 | const sonnatServerStyles = document.getElementById("sonnat-jss-ssr"); 39 | 40 | if (sonnatServerStyles) 41 | sonnatServerStyles.parentElement?.removeChild(sonnatServerStyles); 42 | }, []); 43 | 44 | const [isDarkMode, setIsDarkMode] = React.useState(false); 45 | 46 | const newTheme = useDarkMode(isDarkMode); 47 | 48 | return ( 49 | 50 | 51 | 56 | 57 | 58 | 59 |
    60 | 61 | 62 | 63 | 64 |
    65 |
    66 | ); 67 | }; 68 | 69 | export default App; 70 | -------------------------------------------------------------------------------- /lib/styles/ServerStyleSheets/ServerStyleSheets.tsx: -------------------------------------------------------------------------------- 1 | import { SheetsRegistry } from "jss"; 2 | import React from "react"; 3 | import type { 4 | AnyObject, 5 | EmptyIntersectionObject, 6 | GenerateClassName 7 | } from "../../typings"; 8 | import createGenerateClassName from "../createGenerateClassName"; 9 | import ServerProvider from "./Provider"; 10 | 11 | export interface ServerStyleSheetsOptions { 12 | /** The id attribute for