paris 0.3.0 → 0.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 (55) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/package.json +14 -12
  3. package/src/pages/_app.tsx +1 -1
  4. package/src/pages/index.tsx +1 -1
  5. package/src/stories/Pagination.mdx +73 -0
  6. package/src/stories/button/Button.module.scss +11 -1
  7. package/src/stories/button/Button.tsx +40 -17
  8. package/src/stories/card/Card.module.scss +14 -0
  9. package/src/stories/card/Card.stories.ts +33 -0
  10. package/src/stories/card/Card.tsx +55 -0
  11. package/src/stories/card/index.ts +1 -0
  12. package/src/stories/checkbox/Checkbox.module.scss +57 -0
  13. package/src/stories/checkbox/Checkbox.stories.ts +27 -0
  14. package/src/stories/checkbox/Checkbox.tsx +58 -0
  15. package/src/stories/checkbox/index.ts +1 -0
  16. package/src/stories/combobox/Combobox.module.scss +5 -0
  17. package/src/stories/combobox/Combobox.stories.ts +84 -0
  18. package/src/stories/combobox/Combobox.tsx +264 -0
  19. package/src/stories/combobox/index.ts +1 -0
  20. package/src/stories/dialog/Dialog.module.scss +187 -0
  21. package/src/stories/dialog/Dialog.stories.tsx +70 -0
  22. package/src/stories/dialog/Dialog.tsx +279 -0
  23. package/src/stories/dialog/index.ts +1 -0
  24. package/src/stories/drawer/Drawer.module.scss +284 -0
  25. package/src/stories/drawer/Drawer.stories.tsx +94 -0
  26. package/src/stories/drawer/Drawer.tsx +339 -0
  27. package/src/stories/drawer/index.ts +1 -0
  28. package/src/stories/field/Field.module.scss +5 -0
  29. package/src/stories/field/Field.stories.ts +32 -0
  30. package/src/stories/field/Field.tsx +106 -0
  31. package/src/stories/field/index.ts +1 -0
  32. package/src/stories/icon/ChevronLeft.tsx +11 -0
  33. package/src/stories/icon/ChevronRight.tsx +11 -0
  34. package/src/stories/icon/Close.tsx +11 -0
  35. package/src/stories/icon/Icon.module.scss +5 -0
  36. package/src/stories/icon/Icon.stories.ts +28 -0
  37. package/src/stories/icon/Icon.tsx +46 -0
  38. package/src/stories/icon/index.ts +4 -0
  39. package/src/stories/input/Input.module.scss +3 -2
  40. package/src/stories/input/Input.stories.ts +2 -0
  41. package/src/stories/input/Input.tsx +38 -73
  42. package/src/stories/pagination/index.ts +1 -0
  43. package/src/stories/pagination/usePagination.ts +106 -0
  44. package/src/stories/select/Select.module.scss +8 -4
  45. package/src/stories/select/Select.stories.ts +5 -3
  46. package/src/stories/select/Select.tsx +80 -7
  47. package/src/stories/theme/themes.ts +75 -2
  48. package/src/stories/theme/tw-preflight.css +3 -1
  49. package/src/stories/tilt/Tilt.module.scss +1 -0
  50. package/src/stories/tilt/Tilt.stories.tsx +43 -0
  51. package/src/stories/tilt/Tilt.tsx +65 -0
  52. package/src/stories/tilt/index.ts +1 -0
  53. package/src/stories/utility/RemoveFromDOM.tsx +19 -0
  54. package/src/stories/utility/TextWhenString.tsx +28 -0
  55. package/src/stories/utility/VisuallyHidden.tsx +25 -0
