@moda/om 19.2.0 → 19.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +6 -6
  2. package/dist/index.cjs.js +2632 -2172
  3. package/dist/index.cjs.js.map +1 -1
  4. package/dist/index.esm.js +2631 -2174
  5. package/dist/index.esm.js.map +1 -1
  6. package/dist/src/components/Breakpoint/Breakpoint.d.ts +6 -0
  7. package/dist/src/components/Confirm/Confirm.d.ts +32 -0
  8. package/dist/src/components/Confirm/Confirm.stories.d.ts +5 -0
  9. package/dist/src/components/Confirm/Confirm.test.d.ts +1 -0
  10. package/dist/src/components/Confirm/index.d.ts +1 -0
  11. package/dist/src/components/Dialog/Dialog.d.ts +9 -0
  12. package/dist/src/components/Dialog/Dialog.stories.d.ts +6 -0
  13. package/dist/src/components/Dialog/Dialog.test.d.ts +1 -0
  14. package/dist/src/components/Dialog/index.d.ts +1 -0
  15. package/dist/src/components/Select/Select.d.ts +1 -0
  16. package/dist/src/components/index.d.ts +7 -5
  17. package/dist/styles.css +1 -1
  18. package/package.json +91 -88
  19. package/src/components/Breakpoint/Breakpoint.tsx +3 -3
  20. package/src/components/Confirm/Confirm.stories.tsx +35 -0
  21. package/src/components/Confirm/Confirm.test.tsx +44 -0
  22. package/src/components/Confirm/Confirm.tsx +90 -0
  23. package/src/components/Confirm/index.ts +1 -0
  24. package/src/components/Dialog/Dialog.scss +15 -0
  25. package/src/components/Dialog/Dialog.stories.tsx +28 -0
  26. package/src/components/Dialog/Dialog.test.tsx +38 -0
  27. package/src/components/Dialog/Dialog.tsx +33 -0
  28. package/src/components/Dialog/index.ts +1 -0
  29. package/src/components/Select/Select.tsx +1 -0
  30. package/src/components/index.ts +7 -5
  31. package/CHANGELOG.md +0 -764
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import { render, screen, waitForElementToBeRemoved } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+
5
+ import { ConfirmProvider, useConfirm } from './Confirm';
6
+
7
+ const ConfirmComponent: React.FC = () => {
8
+ const { confirm } = useConfirm();
9
+
10
+ return (
11
+ <button onClick={() => confirm({ title: 'Are you sure?', confirmationText: 'Yes' })}>
12
+ Click Me
13
+ </button>
14
+ );
15
+ };
16
+
17
+ describe('Dialog', () => {
18
+ it('should show the confirmation and hide it on confirm/cancel', async () => {
19
+ render(
20
+ <ConfirmProvider>
21
+ <ConfirmComponent />
22
+ </ConfirmProvider>
23
+ );
24
+
25
+ // Should not show by default
26
+ expect(screen.queryByText('Are you sure?')).not.toBeInTheDocument();
27
+
28
+ // Should show after clicking button
29
+ await userEvent.click(screen.getByRole('button', { name: 'Click Me' }));
30
+ await screen.findByText('Are you sure?');
31
+
32
+ // Should hide after clicking Yes
33
+ await userEvent.click(screen.getByRole('button', { name: 'Yes' }));
34
+ await waitForElementToBeRemoved(() => screen.queryByText('Are you sure?'));
35
+
36
+ // Should show after clicking button
37
+ await userEvent.click(screen.getByRole('button', { name: 'Click Me' }));
38
+ await screen.findByText('Are you sure?');
39
+
40
+ // Should hide after clicking Cancel
41
+ await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
42
+ await waitForElementToBeRemoved(() => screen.queryByText('Are you sure?'));
43
+ });
44
+ });
@@ -0,0 +1,90 @@
1
+ import React, { ReactNode, createContext, useCallback, useContext, useMemo, useState } from 'react';
2
+ import { Dialog } from '../Dialog';
3
+ import { Button } from '../Button';
4
+
5
+ export type TConfirmOptions = {
6
+ title: ReactNode;
7
+ message: ReactNode;
8
+ confirmationText: ReactNode;
9
+ cancellationText: ReactNode;
10
+ };
11
+
12
+ type TConfirm = (options?: Partial<TConfirmOptions>) => Promise<boolean>;
13
+
14
+ const DEFAULT_OPTIONS: TConfirmOptions = {
15
+ title: 'Are you sure?',
16
+ message: '',
17
+ confirmationText: 'Ok',
18
+ cancellationText: 'Cancel'
19
+ };
20
+
21
+ const ConfirmContext = createContext<{ confirm: TConfirm }>({
22
+ confirm: () => Promise.resolve(false)
23
+ });
24
+
25
+ export const ConfirmProvider: React.FC<{ children?: ReactNode }> = ({ children }) => {
26
+ const [options, setOptions] = useState<Partial<TConfirmOptions>>({});
27
+ const [resolve, setResolve] = useState<((confirmed: boolean) => void) | null>(null);
28
+
29
+ const { title, message, confirmationText, cancellationText } = { ...DEFAULT_OPTIONS, ...options };
30
+
31
+ const confirm = useCallback(
32
+ (options?: Partial<TConfirmOptions>) =>
33
+ new Promise<boolean>(resolve => {
34
+ setOptions(options ?? {});
35
+ setResolve(() => resolve);
36
+ }),
37
+ []
38
+ );
39
+
40
+ const handleCancel = useCallback(() => {
41
+ resolve?.(false);
42
+ setResolve(null);
43
+ }, [resolve]);
44
+
45
+ const handleConfirm = useCallback(() => {
46
+ resolve?.(true);
47
+ setResolve(null);
48
+ }, [resolve]);
49
+
50
+ return (
51
+ <ConfirmContext.Provider value={useMemo(() => ({ confirm }), [confirm])}>
52
+ {children}
53
+ <Dialog
54
+ show={resolve != null}
55
+ onClose={handleCancel}
56
+ title={title}
57
+ message={message}
58
+ actions={
59
+ <>
60
+ {cancellationText && (
61
+ <Button secondary onClick={handleCancel}>
62
+ {cancellationText}
63
+ </Button>
64
+ )}
65
+ <Button onClick={handleConfirm}>{confirmationText}</Button>
66
+ </>
67
+ }
68
+ />
69
+ </ConfirmContext.Provider>
70
+ );
71
+ };
72
+
73
+ /**
74
+ * Shows a confirmation dialog and returns a Promise<boolean> representing the user choice (resolves with true on confirmation and with false on cancellation).
75
+ *
76
+ * Inspired by https://www.npmjs.com/package/material-ui-confirm
77
+ *
78
+ * Usage:
79
+ * ```
80
+ * const {confirm} = useConfirm();
81
+ *
82
+ * confirm({
83
+ * title: 'Are you sure you want to delete this?',
84
+ * confirmationText: 'Delete'
85
+ * }).then(confirmed => {
86
+ * // ...
87
+ * });
88
+ * ```
89
+ */
90
+ export const useConfirm = () => useContext(ConfirmContext);
@@ -0,0 +1 @@
1
+ export * from './Confirm';
@@ -0,0 +1,15 @@
1
+ @import '~om';
2
+
3
+ .Dialog {
4
+ max-width: 90vw;
5
+ background-color: color('snow');
6
+ text-align: center;
7
+
8
+ &__content {
9
+ padding: spacing(6);
10
+ }
11
+
12
+ @include breakpoint(md) {
13
+ max-width: 40rem;
14
+ }
15
+ }
@@ -0,0 +1,28 @@
1
+ import React, { useState } from 'react';
2
+
3
+ import { Button } from '../Button';
4
+ import { Dialog } from './Dialog';
5
+
6
+ export default { title: 'Components/Dialog' };
7
+
8
+ enum Mode {
9
+ Resting,
10
+ Open
11
+ }
12
+
13
+ export const Default = () => {
14
+ const [mode, setMode] = useState(Mode.Resting);
15
+
16
+ return (
17
+ <>
18
+ <Button onClick={() => setMode(Mode.Open)}>Open Dialog</Button>
19
+ <Dialog
20
+ show={mode === Mode.Open}
21
+ onClose={() => setMode(Mode.Resting)}
22
+ title='Exclusive Offer'
23
+ message='Get 20% off your next purchase when you sign up for our newsletter today. Be the first to know about our latest products, promotions, and exclusive deals!'
24
+ actions={<Button onClick={() => setMode(Mode.Resting)}>OK</Button>}
25
+ />
26
+ </>
27
+ );
28
+ };
@@ -0,0 +1,38 @@
1
+ import React, { useState } from 'react';
2
+ import { render, screen, waitForElementToBeRemoved } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+
5
+ import { Dialog } from './Dialog';
6
+
7
+ const DialogComponent: React.FC = () => {
8
+ const [showDialog, setShowDialog] = useState(false);
9
+
10
+ return (
11
+ <>
12
+ <button onClick={() => setShowDialog(true)}>Click Me</button>
13
+ <Dialog
14
+ show={showDialog}
15
+ onClose={() => setShowDialog(false)}
16
+ title='This is a dialog'
17
+ actions={<button onClick={() => setShowDialog(false)}>Close</button>}
18
+ />
19
+ </>
20
+ );
21
+ };
22
+
23
+ describe('Dialog', () => {
24
+ it('should open and close the dialog', async () => {
25
+ render(<DialogComponent />);
26
+
27
+ // Should not show by default
28
+ expect(screen.queryByText('This is a dialog')).not.toBeInTheDocument();
29
+
30
+ // Should show after clicking button
31
+ await userEvent.click(screen.getByRole('button', { name: 'Click Me' }));
32
+ await screen.findByText('This is a dialog');
33
+
34
+ // Should hide after clicking Close
35
+ await userEvent.click(screen.getByRole('button', { name: 'Close' }));
36
+ await waitForElementToBeRemoved(() => screen.queryByText('This is a dialog'));
37
+ });
38
+ });
@@ -0,0 +1,33 @@
1
+ import React, { ReactNode } from 'react';
2
+ import classNames from 'classnames';
3
+ import { ModalOverlay, ModalOverlayProps } from '../ModalOverlay';
4
+ import { Stack } from '../Stack';
5
+ import { Text } from '../Text';
6
+
7
+ import './Dialog.scss';
8
+
9
+ export type DialogProps = Omit<ModalOverlayProps, 'title'> & {
10
+ title?: ReactNode;
11
+ message?: ReactNode;
12
+ actions?: ReactNode;
13
+ };
14
+
15
+ export const Dialog: React.FC<DialogProps> = ({
16
+ title,
17
+ message,
18
+ actions,
19
+ contentClassName,
20
+ ...rest
21
+ }) => (
22
+ <ModalOverlay contentClassName={classNames('Dialog', contentClassName)} {...rest}>
23
+ <Stack className='Dialog__content' space={6}>
24
+ {title && <Text treatment='h5'>{title}</Text>}
25
+ {message && <Text>{message}</Text>}
26
+ {actions && (
27
+ <Stack direction='horizontal' space={3} alignItems='center' justifyContent='center'>
28
+ {actions}
29
+ </Stack>
30
+ )}
31
+ </Stack>
32
+ </ModalOverlay>
33
+ );
@@ -0,0 +1 @@
1
+ export * from './Dialog';
@@ -26,6 +26,7 @@ export type SelectProps = Omit<
26
26
  name?: string;
