(
15 | formConfig,
16 | );
17 |
18 | return (
19 |
20 | {errors.name}
21 |
29 |
30 | );
31 | }
32 |
33 | return render(
34 |
35 |
36 | ,
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/packages/forms/src/enhanced/FormikEnhanced.tsx:
--------------------------------------------------------------------------------
1 | import { FormikProvider, FormikValues } from 'formik';
2 | import { ReactElement, ReactNode } from 'react';
3 | import {
4 | FormikContextTypeEnhanced,
5 | FormikEnhancedConfig,
6 | useFormikEnhanced,
7 | } from './useFormikEnhanced';
8 |
9 | export interface FormikEnhancedProps
10 | extends FormikEnhancedConfig {
11 | children?:
12 | | ReactNode
13 | | ((formik: FormikContextTypeEnhanced) => ReactNode);
14 | }
15 |
16 | export function FormikEnhanced({
17 | children,
18 | ...config
19 | }: FormikEnhancedProps): null | ReactElement {
20 | const formik = useFormikEnhanced(config);
21 |
22 | return (
23 |
24 | {typeof children === 'function' ? children(formik) : children}
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/packages/forms/src/enhanced/FormsProvider.tsx:
--------------------------------------------------------------------------------
1 | import { FormikErrors } from 'formik';
2 | import {
3 | createContext,
4 | PropsWithChildren,
5 | ReactElement,
6 | useContext,
7 | useMemo,
8 | } from 'react';
9 |
10 | export interface FormsContext {
11 | getFormErrors?: (error: unknown) => FormikErrors;
12 | }
13 |
14 | const Context = createContext({});
15 |
16 | export function useFormsContext(): FormsContext {
17 | return useContext(Context);
18 | }
19 |
20 | export function FormsProvider({
21 | children,
22 | getFormErrors,
23 | }: PropsWithChildren): ReactElement {
24 | const ctx = useMemo(
25 | () => ({
26 | getFormErrors,
27 | }),
28 | [getFormErrors],
29 | );
30 | return {children};
31 | }
32 |
--------------------------------------------------------------------------------
/packages/forms/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './checkbox/FormikCheckboxField';
2 | export * from './date/FormikDateField';
3 | export * from './enhanced/FormikEnhanced';
4 | export * from './enhanced/FormsProvider';
5 | export * from './enhanced/useFormikEnhanced';
6 | export * from './phone/FormikPhoneField';
7 | export * from './radio/FormikRadioFieldCard';
8 | export * from './radio/FormikRadioGroupField';
9 | export * from './text/constants';
10 | export * from './text/FormikCurrencyField';
11 | export * from './text/FormikMaxLengthTextField';
12 | export * from './text/FormikNumberField';
13 | export * from './text/FormikPasswordField';
14 | export * from './text/FormikPatternField';
15 | export * from './text/FormikTextField';
16 |
--------------------------------------------------------------------------------
/packages/forms/src/radio/FormikRadioFieldCard.tsx:
--------------------------------------------------------------------------------
1 | import { ButtonBaseProps } from '@material-ui/core';
2 | import { RadioFieldCard } from '@superdispatch/ui';
3 | import { useField } from 'formik';
4 | import { forwardRef, ForwardRefExoticComponent } from 'react';
5 |
6 | export interface FormikRadioCardFieldProps
7 | extends Omit {
8 | name: string;
9 | value: string;
10 | label: string;
11 | caption?: string;
12 | icon?: React.ReactNode;
13 | }
14 |
15 | export const FormikRadioCardField: ForwardRefExoticComponent =
16 | forwardRef(({ name, value, label, caption, icon }, ref) => {
17 | const [field, _, { setValue }] = useField({
18 | name,
19 | });
20 |
21 | return (
22 | {
31 | setValue(value);
32 | }}
33 | />
34 | );
35 | });
36 |
--------------------------------------------------------------------------------
/packages/forms/src/text/constants.ts:
--------------------------------------------------------------------------------
1 | export const EMPTY_ERROR_MESSAGE = 'EMPTY_ERROR_MESSAGE';
2 |
--------------------------------------------------------------------------------
/packages/forms/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.build",
3 | "exclude": [
4 | "pkg",
5 | "playroom.ts",
6 | "**/*.spec.*",
7 | "**/*.stories.*",
8 | "**/*.playroom.*",
9 | "**/__tests__/**",
10 | "**/__testutils__/**"
11 | ],
12 | "references": [
13 | { "path": "../dates" },
14 | { "path": "../phones" },
15 | { "path": "../hooks" },
16 | { "path": "../ui" }
17 | ],
18 | "compilerOptions": {
19 | "outDir": "pkg/dist-types",
20 | "composite": true,
21 | "rootDir": "src",
22 | "tsBuildInfoFile": "pkg/dist-types/.tsbuildinfo"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/hooks/.babelrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const createBabelConfig = require('../../config/createBabelConfig');
4 |
5 | module.exports = createBabelConfig();
6 |
--------------------------------------------------------------------------------
/packages/hooks/README.md:
--------------------------------------------------------------------------------
1 | ### `@superdispatch/hooks`
2 |
3 | [](https://www.npmjs.com/package/@superdispatch/hooks)
4 | [](https://bundlephobia.com/result?p=@superdispatch/hooks)
5 |
6 | #### Installation
7 |
8 | ```bash
9 | yarn add @superdispatch/hooks
10 | ```
11 |
--------------------------------------------------------------------------------
/packages/hooks/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@superdispatch/hooks",
3 | "version": "0.45.0",
4 | "repository": {
5 | "type": "git",
6 | "url": "https://github.com/superdispatch/ui.git"
7 | },
8 | "license": "MIT",
9 | "main": "src/index.ts",
10 | "module": "src/index.ts",
11 | "browser": "pkg/dist-web/index.js",
12 | "types": "src/index.ts",
13 | "scripts": {
14 | "version": "pika build"
15 | },
16 | "dependencies": {
17 | "dequal": "^2.0.2"
18 | },
19 | "publishConfig": {
20 | "access": "public",
21 | "directory": "pkg"
22 | },
23 | "@pika/pack": {
24 | "pipeline": [
25 | [
26 | "@pika/plugin-standard-pkg",
27 | {
28 | "exclude": [
29 | "**/*.spec.*",
30 | "**/__tests__/**",
31 | "**/__testutils__/**"
32 | ]
33 | }
34 | ],
35 | [
36 | "@pika/plugin-build-web"
37 | ],
38 | [
39 | "@pika/plugin-build-node"
40 | ],
41 | [
42 | "@pika/plugin-build-types"
43 | ],
44 | [
45 | "@pika/plugin-bundle-types"
46 | ]
47 | ]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/hooks/src/constant/useConstant.ts:
--------------------------------------------------------------------------------
1 | import { MutableRefObject, useRef } from 'react';
2 |
3 | export function useConstant(factory: () => T): T {
4 | const ref = useRef>(null);
5 |
6 | if (ref.current == null) {
7 | ref.current = { current: factory() };
8 | }
9 |
10 | return ref.current.current;
11 | }
12 |
--------------------------------------------------------------------------------
/packages/hooks/src/deep-equal-memo/useDeepEqualMemo.ts:
--------------------------------------------------------------------------------
1 | import { dequal } from 'dequal';
2 | import { DependencyList, useEffect, useRef } from 'react';
3 |
4 | interface State {
5 | value: T;
6 | }
7 |
8 | export function useDeepEqualMemo(
9 | factory: (prevValue: T | undefined) => T,
10 | deps: DependencyList,
11 | ): T {
12 | const depsRef = useRef(deps);
13 | const stateRef = useRef>();
14 |
15 | let nextState: undefined | State = undefined;
16 |
17 | if (stateRef.current == null) {
18 | stateRef.current = { value: factory(undefined) };
19 | } else if (!dequal(depsRef.current, deps)) {
20 | const nextValue = factory(stateRef.current.value);
21 |
22 | if (!dequal(nextValue, stateRef.current.value)) {
23 | nextState = { value: nextValue };
24 | }
25 | }
26 |
27 | useEffect(() => {
28 | depsRef.current = deps;
29 |
30 | if (nextState) {
31 | stateRef.current = nextState;
32 | }
33 | });
34 |
35 | return nextState != null ? nextState.value : stateRef.current.value;
36 | }
37 |
--------------------------------------------------------------------------------
/packages/hooks/src/deep-equal-value/useDeepEqualValue.spec.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import { useDeepEqualValue } from './useDeepEqualValue';
3 |
4 | test('basic', () => {
5 | const value1 = new Date(0);
6 | const value2 = new Date(0);
7 | const value3 = new Date(10);
8 |
9 | const { result, rerender } = renderHook(
10 | ({ value }) => useDeepEqualValue(value),
11 | { initialProps: { value: value1 } },
12 | );
13 |
14 | expect(result.current).toBe(value1);
15 |
16 | rerender({ value: value2 });
17 |
18 | expect(result.current).toBe(value1);
19 | expect(result.current).not.toBe(value2);
20 |
21 | rerender({ value: value3 });
22 |
23 | expect(result.current).toBe(value3);
24 | });
25 |
--------------------------------------------------------------------------------
/packages/hooks/src/deep-equal-value/useDeepEqualValue.ts:
--------------------------------------------------------------------------------
1 | import { dequal } from 'dequal';
2 | import { useRef } from 'react';
3 | import { useIsomorphicLayoutEffect } from '../isomorphic-layout-effect/useIsomorphicLayoutEffect';
4 |
5 | export function useDeepEqualValue(value: T): T {
6 | const ref = useRef(value);
7 | const isEqual = dequal(value, ref.current);
8 |
9 | useIsomorphicLayoutEffect(() => {
10 | if (!isEqual) {
11 | ref.current = value;
12 | }
13 | });
14 |
15 | return isEqual ? ref.current : value;
16 | }
17 |
--------------------------------------------------------------------------------
/packages/hooks/src/index.spec.ts:
--------------------------------------------------------------------------------
1 | import * as api from '.';
2 |
3 | test('public api', () => {
4 | expect(api).toMatchInlineSnapshot(`
5 | Object {
6 | "useConstant": [Function],
7 | "useDeepEqualMemo": [Function],
8 | "useDeepEqualValue": [Function],
9 | "useEventHandler": [Function],
10 | "useIsMounted": [Function],
11 | "useValueObserver": [Function],
12 | }
13 | `);
14 | });
15 |
--------------------------------------------------------------------------------
/packages/hooks/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './constant/useConstant';
2 | export * from './deep-equal-memo/useDeepEqualMemo';
3 | export * from './deep-equal-value/useDeepEqualValue';
4 | export * from './event-handler/useEventHandler';
5 | export * from './is-mounted/useIsMounted';
6 | export * from './value-observer/useValueObserver';
7 |
--------------------------------------------------------------------------------
/packages/hooks/src/is-mounted/useIsMounted.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useRef } from 'react';
2 | import { useIsomorphicLayoutEffect } from '../isomorphic-layout-effect/useIsomorphicLayoutEffect';
3 |
4 | export function useIsMounted(): () => boolean {
5 | const ref = useRef(false);
6 |
7 | useIsomorphicLayoutEffect(() => {
8 | ref.current = true;
9 |
10 | return () => {
11 | ref.current = false;
12 | };
13 | }, []);
14 |
15 | return useCallback(() => ref.current, []);
16 | }
17 |
--------------------------------------------------------------------------------
/packages/hooks/src/isomorphic-layout-effect/useIsomorphicLayoutEffect.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useLayoutEffect } from 'react';
2 |
3 | export const useIsomorphicLayoutEffect =
4 | typeof document != 'undefined' ? useEffect : useLayoutEffect;
5 |
--------------------------------------------------------------------------------
/packages/hooks/src/value-observer/useValueObserver.spec.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import { useValueObserver } from './useValueObserver';
3 |
4 | test('basic', () => {
5 | const observer = jest.fn();
6 |
7 | const { rerender } = renderHook(
8 | ({ value }) => {
9 | useValueObserver(value, observer);
10 | },
11 | { initialProps: { value: 'foo' } },
12 | );
13 |
14 | expect(observer).toHaveBeenCalledTimes(0);
15 |
16 | rerender({ value: 'bar' });
17 |
18 | expect(observer).toHaveBeenCalledTimes(1);
19 | expect(observer).toHaveBeenLastCalledWith('foo');
20 |
21 | rerender({ value: 'bar' });
22 |
23 | expect(observer).toHaveBeenCalledTimes(1);
24 |
25 | rerender({ value: 'baz' });
26 |
27 | expect(observer).toHaveBeenCalledTimes(2);
28 | expect(observer).toHaveBeenLastCalledWith('bar');
29 | });
30 |
--------------------------------------------------------------------------------
/packages/hooks/src/value-observer/useValueObserver.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | export function useValueObserver(
4 | value: T,
5 | observer: (prev: T) => void,
6 | ): void {
7 | const ref = useRef(value);
8 |
9 | useEffect(() => {
10 | if (!Object.is(value, ref.current)) {
11 | observer(ref.current);
12 | ref.current = value;
13 | }
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/packages/hooks/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.build",
3 | "exclude": [
4 | "pkg",
5 | "playroom.ts",
6 | "**/*.spec.*",
7 | "**/*.stories.*",
8 | "**/*.playroom.*",
9 | "**/__tests__/**",
10 | "**/__testutils__/**"
11 | ],
12 | "references": [{ "path": "../__testutils__" }],
13 | "compilerOptions": {
14 | "composite": true,
15 | "rootDir": "src",
16 | "outDir": "pkg/dist-types",
17 | "tsBuildInfoFile": "pkg/dist-types/.tsbuildinfo"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/jestutils/.babelrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const createBabelConfig = require('../../config/createBabelConfig');
4 |
5 | module.exports = createBabelConfig();
6 |
--------------------------------------------------------------------------------
/packages/jestutils/README.md:
--------------------------------------------------------------------------------
1 | ### `@superdispatch/ui`
2 |
3 | [](https://www.npmjs.com/package/@superdispatch/testutils)
4 |
5 | #### Installation
6 |
7 | ```bash
8 | yarn add @superdispatch/testutils @testing-library/react
9 | ```
10 |
11 | #### Setup
12 |
13 | ```javascript
14 | // In setupTests
15 | import '@superdispatch/jestutils';
16 | ```
17 |
--------------------------------------------------------------------------------
/packages/jestutils/src/__tests__/index.ts:
--------------------------------------------------------------------------------
1 | import * as api from '..';
2 |
3 | it('exposes public api', () => {
4 | expect(api).toMatchInlineSnapshot();
5 | });
6 |
--------------------------------------------------------------------------------
/packages/jestutils/src/index.ts:
--------------------------------------------------------------------------------
1 | import './serializers/blob-serializer';
2 | import './serializers/form-data-serializer';
3 | import './serializers/location-serializer';
4 |
5 | export * from './matchers/date-matchers';
6 | export * from './mockEndpoint';
7 | export * from './mockEndpoints';
8 | export * from './spyLogs';
9 |
--------------------------------------------------------------------------------
/packages/jestutils/src/matchers/date-matchers.ts:
--------------------------------------------------------------------------------
1 | import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils';
2 |
3 | declare global {
4 | namespace jest {
5 | interface Matchers {
6 | toBeSameDate: (expected: unknown) => R;
7 | }
8 | }
9 | }
10 |
11 | expect.extend({
12 | toBeSameDate(received: unknown, expected: unknown) {
13 | const pass =
14 | (received == null && expected == null) ||
15 | (received instanceof Date &&
16 | expected instanceof Date &&
17 | (Object.is(received, expected) ||
18 | Object.is(received.getTime(), expected.getTime())));
19 |
20 | return {
21 | pass,
22 | message: () =>
23 | !pass
24 | ? `${matcherHint('.toBeSameDate')}\n` +
25 | `Expected dates to be same:\n` +
26 | `Expected: ${printExpected(expected)}\n` +
27 | `Received: ${printReceived(received)}`
28 | : `${matcherHint('.not.toBeSameDate')}\n` +
29 | `Expected dates to not be same:\n` +
30 | `Expected: ${printExpected(expected)}\n` +
31 | `Received: ${printReceived(received)}`,
32 | };
33 | },
34 | });
35 |
--------------------------------------------------------------------------------
/packages/jestutils/src/mockEndpoints.ts:
--------------------------------------------------------------------------------
1 | import {
2 | mockEndpoint,
3 | MockEndpointOptions,
4 | MockEndpointRequest,
5 | MockEndpointResponse,
6 | } from './mockEndpoint';
7 |
8 | export type MockEndpoints = {
9 | [TKey in TEndpointName]: MockEndpointOptions;
10 | };
11 |
12 | export type MockEndpointResolvers = {
13 | [TKey in TEndpointName]: jest.Mock<
14 | MockEndpointResponse,
15 | [MockEndpointRequest]
16 | >;
17 | };
18 |
19 | export function mockEndpoints(
20 | endpoints: MockEndpoints,
21 | defaults?: Pick,
22 | ): MockEndpointResolvers {
23 | const mocks = {} as MockEndpointResolvers;
24 |
25 | for (const name of Object.keys(endpoints) as TEndpointName[]) {
26 | mocks[name] = mockEndpoint(name, { ...defaults, ...endpoints[name] });
27 | }
28 |
29 | return mocks;
30 | }
31 |
--------------------------------------------------------------------------------
/packages/jestutils/src/serializers/__tests__/blob-serializer.spec.ts:
--------------------------------------------------------------------------------
1 | import '../../setupTestUtils';
2 |
3 | it('serializes File', () => {
4 | expect(new File(['id,name'], 'report.csv', { type: 'text/csv' }))
5 | .toMatchInlineSnapshot(`
6 | File {
7 | "name": "report.csv",
8 | "size": 7,
9 | "type": "text/csv",
10 | }
11 | `);
12 | });
13 |
14 | it('serializes Blob', () => {
15 | expect(new Blob(['foo'], { type: 'image/png' })).toMatchInlineSnapshot(`
16 | Blob {
17 | "size": 3,
18 | "type": "image/png",
19 | }
20 | `);
21 | });
22 |
--------------------------------------------------------------------------------
/packages/jestutils/src/serializers/__tests__/form-data-serializer.spec.ts:
--------------------------------------------------------------------------------
1 | import '../../setupTestUtils';
2 |
3 | it('serializes empty FormData', () => {
4 | expect(new FormData()).toMatchInlineSnapshot(`FormData {}`);
5 | });
6 |
7 | it('serializes FormData with some data', () => {
8 | const formData = new FormData();
9 |
10 | formData.append('name', 'John');
11 | formData.append('file', new File([], 'profile.jpg', { type: 'image/jpg' }));
12 |
13 | expect(formData).toMatchInlineSnapshot(`
14 | FormData {
15 | "file": File {
16 | "name": "profile.jpg",
17 | "size": 0,
18 | "type": "image/jpg",
19 | },
20 | "name": "John",
21 | }
22 | `);
23 | });
24 |
25 | it('serialize composed FormData', () => {
26 | const formData = new FormData();
27 | formData.append('name', 'John');
28 |
29 | expect({ foo: 'bar', form: formData }).toMatchInlineSnapshot(`
30 | Object {
31 | "foo": "bar",
32 | "form": FormData {
33 | "name": "John",
34 | },
35 | }
36 | `);
37 | });
38 |
--------------------------------------------------------------------------------
/packages/jestutils/src/serializers/blob-serializer.ts:
--------------------------------------------------------------------------------
1 | expect.addSnapshotSerializer({
2 | test(value: unknown) {
3 | return value instanceof Blob;
4 | },
5 |
6 | print(blob, serialize) {
7 | const { type, size } = blob as Blob;
8 |
9 | if (blob instanceof File) {
10 | return serialize({ type, size, name: blob.name }).replace(
11 | /^Object/,
12 | 'File',
13 | );
14 | }
15 |
16 | return serialize({ type, size }).replace(/^Object/, 'Blob');
17 | },
18 | });
19 |
--------------------------------------------------------------------------------
/packages/jestutils/src/serializers/form-data-serializer.ts:
--------------------------------------------------------------------------------
1 | import { fromPairs } from 'lodash';
2 |
3 | expect.addSnapshotSerializer({
4 | test(value: unknown) {
5 | return value instanceof FormData;
6 | },
7 |
8 | print(formData, serialize) {
9 | return serialize(fromPairs(Array.from(formData as FormData))).replace(
10 | /^Object/,
11 | 'FormData',
12 | );
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/packages/jestutils/src/serializers/location-serializer.ts:
--------------------------------------------------------------------------------
1 | import { parse } from 'qs';
2 |
3 | expect.addSnapshotSerializer({
4 | test(value: unknown) {
5 | return (
6 | typeof value === 'object' &&
7 | value != null &&
8 | 'hash' in value &&
9 | 'search' in value &&
10 | 'pathname' in value
11 | );
12 | },
13 |
14 | print(location, serialize) {
15 | const { hash, search, pathname } = location as Location;
16 |
17 | const searchParams = parse(search, {
18 | ignoreQueryPrefix: true,
19 | decoder: (str, defaultDecoder, charset) => {
20 | const decoded = defaultDecoder(str, defaultDecoder, charset);
21 |
22 | try {
23 | return JSON.parse(decoded) as unknown;
24 | } catch {
25 | return decoded;
26 | }
27 | },
28 | });
29 |
30 | return serialize({
31 | pathname,
32 | ...(!!hash && { hash }),
33 | ...(Object.keys(searchParams).length > 0 && { searchParams }),
34 | });
35 | },
36 | });
37 |
--------------------------------------------------------------------------------
/packages/jestutils/src/spyLogs.ts:
--------------------------------------------------------------------------------
1 | export interface SpyLogsOptions {
2 | warn: false | 'mute' | 'forbid';
3 | error: false | 'mute' | 'forbid';
4 | }
5 |
6 | export function spyLogs(options: SpyLogsOptions) {
7 | const methods = ['warn', 'error'] as const;
8 | const spies = new Map();
9 |
10 | beforeEach(() => {
11 | for (const method of methods) {
12 | const option = options[method];
13 |
14 | if (option) {
15 | const spy = jest.spyOn(console, method);
16 |
17 | if (option === 'mute') {
18 | spy.mockImplementation();
19 | }
20 |
21 | spies.set(method, spy);
22 | }
23 | }
24 | });
25 |
26 | afterEach(() => {
27 | for (const [method, spy] of spies) {
28 | if (options[method] === 'forbid') {
29 | expect(spy).not.toHaveBeenCalled();
30 | }
31 | }
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/packages/jestutils/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.build",
3 | "exclude": [
4 | "pkg",
5 | "playroom.ts",
6 | "**/*.spec.*",
7 | "**/*.stories.*",
8 | "**/*.playroom.*",
9 | "**/__tests__/**",
10 | "**/__testutils__/**"
11 | ],
12 | "compilerOptions": {
13 | "composite": true,
14 | "rootDir": "src",
15 | "outDir": "pkg/dist-types",
16 | "tsBuildInfoFile": "pkg/dist-types/.tsbuildinfo"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/lab/.babelrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const createBabelConfig = require('../../config/createBabelConfig');
4 |
5 | module.exports = createBabelConfig();
6 |
--------------------------------------------------------------------------------
/packages/lab/README.md:
--------------------------------------------------------------------------------
1 | ### `@superdispatch/ui-lab`
2 |
3 | [](https://www.npmjs.com/package/@superdispatch/ui-lab)
4 | [](https://bundlephobia.com/result?p=@superdispatch/ui-lab)
5 |
6 | #### Installation
7 |
8 | ```bash
9 | yarn add @superdispatch/ui-lab
10 | ```
11 |
--------------------------------------------------------------------------------
/packages/lab/playroom.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Alert,
3 | Banner,
4 | Box,
5 | Button,
6 | ButtonArea,
7 | DescriptionItem,
8 | FileDropZone,
9 | FileListItem,
10 | FlagList,
11 | LinkedText,
12 | MultilineText,
13 | Navbar,
14 | NavbarAvatar,
15 | NavbarMenu,
16 | Sidebar,
17 | SidebarContainer,
18 | SidebarDivider,
19 | SidebarMenuItem,
20 | SidebarMenuItemAction,
21 | SidebarMenuItemAvatar,
22 | SidebarSubheader,
23 | TextBox,
24 | } from './src';
25 |
--------------------------------------------------------------------------------
/packages/lab/src/box/Box.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta } from '@storybook/react';
2 | import { Box } from './Box';
3 |
4 | export default { title: 'Lab/Box', component: Box } as Meta;
5 |
6 | export const basic = () => (
7 |
18 | Hey
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/packages/lab/src/chat/README.MD:
--------------------------------------------------------------------------------
1 | # Chat component
2 |
3 | ## Description
4 |
5 | Chat component is a container component that renders a list of messages and a form to send new messages.
6 | Each message displays the text of the message, the author, role, and date/time of the message.
7 | It displays the author's message on the right and the receiver's message on the left
8 |
--------------------------------------------------------------------------------
/packages/lab/src/chat/__tests__/Chat.spec.tsx:
--------------------------------------------------------------------------------
1 | import { renderComponent } from '@superdispatch/ui-testutils';
2 | import { screen } from '@testing-library/react';
3 | import { Chat } from '../Chat';
4 | import { ChatMessage } from '../ChatMessage';
5 |
6 | describe('Chat', () => {
7 | it('Displays placeholder message when no messages are provided', () => {
8 | renderComponent();
9 |
10 | expect(screen.getByText(/No new messages/i)).toBeInTheDocument();
11 | });
12 |
13 | it('Renders provided messages', () => {
14 | renderComponent(
15 |
16 |
23 | ,
24 | );
25 |
26 | expect(screen.getByText('Steve Trabajo')).toBeInTheDocument();
27 | expect(screen.getByText('Hello, Vin! ...')).toBeInTheDocument();
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/packages/lab/src/description-line-item/DescriptionLineItem.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta } from '@storybook/react';
2 | import { TextBox } from '../text-box/TextBox';
3 | import { DescriptionLineItem } from './DescriptionLineItem';
4 |
5 | export default {
6 | title: 'Lab/DescriptionLineItem',
7 | component: DescriptionLineItem,
8 | } as Meta;
9 |
10 | export const description = () => (
11 | <>
12 | Payment}>
13 |
14 | $1,200
15 |
16 |
17 | Method}>
18 |
19 | Comcheck
20 |
21 |
22 | >
23 | );
24 |
--------------------------------------------------------------------------------
/packages/lab/src/description-line-item/DescriptionLineItem.tsx:
--------------------------------------------------------------------------------
1 | import { ColorDynamic, Column, Columns } from '@superdispatch/ui';
2 | import { forwardRef, ReactNode } from 'react';
3 | import styled from 'styled-components';
4 |
5 | export const DottedLine = styled.div`
6 | border-bottom: 1px dotted ${ColorDynamic.Silver400};
7 | margin: 0px 8px;
8 | height: 7px;
9 | `;
10 |
11 | interface DescriptionLineItemProps {
12 | title: ReactNode;
13 | children: ReactNode;
14 | }
15 |
16 | export const DescriptionLineItem = forwardRef<
17 | HTMLDivElement,
18 | DescriptionLineItemProps
19 | >(({ title, children }, ref) => (
20 |
21 | {title}
22 |
23 |
24 |
25 | {children}
26 |
27 | ));
28 |
29 | DescriptionLineItem.displayName = 'DescriptionLineItem';
30 |
--------------------------------------------------------------------------------
/packages/lab/src/multiline-text/MultilineText.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta } from '@storybook/react';
2 | import { Box } from '../box/Box';
3 | import { MultilineText } from './MultilineText';
4 |
5 | export default {
6 | title: 'Lab/MultilineText',
7 | component: MultilineText,
8 | decorators: [
9 | (Story) => (
10 |
11 |
12 |
13 | ),
14 | ],
15 | } as Meta;
16 |
17 | export const basic = () => (
18 |
19 | {`This is a multiline text with a lot of white space.
20 |
21 | Lorem ipsum dolor sit amet,
22 | consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
23 |
24 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
25 |
26 |
27 |
28 | Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.`}
29 |
30 | );
31 |
--------------------------------------------------------------------------------
/packages/lab/src/multiline-text/MultilineText.ts:
--------------------------------------------------------------------------------
1 | import { Property } from 'csstype';
2 | import styled from 'styled-components';
3 |
4 | export interface MultilineTextProps {
5 | overflowWrap?: Extract<
6 | Property.OverflowWrap,
7 | 'anywhere' | 'break-word' | 'normal'
8 | >;
9 | }
10 |
11 | export const MultilineText = styled.span`
12 | white-space: pre-wrap;
13 | overflow-wrap: ${({ overflowWrap = 'normal' }) => overflowWrap};
14 | `;
15 |
16 | MultilineText.displayName = 'MultilineText';
17 |
--------------------------------------------------------------------------------
/packages/lab/src/navbar/NavbarContext.tsx:
--------------------------------------------------------------------------------
1 | import { noop } from 'lodash';
2 | import { createContext, useContext } from 'react';
3 |
4 | export interface NavbarContextType {
5 | isDrawerOpen: boolean;
6 | isMenuExpanded: boolean;
7 | isNavbarExpanded: boolean;
8 |
9 | setDrawerOpen: (value: boolean) => void;
10 | setMenuExpanded: (value: boolean) => void;
11 | }
12 |
13 | export const NavbarContext = createContext({
14 | isDrawerOpen: false,
15 | isMenuExpanded: false,
16 | isNavbarExpanded: false,
17 | setMenuExpanded: noop,
18 | setDrawerOpen: noop,
19 | });
20 |
21 | export function useNavbarContext(): NavbarContextType {
22 | return useContext(NavbarContext);
23 | }
24 |
--------------------------------------------------------------------------------
/packages/lab/src/sidebar/SidebarBackButton.tsx:
--------------------------------------------------------------------------------
1 | import { IconButton, IconButtonProps } from '@material-ui/core';
2 | import { ArrowBack } from '@material-ui/icons';
3 | import { useCollapseBreakpoint } from '@superdispatch/ui';
4 | import { ReactElement } from 'react';
5 | import { useSidebarContext } from './SidebarContainer';
6 |
7 | export function SidebarBackButton({
8 | onClick,
9 | children = ,
10 | ...props
11 | }: IconButtonProps): ReactElement | null {
12 | const isCollapsed = useCollapseBreakpoint('sm');
13 | const { openSidebar } = useSidebarContext();
14 |
15 | if (!isCollapsed) {
16 | return null;
17 | }
18 |
19 | return (
20 | {
24 | onClick?.(event);
25 | if (!event.isDefaultPrevented()) {
26 | openSidebar();
27 | }
28 | }}
29 | >
30 | {children}
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/packages/lab/src/sidebar/SidebarDivider.tsx:
--------------------------------------------------------------------------------
1 | import { Divider } from '@material-ui/core';
2 | import { forwardRef } from 'react';
3 | import styled from 'styled-components';
4 |
5 | const SidebarDividerRoot = styled.div`
6 | padding: 20px 24px;
7 | `;
8 |
9 | export const SidebarDivider = forwardRef((_, ref) => {
10 | return (
11 |
12 |
13 |
14 | );
15 | });
16 |
--------------------------------------------------------------------------------
/packages/lab/src/sidebar/SidebarMenuItemAction.tsx:
--------------------------------------------------------------------------------
1 | import { IconButton, Tooltip, TooltipProps } from '@material-ui/core';
2 | import { forwardRef, ReactElement } from 'react';
3 | import styled from 'styled-components';
4 |
5 | const SidebarMenuItemActionRoot = styled(IconButton)`
6 | & .MuiSvgIcon-root {
7 | font-size: 16px;
8 | }
9 | `;
10 |
11 | export interface SidebarMenuItemActionProps
12 | extends Pick {
13 | children: ReactElement;
14 | }
15 |
16 | export const SidebarMenuItemAction = forwardRef<
17 | HTMLButtonElement,
18 | SidebarMenuItemActionProps
19 | >(({ title, placement, children }, ref) => {
20 | return (
21 |
22 |
23 | {children}
24 |
25 |
26 | );
27 | });
28 |
--------------------------------------------------------------------------------
/packages/lab/src/sidebar/SidebarMenuItemContext.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | createContext,
3 | ReactElement,
4 | ReactNode,
5 | useContext,
6 | useMemo,
7 | } from 'react';
8 |
9 | export interface SidebarMenuItemContext {
10 | hovered?: boolean;
11 | disabled?: boolean;
12 | }
13 |
14 | const context = createContext({});
15 |
16 | export function useSidebarMenuItemContext(): SidebarMenuItemContext {
17 | return useContext(context);
18 | }
19 |
20 | export interface SidebarMenuItemContextProviderProps
21 | extends SidebarMenuItemContext {
22 | children?: ReactNode;
23 | }
24 |
25 | export function SidebarMenuItemContextProvider({
26 | children,
27 | hovered = false,
28 | disabled = false,
29 | }: SidebarMenuItemContextProviderProps): ReactElement {
30 | const ctx = useMemo(() => ({ hovered, disabled }), [hovered, disabled]);
31 |
32 | return {children};
33 | }
34 |
--------------------------------------------------------------------------------
/packages/lab/src/sidebar/SidebarSubheader.tsx:
--------------------------------------------------------------------------------
1 | import { Column, Columns } from '@superdispatch/ui';
2 | import { forwardRef, ReactNode } from 'react';
3 | import styled from 'styled-components';
4 | import { TextBox } from '../text-box/TextBox';
5 |
6 | const SidebarSubheaderRoot = styled.div`
7 | display: flex;
8 | align-items: center;
9 |
10 | height: 40px;
11 | max-height: 40px;
12 | padding-left: 24px;
13 | padding-right: 24px;
14 | `;
15 |
16 | export interface SidebarSubheaderProps {
17 | id?: string;
18 | action?: ReactNode;
19 | children?: ReactNode;
20 | }
21 |
22 | export const SidebarSubheader = forwardRef<
23 | HTMLDivElement,
24 | SidebarSubheaderProps
25 | >(({ id, action, children }, ref) => {
26 | return (
27 |
28 |
29 |
30 |
31 | {children}
32 |
33 |
34 | {!!action && {action}}
35 |
36 |
37 | );
38 | });
39 |
--------------------------------------------------------------------------------
/packages/lab/src/styled.d.ts:
--------------------------------------------------------------------------------
1 | import { SuperDispatchTheme } from '@superdispatch/ui';
2 | import 'styled-components';
3 |
4 | declare module 'styled-components' {
5 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
6 | export interface DefaultTheme extends SuperDispatchTheme {}
7 |
8 | // eslint-disable-next-line @typescript-eslint/ban-types
9 | export interface StyledConfig {
10 | displayName?: string;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/lab/src/utils/RuleNormalizer.ts:
--------------------------------------------------------------------------------
1 | export type RuleNormalizer = (input: unknown) => string | undefined;
2 | export type RuleNormalizerRecord = Readonly<
3 | Record
4 | >;
5 |
6 | export function createRuleNormalizer(
7 | rules: Record,
8 | ): RuleNormalizer {
9 | return (input) => {
10 | if (typeof input == 'string') {
11 | const value = rules[input as TKey];
12 |
13 | if (typeof value == 'number') {
14 | return `${value}px`;
15 | }
16 |
17 | if (typeof value == 'string') {
18 | return value;
19 | }
20 | }
21 |
22 | return undefined;
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/packages/lab/src/utils/mergeStyles.ts:
--------------------------------------------------------------------------------
1 | import { CSSObject } from 'styled-components';
2 |
3 | export function mergeStyles(
4 | styles: CSSObject,
5 | ...sources: Array
6 | ): CSSObject {
7 | for (const source of sources) {
8 | if (source) {
9 | for (const key in source) {
10 | if (Object.prototype.hasOwnProperty.call(source, key)) {
11 | const stylesValue = styles[key];
12 | const sourceValue = source[key];
13 |
14 | if (
15 | typeof stylesValue == 'object' &&
16 | typeof sourceValue == 'object'
17 | ) {
18 | mergeStyles(stylesValue, sourceValue);
19 | } else {
20 | styles[key] = source[key];
21 | }
22 | }
23 | }
24 | }
25 | }
26 |
27 | return styles;
28 | }
29 |
--------------------------------------------------------------------------------
/packages/lab/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.build",
3 | "exclude": [
4 | "pkg",
5 | "playroom.ts",
6 | "**/*.spec.*",
7 | "**/*.stories.*",
8 | "**/*.playroom.*",
9 | "**/__tests__/**",
10 | "**/__testutils__/**"
11 | ],
12 | "references": [{ "path": "../hooks" }, { "path": "../__testutils__" }],
13 | "compilerOptions": {
14 | "composite": true,
15 | "rootDir": "src",
16 | "outDir": "pkg/dist-types",
17 | "tsBuildInfoFile": "pkg/dist-types/.tsbuildinfo"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/phones/.babelrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const createBabelConfig = require('../../config/createBabelConfig');
4 |
5 | module.exports = createBabelConfig();
6 |
--------------------------------------------------------------------------------
/packages/phones/README.md:
--------------------------------------------------------------------------------
1 | ### `@superdispatch/phones`
2 |
3 | [](https://www.npmjs.com/package/@superdispatch/phones)
4 | [](https://bundlephobia.com/result?p=@superdispatch/phones)
5 |
6 | #### Installation
7 |
8 | ```bash
9 | yarn add @superdispatch/phones awesome-phonenumber @material-ui/core @material-ui/icons
10 | ```
11 |
--------------------------------------------------------------------------------
/packages/phones/playroom.ts:
--------------------------------------------------------------------------------
1 | export { PhoneField } from './src/phone-field/PhoneField.playroom';
2 | export { PhoneLink } from './src/phone-link/PhoneLink';
3 | export { PhoneText } from './src/phone-text/PhoneText';
4 |
--------------------------------------------------------------------------------
/packages/phones/src/__tests__/index.spec.ts:
--------------------------------------------------------------------------------
1 | import * as api from '..';
2 |
3 | test('api', () => {
4 | expect(api).toMatchInlineSnapshot(`
5 | Object {
6 | "DEFAULT_COUNTRY": "US",
7 | "PhoneField": React.forwardRef(PhoneField),
8 | "PhoneLink": React.forwardRef(PhoneLink),
9 | "PhoneService": [Function],
10 | "PhoneText": [Function],
11 | "SuspendedPhoneField": React.forwardRef(SuspendedPhoneField),
12 | "SuspendedPhoneLink": React.forwardRef(SuspendedPhoneLink),
13 | "SuspendedPhoneText": [Function],
14 | "formatCountry": [Function],
15 | "getCountryCode": [Function],
16 | "isCountryISO": [Function],
17 | "listCountries": [Function],
18 | "toCountryISO": [Function],
19 | "useFormattedPhoneNumber": [Function],
20 | "usePhoneService": [Function],
21 | }
22 | `);
23 | });
24 |
--------------------------------------------------------------------------------
/packages/phones/src/formatted-phone-number/FormattedPhoneNumber.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import {
3 | PhoneFormatOptions,
4 | usePhoneService,
5 | } from '../phone-service/PhoneService';
6 |
7 | export function useFormattedPhoneNumber(
8 | input: unknown,
9 | { format, country, fallback = '' }: PhoneFormatOptions = {},
10 | ): string {
11 | const phoneService = usePhoneService();
12 |
13 | return useMemo(() => {
14 | if (phoneService.checkPossibility(input) !== 'is-possible') {
15 | return fallback;
16 | }
17 |
18 | return phoneService.format(input, { format, country, fallback });
19 | }, [phoneService, input, format, country, fallback]);
20 | }
21 |
--------------------------------------------------------------------------------
/packages/phones/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './country-code-metadata/CountryCodeMetadata';
2 | export * from './formatted-phone-number/FormattedPhoneNumber';
3 | export * from './phone-field/PhoneField';
4 | export * from './phone-link/PhoneLink';
5 | export * from './phone-service/PhoneService';
6 | export * from './phone-text/PhoneText';
7 |
--------------------------------------------------------------------------------
/packages/phones/src/phone-field/PhoneField.playroom.tsx:
--------------------------------------------------------------------------------
1 | import { forwardRef, useMemo, useState } from 'react';
2 | import { usePhoneService } from '../phone-service/PhoneService';
3 | import { PhoneField as SdPhoneField, PhoneFieldProps } from './PhoneField';
4 |
5 | export const PhoneField = forwardRef(
6 | ({ value, onChange, error, helperText, ...props }, ref) => {
7 | const [state, setState] = useState('');
8 | const phoneService = usePhoneService();
9 | const errorMessage = useMemo(
10 | () => phoneService.validate(value),
11 | [value, phoneService],
12 | );
13 |
14 | return (
15 | {
22 | setState(nextValue);
23 | onChange?.(nextValue);
24 | }}
25 | />
26 | );
27 | },
28 | );
29 |
--------------------------------------------------------------------------------
/packages/phones/src/phone-field/PhoneField.stories.tsx:
--------------------------------------------------------------------------------
1 | import { PhoneField } from './PhoneField.playroom';
2 |
3 | export default { title: 'Phones/PhoneField', component: PhoneField };
4 |
5 | export const basic = () => ;
6 | export const validation = () => ;
7 | export const disabled = () => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/packages/phones/src/phone-field/PhoneFieldMenuItem.spec.tsx:
--------------------------------------------------------------------------------
1 | import { renderCSS } from '@superdispatch/ui-testutils';
2 | import { PhoneFieldMenuItem } from './PhoneFieldMenuItem';
3 |
4 | test('css', () => {
5 | expect(
6 | renderCSS(, ['SD-PhoneFieldMenuItem']),
7 | ).toMatchInlineSnapshot(`
8 | .SD-PhoneFieldMenuItem-flag {
9 | margin-right: 8px;
10 | }
11 | `);
12 | });
13 |
--------------------------------------------------------------------------------
/packages/phones/src/phone-link/PhoneLink.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta } from '@storybook/react';
2 | import { PhoneLink } from './PhoneLink';
3 |
4 | export default { title: 'Phones/PhoneLink', component: PhoneLink } as Meta;
5 |
6 | export const basic = () => ;
7 | export const fallback = () => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/packages/phones/src/phone-text/PhoneText.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta } from '@storybook/react';
2 | import { PhoneText } from './PhoneText';
3 |
4 | export default { title: 'Phones/PhoneText', component: PhoneText } as Meta;
5 |
6 | export const basic = () => ;
7 | export const fallback = () => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/packages/phones/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.build",
3 | "exclude": [
4 | "pkg",
5 | "playroom.ts",
6 | "**/*.spec.*",
7 | "**/*.stories.*",
8 | "**/*.playroom.*",
9 | "**/__tests__/**",
10 | "**/__testutils__/**"
11 | ],
12 | "references": [{ "path": "../ui" }, { "path": "../__testutils__" }],
13 | "compilerOptions": {
14 | "composite": true,
15 | "rootDir": "src",
16 | "outDir": "pkg/dist-types",
17 | "tsBuildInfoFile": "pkg/dist-types/.tsbuildinfo"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/testutils/.babelrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const createBabelConfig = require('../../config/createBabelConfig');
4 |
5 | module.exports = createBabelConfig();
6 |
--------------------------------------------------------------------------------
/packages/testutils/README.md:
--------------------------------------------------------------------------------
1 | ### `@superdispatch/ui`
2 |
3 | [](https://www.npmjs.com/package/@superdispatch/testutils)
4 |
5 | #### Installation
6 |
7 | ```bash
8 | yarn add @superdispatch/testutils @testing-library/react
9 | ```
10 |
--------------------------------------------------------------------------------
/packages/testutils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@superdispatch/testutils",
3 | "version": "0.45.0",
4 | "repository": {
5 | "type": "git",
6 | "url": "https://github.com/superdispatch/ui.git"
7 | },
8 | "license": "MIT",
9 | "main": "src/index.ts",
10 | "types": "src/index.ts",
11 | "scripts": {
12 | "version": "pika build"
13 | },
14 | "dependencies": {
15 | "faker": "^5.5.3"
16 | },
17 | "peerDependencies": {
18 | "@babel/runtime": "^7.0.0"
19 | },
20 | "publishConfig": {
21 | "access": "public",
22 | "directory": "pkg"
23 | },
24 | "@pika/pack": {
25 | "pipeline": [
26 | [
27 | "@pika/plugin-standard-pkg",
28 | {
29 | "exclude": [
30 | "**/*.spec.*",
31 | "**/__tests__/**/*"
32 | ]
33 | }
34 | ],
35 | [
36 | "@pika/plugin-build-node"
37 | ],
38 | [
39 | "@pika/plugin-build-types"
40 | ],
41 | [
42 | "@pika/plugin-bundle-types"
43 | ]
44 | ]
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/testutils/src/__tests__/index.ts:
--------------------------------------------------------------------------------
1 | import * as api from '..';
2 |
3 | it('exposes public api', () => {
4 | expect(api).toMatchInlineSnapshot(`
5 | Object {
6 | "MockSchema": [Function],
7 | }
8 | `);
9 | });
10 |
--------------------------------------------------------------------------------
/packages/testutils/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './MockSchema';
2 |
--------------------------------------------------------------------------------
/packages/testutils/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.build",
3 | "exclude": [
4 | "pkg",
5 | "playroom.ts",
6 | "**/*.spec.*",
7 | "**/*.stories.*",
8 | "**/*.playroom.*",
9 | "**/__tests__/**",
10 | "**/__testutils__/**"
11 | ],
12 | "compilerOptions": {
13 | "composite": true,
14 | "rootDir": "src",
15 | "outDir": "pkg/dist-types",
16 | "tsBuildInfoFile": "pkg/dist-types/.tsbuildinfo"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/ui/.babelrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const createBabelConfig = require('../../config/createBabelConfig');
4 |
5 | module.exports = createBabelConfig();
6 |
--------------------------------------------------------------------------------
/packages/ui/README.md:
--------------------------------------------------------------------------------
1 | ### `@superdispatch/ui`
2 |
3 | [](https://www.npmjs.com/package/@superdispatch/ui)
4 | [](https://bundlephobia.com/result?p=@superdispatch/ui)
5 |
6 | #### Installation
7 |
8 | ```bash
9 | yarn add @superdispatch/ui @material-ui/core @material-ui/icons @material-ui/styles @mdi/js
10 | ```
11 |
12 | #### Usage
13 |
14 | ```tsx
15 | import { Button, ThemeProvider } from '@superdispatch/ui';
16 | import { Typography } from '@material-ui/core';
17 |
18 | export function App() {
19 | return (
20 |
21 |
22 | Hello 👋
23 |
24 |
25 |
26 |
27 |
28 | );
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/packages/ui/playroom.ts:
--------------------------------------------------------------------------------
1 | export {
2 | AdaptiveToolbar,
3 | AdaptiveVerticalToolbar,
4 | AvatarButton,
5 | CardButton,
6 | CheckboxField,
7 | CheckboxGroupField,
8 | Color,
9 | ColorDynamic,
10 | Column,
11 | Columns,
12 | DescriptionList,
13 | DescriptionListItem,
14 | DrawerActions,
15 | DrawerContent,
16 | DrawerList,
17 | DrawerTitle,
18 | InfoCard,
19 | Inline,
20 | NumberField,
21 | OverflowText,
22 | PatternField,
23 | RadioField,
24 | RadioGroupField,
25 | Snackbar,
26 | SnackbarContent,
27 | Stack,
28 | Tag,
29 | Tiles,
30 | VisibilityObserver,
31 | } from './src';
32 | export { Dialog } from './src/dialog/Dialog.playroom';
33 | export { Drawer } from './src/drawer/Drawer.playroom';
34 |
--------------------------------------------------------------------------------
/packages/ui/src/Image/Image.tsx:
--------------------------------------------------------------------------------
1 | import { useTheme } from '@material-ui/core';
2 | import { ImgHTMLAttributes } from 'react';
3 |
4 | interface ImageProps extends ImgHTMLAttributes {
5 | srcDark?: string;
6 | }
7 |
8 | export function Image({ srcDark, src, ...props }: ImageProps): JSX.Element {
9 | const theme = useTheme();
10 | const value = theme.palette.type === 'dark' && srcDark ? srcDark : src;
11 | return
;
12 | }
13 |
--------------------------------------------------------------------------------
/packages/ui/src/LightDark/LightDark.tsx:
--------------------------------------------------------------------------------
1 | import { useTheme } from '@material-ui/core';
2 | import { renderChildren } from '@superdispatch/ui';
3 | import { ReactElement, ReactNode } from 'react';
4 |
5 | interface LightDarkProps {
6 | light: ReactNode;
7 | dark: ReactNode;
8 | }
9 |
10 | export function LightDark({
11 | light,
12 | dark,
13 | }: LightDarkProps): ReactElement | null {
14 | const theme = useTheme();
15 | const isDarkMode = theme.palette.type === 'dark';
16 | const mode: ReactNode = isDarkMode ? dark : light;
17 | return renderChildren(mode);
18 | }
19 |
--------------------------------------------------------------------------------
/packages/ui/src/adaptive-toolbar/__tests__/AdaptiveToolbar.spec.tsx:
--------------------------------------------------------------------------------
1 | import { renderCSS } from '@superdispatch/ui-testutils';
2 | import { AdaptiveToolbar } from '../AdaptiveToolbar';
3 |
4 | it('checks component css', () => {
5 | expect(renderCSS(, ['SD-AdaptiveToolbar']))
6 | .toMatchInlineSnapshot(`
7 | .SD-AdaptiveToolbar-actions {
8 | overflow: hidden;
9 | }
10 | `);
11 | });
12 |
--------------------------------------------------------------------------------
/packages/ui/src/app-bar/AppBarOverrides.ts:
--------------------------------------------------------------------------------
1 | import { SuperDispatchTheme } from '../theme/SuperDispatchTheme';
2 |
3 | export function overrideAppBar(theme: SuperDispatchTheme): void {
4 | theme.props.MuiAppBar = {
5 | elevation: 0,
6 | color: 'inherit',
7 | position: 'static',
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/packages/ui/src/app-drawer/constants.ts:
--------------------------------------------------------------------------------
1 | export const DRAWER_SIZE_VALUES = ['md', 'lg', 'xl', 'xxl'] as const;
2 |
--------------------------------------------------------------------------------
/packages/ui/src/avatar/Avatar.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar } from '@material-ui/core';
2 | import { PropsLink } from '@superdispatch/ui-docs';
3 |
4 | export default {
5 | title: 'Data Display/Avatar',
6 | parameters: {
7 | componentSubtitle: (
8 |
9 | ),
10 | },
11 | };
12 |
13 | export const basic = () => ;
14 | export const text = () => A1;
15 | export const image = () => (
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/packages/ui/src/avatar/AvatarOverrides.ts:
--------------------------------------------------------------------------------
1 | import { ColorDynamic } from '../theme/Color';
2 | import { SuperDispatchTheme } from '../theme/SuperDispatchTheme';
3 |
4 | export function overrideAvatar(theme: SuperDispatchTheme): void {
5 | theme.overrides.MuiAvatar = {
6 | root: {
7 | ...theme.typography.h5,
8 | textTransform: 'uppercase',
9 |
10 | width: theme.spacing(5),
11 | height: theme.spacing(5),
12 |
13 | [theme.breakpoints.up('sm')]: {
14 | width: theme.spacing(4),
15 | height: theme.spacing(4),
16 | },
17 | },
18 |
19 | colorDefault: {
20 | color: ColorDynamic.Dark300,
21 | backgroundColor: ColorDynamic.Silver400,
22 | },
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/packages/ui/src/card/CardOverrides.ts:
--------------------------------------------------------------------------------
1 | import { SuperDispatchTheme } from '../theme/SuperDispatchTheme';
2 |
3 | export function overrideCard(theme: SuperDispatchTheme): void {
4 | theme.props.MuiCard = { variant: 'outlined' };
5 |
6 | theme.overrides.MuiCardContent = {
7 | root: { '&:last-child': { paddingBottom: undefined } },
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/packages/ui/src/chip/Chip.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Chip } from '@material-ui/core';
2 | import { PropsLink } from '@superdispatch/ui-docs';
3 | import { Inline } from '..';
4 |
5 | export default {
6 | title: 'Data Display/Chip',
7 | parameters: {
8 | componentSubtitle: (
9 |
10 | ),
11 | },
12 | };
13 |
14 | export const examples = () => (
15 |
16 |
17 |
18 |
19 | {
22 | alert('Delete!');
23 | }}
24 | />
25 |
26 | );
27 |
--------------------------------------------------------------------------------
/packages/ui/src/dialog/Dialog.playroom.tsx:
--------------------------------------------------------------------------------
1 | import { Dialog as MuiDialog, DialogProps } from '@material-ui/core';
2 | import { forwardRef } from 'react';
3 |
4 | export const Dialog = forwardRef(
5 | (
6 | {
7 | open = true,
8 | disableAutoFocus = true,
9 | disableEnforceFocus = true,
10 | disableRestoreFocus = true,
11 | ...props
12 | },
13 | ref,
14 | ) => (
15 |
23 | ),
24 | );
25 |
--------------------------------------------------------------------------------
/packages/ui/src/dialog/DialogOverrides.ts:
--------------------------------------------------------------------------------
1 | import { SuperDispatchTheme } from '../theme/SuperDispatchTheme';
2 |
3 | export function overrideDialog(theme: SuperDispatchTheme): void {
4 | theme.props.MuiDialogTitle = { disableTypography: true };
5 |
6 | theme.overrides.MuiDialog = {
7 | paper: {
8 | margin: theme.spacing(3),
9 | },
10 |
11 | paperWidthXs: {
12 | maxWidth: Math.max(theme.breakpoints.values.xs, 360),
13 |
14 | '&$paperScrollBody': {
15 | [theme.breakpoints.down(
16 | Math.max(theme.breakpoints.values.xs, 360) + 32 * 2,
17 | )]: {
18 | maxWidth: 'calc(100% - 64px)',
19 | },
20 | },
21 | },
22 | };
23 |
24 | theme.overrides.MuiDialogTitle = {
25 | root: { ...theme.typography.h3 },
26 | };
27 |
28 | theme.overrides.MuiDialogContent = {
29 | root: { padding: theme.spacing(0, 3) },
30 | };
31 |
32 | theme.overrides.MuiDialogActions = {
33 | root: { padding: theme.spacing(3) },
34 |
35 | spacing: {
36 | '& > :not(:first-child)': { marginLeft: theme.spacing(2) },
37 | },
38 | };
39 | }
40 |
--------------------------------------------------------------------------------
/packages/ui/src/drawer/Drawer.playroom.tsx:
--------------------------------------------------------------------------------
1 | import { Drawer as MuiDrawer, DrawerProps } from '@material-ui/core';
2 | import { forwardRef } from 'react';
3 |
4 | export const Drawer = forwardRef(
5 | (
6 | {
7 | disableAutoFocus = true,
8 | disableEnforceFocus = true,
9 | disableRestoreFocus = true,
10 | ...props
11 | },
12 | ref,
13 | ) => (
14 |
21 | ),
22 | );
23 |
--------------------------------------------------------------------------------
/packages/ui/src/drawer/DrawerContent.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@material-ui/styles';
2 | import { clsx } from 'clsx';
3 | import { forwardRef, HTMLAttributes } from 'react';
4 | import { SuperDispatchTheme } from '../theme/SuperDispatchTheme';
5 |
6 | const useStyles = makeStyles(
7 | (theme: SuperDispatchTheme) => ({
8 | root: {
9 | maxWidth: '100%',
10 | padding: theme.spacing(2, 3),
11 |
12 | [theme.breakpoints.up('md')]: {
13 | padding: theme.spacing(2, 4),
14 | },
15 | },
16 | }),
17 | { name: 'SD-DrawerContent' },
18 | );
19 |
20 | export type DrawerContentProps = HTMLAttributes;
21 |
22 | /** @deprecated Use `` instead. */
23 | export const DrawerContent = forwardRef(
24 | ({ className, ...props }, ref) => {
25 | const styles = useStyles();
26 |
27 | return (
28 |
29 | );
30 | },
31 | );
32 |
--------------------------------------------------------------------------------
/packages/ui/src/drawer/DrawerOverrides.ts:
--------------------------------------------------------------------------------
1 | import { SuperDispatchTheme } from '../theme/SuperDispatchTheme';
2 |
3 | /** @deprecated Use `` instead. */
4 | export function overrideDrawer(theme: SuperDispatchTheme): void {
5 | theme.props.MuiDrawer = {
6 | anchor: 'right',
7 | };
8 |
9 | theme.overrides.MuiDrawer = {
10 | paper: {
11 | maxWidth: '100%',
12 | minWidth: '100%',
13 |
14 | [theme.breakpoints.up('sm')]: {
15 | minWidth: theme.spacing(54),
16 | maxWidth: theme.breakpoints.values.sm,
17 | },
18 | },
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/packages/ui/src/dropdown-button/DropdownButton.stories.tsx:
--------------------------------------------------------------------------------
1 | import { MenuItem } from '@material-ui/core';
2 | import { Meta } from '@storybook/react';
3 | import { DropdownButton } from './DropdownButton';
4 |
5 | export default {
6 | title: 'Navigation/DropdownButton',
7 | component: DropdownButton,
8 | } as Meta;
9 |
10 | export const basic = () => (
11 |
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/packages/ui/src/info-card/InfoCard.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Typography } from '@material-ui/core';
2 | import { Meta } from '@storybook/react';
3 | import { Button } from '@superdispatch/ui-lab';
4 | import { Stack } from '../stack/Stack';
5 | import { InfoCard } from './InfoCard';
6 |
7 | export default { title: 'Surfaces/InfoCard', component: InfoCard } as Meta;
8 |
9 | export const basic = () => (
10 |
11 |
12 | Title
13 | Content
14 |
15 |
16 |
17 |
18 | );
19 |
20 | export const large = () => (
21 |
22 |
23 | Title
24 | Content
25 |
26 |
27 |
28 |
29 | );
30 |
--------------------------------------------------------------------------------
/packages/ui/src/info-card/__tests__/InfoCard.spec.tsx:
--------------------------------------------------------------------------------
1 | import { renderCSS } from '@superdispatch/ui-testutils';
2 | import { InfoCard } from '../..';
3 |
4 | it('checks component css', () => {
5 | expect(renderCSS(, ['SD-InfoCard'])).toMatchInlineSnapshot(`
6 | .SD-InfoCard-root.SD-InfoCard-fullWidth {
7 | border-radius: 0;
8 | border-left-width: 0;
9 | border-right-width: 0;
10 | }
11 |
12 | .SD-InfoCard-content {
13 | padding: 16px;
14 | }
15 |
16 | @media (min-width: 600px) {
17 | .SD-InfoCard-sizeLarge > .SD-InfoCard-content {
18 | padding: 24px;
19 | }
20 | }
21 | `);
22 | });
23 |
--------------------------------------------------------------------------------
/packages/ui/src/list/ListOverrides.ts:
--------------------------------------------------------------------------------
1 | import { alpha } from '@material-ui/core';
2 | import { Color, ColorDynamic } from '../theme/Color';
3 | import { SuperDispatchTheme } from '../theme/SuperDispatchTheme';
4 |
5 | export function overrideList(theme: SuperDispatchTheme): void {
6 | const color =
7 | theme.palette.type === 'dark'
8 | ? alpha(Color.White, 0.08)
9 | : ColorDynamic.Silver200;
10 |
11 | theme.overrides.MuiListItem = {
12 | root: {
13 | '&$selected, &$selected:hover': {
14 | backgroundColor: ColorDynamic.Blue50,
15 | },
16 |
17 | '& .MuiTouchRipple-root': {
18 | color: ColorDynamic.Blue100,
19 | },
20 | },
21 | button: {
22 | '&:hover': {
23 | backgroundColor: color,
24 | },
25 | },
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/packages/ui/src/menu/Menu.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Menu, MenuItem } from '@material-ui/core';
2 | import { Meta } from '@storybook/react';
3 | import { UseState } from '@superdispatch/ui-docs';
4 | import { Button } from '@superdispatch/ui-lab';
5 |
6 | export default { title: 'Navigation/Menu' } as Meta;
7 |
8 | export const basic = () => (
9 |
10 | {(state, setState) => (
11 | <>
12 |
20 |
21 |
32 | >
33 | )}
34 |
35 | );
36 |
--------------------------------------------------------------------------------
/packages/ui/src/menu/MenuOverrides.ts:
--------------------------------------------------------------------------------
1 | import { alpha } from '@material-ui/core';
2 | import { Color, ColorDynamic } from '../theme/Color';
3 | import { SuperDispatchTheme } from '../theme/SuperDispatchTheme';
4 |
5 | export function overrideMenu(theme: SuperDispatchTheme): void {
6 | const color =
7 | theme.palette.type === 'dark'
8 | ? alpha(Color.White, 0.08)
9 | : ColorDynamic.Silver200;
10 |
11 | theme.props.MuiMenu = {
12 | getContentAnchorEl: null,
13 | anchorOrigin: { vertical: 'bottom', horizontal: 'left' },
14 | transformOrigin: { vertical: 'top', horizontal: 'left' },
15 | };
16 |
17 | theme.overrides.MuiMenu = {
18 | paper: {
19 | border: `1px solid ${ColorDynamic.Silver400}`,
20 | },
21 | };
22 |
23 | theme.overrides.MuiMenuItem = {
24 | root: {
25 | ...theme.typography.body2,
26 | paddingTop: theme.spacing(1),
27 | paddingBottom: theme.spacing(1),
28 | '&:hover': {
29 | backgroundColor: color,
30 | },
31 | },
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/packages/ui/src/overflow-text/OverflowText.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta } from '@storybook/react';
2 | import { Box } from '@superdispatch/ui-lab';
3 | import { OverflowText } from './OverflowText';
4 |
5 | export default {
6 | title: 'Data Display/OverflowText',
7 | component: OverflowText,
8 | decorators: [
9 | (Story) => (
10 |
11 |
12 |
13 | ),
14 | ],
15 | } as Meta;
16 |
17 | export const basic = () => (
18 |
19 | Lorem ipsum dolor sit amet, consectetur adipiscing elit.
20 |
21 | );
22 |
23 | export const customTooltip = () => (
24 |
30 | Lorem ipsum dolor sit amet, consectetur adipiscing elit.
31 |
32 | );
33 |
--------------------------------------------------------------------------------
/packages/ui/src/overflow-text/__tests__/OverflowText.spec.tsx:
--------------------------------------------------------------------------------
1 | import { renderCSS } from '@superdispatch/ui-testutils';
2 | import { OverflowText } from '../OverflowText';
3 |
4 | it('checks component css', () => {
5 | expect(renderCSS(, ['SD-OverflowText']))
6 | .toMatchInlineSnapshot(`
7 | .SD-OverflowText-root {
8 | transition: border 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
9 | border-bottom: 1px dashed transparent;
10 | margin-bottom: -1px;
11 | }
12 |
13 | .SD-OverflowText-root.SD-OverflowText-truncated {
14 | cursor: pointer;
15 | border-bottom-color: ColorDynamic.Silver500;
16 | }
17 |
18 | .SD-OverflowText-sentinel {
19 | width: 1px;
20 | height: 100%;
21 | display: inline-block;
22 | }
23 | `);
24 | });
25 |
--------------------------------------------------------------------------------
/packages/ui/src/pagination/Pagination.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Pagination } from '@material-ui/lab';
2 | import { Meta } from '@storybook/react';
3 | import { Stack } from '@superdispatch/ui';
4 | import { PropsLink } from '@superdispatch/ui-docs';
5 | import { Box } from '@superdispatch/ui-lab';
6 |
7 | export default {
8 | title: 'Inputs/Pagination',
9 | component: Pagination,
10 | decorators: [
11 | (Story) => (
12 |
13 |
14 |
15 | ),
16 | ],
17 | parameters: {
18 | componentSubtitle: (
19 |
20 | ),
21 | },
22 | } as Meta;
23 |
24 | export const basic = () => (
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 |
34 | export const disabled = () => (
35 |
36 | );
37 |
--------------------------------------------------------------------------------
/packages/ui/src/paper/PaperOverrides.ts:
--------------------------------------------------------------------------------
1 | import { ColorDynamic } from '../theme/Color';
2 | import { SuperDispatchTheme } from '../theme/SuperDispatchTheme';
3 |
4 | export function overridePaper(theme: SuperDispatchTheme): void {
5 | theme.props.MuiPaper = { elevation: 0 };
6 |
7 | theme.overrides.MuiPaper = {
8 | elevation0: { border: `1px solid ${ColorDynamic.Silver400}` },
9 | };
10 | }
11 |
--------------------------------------------------------------------------------
/packages/ui/src/preloader/PreLoaderOverrides.ts:
--------------------------------------------------------------------------------
1 | import { SuperDispatchTheme } from '../theme/SuperDispatchTheme';
2 |
3 | export function overridePreloader(theme: SuperDispatchTheme): void {
4 | theme.overrides.MuiSkeleton = {
5 | text: {
6 | borderRadius: '3px',
7 | },
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/packages/ui/src/props/AlignProps.ts:
--------------------------------------------------------------------------------
1 | import { Property } from 'csstype';
2 |
3 | export type VerticalAlign = 'top' | 'center' | 'bottom';
4 | export type HorizontalAlign = 'left' | 'center' | 'right';
5 |
6 | export function parseAlignProp(
7 | align: VerticalAlign | HorizontalAlign,
8 | ): Property.AlignItems {
9 | switch (align) {
10 | case 'top':
11 | case 'left':
12 | default:
13 | return 'initial';
14 |
15 | case 'center':
16 | return 'center';
17 |
18 | case 'right':
19 | case 'bottom':
20 | return 'flex-end';
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/ui/src/props/CollapseProp.ts:
--------------------------------------------------------------------------------
1 | import { ResponsivePropTuple } from './ResponsiveProp';
2 |
3 | export type CollapseProp = 'tablet' | 'desktop';
4 |
5 | export function parseCollapsedBelow(
6 | collapsedBelow: undefined | CollapseProp,
7 | ): ResponsivePropTuple {
8 | return [
9 | collapsedBelow === 'tablet' || collapsedBelow === 'desktop',
10 | collapsedBelow === 'desktop',
11 | false,
12 | ];
13 | }
14 |
--------------------------------------------------------------------------------
/packages/ui/src/props/SpaceProp.ts:
--------------------------------------------------------------------------------
1 | export type SpaceProp =
2 | | 'none'
3 | | 'xxsmall'
4 | | 'xsmall'
5 | | 'small'
6 | | 'medium'
7 | | 'large'
8 | | 'xlarge'
9 | | 'xxlarge';
10 |
11 | export type NegativeSpaceProp =
12 | | '-xxsmall'
13 | | '-xsmall'
14 | | '-small'
15 | | '-medium'
16 | | '-large'
17 | | '-xlarge'
18 | | '-xxlarge';
19 |
20 | export function parseSpaceProp(prop: SpaceProp): number {
21 | switch (prop) {
22 | case 'none':
23 | default:
24 | return 0;
25 | case 'xxsmall':
26 | return 4;
27 | case 'xsmall':
28 | return 8;
29 | case 'small':
30 | return 16;
31 | case 'medium':
32 | return 24;
33 | case 'large':
34 | return 32;
35 | case 'xlarge':
36 | return 40;
37 | case 'xxlarge':
38 | return 48;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/packages/ui/src/radio/__tests__/RadioFieldCard.spec.tsx:
--------------------------------------------------------------------------------
1 | import { RadioFieldCard } from '../RadioFieldCard';
2 | import { RadioGroupField } from '../RadioGroupField';
3 |
4 | test('radio card component', () => {
5 | const onChange = jest.fn();
6 | const onBlur = jest.fn();
7 | expect(
8 |
14 |
15 |
16 | ,
17 | ).toBeDefined();
18 | });
19 |
--------------------------------------------------------------------------------
/packages/ui/src/responsive/CollapseBreakpoint.ts:
--------------------------------------------------------------------------------
1 | import type { Breakpoint } from '@material-ui/core/styles/createBreakpoints';
2 | import { useResponsiveContext } from './ResponsiveContext';
3 |
4 | export type CollapseBreakpoint = 'sm' | 'md' | 'lg' | 'xl';
5 |
6 | const BREAKPOINTS: Breakpoint[] = ['xs', 'sm', 'md', 'lg', 'xl'];
7 |
8 | export function useCollapseBreakpoint(
9 | collapseBreakpoint: undefined | CollapseBreakpoint,
10 | ): boolean {
11 | const { breakpoint = 'xs' } = useResponsiveContext();
12 |
13 | if (collapseBreakpoint == null) {
14 | return false;
15 | }
16 |
17 | const breakpointIDX = BREAKPOINTS.indexOf(breakpoint);
18 | const collapseBreakpointIDX = BREAKPOINTS.indexOf(collapseBreakpoint);
19 |
20 | return breakpointIDX < collapseBreakpointIDX;
21 | }
22 |
--------------------------------------------------------------------------------
/packages/ui/src/responsive/MinBreakpoint.ts:
--------------------------------------------------------------------------------
1 | import type { Breakpoint } from '@material-ui/core/styles/createBreakpoints';
2 | import { useResponsiveContext } from './ResponsiveContext';
3 |
4 | export type MinBreakpoint = 'sm' | 'md' | 'lg' | 'xl';
5 |
6 | const BREAKPOINTS: Breakpoint[] = ['xs', 'sm', 'md', 'lg', 'xl'];
7 |
8 | export function useMinBreakpoint(
9 | minBreakpoint: undefined | MinBreakpoint,
10 | ): boolean {
11 | const { breakpoint = 'xs' } = useResponsiveContext();
12 |
13 | if (minBreakpoint == null) {
14 | return false;
15 | }
16 |
17 | const breakpointIDX = BREAKPOINTS.indexOf(breakpoint);
18 | const minBreakpointIDX = BREAKPOINTS.indexOf(minBreakpoint);
19 |
20 | return minBreakpointIDX < breakpointIDX;
21 | }
22 |
--------------------------------------------------------------------------------
/packages/ui/src/snackbar/SnackbarOverrides.ts:
--------------------------------------------------------------------------------
1 | import { SuperDispatchTheme } from '../theme/SuperDispatchTheme';
2 |
3 | export function overrideSnackbar(theme: SuperDispatchTheme): void {
4 | theme.overrides.MuiSnackbar = {
5 | root: {
6 | left: 0,
7 | right: 0,
8 | bottom: 0,
9 | padding: theme.spacing(0),
10 | background: 'transparent',
11 | },
12 | anchorOriginBottomCenter: {
13 | left: 0,
14 | right: 0,
15 | bottom: 0,
16 | padding: theme.spacing(0),
17 | background: 'transparent',
18 | },
19 | };
20 |
21 | theme.overrides.MuiSnackbarContent = {
22 | root: {
23 | minHeight: theme.spacing(7.5),
24 |
25 | '&.SD-SnackbarContent-root': {
26 | width: '100%',
27 | position: 'relative',
28 | left: 0,
29 | right: 0,
30 | bottom: 0,
31 | borderRadius: theme.spacing(0),
32 |
33 | [theme.breakpoints.up('sm')]: {
34 | borderRadius: theme.spacing(0.5),
35 | },
36 | },
37 | },
38 |
39 | message: { flex: 1, display: 'flex' },
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/packages/ui/src/svg-icon/SvgIcon.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Home as HomeIcon } from '@material-ui/icons';
2 | import { PropsLink } from '@superdispatch/ui-docs';
3 | import { Inline } from '../inline/Inline';
4 |
5 | export default {
6 | title: 'Data Display/SvgIcon',
7 | parameters: {
8 | componentSubtitle: (
9 |
10 | ),
11 | },
12 | };
13 |
14 | export const basic = () => (
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 |
23 | export const customSize = () => (
24 |
25 |
26 |
27 |
28 |
29 | );
30 |
--------------------------------------------------------------------------------
/packages/ui/src/svg-icon/SvgIconOverrides.ts:
--------------------------------------------------------------------------------
1 | import { ColorDynamic } from '../theme/Color';
2 | import { SuperDispatchTheme } from '../theme/SuperDispatchTheme';
3 |
4 | export function overrideSvgIcon(theme: SuperDispatchTheme): void {
5 | const sm = theme.breakpoints.up('sm');
6 |
7 | theme.overrides.MuiSvgIcon = {
8 | root: {
9 | display: 'inherit',
10 |
11 | fontSize: 'var(--mui-svg-icon-size, 32px)',
12 | [sm]: {
13 | fontSize: 'var(--mui-svg-icon-size, 24px)',
14 | },
15 | },
16 |
17 | fontSizeSmall: {
18 | fontSize: 'var(--mui-svg-icon-size, 24px)',
19 | [sm]: {
20 | fontSize: 'var(--mui-svg-icon-size, 16px)',
21 | },
22 | },
23 |
24 | fontSizeLarge: {
25 | fontSize: 'var(--mui-svg-icon-size, 32px)',
26 | },
27 |
28 | colorAction: {
29 | color: ColorDynamic.Dark100,
30 | },
31 |
32 | colorPrimary: {
33 | color: ColorDynamic.Blue500,
34 | },
35 | };
36 | }
37 |
--------------------------------------------------------------------------------
/packages/ui/src/tabs/Tabs.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Tab, Tabs } from '@material-ui/core';
2 | import { Meta } from '@storybook/react';
3 | import { UseState } from '@superdispatch/ui-docs';
4 |
5 | export default { title: 'Navigation/Tabs' } as Meta;
6 |
7 | export const basic = () => (
8 |
9 | {(state, setState) => (
10 | {
13 | setState(next);
14 | }}
15 | >
16 |
17 |
18 |
19 |
20 | )}
21 |
22 | );
23 |
24 | export const scrollButtons = () => (
25 |
26 | {(state, setState) => (
27 | {
31 | setState(next);
32 | }}
33 | >
34 |
35 |
36 |
37 |
38 | )}
39 |
40 | );
41 |
--------------------------------------------------------------------------------
/packages/ui/src/theme/SuperDispatchTheme.ts:
--------------------------------------------------------------------------------
1 | import { Theme } from '@material-ui/core';
2 | import { SkeletonClassKey } from '@material-ui/lab';
3 |
4 | declare module '@material-ui/core/styles/overrides' {
5 | export interface ComponentNameToClassKey {
6 | MuiSkeleton: SkeletonClassKey;
7 | }
8 | }
9 |
10 | export type SuperDispatchTheme = Readonly>;
11 |
--------------------------------------------------------------------------------
/packages/ui/src/theme/__tests__/CssBaseline.spec.tsx:
--------------------------------------------------------------------------------
1 | import { renderCSS } from '@superdispatch/ui-testutils';
2 |
3 | it('checks component css', () => {
4 | expect(renderCSS(, ['MuiCssBaseline'])).toMatchInlineSnapshot(`
5 | html {
6 | box-sizing: border-box;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | *,
12 | *::before,
13 | *::after {
14 | box-sizing: inherit;
15 | }
16 |
17 | strong,
18 | b {
19 | font-weight: 600;
20 | }
21 |
22 | body {
23 | color: Color.Dark500;
24 | margin: 0;
25 | font-size: 14px;
26 | font-family: 'Inter', sans-serif;
27 | font-weight: 400;
28 | line-height: 20px;
29 | background-color: #fafafa;
30 | }
31 |
32 | @media (min-width: 0px) and (max-width: 599.95px) {
33 | body {
34 | font-size: 16px;
35 | line-height: 24px;
36 | }
37 | }
38 |
39 | @media print {
40 | body {
41 | background-color: Color.White;
42 | }
43 | }
44 |
45 | body::backdrop {
46 | background-color: #fafafa;
47 | }
48 | `);
49 | });
50 |
--------------------------------------------------------------------------------
/packages/ui/src/toolbar/ToolbarOverrides.ts:
--------------------------------------------------------------------------------
1 | import { SuperDispatchTheme } from '../theme/SuperDispatchTheme';
2 |
3 | export function overrideToolbar(theme: SuperDispatchTheme): void {
4 | theme.overrides.MuiToolbar = {
5 | regular: { minHeight: theme.spacing(8) },
6 |
7 | gutters: {
8 | [theme.breakpoints.up('sm')]: {
9 | paddingLeft: theme.spacing(2),
10 | paddingRight: theme.spacing(2),
11 | },
12 | },
13 | };
14 | }
15 |
--------------------------------------------------------------------------------
/packages/ui/src/tooltip/Tooltip.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Tooltip } from '@material-ui/core';
2 | import { PropsLink } from '@superdispatch/ui-docs';
3 | import { Button } from '@superdispatch/ui-lab';
4 | import { Inline } from '../inline/Inline';
5 |
6 | export default {
7 | title: 'Data Display/Tooltip',
8 | parameters: {
9 | componentSubtitle: (
10 |
11 | ),
12 | },
13 | };
14 |
15 | export const basic = () => (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | );
34 |
--------------------------------------------------------------------------------
/packages/ui/src/tooltip/TooltipOverrides.ts:
--------------------------------------------------------------------------------
1 | import { ColorDynamic } from '../theme/Color';
2 | import { SuperDispatchTheme } from '../theme/SuperDispatchTheme';
3 |
4 | export function overrideTooltip(theme: SuperDispatchTheme): void {
5 | theme.overrides.MuiTooltip = {
6 | tooltip: {
7 | ...theme.typography.body2,
8 | padding: theme.spacing(1, 1.5),
9 | backgroundColor: ColorDynamic.Dark500,
10 | '--sd-dark-300': ColorDynamic.Silver500, //tooltip secondary color(Dark300) is invisible in dark mode
11 | },
12 |
13 | popperArrow: {
14 | '&[x-placement*="top"] $arrow': {
15 | '&::before': { borderBottomRightRadius: 2 },
16 | },
17 | '&[x-placement*="left"] $arrow': {
18 | '&::before': { borderTopRightRadius: 2 },
19 | },
20 | '&[x-placement*="right"] $arrow': {
21 | '&::before': { borderBottomLeftRadius: 2 },
22 | },
23 | '&[x-placement*="bottom"] $arrow': {
24 | '&::before': { borderTopLeftRadius: 2 },
25 | },
26 | },
27 |
28 | arrow: {
29 | color: ColorDynamic.Silver500,
30 | fontSize: theme.spacing(1),
31 | },
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/packages/ui/src/utils/ExitTransitionPlaceholder.tsx:
--------------------------------------------------------------------------------
1 | import { ReactElement, ReactNode, useEffect, useState } from 'react';
2 | import { renderChildren } from './renderChildren';
3 |
4 | interface ExitTransitionPlaceholderProps {
5 | in: boolean;
6 | children: ReactNode;
7 | }
8 |
9 | export function ExitTransitionPlaceholder({
10 | in: inProp,
11 | children: childrenProp,
12 | }: ExitTransitionPlaceholderProps): null | ReactElement {
13 | const [children, setChildren] = useState(childrenProp);
14 |
15 | useEffect(() => {
16 | if (inProp) {
17 | setChildren(childrenProp);
18 | }
19 | }, [inProp, childrenProp]);
20 |
21 | return renderChildren(children);
22 | }
23 |
--------------------------------------------------------------------------------
/packages/ui/src/utils/ResizeObserver.tsx:
--------------------------------------------------------------------------------
1 | import { ResizeObserver } from '@juggle/resize-observer';
2 | import { useEventHandler } from '@superdispatch/hooks';
3 | import { useLayoutEffect } from 'react';
4 |
5 | export function useResizeObserver(
6 | node: null | undefined | T,
7 | observer: (node: T) => void,
8 | ): void {
9 | const handler = useEventHandler(observer);
10 |
11 | useLayoutEffect(() => {
12 | if (!node) {
13 | return;
14 | }
15 |
16 | const resizeObserver = new ResizeObserver(() => {
17 | handler(node);
18 | });
19 |
20 | resizeObserver.observe(node);
21 |
22 | handler(node);
23 |
24 | return () => {
25 | resizeObserver.disconnect();
26 | };
27 | }, [node, handler]);
28 | }
29 |
--------------------------------------------------------------------------------
/packages/ui/src/utils/__tests__/ExitTransitionPlaceholder.spec.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import { ExitTransitionPlaceholder } from '../ExitTransitionPlaceholder';
3 |
4 | it('render null child', () => {
5 | const child = null;
6 | const { container } = render(
7 | {child},
8 | );
9 | expect(container).toBeEmptyDOMElement();
10 | });
11 |
12 | it('render null with boolean child', () => {
13 | const child = true;
14 | const { container } = render(
15 | {child},
16 | );
17 | expect(container).toBeEmptyDOMElement();
18 | });
19 |
20 | it('render child', () => {
21 | const { rerender } = render(
22 |
23 | Test
24 | ,
25 | );
26 | expect(screen.getByText('Test')).toBeInTheDocument();
27 |
28 | rerender(
29 |
30 |
31 | ,
32 | );
33 | expect(screen.getByText('Test')).toBeInTheDocument();
34 | });
35 |
--------------------------------------------------------------------------------
/packages/ui/src/utils/isEmptyReactNode.ts:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react';
2 |
3 | export function isEmptyReactNode(
4 | node: ReactNode,
5 | ): node is null | undefined | boolean | string {
6 | return (
7 | node == null ||
8 | typeof node == 'boolean' ||
9 | (typeof node == 'string' && node.length === 0)
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/packages/ui/src/utils/mergeRefs.ts:
--------------------------------------------------------------------------------
1 | import { MutableRefObject, Ref } from 'react';
2 |
3 | export function mergeRefs(
4 | ...refs: Array>
5 | ): (node: T) => void {
6 | return (node) => {
7 | refs.forEach((ref) => {
8 | assignRef(ref, node);
9 | });
10 | };
11 | }
12 |
13 | export function assignRef(ref: Ref | undefined, value: T): void {
14 | if (ref) {
15 | if (typeof ref === 'function') {
16 | ref(value);
17 | } else {
18 | (ref as MutableRefObject).current = value;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/ui/src/utils/renderChildren.ts:
--------------------------------------------------------------------------------
1 | import { ReactElement, ReactNode } from 'react';
2 |
3 | export function renderChildren(node: ReactNode): null | ReactElement {
4 | if (node == null || typeof node == 'boolean') {
5 | return null;
6 | }
7 |
8 | // Workaround for https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
9 | return node as ReactElement;
10 | }
11 |
--------------------------------------------------------------------------------
/packages/ui/src/utils/useUID.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 |
3 | let current = 0;
4 |
5 | export function useUID(defaultID?: string): string {
6 | const uid = useMemo(() => `uid_${(current += 1)}`, []);
7 |
8 | return defaultID || uid;
9 | }
10 |
--------------------------------------------------------------------------------
/packages/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.build",
3 | "exclude": [
4 | "pkg",
5 | "playroom.ts",
6 | "**/*.spec.*",
7 | "**/*.stories.*",
8 | "**/*.playroom.*",
9 | "**/__tests__/**",
10 | "**/__testutils__/**"
11 | ],
12 | "references": [{ "path": "../hooks" }, { "path": "../__testutils__" }],
13 | "compilerOptions": {
14 | "composite": true,
15 | "rootDir": "src",
16 | "outDir": "pkg/dist-types",
17 | "tsBuildInfoFile": "pkg/dist-types/.tsbuildinfo"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/patches/@pika+pack+0.5.0.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/@pika/pack/dist-node/index.js b/node_modules/@pika/pack/dist-node/index.js
2 | index 585902c..4a19167 100644
3 | --- a/node_modules/@pika/pack/dist-node/index.js
4 | +++ b/node_modules/@pika/pack/dist-node/index.js
5 | @@ -2210,7 +2210,7 @@ async function generatePublishManifest(manifest, config, _dists) {
6 | version,
7 | license,
8 | bin,
9 | - files: ['dist-*/', 'bin/'],
10 | + files: ['dist-*/**', 'bin/'],
11 | pika: true,
12 | sideEffects: sideEffects || false,
13 | keywords,
14 |
--------------------------------------------------------------------------------
/scripts/plop-templates/README.hbs:
--------------------------------------------------------------------------------
1 | # {{pascalCase name}} component
2 |
3 | #### Was made during task:
4 |
5 | **link to task**
6 |
7 | ####Description:
8 |
9 | **place your text here**
--------------------------------------------------------------------------------
/scripts/plop-templates/lab-component/component.hbs:
--------------------------------------------------------------------------------
1 | import { forwardRef } from 'react';
2 | import styled from 'styled-components';
3 |
4 | // Import your necessary UI elements and other libraries here
5 |
6 | // Define your CSS mixins, animations, and other styled-components here
7 |
8 | const Styled{{pascalCase name}} = styled(`div`)`
9 | /* Some CSS styles here */
10 | `;
11 |
12 | export interface {{pascalCase name}}Props {
13 | /* Some component props here */
14 | }
15 |
16 | export const {{pascalCase name}} = forwardRef(
17 | (props, ref) => {
18 | // Use useEffect, useState and other hooks as per your need
19 |
20 | // Implement your component's functionality here
21 |
22 | return (
23 |
24 | {/* Render your component's children or other UI elements here */}
25 |
26 | );
27 | },
28 | );
29 |
30 | {{pascalCase name}}.displayName = '{{pascalCase name}}';
--------------------------------------------------------------------------------
/scripts/plop-templates/lab-component/story.hbs:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Meta } from '@storybook/react';
3 | import { {{pascalCase name}} } from './{{pascalCase name}}';
4 |
5 | export default { title: 'Lab/{{pascalCase name}}', component: {{pascalCase name}} } as Meta;
6 |
7 | export const basic = () => (
8 | <{{pascalCase name}}>
9 | {/* Some component props and children here */}
10 | {{pascalCase name}}>
11 | );
12 |
--------------------------------------------------------------------------------
/scripts/plop-templates/lab-component/test.hbs:
--------------------------------------------------------------------------------
1 | import { renderComponent } from '@superdispatch/ui-testutils';
2 | import { {{pascalCase name}} } from '../{{pascalCase name}}';
3 | import { screen } from '@testing-library/react';
4 |
5 | test('{{pascalCase name}} renders', () => {
6 | renderComponent(<{{pascalCase name}}>{{pascalCase name}}{{pascalCase name}}>);
7 | expect(screen.getByTestId('{{camelCase name}}')).toMatchSnapshot();
8 | });
9 |
--------------------------------------------------------------------------------
/scripts/plop-templates/overrides/component.hbs:
--------------------------------------------------------------------------------
1 | import { Color } from '../theme/Color';
2 | import { SuperDispatchTheme } from '../theme/SuperDispatchTheme';
3 |
4 | export function override{{pascalCase name}}(theme: SuperDispatchTheme): void {
5 | theme.props.Mui{{pascalCase name}} = {
6 | // props configuration here
7 | };
8 |
9 | theme.overrides.Mui{{pascalCase name}} = {
10 | root: {
11 | // root configuration here
12 | },
13 |
14 | // add other necessary classes configurations here
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/scripts/plop-templates/overrides/playroom.hbs:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { {{name}} as Mui{{name}}, {{name}}Props } from '@material-ui/core';
3 | import { forwardRef } from 'react';
4 |
5 | export const {{name}} = forwardRef(
6 | ({ ...props }, ref) => (
7 |
11 | ),
12 | );
13 |
--------------------------------------------------------------------------------
/scripts/plop-templates/overrides/story.hbs:
--------------------------------------------------------------------------------
1 | import { Meta } from '@storybook/react';
2 | import { PropsLink } from '@superdispatch/ui-docs';
3 | import { {{pascalCase name}} } from '@material-ui/core';
4 |
5 | export default {
6 | title: '{{title}}/{{pascalCase name}}',
7 | parameters: {
8 | componentSubtitle: (
9 |
10 | ),
11 | },
12 | } as Meta;
13 |
14 | export const basic = () => (
15 | <{{pascalCase name}}>
16 | {
17 | // TODO: Add your component details here
18 | }
19 | {{pascalCase name}}>
20 | );
21 |
--------------------------------------------------------------------------------
/scripts/plop-templates/overrides/test.hbs:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { renderCSS } from '@superdispatch/ui-testutils';
3 | import { {{name}} } from '@material-ui/core';
4 |
5 | it('checks component css', () => {
6 | expect(renderCSS(<{{name}} />, ['SD-{{name}}'])).toMatchInlineSnapshot(`
7 | //TODO: Write component's CSS Snapshot
8 | `);
9 | });
10 |
11 | //TODO: Add more tests
12 |
--------------------------------------------------------------------------------
/scripts/remove-old-tags.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import * as execa from 'execa';
4 | import * as semver from 'semver';
5 | import * as lerna from '../lerna.json';
6 |
7 | const version = semver.parse(lerna.version);
8 |
9 | if (!version) {
10 | throw new Error('Failed to parse lerna version');
11 | }
12 |
13 | const { stdout: rawTags } = execa.sync('git', ['tag']);
14 | const versionRange = `>=${version.major}.${Math.max(0, version.minor - 1)}`;
15 |
16 | const tags = rawTags
17 | .trim()
18 | .split('\n')
19 | .filter((tag) => !semver.valid(tag) || !semver.satisfies(tag, versionRange));
20 |
21 | if (tags.length > 0) {
22 | console.log('Removing %d tags…', tags.length);
23 | execa.sync('git', ['tag', '-d', ...tags]);
24 | execa.sync('git', ['push', '--delete', 'origin', ...tags]);
25 | } else {
26 | console.log('There are no outdated tags 🎉');
27 | }
28 |
--------------------------------------------------------------------------------
/setupTests.ts:
--------------------------------------------------------------------------------
1 | import { spyLogs } from '@superdispatch/jestutils';
2 | import { resetMockDate } from '@superdispatch/ui-testutils';
3 | import '@testing-library/jest-dom';
4 | import { ForwardRef } from 'react-is';
5 |
6 | spyLogs({ warn: 'forbid', error: 'forbid' });
7 |
8 | afterEach(resetMockDate);
9 |
10 | // Mock `getComputedStyle` to workaround `accessible-name-and-description`
11 | // selector of the `dom-accessibility-api`.
12 | Object.defineProperty(window, 'getComputedStyle', {
13 | writable: false,
14 | enumerable: false,
15 | configurable: false,
16 | value: () => ({ getPropertyValue: () => '' }),
17 | });
18 |
19 | // @eslint/eslintrc uses document.baseURI in cjs builds.
20 | // Remove when migrating tests to ESModules.
21 | Object.defineProperty(document, 'baseURI', {
22 | value: 'file:',
23 | writable: true,
24 | });
25 |
26 | expect.addSnapshotSerializer({
27 | test: (value) => value?.$$typeof === ForwardRef,
28 | serialize: (value) => `React.forwardRef(${value.displayName || 'unknown'})`,
29 | });
30 |
--------------------------------------------------------------------------------
/tools/codemods/normalize-story-name.js:
--------------------------------------------------------------------------------
1 | // yarn codemod --plugin ./tools/codemods/normalize-story-name.js packages/
2 |
3 | 'use strict';
4 |
5 | const { camelCase } = require('lodash');
6 |
7 | module.exports = ({ types }) => ({
8 | visitor: {
9 | ExportNamedDeclaration(path, { filename }) {
10 | const { node } = path;
11 |
12 | if (!filename || !filename.endsWith('.stories.tsx')) {
13 | return;
14 | }
15 |
16 | if (!types.isVariableDeclaration(node.declaration)) {
17 | return;
18 | }
19 |
20 | const { declaration } = node;
21 |
22 | for (const declarator of declaration.declarations) {
23 | if (!types.isVariableDeclarator(declarator)) {
24 | continue;
25 | }
26 |
27 | const { id, init } = declarator;
28 |
29 | if (!types.isArrowFunctionExpression(init)) {
30 | continue;
31 | }
32 |
33 | const camelCaseID = camelCase(id.name);
34 |
35 | if (id.name !== camelCaseID) {
36 | declarator.id = types.identifier(camelCaseID);
37 | }
38 | }
39 | },
40 | },
41 | });
42 |
--------------------------------------------------------------------------------
/tools/codemods/use-dark-color.js:
--------------------------------------------------------------------------------
1 | // yarn codemod --plugin ./tools/codemods/use-dark-color.js packages/
2 |
3 | 'use strict';
4 |
5 | module.exports = ({ types }) => ({
6 | visitor: {
7 | JSXAttribute({ node }) {
8 | if (
9 | types.isStringLiteral(node.value) &&
10 | node.value.value.startsWith('Grey')
11 | ) {
12 | node.value = types.stringLiteral(
13 | node.value.value.replace('Grey', 'Dark'),
14 | );
15 | }
16 | },
17 | MemberExpression(path) {
18 | const { node } = path;
19 |
20 | if (
21 | node.object.name === 'Color' &&
22 | types.isIdentifier(node.property) &&
23 | node.property.name.startsWith('Grey')
24 | ) {
25 | node.property = types.identifier(
26 | node.property.name.replace('Grey', 'Dark'),
27 | );
28 | }
29 | },
30 | },
31 | });
32 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@superdispatch/tsconfig",
3 | "compilerOptions": {
4 | "noEmit": false
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["**/pkg/**/*"],
3 | "include": ["scripts", "packages", "setupTests.ts", "globalSetup.ts"],
4 | "extends": "@superdispatch/tsconfig",
5 | "compilerOptions": {
6 | "module": "CommonJS",
7 | "esModuleInterop": false,
8 | "allowSyntheticDefaultImports": false,
9 | "tsBuildInfoFile": "./node_modules/.cache/tsconfig.tsbuildinfo"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------