@@ -0,0 +1,279 @@
1
+ 'use client';
2
+
3
+ import type {
4
+ ComponentPropsWithoutRef, FC, MouseEventHandler, PropsWithChildren, ReactNode,
5
+ } from 'react';
6
+ import { Dialog as HDialog, Transition } from '@headlessui/react';
7
+ import { Fragment, useEffect, useState } from 'react';
8
+ import clsx from 'clsx';
9
+ import styles from './Dialog.module.scss';
10
+ import { Text } from '../text';
11
+ import { Button } from '../button';
12
+ import { pvar } from '../theme';
13
+ import { TextWhenString } from '../utility/TextWhenString';
14
+ import { VisuallyHidden } from '../utility/VisuallyHidden';
15
+ import { RemoveFromDOM } from '../utility/RemoveFromDOM';
16
+ import { Close, Icon } from '../icon';
17
+
18
+ export type DialogProps = {
19
+ /**
20
+ * The dialog's open state.
21
+ */
22
+ isOpen?: boolean;
23
+ /**
24
+ * A callback that will be called when the user closes the dialog by clicking the close button or the backdrop overlay.
25
+ * @param value {boolean} - The new open state of the dialog.
26
+ */
27
+ onClose?: (value: boolean) => void | Promise<void>;
28
+ /**
29
+ * The title of the dialog. Required for accessibility, but can be hidden with the `hideTitle` prop.
30
+ *
31
+ * If a string is passed, it will be wrapped in a {@link Text} component with `headingXSmall` styling.
32
+ */
33
+ title: ReactNode;
34
+ /**
35
+ * Whether the title should be hidden. If `true`, the title will be visually hidden but still accessible to screen readers.
36
+ *
37
+ * If you're hiding the title to add a custom header, you can also hide the close button and render your own by using the `hideCloseButton` prop.
38
+ *
39
+ * @default false
40
+ */
41
+ hideTitle?: boolean;
42
+ /**
43
+ * Whether the close button should be hidden. This will entirely remove the close button from the DOM, so you should provide your own way to close the dialog.
44
+ *
45
+ * @default false
46
+ */
47
+ hideCloseButton?: boolean;
48
+ /**
49
+ * The width of the dialog.
50
+ *
51
+ * @default 'default'
52
+ */
53
+ width?: 'compact' | 'default' | 'large' | 'full';
54
+ /**
55
+ * The height of the dialog.
56
+ *
57
+ * @default 'content'
58
+ */
59
+ height?: 'content' | 'full';
60
+ /**
61
+ * Whether the dialog can be moved with mouse dragging.
62
+ *
63
+ * @default true
64
+ */
65
+ draggable?: boolean;
66
+ /**
67
+ * The dialog's appearance styling. `simple` is a simple white dialog with a border. `glass` is a glassmorphic dialog with a blurred background.
68
+ *
69
+ * @default 'simple'
70
+ */
71
+ appearance?: 'simple' | 'glass';
72
+ /**
73
+ * Optional overrides for props of each dialog component.
74
+ *
75
+ * Valid keys are: `root`, `overlayContainer`, `overlay`, `panelContainer`, `panel`, `panelHeader`, `panelTitle`, `panelCloseButton`.
76
+ */
77
+ overrides?: {
78
+ /** The root element containing all elements */
79
+ root?: ComponentPropsWithoutRef<'div'>,
80
+ /** The container for the backdrop overlay */
81
+ overlayContainer?: ComponentPropsWithoutRef<'div'>,
82
+ /** The backdrop overlay */
83
+ overlay?: ComponentPropsWithoutRef<'div'>,
84
+ /** The container for the dialog panel */
85
+ panelContainer?: ComponentPropsWithoutRef<'div'>,
86
+ /** The dialog panel */
87
+ panel?: ComponentPropsWithoutRef<'div'>,
88
+ /** The header of the dialog panel, which contains the title and close button */
89
+ panelHeader?: ComponentPropsWithoutRef<'div'>,
90
+ /** The title within the dialog panel */
91
+ panelTitle?: ComponentPropsWithoutRef<'h1'>,
92
+ /** The close button within the dialog panel */
93
+ panelCloseButton?: ComponentPropsWithoutRef<'button'>,
94
+ }
95
+ };
96
+
97
+ /**
98
+ * Dialogs are modal components that appear on top of the main content and require user interaction to dismiss.
99
+ *
100
+ * They render to a Portal, so they can be used anywhere in the DOM.
101
+ *
102
+ * <hr />
103
+ *
104
+ * To use this component, import it as follows:
105
+ *
106
+ * ```js
107
+ * import { Dialog } from 'paris/dialog';
108
+ * ```
109
+ * @constructor
110
+ */
111
+ export const Dialog: FC<PropsWithChildren<DialogProps>> = ({
112
+ isOpen = false,
113
+ onClose = () => {},
114
+ title,
115
+ hideTitle = false,
116
+ hideCloseButton = false,
117
+ overrides = {},
118
+ width = 'default',
119
+ height = 'content',
120
+ draggable = false,
121
+ appearance = 'simple',
122
+ children,
123
+ }) => {
124
+ const [dragging, setDragging] = useState(false);
125
+ const [position, setPosition] = useState({ top: 0, left: 0 });
126
+ const [startPosition, setStartPosition] = useState({ x: 0, y: 0 });
127
+
128
+ const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
129
+ if (draggable) {
130
+ setDragging(true);
131
+ setStartPosition({ x: e.clientX, y: e.clientY });
132
+ }
133
+ };
134
+
135
+ const handleMouseUp = () => {
136
+ if (draggable) {
137
+ setDragging(false);
138
+ }
139
+ };
140
+
141
+ const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
142
+ if (dragging && draggable) {
143
+ setPosition({
144
+ top: position.top + (e.clientY - startPosition.y),
145
+ left: position.left + (e.clientX - startPosition.x),
146
+ });
147
+ setStartPosition({ x: e.clientX, y: e.clientY });
148
+ }
149
+ };
150
+
151
+ useEffect(() => {
152
+ if (isOpen) {
153
+ setDragging(false);
154
+ setPosition({ top: 0, left: 0 });
155
+ setStartPosition({ x: 0, y: 0 });
156
+ }
157
+ }, [isOpen]);
158
+
159
+ return (
160
+ <Transition
161
+ appear
162
+ show={isOpen}
163
+ as={Fragment}
164
+ >
165
+ <HDialog
166
+ as="div"
167
+ onClose={onClose}
168
+ {...overrides.root}
169
+ className={clsx(
170
+ styles.root,
171
+ overrides.root?.className,
172
+ )}
173
+ >
174
+ <HDialog.Overlay
175
+ {...overrides.overlayContainer}
176
+ className={clsx(
177
+ styles.overlayContainer,
178
+ overrides.overlayContainer?.className,
179
+ )}
180
+ >
181
+ <Transition.Child
182
+ as={Fragment}
183
+ enter={styles.enter}
184
+ enterFrom={styles.enterFrom}
185
+ enterTo={styles.enterTo}
186
+ leave={styles.leave}
187
+ leaveFrom={styles.leaveFrom}
188
+ leaveTo={styles.leaveTo}
189
+ >
190
+ <div
191
+ {...overrides.overlay}
192
+ className={clsx(
193
+ styles.overlay,
194
+ overrides.overlay?.className,
195
+ )}
196
+ />
197
+ </Transition.Child>
198
+ </HDialog.Overlay>
199
+
200
+ <div
201
+ {...overrides.panelContainer}
202
+ className={clsx(
203
+ styles.panelContainer,
204
+ overrides.panelContainer?.className,
205
+ )}
206
+ >
207
+ <Transition.Child
208
+ as={Fragment}
209
+ enter={styles.enter}
210
+ enterFrom={styles.enterFrom}
211
+ enterTo={styles.enterTo}
212
+ leave={styles.leave}
213
+ leaveFrom={styles.leaveFrom}
214
+ leaveTo={styles.leaveTo}
215
+ >
216
+ <HDialog.Panel
217
+ {...overrides.panel}
218
+ className={clsx(
219
+ styles.panel,
220
+ styles[appearance],
221
+ styles[`w-${width}`],
222
+ styles[`h-${height}`],
223
+ overrides.panel?.className,
224
+ )}
225
+ style={{ top: `${position.top}px`, left: `${position.left}px` }}
226
+ onMouseDown={handleMouseDown}
227
+ onMouseUp={handleMouseUp}
228
+ onMouseMove={handleMouseMove}
229
+ >
230
+ <div
231
+ {...overrides.panelHeader}
232
+ className={clsx(
233
+ styles.header,
234
+ overrides.panelHeader?.className,
235
+ )}
236
+ >
237
+ <VisuallyHidden when={hideTitle}>
238
+ <HDialog.Title
239
+ {...overrides.panelTitle}
240
+ as="h1"
241
+ className={clsx(
242
+ styles.title,
243
+ overrides.panelTitle?.className,
244
+ )}
245
+ >
246
+ <TextWhenString
247
+ kind="headingXSmall"
248
+ >
249
+ {title}
250
+ </TextWhenString>
251
+ </HDialog.Title>
252
+ </VisuallyHidden>
253
+ <RemoveFromDOM when={hideCloseButton}>
254
+ <Button
255
+ kind="tertiary"
256
+ shape="circle"
257
+ onClick={() => onClose(false)}
258
+ startEnhancer={(
259
+ <Icon size={20} icon={Close} />
260
+ )}
261
+ {...overrides.panelCloseButton}
262
+ data-title-hidden={hideTitle}
263
+ className={clsx(
264
+ styles.closeButton,
265
+ overrides.panelCloseButton?.className,
266
+ )}
267
+ >
268
+ Close dialog
269
+ </Button>
270
+ </RemoveFromDOM>
271
+ </div>
272
+ {children}
273
+ </HDialog.Panel>
274
+ </Transition.Child>
275
+ </div>
276
+ </HDialog>
277
+ </Transition>
278
+ );
279
+ };
@@ -0,0 +1 @@
1
+ export * from './Dialog';
@@ -0,0 +1,284 @@
1
+ $translateInX: 30%;
2
+ $translateInY: 100%;
3
+ $translateOutX: 20%;
4
+ $translateOutY: 60%;
5
+
6
+ $panelMinMargin: 16px;
7
+ $defaultSize: 360px;
8
+
9
+ $panelPaddingX: 20px;
10
+ $panelPaddingY: 24px;
11
+
12
+ $duration: var(--pte-animations-duration-relaxed);
13
+ $paginationDuration: var(--pte-animations-duration-normal);
14
+ $panelAnimationDelay: var(--pte-animations-duration-fast);
15
+ //$panelAnimationDelay: var(--pte-animations-duration-rapid);
16
+ //$duration: var(--pte-animations-duration-normal);
17
+
18
+ .root {
19
+ position: fixed;
20
+ inset: 0;
21
+ overflow: hidden;
22
+ z-index: 10;
23
+ user-select: var(--pte-utils-defaultUserSelect);
24
+ }
25
+
26
+ .overlay {
27
+ position: absolute;
28
+ inset: 0;
29
+ background-color: var(--pte-colors-backgroundOverlayDark);
30
+ will-change: backdrop-filter, opacity;
31
+ backdrop-filter: blur(0);
32
+ }
33
+
34
+ .overlayContainer {
35
+ .enterFrom {
36
+ backdrop-filter: blur(0);
37
+ }
38
+ .enterTo {
39
+ backdrop-filter: blur(2px);
40
+ }
41
+ .leave {
42
+ transition-delay: $panelAnimationDelay;
43
+ }
44
+ .leaveFrom {
45
+ backdrop-filter: blur(2px);
46
+ }
47
+ .leaveTo {
48
+ backdrop-filter: blur(0);
49
+ }
50
+ }
51
+
52
+ .panelContainer {
53
+ position: fixed;
54
+
55
+ justify-self: flex-end;
56
+
57
+ z-index: 10;
58
+
59
+ .enter {
60
+ transition: $duration var(--pte-animations-timing-easeOut);
61
+ transition-delay: $panelAnimationDelay;
62
+ //transition-delay: 50ms;
63
+ }
64
+
65
+ .leave {
66
+ transition: $duration var(--pte-animations-timing-easeIn);
67
+ }
68
+
69
+ &.from-right {
70
+ right: 0;
71
+ padding-left: $panelMinMargin;
72
+
73
+ .enterFrom {
74
+ transform: translateX($translateInX);
75
+ }
76
+
77
+ .enterTo, .leaveFrom {
78
+ transform: translateX(0);
79
+ }
80
+
81
+ .leaveTo {
82
+ transform: translateX($translateOutX);
83
+ }
84
+ }
85
+
86
+ &.from-left {
87
+ left: 0;
88
+ padding-right: $panelMinMargin;
89
+
90
+ .enterFrom {
91
+ transform: translateX(-$translateInX);
92
+ }
93
+
94
+ .enterTo, .leaveFrom {
95
+ transform: translateX(0);
96
+ }
97
+
98
+ .leaveTo {
99
+ transform: translateX(-$translateOutX);
100
+
101
+ }
102
+ }
103
+
104
+
105
+ &.from-left, &.from-right {
106
+ height: 100vh;
107
+ top: 0;
108
+ bottom: 0;
109
+
110
+ &.size-default {
111
+ width: min(100vw, $defaultSize);
112
+ }
113
+
114
+ &.size-content {
115
+ width: auto;
116
+ }
117
+
118
+ &.size-full {
119
+ width: 100vw;
120
+ }
121
+ }
122
+
123
+ &.from-top {
124
+ top: 0;
125
+ padding-bottom: $panelMinMargin;
126
+
127
+ .enterFrom {
128
+ transform: translateY(-$translateInY);
129
+ }
130
+
131
+ .enterTo, .leaveFrom {
132
+ transform: translateY(0);
133
+ }
134
+
135
+ .leaveTo {
136
+ transform: translateY(-$translateOutY);
137
+ }
138
+ }
139
+
140
+ &.from-bottom {
141
+ bottom: 0;
142
+ padding: $panelMinMargin 0 0;
143
+
144
+ .enterFrom {
145
+ transform: translateY($translateInY);
146
+ }
147
+
148
+ .enterTo, .leaveFrom {
149
+ transform: translateY(0);
150
+ }
151
+
152
+ .leaveTo {
153
+ transform: translateY($translateOutY);
154
+ }
155
+ }
156
+
157
+ &.from-top, &.from-bottom {
158
+ width: 100vw;
159
+ left: 0;
160
+ right: 0;
161
+
162
+ &.size-default {
163
+ height: min(100vh, $defaultSize);
164
+ }
165
+
166
+ &.size-content {
167
+ height: auto;
168
+ }
169
+
170
+ &.size-full {
171
+ height: 100vh;
172
+ }
173
+ }
174
+ }
175
+
176
+ .panel {
177
+ position: relative;
178
+ height: 100%;
179
+ padding: $panelPaddingY $panelPaddingX;
180
+ background-color: var(--pte-colors-backgroundPrimary);
181
+ overflow: auto;
182
+ border: 1px solid transparent;
183
+
184
+ display: flex;
185
+ flex-direction: column;
186
+ justify-content: flex-start;
187
+ align-items: stretch;
188
+ gap: $panelPaddingY;
189
+
190
+ &.enterFrom {
191
+ box-shadow: none;
192
+ }
193
+
194
+ &.enterTo, &.leaveFrom {
195
+ &.from-right {
196
+ border-left-color: var(--pte-borders-dropdown-color);
197
+ box-shadow: var(--pte-lighting-shallowLeft);
198
+ }
199
+
200
+ &.from-left {
201
+ border-right-color: var(--pte-borders-dropdown-color);
202
+ box-shadow: var(--pte-lighting-shallowRight);
203
+ }
204
+
205
+ &.from-top {
206
+ border-bottom-color: var(--pte-borders-dropdown-color);
207
+ box-shadow: var(--pte-lighting-shallowBottom);
208
+ }
209
+
210
+ &.from-bottom {
211
+ border-top-color: var(--pte-borders-dropdown-color);
212
+ box-shadow: var(--pte-lighting-shallowTop);
213
+ }
214
+ }
215
+
216
+ &.leaveTo {
217
+ box-shadow: none;
218
+ }
219
+ }
220
+
221
+ .closeButton {
222
+ position: absolute;
223
+ right: $panelPaddingX;
224
+ top: $panelPaddingY - 5px;
225
+ }
226
+
227
+ .paginationNav {
228
+ width: 100%;
229
+ display: flex;
230
+ flex-direction: row;
231
+ justify-content: space-between;
232
+ align-items: center;
233
+
234
+ font-size: 16px;
235
+
236
+ .closeButton {
237
+ position: relative;
238
+ right: 0;
239
+ top: 0;
240
+ }
241
+
242
+ .navButton {
243
+ padding: 2px;
244
+ }
245
+ }
246
+
247
+ .paginationButtons {
248
+ display: flex;
249
+ flex-direction: row;
250
+ align-items: center;
251
+ gap: 8px;
252
+ }
253
+
254
+ .enter {
255
+ transition: $duration var(--pte-animations-timing-easeOut);
256
+ }
257
+
258
+ .enterFrom, .enterFromOpacity {
259
+ opacity: 0;
260
+ }
261
+
262
+ .enterTo, .enterToOpacity {
263
+ opacity: 1;
264
+ }
265
+
266
+ .leave {
267
+ transition: $duration var(--pte-animations-timing-easeIn);
268
+ }
269
+
270
+ .leaveFrom, .leaveFromOpacity {
271
+ opacity: 1;
272
+ }
273
+
274
+ .leaveTo, .leaveToOpacity {
275
+ opacity: 0;
276
+ }
277
+
278
+ .paginationEnter {
279
+ transition: $paginationDuration var(--pte-animations-timing-easeInQuad);
280
+ }
281
+
282
+ .paginationLeave {
283
+ transition: $paginationDuration var(--pte-animations-timing-easeOutQuad);
284
+ }
@@ -0,0 +1,94 @@
1
+ /* eslint-disable react-hooks/rules-of-hooks */
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { memo, useState } from 'react';
4
+ import { Drawer } from './Drawer';
5
+ import { Button } from '../button';
6
+ import { usePagination } from '../pagination';
7
+
8
+ const meta: Meta<typeof Drawer> = {
9
+ title: 'Surfaces/Drawer',
10
+ component: Drawer,
11
+ tags: ['autodocs'],
12
+ };
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof Drawer>;
16
+
17
+ export const Default: Story = {
18
+ args: {
19
+ title: 'Transaction details',
20
+ children: 'This was a transaction for $22.89 at Il Tramezzino in Beverly Hills, CA.',
21
+ },
22
+ render: (args) => {
23
+ const [isOpen, setIsOpen] = useState(false);
24
+ return (
25
+ <>
26
+ <Button
27
+ onClick={() => setIsOpen(true)}
28
+ >
29
+ View details
30
+ </Button>
31
+ <Drawer
32
+ {...args}
33
+ isOpen={isOpen}
34
+ onClose={setIsOpen}
35
+ >
36
+ {args.children}
37
+ </Drawer>
38
+ </>
39
+ );
40
+ },
41
+ };
42
+
43
+ export const Paginated: Story = {
44
+ args: {
45
+ title: 'Creation process',
46
+ children: [],
47
+ },
48
+ render: (args) => {
49
+ const [isOpen, setIsOpen] = useState(false);
50
+ const pages = ['step1', 'step2', 'step3'] as const;
51
+ const pagination = usePagination<typeof pages>('step1');
52
+
53
+ return (
54
+ <>
55
+ <Button
56
+ onClick={() => setIsOpen(true)}
57
+ >
58
+ Start process
59
+ </Button>
60
+ <Drawer
61
+ {...args}
62
+ isOpen={isOpen}
63
+ onClose={setIsOpen}
64
+ pagination={pagination}
65
+ >
66
+ <div key="step1" style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
67
+ Step 1: Enter your name
68
+ <Button
69
+ onClick={() => pagination.open('step2')}
70
+ >
71
+ Go to step 2
72
+ </Button>
73
+ <Button
74
+ onClick={() => pagination.open('step3')}
75
+ >
76
+ Go to step 3
77
+ </Button>
78
+ </div>
79
+ <div key="step2" style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
80
+ Step 2: Enter your address
81
+ <Button
82
+ onClick={() => pagination.open('step3')}
83
+ >
84
+ Go to step 3
85
+ </Button>
86
+ </div>
87
+ <div key="step3" style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
88
+ Step 3: Enter your credit card information
89
+ </div>
90
+ </Drawer>
91
+ </>
92
+ );
93
+ },
94
+ };