27
27
  onChange?: (value: string) => void;
28
28
  options: SelectableOption[];
29
+ placeholder?: string;
29
30
  searchable?: boolean;
30
31
  shiftIconLeftwards?: boolean;
31
32
  value?: string | undefined;
@@ -6,13 +6,15 @@ export * from './Button';
6
6
  export * from './Checkbox';
7
7
  export * from './Clickable';
8
8
  export * from './ColorSwatch';
9
- export * from './ControlLink';
9
+ export * from './Confirm';
10
10
  export * from './Constrain';
11
+ export * from './ControlLink';
11
12
  export * from './CreditCardNumberInput';
12
13
  export * from './DefinitionList';
14
+ export * from './Dialog';
15
+ export * from './Divider';
13
16
  export * from './Expandable';
14
17
  export * from './Field';
15
- export * from './Divider';
16
18
  export * from './Label';
17
19
  export * from './Loading';
18
20
  export * from './LoadingBalls';
@@ -22,6 +24,7 @@ export * from './ModalOverlay';
22
24
  export * from './NavigationPreventer';
23
25
  export * from './Overlay';
24
26
  export * from './Paginator';
27
+ export * from './PasswordInput';
25
28
  export * from './Popover';
26
29
  export * from './PromoBanner';
27
30
  export * from './RadioButton';
@@ -29,13 +32,12 @@ export * from './SearchInput';
29
32
  export * from './Select';
30
33
  export * from './SelectableButton';
31
34
  export * from './Shape';
35
+ export * from './SlidingPane';
32
36
  export * from './Stack';
33
- export * from './Tag';
34
37
  export * from './Tabs';
38
+ export * from './Tag';
35
39
  export * from './Text';
36
40
  export * from './Textarea';
37
41
  export * from './TextInput';
38
42
  export * from './Toast';
39
43
  export * from './VerticalDivider';
40
- export * from './PasswordInput';
41
- export * from './SlidingPane';