@sanity/ui 2.0.0-alpha.20 → 2.0.0-alpha.22

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/ui",
3
- "version": "2.0.0-alpha.20",
3
+ "version": "2.0.0-alpha.22",
4
4
  "sideEffects": false,
5
5
  "types": "./dist/index.d.ts",
6
6
  "source": "./src/index.ts",
@@ -141,7 +141,7 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
141
141
  padding: paddingProp = 3,
142
142
  popover = EMPTY_RECORD,
143
143
  prefix,
144
- radius = 3,
144
+ radius = 2,
145
145
  readOnly,
146
146
  relatedElements,
147
147
  renderOption: renderOptionProp,
@@ -10,6 +10,7 @@ export interface ToastProps {
10
10
  closable?: boolean
11
11
  description?: React.ReactNode
12
12
  onClose?: () => void
13
+ radius?: number | number[]
13
14
  title?: React.ReactNode
14
15
  status?: 'error' | 'warning' | 'success' | 'info'
15
16
  }
@@ -46,7 +47,7 @@ const TextBox = styled(Flex)`
46
47
  export function Toast(
47
48
  props: ToastProps & Omit<React.HTMLProps<HTMLDivElement>, 'as' | 'height' | 'ref' | 'title'>,
48
49
  ): React.ReactElement {
49
- const {closable, description, onClose, title, status, ...restProps} = props
50
+ const {closable, description, onClose, radius = 3, title, status, ...restProps} = props
50
51
  const cardTone = status ? STATUS_CARD_TONE[status] : 'default'
51
52
  const role = status ? ROLES[status] : 'status'
52
53
 
@@ -56,14 +57,18 @@ export function Toast(
56
57
  role={role}
57
58
  {...restProps}
58
59
  marginTop={3}
59
- radius={2}
60
+ radius={radius}
60
61
  shadow={2}
61
62
  tone={cardTone}
62
63
  >
63
64
  <Flex align="flex-start">
64
65
  <TextBox flex={1} padding={3}>
65
66
  <Stack space={3}>
66
- {title && <Text weight="semibold">{title}</Text>}
67
+ {title && (
68
+ <Text size={1} weight="medium">
69
+ {title}
70
+ </Text>
71
+ )}
67
72
  {description && (
68
73
  <Text muted size={1}>
69
74
  {description}
@@ -8,7 +8,9 @@ export function badgeStyle(props: BadgeStyleProps & ThemeProps): CSSObject {
8
8
  const color = palette[$tone] || palette.default
9
9
 
10
10
  return {
11
- backgroundColor: color.enabled.bg,
11
+ '--card-fg-color': color.enabled.fg,
12
+
13
+ backgroundColor: color.enabled.bg2,
12
14
  color: color.enabled.fg,
13
15
  boxShadow: `inset 0 0 0 1px ${color.enabled.border}`,
14
16
  cursor: 'default',
@@ -0,0 +1,99 @@
1
+ import {
2
+ BoundaryElementProvider,
3
+ Button,
4
+ Card,
5
+ Flex,
6
+ Portal,
7
+ PortalProvider,
8
+ Stack,
9
+ Text,
10
+ Tooltip,
11
+ } from '@sanity/ui'
12
+ import {useBoolean, useSelect, useText} from '@sanity/ui-workshop'
13
+ import {useState} from 'react'
14
+ import {WORKSHOP_PLACEMENT_OPTIONS} from '../../../__workshop__/constants'
15
+
16
+ const PORTAL_OPTIONS = {
17
+ '(true)': true,
18
+ '(false)': false,
19
+ portal1: 'portal1',
20
+ }
21
+
22
+ export default function CustomPortalStory() {
23
+ const content = useText(
24
+ 'Content',
25
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut mollis consectetur malesuada. Sed lobortis est dolor, eget imperdiet velit placerat et. Aenean posuere mi non aliquet iaculis. Donec fermentum pulvinar purus at sagittis. Ut tincidunt massa odio, sed finibus justo ullamcorper id. Nam venenatis justo non ligula elementum cursus. Pellentesque laoreet justo in mollis sagittis. In lacinia ornare ultrices. Suspendisse potenti.',
26
+ )
27
+ const placement = useSelect('Placement', WORKSHOP_PLACEMENT_OPTIONS, 'top')
28
+ const portal = useSelect('Portal', PORTAL_OPTIONS, undefined, 'Props')
29
+ const useBoundaryElement = useBoolean('Use boundary element', true)
30
+
31
+ const [portal1Element, setPortal1Element] = useState<HTMLDivElement | null>(null)
32
+ const [boundaryElement, setBoundaryElement] = useState<HTMLDivElement | null>(null)
33
+
34
+ return (
35
+ <PortalProvider
36
+ __unstable_elements={{
37
+ portal1: portal1Element,
38
+ }}
39
+ >
40
+ <Flex align="center" height="fill" justify="center">
41
+ <BoundaryElementProvider element={useBoundaryElement ? boundaryElement : null}>
42
+ <Card
43
+ border
44
+ padding={4}
45
+ ref={setBoundaryElement}
46
+ style={{
47
+ height: 'calc(100vh - 100px)',
48
+ position: 'relative',
49
+ width: '500px',
50
+ }}
51
+ >
52
+ <Text>Boundary element</Text>
53
+ <Flex align="center" height="fill" justify="center">
54
+ <Flex justify="center">
55
+ <Stack space={2}>
56
+ <Tooltip
57
+ boundaryElement={useBoundaryElement ? boundaryElement : null}
58
+ content={<Text size={1}>{content}</Text>}
59
+ padding={2}
60
+ placement={placement}
61
+ portal={portal}
62
+ >
63
+ <Button mode="bleed" text="Tooltip" />
64
+ </Tooltip>
65
+ </Stack>
66
+ </Flex>
67
+ </Flex>
68
+ </Card>
69
+ </BoundaryElementProvider>
70
+ </Flex>
71
+
72
+ <Card
73
+ border
74
+ margin={4}
75
+ padding={4}
76
+ ref={setPortal1Element}
77
+ style={{
78
+ left: 0,
79
+ position: 'absolute',
80
+ top: 0,
81
+ width: '175px',
82
+ }}
83
+ tone="critical"
84
+ />
85
+
86
+ <Portal __unstable_name="portal1">
87
+ <Stack space={4}>
88
+ <Text size={1} weight="medium">
89
+ Portal 1 content
90
+ </Text>
91
+ <Text size={1}>
92
+ The tooltip's max-width should not exceed this container's width (minus padding) if it's
93
+ set to use this portal
94
+ </Text>
95
+ </Stack>
96
+ </Portal>
97
+ </PortalProvider>
98
+ )
99
+ }
@@ -10,5 +10,20 @@ export default defineScope({
10
10
  title: 'Props',
11
11
  component: lazy(() => import('./props')),
12
12
  },
13
+ {
14
+ name: 'resizableBoundary',
15
+ title: 'Resizable Boundary',
16
+ component: lazy(() => import('./resizableBoundary')),
17
+ },
18
+ {
19
+ name: 'overflowBoundary',
20
+ title: 'Overflowing Boundary',
21
+ component: lazy(() => import('./overflowingBoundary')),
22
+ },
23
+ {
24
+ name: 'customPortalStory',
25
+ title: 'Custom Portal',
26
+ component: lazy(() => import('./customPortal')),
27
+ },
13
28
  ],
14
29
  })
@@ -0,0 +1,73 @@
1
+ import {BoundaryElementProvider, Button, Card, Code, Flex, Stack, Text, Tooltip} from '@sanity/ui'
2
+ import {useBoolean, useSelect, useText} from '@sanity/ui-workshop'
3
+ import {useCallback, useState} from 'react'
4
+ import {WORKSHOP_PLACEMENT_OPTIONS} from '../../../__workshop__/constants'
5
+
6
+ export default function OverflowingBoundaryStory() {
7
+ const content = useText(
8
+ 'Content',
9
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut mollis consectetur malesuada. Sed lobortis est dolor, eget imperdiet velit placerat et. Aenean posuere mi non aliquet iaculis. Donec fermentum pulvinar purus at sagittis. Ut tincidunt massa odio, sed finibus justo ullamcorper id. Nam venenatis justo non ligula elementum cursus. Pellentesque laoreet justo in mollis sagittis. In lacinia ornare ultrices. Suspendisse potenti.',
10
+ )
11
+ const placement = useSelect('Placement', WORKSHOP_PLACEMENT_OPTIONS, 'top')
12
+ const portal = useBoolean('Portal', false)
13
+ const useBoundaryElement = useBoolean('Use boundary element', true)
14
+
15
+ const [boundaryElement, setBoundaryElement] = useState<HTMLDivElement | null>(null)
16
+ const [buttonsVisible, setButtonsVisible] = useState(true)
17
+
18
+ const handleHideButtons = useCallback(() => {
19
+ setButtonsVisible(false)
20
+ }, [])
21
+
22
+ const handleShowButtons = useCallback(() => {
23
+ setButtonsVisible(true)
24
+ }, [])
25
+
26
+ return (
27
+ <Flex align="center" height="fill" justify="center" style={{height: '200vh'}}>
28
+ <BoundaryElementProvider element={useBoundaryElement ? boundaryElement : null}>
29
+ <Card
30
+ border
31
+ ref={setBoundaryElement}
32
+ style={{
33
+ height: 'calc(100vh - 100px)',
34
+ overflow: 'hidden',
35
+ position: 'relative',
36
+ resize: 'both',
37
+ width: '500px',
38
+ }}
39
+ >
40
+ <Flex align="center" height="fill" justify="center">
41
+ <Flex justify="center">
42
+ <Stack space={2}>
43
+ <Code size={1}>Placement: {placement}</Code>
44
+ <Button
45
+ disabled={buttonsVisible}
46
+ fontSize={1}
47
+ onClick={handleShowButtons}
48
+ text="Show buttons"
49
+ />
50
+ </Stack>
51
+ </Flex>
52
+ </Flex>
53
+
54
+ {buttonsVisible && (
55
+ <Tooltip
56
+ content={<Text size={1}>{content}</Text>}
57
+ padding={2}
58
+ placement={placement}
59
+ portal={portal}
60
+ >
61
+ <Button
62
+ mode="bleed"
63
+ onClick={handleHideButtons}
64
+ style={{position: 'absolute', top: 10, right: 10}}
65
+ text="Tooltip"
66
+ />
67
+ </Tooltip>
68
+ )}
69
+ </Card>
70
+ </BoundaryElementProvider>
71
+ </Flex>
72
+ )
73
+ }
@@ -0,0 +1,85 @@
1
+ import {BoundaryElementProvider, Button, Card, Code, Flex, Text, Tooltip} from '@sanity/ui'
2
+ import {useBoolean, useSelect, useText} from '@sanity/ui-workshop'
3
+ import {useState} from 'react'
4
+ import {WORKSHOP_PLACEMENT_OPTIONS} from '../../../__workshop__/constants'
5
+
6
+ export default function ResizableBoundaryStory() {
7
+ const content = useText(
8
+ 'Content',
9
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut mollis consectetur malesuada. Sed lobortis est dolor, eget imperdiet velit placerat et. Aenean posuere mi non aliquet iaculis. Donec fermentum pulvinar purus at sagittis. Ut tincidunt massa odio, sed finibus justo ullamcorper id. Nam venenatis justo non ligula elementum cursus. Pellentesque laoreet justo in mollis sagittis. In lacinia ornare ultrices. Suspendisse potenti.',
10
+ )
11
+ const placement = useSelect('Placement', WORKSHOP_PLACEMENT_OPTIONS, 'right')
12
+ const portal = useBoolean('Portal', true)
13
+ const useBoundaryElement = useBoolean('Use boundary element', true)
14
+
15
+ const [boundaryElement, setBoundaryElement] = useState<HTMLDivElement | null>(null)
16
+
17
+ return (
18
+ <Flex align="center" height="fill" justify="center">
19
+ <BoundaryElementProvider element={useBoundaryElement ? boundaryElement : null}>
20
+ <Card
21
+ border
22
+ ref={setBoundaryElement}
23
+ style={{
24
+ height: 'calc(100vh - 100px)',
25
+ overflow: 'hidden',
26
+ position: 'relative',
27
+ resize: 'both',
28
+ width: '500px',
29
+ }}
30
+ >
31
+ <Flex align="center" height="fill" justify="center">
32
+ <Flex justify="center">
33
+ <Code size={1}>Placement: {placement}</Code>
34
+ </Flex>
35
+ </Flex>
36
+
37
+ <Tooltip
38
+ content={<Text size={1}>{content}</Text>}
39
+ padding={2}
40
+ placement={placement}
41
+ portal={portal}
42
+ >
43
+ <Button mode="bleed" style={{position: 'absolute', top: 10, left: 10}} text="Tooltip" />
44
+ </Tooltip>
45
+ <Tooltip
46
+ content={<Text size={1}>{content}</Text>}
47
+ padding={2}
48
+ placement={placement}
49
+ portal={portal}
50
+ >
51
+ <Button
52
+ mode="bleed"
53
+ style={{position: 'absolute', top: 10, right: 10}}
54
+ text="Tooltip"
55
+ />
56
+ </Tooltip>
57
+ <Tooltip
58
+ content={<Text size={1}>{content}</Text>}
59
+ padding={2}
60
+ placement={placement}
61
+ portal={portal}
62
+ >
63
+ <Button
64
+ mode="bleed"
65
+ style={{position: 'absolute', bottom: 10, left: 10}}
66
+ text="Tooltip"
67
+ />
68
+ </Tooltip>
69
+ <Tooltip
70
+ content={<Text size={1}>{content}</Text>}
71
+ padding={2}
72
+ placement={placement}
73
+ portal={portal}
74
+ >
75
+ <Button
76
+ mode="bleed"
77
+ style={{position: 'absolute', bottom: 10, right: 10}}
78
+ text="Tooltip"
79
+ />
80
+ </Tooltip>
81
+ </Card>
82
+ </BoundaryElementProvider>
83
+ </Flex>
84
+ )
85
+ }
@@ -1,5 +1,7 @@
1
1
  import {Placement} from '@floating-ui/react-dom'
2
2
 
3
+ export const DEFAULT_TOOLTIP_DISTANCE = 4
4
+ export const DEFAULT_TOOLTIP_PADDING = 4
3
5
  export const DEFAULT_FALLBACK_PLACEMENTS: Record<Placement, Placement[]> = {
4
6
  top: ['bottom', 'left', 'right'],
5
7
  'top-start': ['bottom-start', 'left-start', 'right-start'],
@@ -6,6 +6,17 @@ import {render} from '../../../test'
6
6
  import {Text, Button} from '../../primitives'
7
7
  import {Tooltip, TooltipDelayGroupProvider} from '../tooltip'
8
8
 
9
+ // Fake timers using Jest
10
+ beforeEach(() => {
11
+ jest.useFakeTimers()
12
+ })
13
+
14
+ // Running all pending timers and switching to real timers using Jest
15
+ afterEach(() => {
16
+ jest.runOnlyPendingTimers()
17
+ jest.useRealTimers()
18
+ })
19
+
9
20
  describe('Tooltip', () => {
10
21
  describe('Using same delay for open and close', () => {
11
22
  it('should hide and show the tooltip content when hovered, with no delay', () => {
@@ -275,6 +286,7 @@ describe('Tooltip', () => {
275
286
  jest.clearAllMocks()
276
287
  })
277
288
  })
289
+
278
290
  describe('Closing the <Tooltip /> with the Escape key', () => {
279
291
  it('Standalone tooltip closes immediately with Escape key', () => {
280
292
  const delay = 150
@@ -342,4 +354,117 @@ describe('Tooltip', () => {
342
354
  expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
343
355
  })
344
356
  })
357
+
358
+ describe('Clicking the <Tooltip /> child should close the tooltip', () => {
359
+ it('Should close the tooltip when clicked', () => {
360
+ const delay = 150
361
+
362
+ render(
363
+ <Tooltip content={<Text size={1}>{'Tooltip content'}</Text>} delay={delay}>
364
+ <Button mode="bleed" text="Hover me" />
365
+ </Tooltip>,
366
+ )
367
+
368
+ const button = screen.getByText('Hover me')
369
+
370
+ // Assertion: tooltip does not exist in the document
371
+ expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
372
+ fireEvent.focus(button)
373
+
374
+ act(() => jest.advanceTimersByTime(delay))
375
+
376
+ // Assertion: the tooltip is not visible
377
+ expect(screen.queryByText('Tooltip content')).toBeVisible()
378
+
379
+ act(() => fireEvent.click(button))
380
+
381
+ // Assertion: tooltip does not exist in the document
382
+ expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
383
+ })
384
+
385
+ it('Should close the tooltip when the context menu is opened (right click)', () => {
386
+ const delay = 150
387
+
388
+ render(
389
+ <Tooltip content={<Text size={1}>{'Tooltip content'}</Text>} delay={delay}>
390
+ <Button mode="bleed" text="Hover me" />
391
+ </Tooltip>,
392
+ )
393
+
394
+ const button = screen.getByText('Hover me')
395
+
396
+ // Assertion: tooltip does not exist in the document
397
+ expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
398
+ fireEvent.focus(button)
399
+
400
+ act(() => jest.advanceTimersByTime(delay))
401
+
402
+ // Assertion: the tooltip is not visible
403
+ expect(screen.queryByText('Tooltip content')).toBeVisible()
404
+
405
+ act(() => fireEvent.contextMenu(button))
406
+
407
+ // Assertion: tooltip does not exist in the document
408
+ expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
409
+ })
410
+ })
411
+
412
+ describe('Used defined events on <Tooltip /> child should fire correctly', () => {
413
+ const handleBlur = jest.fn()
414
+ const handleClick = jest.fn()
415
+ const handleContextMenu = jest.fn()
416
+ const handleFocus = jest.fn()
417
+ const handleMouseEnter = jest.fn()
418
+ const handleMouseLeave = jest.fn()
419
+
420
+ beforeEach(() => {
421
+ render(
422
+ <Tooltip content={<Text size={1}>{'Tooltip content'}</Text>}>
423
+ <Button
424
+ data-testid="btn"
425
+ mode="bleed"
426
+ onBlur={handleBlur}
427
+ onClick={handleClick}
428
+ onContextMenu={handleContextMenu}
429
+ onFocus={handleFocus}
430
+ onMouseEnter={handleMouseEnter}
431
+ onMouseLeave={handleMouseLeave}
432
+ text="Hover me"
433
+ />
434
+ </Tooltip>,
435
+ )
436
+ })
437
+
438
+ afterEach(() => jest.clearAllMocks())
439
+
440
+ it('should fire the onBlur event', () => {
441
+ fireEvent.blur(screen.getByTestId('btn'))
442
+ expect(handleBlur).toHaveBeenCalledTimes(1)
443
+ })
444
+
445
+ it('should fire the onClick event', () => {
446
+ fireEvent.click(screen.getByTestId('btn'))
447
+ expect(handleClick).toHaveBeenCalledTimes(1)
448
+ })
449
+
450
+ it('should fire the onContextMenu event', () => {
451
+ fireEvent.contextMenu(screen.getByTestId('btn'))
452
+ expect(handleContextMenu).toHaveBeenCalledTimes(1)
453
+ })
454
+
455
+ it('should fire the onFocus event', () => {
456
+ fireEvent.focus(screen.getByTestId('btn'))
457
+ expect(handleFocus).toHaveBeenCalledTimes(1)
458
+ })
459
+
460
+ it('should fire the onMouseEnter event', () => {
461
+ fireEvent.mouseEnter(screen.getByTestId('btn'))
462
+ expect(handleMouseEnter).toHaveBeenCalledTimes(1)
463
+ })
464
+
465
+ it('should fire the onMouseLeave event', () => {
466
+ fireEvent.mouseLeave(screen.getByTestId('btn'))
467
+ expect(handleMouseLeave).toHaveBeenCalledTimes(1)
468
+ })
469
+ })
345
470
  })