@hyphen/hyphen-components 5.8.0 → 6.0.0

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.
@@ -1,74 +1,68 @@
1
- @keyframes fadeIn {
2
- 0% {
3
- opacity: 0;
4
- }
5
-
6
- 100% {
7
- opacity: 1;
8
- }
1
+ .PopoverContent {
2
+ background-color: var(
3
+ --color-background-popover,
4
+ var(--color-background-primary)
5
+ );
6
+ color: var(--color-font-popover, var(--color-font-base));
7
+ box-shadow: var(--size-box-shadow-sm);
8
+ border-radius: var(--size-border-radius-sm);
9
+ animation-duration: 0.6s;
10
+ animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
9
11
  }
10
12
 
11
- .popover {
12
- z-index: var(--size-z-index-popover);
13
- animation: fadeIn 0.2s;
13
+ .PopoverContent[data-side='top'] {
14
+ animation-name: slideUp;
14
15
  }
15
-
16
- .popover-arrow {
17
- position: absolute;
18
- width: 0.8rem;
19
- height: 0.8rem;
20
-
21
- &::before {
22
- position: absolute;
23
- transform: rotate(45deg);
24
- z-index: -1;
25
- background-color: inherit;
26
- width: 0.8rem;
27
- height: 0.8rem;
28
- content: '';
29
- }
16
+ .PopoverContent[data-side='bottom'] {
17
+ animation-name: slideDown;
18
+ }
19
+ .PopoverContent[data-side='left'] {
20
+ animation-name: slideLeft;
21
+ }
22
+ .PopoverContent[data-side='right'] {
23
+ animation-name: slideRight;
30
24
  }
31
25
 
32
- [data-popper-placement*='bottom'] {
33
- .popover-arrow {
34
- top: -0.1rem;
35
-
36
- &::before {
37
- bottom: 0.25rem;
38
- box-shadow: rgb(0 0 0 / 5%) -1px -1px 1px 0;
39
- }
26
+ @keyframes slideDown {
27
+ from {
28
+ opacity: 0;
29
+ transform: translateY(-7px);
30
+ }
31
+ to {
32
+ opacity: 1;
33
+ transform: translateY(0);
40
34
  }
41
35
  }
42
36
 
43
- [data-popper-placement*='top'] {
44
- .popover-arrow {
45
- bottom: -0.1rem;
46
-
47
- &::before {
48
- top: 0.25rem;
49
- box-shadow: rgb(0 0 0 / 8%) 3px 3px 3px 0;
50
- }
37
+ @keyframes slideUp {
38
+ from {
39
+ opacity: 0;
40
+ transform: translateY(7px);
41
+ }
42
+ to {
43
+ opacity: 1;
44
+ transform: translateY(0);
51
45
  }
52
46
  }
53
47
 
54
- [data-popper-placement*='left'] {
55
- .popover-arrow {
56
- right: -0.1rem;
57
-
58
- &::before {
59
- left: 0.25rem;
60
- box-shadow: rgb(0 0 0 / 8%) 1px -1px 1px 0;
61
- }
48
+ @keyframes slideLeft {
49
+ from {
50
+ opacity: 0;
51
+ transform: translateX(7px);
52
+ }
53
+ to {
54
+ opacity: 1;
55
+ transform: translateX(0);
62
56
  }
63
57
  }
64
58
 
65
- [data-popper-placement*='right'] {
66
- .popover-arrow {
67
- left: -0.1rem;
68
-
69
- &::before {
70
- right: 0.25rem;
71
- box-shadow: rgb(0 0 0 / 5%) -2px 2px 2px 0;
72
- }
59
+ @keyframes slideRight {
60
+ from {
61
+ opacity: 0;
62
+ transform: translateX(-7px);
63
+ }
64
+ to {
65
+ opacity: 1;
66
+ transform: translateX(0);
73
67
  }
74
68
  }
@@ -1,17 +1,14 @@
1
- import { Popover } from './Popover';
1
+ import {
2
+ Popover,
3
+ PopoverContent,
4
+ PopoverPortal,
5
+ PopoverTrigger,
6
+ } from './Popover';
2
7
  import type { Meta } from '@storybook/react-vite';
3
- import React, { ChangeEvent } from 'react';
4
- import { useState } from 'react';
8
+ import React from 'react';
5
9
  import { Button } from '../Button/Button';
6
10
  import { Box } from '../Box/Box';
7
- import { SelectInput } from '../SelectInput/SelectInput';
8
- import { TextInput } from '../TextInput/TextInput';
9
- import { Heading } from '../Heading/Heading';
10
- import { useOpenClose } from '../../hooks/useOpenClose/useOpenClose';
11
- import {
12
- BackgroundColor,
13
- FontColor,
14
- } from '@hyphen/hyphen-design-tokens/build/types';
11
+ import { RadioGroup } from '../RadioGroup/RadioGroup';
15
12
 
16
13
  const meta: Meta<typeof Popover> = {
17
14
  title: 'Components/Popover',
@@ -20,452 +17,94 @@ const meta: Meta<typeof Popover> = {
20
17
 
21
18
  export default meta;
22
19
 
23
- export const BasicUsage = () => {
24
- const {
25
- isOpen: isPopoverOpen,
26
- handleToggle: togglePopover,
27
- handleClose: closePopover,
28
- } = useOpenClose();
29
- const popoverContent = <>Hello!</>;
20
+ export const Basic = () => {
30
21
  return (
31
22
  <>
32
- <Popover
33
- content={popoverContent}
34
- isOpen={isPopoverOpen}
35
- placement="right"
36
- contentContainerProps={{
37
- padding: 'sm',
38
- }}
39
- onClickOutside={closePopover}
40
- >
41
- <Button onClick={togglePopover} variant="primary">
42
- Toggle Popover
43
- </Button>
23
+ <Popover>
24
+ <PopoverTrigger asChild>
25
+ <Button variant="primary">Open Popover</Button>
26
+ </PopoverTrigger>
27
+ <PopoverPortal>
28
+ <PopoverContent>
29
+ <Box width="100" maxWidth="8xl" padding="2xl">
30
+ Display content in a portal, triggered by a button.
31
+ </Box>
32
+ </PopoverContent>
33
+ </PopoverPortal>
44
34
  </Popover>
45
35
  </>
46
36
  );
47
37
  };
48
38
 
49
- type PopoverStylingType = {
50
- value: string;
51
- label: string;
52
- } | null;
53
-
54
- export const PopoverStyling = () => {
55
- const { isOpen: isPopoverOpen, handleToggle: togglePopover } = useOpenClose();
56
- const [popoverBackground, setPopoverBackground] =
57
- useState<PopoverStylingType>({
58
- value: 'primary',
59
- label: 'Primary',
60
- });
61
- const [popoverFontColor, setPopoverFontColor] = useState<PopoverStylingType>({
62
- value: 'black',
63
- label: 'Black',
64
- });
65
- const [popoverRadius, setPopoverRadius] = useState<PopoverStylingType>({
66
- value: 'sm',
67
- label: 'Small',
68
- });
69
- const backgroundOptions = [
70
- { value: 'primary', label: 'primary' },
71
- { value: 'secondary', label: 'secondary' },
72
- { value: 'info', label: 'info' },
73
- { value: 'warning', label: 'warn' },
74
- { value: 'danger', label: 'danger' },
75
- ];
76
- const fontColorOptions = [
77
- { value: 'base', label: 'base' },
78
- { value: 'inverse', label: 'inverse' },
79
- ];
80
- const borderRadiusOptions = [
81
- { value: 'sm', label: 'Small' },
82
- { value: 'md', label: 'Medium' },
83
- { value: 'lg', label: 'Large' },
84
- ];
85
- return (
86
- <Box height="400px">
87
- <Box direction="row" gap="md" wrap>
88
- <Popover
89
- content={
90
- <>
91
- <p>Hello world!</p>
92
- <p>Style me any way you want</p>
93
- </>
94
- }
95
- isOpen={isPopoverOpen}
96
- placement={'right'}
97
- contentContainerProps={{
98
- padding: 'sm',
99
- background: popoverBackground!.value as BackgroundColor,
100
- color: popoverFontColor!.value as FontColor,
101
- radius: popoverRadius!.value,
102
- }}
103
- >
104
- <Button onClick={togglePopover} variant="primary">
105
- Toggle Popover
106
- </Button>
107
- </Popover>
108
- </Box>
109
- <Box direction="row" gap="sm" wrap margin="3xl 0 0 0">
110
- <Box width="200px">
111
- <SelectInput
112
- id="backgroundOptions"
113
- options={backgroundOptions}
114
- onChange={(event) => {
115
- // @ts-ignore
116
- setPopoverBackground(event.target.value);
117
- }}
118
- value={popoverBackground}
119
- label="Background Color"
120
- />
121
- </Box>
122
- <Box width="200px">
123
- <SelectInput
124
- id="fontColorOptions"
125
- options={fontColorOptions}
126
- onChange={(event) => {
127
- // @ts-ignore
128
- setPopoverFontColor(event.target.value);
129
- }}
130
- value={popoverFontColor}
131
- label="Font Color"
132
- />
133
- </Box>
134
- <Box width="200px">
135
- <SelectInput
136
- id="borderRadiusOptions"
137
- options={borderRadiusOptions}
138
- onChange={(event) => {
139
- // @ts-ignore
140
- setPopoverRadius(event.target.value);
141
- }}
142
- value={popoverRadius}
143
- label="Border Radius"
144
- />
145
- </Box>
146
- </Box>
147
- </Box>
39
+ export const SidesAndAlign = () => {
40
+ const [align, setAlign] = React.useState<'start' | 'center' | 'end'>(
41
+ 'center'
148
42
  );
149
- };
150
43
 
151
- export const Placement = () => {
152
- const [isPopoverOpen, setPopoverOpen] = useState({
153
- auto: false,
154
- 'auto-start': false,
155
- 'auto-end': false,
156
- top: false,
157
- bottom: false,
158
- right: false,
159
- left: false,
160
- 'top-start': false,
161
- 'top-end': false,
162
- 'bottom-start': false,
163
- 'bottom-end': false,
164
- 'right-start': false,
165
- 'right-end': false,
166
- 'left-start': false,
167
- 'left-end': false,
168
- });
169
- const handleOpenPopover = (key: string) => {
170
- // @ts-ignore
171
- setPopoverOpen({ ...isPopoverOpen, [key]: !isPopoverOpen[key] });
172
- };
173
- const positions = [
174
- 'auto',
175
- 'auto-start',
176
- 'auto-end',
177
- 'top',
178
- 'bottom',
179
- 'right',
180
- 'left',
181
- 'top-start',
182
- 'top-end',
183
- 'bottom-start',
184
- 'bottom-end',
185
- 'right-start',
186
- 'right-end',
187
- 'left-start',
188
- 'left-end',
44
+ const options = [
45
+ { id: 'start', value: 'start', label: 'Start' },
46
+ { id: 'center', value: 'center', label: 'Center' },
47
+ { id: 'end', value: 'end', label: 'End' },
189
48
  ];
190
49
 
191
50
  return (
192
- <Box direction="row" gap="md" wrap>
193
- {positions.map((position) => (
194
- <Box height="100px" padding="5xl" display="inline-block" key={position}>
195
- <Popover
196
- content={<>{position}</>}
197
- // @ts-ignore
198
- isOpen={isPopoverOpen[position]}
199
- // @ts-ignore
200
- placement={position}
201
- contentContainerProps={{
202
- padding: 'sm',
203
- background: 'info',
204
- color: 'base',
205
- }}
206
- >
207
- <Button
208
- onClick={() => handleOpenPopover(position)}
209
- variant="primary"
210
- >
211
- {position}
212
- </Button>
213
- </Popover>
51
+ <Box gap="2xl">
52
+ <Box>
53
+ <Box direction="row" gap="md">
54
+ <RadioGroup
55
+ direction="row"
56
+ title="Align"
57
+ name="noTitleOrDescription"
58
+ value={align}
59
+ onChange={(event) =>
60
+ setAlign(event.target.value as 'start' | 'center' | 'end')
61
+ }
62
+ options={options}
63
+ />
214
64
  </Box>
215
- ))}
216
- </Box>
217
- );
218
- };
219
-
220
- export const WithAPortal = () => {
221
- const {
222
- isOpen: isPopoverOpen,
223
- handleToggle: togglePopover,
224
- handleClose: closePopover,
225
- } = useOpenClose();
226
- const popoverContent = (
227
- <>
228
- <Heading style={{ marginBottom: '0.5rem' }}>
229
- I am living in the body element!
230
- </Heading>
231
- <p>That's why I can break out of my container without getting cut off</p>
232
- </>
233
- );
234
- return (
235
- <Box display="inline-block">
236
- <Popover
237
- content={popoverContent}
238
- isOpen={isPopoverOpen}
239
- placement="right-start"
240
- contentContainerProps={{
241
- padding: 'md',
242
- background: 'danger',
243
- color: 'base',
244
- }}
245
- withPortal
246
- portalTarget={document.body}
247
- onClickOutside={closePopover}
248
- >
249
- <Button onClick={togglePopover} variant="primary">
250
- Toggle Popover
251
- </Button>
252
- </Popover>
253
- </Box>
254
- );
255
- };
256
-
257
- export const HoverTrigger = () => {
258
- const {
259
- isOpen: isPopoverOpen,
260
- handleClose: closePopover,
261
- handleOpen: openPopover,
262
- } = useOpenClose();
263
- const popoverContent = (
264
- <>
265
- <Heading style={{ marginBottom: '0.5rem' }}>
266
- I just appeared on hover!
267
- </Heading>
268
- <p>
269
- My visibility can easily be managed by attaching listeners to the
270
- trigger element
271
- </p>
272
- </>
273
- );
274
- return (
275
- <Box display="inline-block">
276
- <Popover
277
- content={popoverContent}
278
- isOpen={isPopoverOpen}
279
- placement="right-start"
280
- contentContainerProps={{
281
- padding: 'md',
282
- background: 'info',
283
- }}
284
- >
285
- <Button
286
- onMouseOver={openPopover}
287
- onMouseOut={closePopover}
288
- variant="primary"
289
- >
290
- Hover Me
291
- </Button>
292
- </Popover>
293
- </Box>
294
- );
295
- };
296
-
297
- export const RespondToOutsideClicks = () => {
298
- const {
299
- isOpen: isPopoverOpen,
300
- handleClose: closePopover,
301
- handleToggle: togglePopover,
302
- } = useOpenClose();
303
- const popoverContent = (
304
- <>
305
- <Heading style={{ marginBottom: '0.5rem' }}>
306
- I will close if you click outside!
307
- </Heading>
308
- <p>The event listener is attached to the document body.</p>
309
- </>
310
- );
311
- return (
312
- <Box display="inline-block">
313
- <Popover
314
- content={popoverContent}
315
- isOpen={isPopoverOpen}
316
- placement="right-start"
317
- withPortal
318
- portalTarget={document.body}
319
- onClickOutside={closePopover}
320
- contentContainerProps={{
321
- padding: 'md',
322
- background: 'success',
323
- }}
324
- >
325
- <Button onClick={togglePopover} variant="primary">
326
- Toggle Popover
327
- </Button>
328
- </Popover>
329
- </Box>
330
- );
331
- };
332
-
333
- export const TrappingFocus = () => {
334
- const [inputValue, setInputValue] = useState<string>('');
335
- const {
336
- isOpen: isPopoverOpen,
337
- handleClose: closePopover,
338
- handleToggle: togglePopover,
339
- } = useOpenClose();
340
- const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
341
- setInputValue(event.target.value);
342
- };
343
- const popoverContent = (
344
- <>
345
- <Box direction="column" gap="sm">
346
- <Heading>Only the elements on this Popover can be tabbed into</Heading>
347
- <TextInput
348
- id="textInput"
349
- label="Text Input"
350
- onChange={handleInputChange}
351
- value={inputValue}
352
- />
353
- <Button>Submit</Button>
354
65
  </Box>
355
- </>
356
- );
357
- return (
358
- <Box display="inline-block">
359
- <Popover
360
- content={popoverContent}
361
- isOpen={isPopoverOpen}
362
- placement="right-start"
363
- contentContainerProps={{
364
- padding: 'md',
365
- background: 'primary',
366
- }}
367
- withPortal
368
- portalTarget={document.body}
369
- onClickOutside={closePopover}
370
- trapFocus
371
- >
372
- <Button onClick={togglePopover} variant="primary">
373
- Toggle Popover
374
- </Button>
375
- </Popover>
376
- </Box>
377
- );
378
- };
379
-
380
- export const WithoutAnArrow = () => {
381
- const {
382
- isOpen: isPopoverOpen,
383
- handleToggle: togglePopover,
384
- handleClose: closePopover,
385
- } = useOpenClose();
386
- const popoverContent = (
387
- <>
388
- <Box direction="column" gap="sm">
389
- <Heading>With no arrow</Heading>
390
- <p>I am floating in space</p>
391
- </Box>
392
- </>
393
- );
394
- return (
395
- <>
396
- <Popover
397
- content={popoverContent}
398
- isOpen={isPopoverOpen}
399
- placement="right"
400
- contentContainerProps={{
401
- padding: 'sm',
402
- }}
403
- onClickOutside={closePopover}
404
- hasArrow={false}
405
- >
406
- <Button onClick={togglePopover} variant="primary">
407
- Toggle Popover
408
- </Button>
409
- </Popover>
410
- </>
411
- );
412
- };
413
-
414
- export const OffsetDistance = () => {
415
- const [offset, setOffset] = useState<number>(12);
416
- const { isOpen: isPopoverOpen, handleToggle: togglePopover } = useOpenClose();
417
-
418
- const popoverContent = (
419
- <>
420
- <Box direction="column" gap="sm">
421
- <Heading>Custom Offset</Heading>
422
- <p>Near, far, wherever your are...</p>
423
- </Box>
424
- </>
425
- );
426
- return (
427
- <>
428
- <Box display="inline-block">
429
- <Popover
430
- content={popoverContent}
431
- isOpen={isPopoverOpen}
432
- placement="right-start"
433
- contentContainerProps={{
434
- padding: 'md',
435
- background: 'secondary',
436
- }}
437
- withPortal
438
- portalTarget={document.body}
439
- hasArrow={false}
440
- offsetFromTarget={offset}
441
- >
442
- <Button onClick={togglePopover} variant="primary">
443
- Toggle Popover
444
- </Button>
66
+ <Box direction="row" gap="2xl">
67
+ <Popover>
68
+ <PopoverTrigger asChild>
69
+ <Button variant="primary">Top</Button>
70
+ </PopoverTrigger>
71
+ <PopoverPortal>
72
+ <PopoverContent side="top" align={align}>
73
+ <p>popover content</p>
74
+ </PopoverContent>
75
+ </PopoverPortal>
76
+ </Popover>
77
+ <Popover>
78
+ <PopoverTrigger asChild>
79
+ <Button variant="primary">Right</Button>
80
+ </PopoverTrigger>
81
+ <PopoverPortal>
82
+ <PopoverContent side="right" align={align}>
83
+ <p>popover content</p>
84
+ </PopoverContent>
85
+ </PopoverPortal>
86
+ </Popover>
87
+ <Popover>
88
+ <PopoverTrigger asChild>
89
+ <Button variant="primary">Bottom</Button>
90
+ </PopoverTrigger>
91
+ <PopoverPortal>
92
+ <PopoverContent side="bottom" align={align}>
93
+ <p>popover content</p>
94
+ </PopoverContent>
95
+ </PopoverPortal>
96
+ </Popover>
97
+ <Popover>
98
+ <PopoverTrigger asChild>
99
+ <Button variant="primary">Left</Button>
100
+ </PopoverTrigger>
101
+ <PopoverPortal>
102
+ <PopoverContent side="left" align={align}>
103
+ <p>popover content</p>
104
+ </PopoverContent>
105
+ </PopoverPortal>
445
106
  </Popover>
446
107
  </Box>
447
- <Box margin="2xl 0 0 0" maxWidth="300px">
448
- <label
449
- htmlFor="offset"
450
- style={{ fontWeight: 'bold', marginBottom: '0.25rem' }}
451
- >
452
- Offset
453
- </label>
454
- <input
455
- type="range"
456
- id="offset"
457
- name="offset"
458
- min="0"
459
- max="24"
460
- step="1"
461
- value={offset}
462
- onChange={(event) => {
463
- setOffset(event.target.value as unknown as number);
464
- }}
465
- style={{ marginBottom: '0.25rem' }}
466
- />
467
- <span style={{ display: 'inline' }}>Value: {offset}</span>
468
- </Box>
469
- </>
108
+ </Box>
470
109
  );
471
110
  };