@dxos/react-ui 0.1.24 → 0.1.25-next.3c6b542

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 (132) hide show
  1. package/dist/lib/browser/index.mjs +1092 -452
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/IdentityList/DeviceList.d.ts +7 -0
  5. package/dist/types/src/components/IdentityList/DeviceList.d.ts.map +1 -0
  6. package/dist/types/src/components/IdentityList/IdentityListItem.d.ts +2 -1
  7. package/dist/types/src/components/IdentityList/IdentityListItem.d.ts.map +1 -1
  8. package/dist/types/src/components/IdentityList/IdentityListItem.stories.d.ts +2 -1
  9. package/dist/types/src/components/IdentityList/IdentityListItem.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/IdentityList/SpaceMemberList.d.ts +3 -2
  11. package/dist/types/src/components/IdentityList/SpaceMemberList.d.ts.map +1 -1
  12. package/dist/types/src/components/IdentityList/SpaceMemberListContainer.d.ts +3 -1
  13. package/dist/types/src/components/IdentityList/SpaceMemberListContainer.d.ts.map +1 -1
  14. package/dist/types/src/components/IdentityList/index.d.ts +1 -0
  15. package/dist/types/src/components/IdentityList/index.d.ts.map +1 -1
  16. package/dist/types/src/components/InvitationList/InvitationList.d.ts.map +1 -1
  17. package/dist/types/src/components/InvitationList/InvitationListItem.d.ts.map +1 -1
  18. package/dist/types/src/components/InvitationList/InvitationStatusAvatar.d.ts +6 -1
  19. package/dist/types/src/components/InvitationList/InvitationStatusAvatar.d.ts.map +1 -1
  20. package/dist/types/src/components/InvitationList/InvitationStatusAvatar.stories.d.ts +1 -1
  21. package/dist/types/src/components/SpaceList/SpaceListItem.d.ts +7 -0
  22. package/dist/types/src/components/SpaceList/SpaceListItem.d.ts.map +1 -0
  23. package/dist/types/src/components/SpaceList/index.d.ts +2 -0
  24. package/dist/types/src/components/SpaceList/index.d.ts.map +1 -0
  25. package/dist/types/src/components/index.d.ts +1 -0
  26. package/dist/types/src/components/index.d.ts.map +1 -1
  27. package/dist/types/src/composites/DevicesDialog/DevicesDialog.d.ts +6 -0
  28. package/dist/types/src/composites/DevicesDialog/DevicesDialog.d.ts.map +1 -0
  29. package/dist/types/src/composites/DevicesDialog/index.d.ts +2 -0
  30. package/dist/types/src/composites/DevicesDialog/index.d.ts.map +1 -0
  31. package/dist/types/src/composites/JoinDialog/JoinDialog.d.ts +1 -1
  32. package/dist/types/src/composites/JoinDialog/JoinDialog.d.ts.map +1 -1
  33. package/dist/types/src/composites/JoinDialog/JoinDialog.stories.d.ts +2 -2
  34. package/dist/types/src/composites/JoinDialog/JoinDialog.stories.d.ts.map +1 -1
  35. package/dist/types/src/composites/Shell/Shell.d.ts +6 -0
  36. package/dist/types/src/composites/Shell/Shell.d.ts.map +1 -0
  37. package/dist/types/src/composites/Shell/index.d.ts +2 -0
  38. package/dist/types/src/composites/Shell/index.d.ts.map +1 -0
  39. package/dist/types/src/composites/SpaceDialog/SpaceDialog.d.ts +6 -0
  40. package/dist/types/src/composites/SpaceDialog/SpaceDialog.d.ts.map +1 -0
  41. package/dist/types/src/composites/SpaceDialog/index.d.ts +2 -0
  42. package/dist/types/src/composites/SpaceDialog/index.d.ts.map +1 -0
  43. package/dist/types/src/composites/index.d.ts +2 -0
  44. package/dist/types/src/composites/index.d.ts.map +1 -1
  45. package/dist/types/src/layouts/PanelAlertDialog/PanelAlertDialog.d.ts.map +1 -1
  46. package/dist/types/src/layouts/PanelDialog/PanelDialog.d.ts +14 -0
  47. package/dist/types/src/layouts/PanelDialog/PanelDialog.d.ts.map +1 -0
  48. package/dist/types/src/layouts/PanelDialog/index.d.ts +2 -0
  49. package/dist/types/src/layouts/PanelDialog/index.d.ts.map +1 -0
  50. package/dist/types/src/layouts/index.d.ts +1 -0
  51. package/dist/types/src/layouts/index.d.ts.map +1 -1
  52. package/dist/types/src/panels/DevicesPanel/DevicesPanel.d.ts +10 -0
  53. package/dist/types/src/panels/DevicesPanel/DevicesPanel.d.ts.map +1 -0
  54. package/dist/types/src/panels/DevicesPanel/index.d.ts +2 -0
  55. package/dist/types/src/panels/DevicesPanel/index.d.ts.map +1 -0
  56. package/dist/types/src/panels/JoinPanel/JoinHeading.d.ts +3 -0
  57. package/dist/types/src/panels/JoinPanel/JoinHeading.d.ts.map +1 -1
  58. package/dist/types/src/panels/JoinPanel/JoinPanel.d.ts +1 -1
  59. package/dist/types/src/panels/JoinPanel/JoinPanel.d.ts.map +1 -1
  60. package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts +4 -3
  61. package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts.map +1 -1
  62. package/dist/types/src/panels/JoinPanel/view-states/AdditionMethodSelector.d.ts +1 -1
  63. package/dist/types/src/panels/JoinPanel/view-states/AdditionMethodSelector.d.ts.map +1 -1
  64. package/dist/types/src/panels/JoinPanel/view-states/IdentityAdded.d.ts +10 -2
  65. package/dist/types/src/panels/JoinPanel/view-states/IdentityAdded.d.ts.map +1 -1
  66. package/dist/types/src/panels/JoinPanel/view-states/IdentityInput.d.ts.map +1 -1
  67. package/dist/types/src/panels/JoinPanel/view-states/IdentitySelector.d.ts +1 -1
  68. package/dist/types/src/panels/JoinPanel/view-states/IdentitySelector.d.ts.map +1 -1
  69. package/dist/types/src/panels/JoinPanel/view-states/InvitationAccepted.d.ts.map +1 -1
  70. package/dist/types/src/panels/JoinPanel/view-states/InvitationAuthenticator.d.ts.map +1 -1
  71. package/dist/types/src/panels/JoinPanel/view-states/InvitationInput.d.ts.map +1 -1
  72. package/dist/types/src/panels/JoinPanel/view-states/InvitationRescuer.d.ts.map +1 -1
  73. package/dist/types/src/panels/SpacePanel/SpacePanel.d.ts +18 -0
  74. package/dist/types/src/panels/SpacePanel/SpacePanel.d.ts.map +1 -0
  75. package/dist/types/src/panels/SpacePanel/SpacePanel.stories.d.ts +9 -0
  76. package/dist/types/src/panels/SpacePanel/SpacePanel.stories.d.ts.map +1 -0
  77. package/dist/types/src/panels/SpacePanel/index.d.ts +2 -0
  78. package/dist/types/src/panels/SpacePanel/index.d.ts.map +1 -0
  79. package/dist/types/src/panels/index.d.ts +2 -0
  80. package/dist/types/src/panels/index.d.ts.map +1 -1
  81. package/dist/types/src/styles/dialogStyles.d.ts +3 -0
  82. package/dist/types/src/styles/dialogStyles.d.ts.map +1 -0
  83. package/dist/types/src/styles/index.d.ts +1 -0
  84. package/dist/types/src/styles/index.d.ts.map +1 -1
  85. package/dist/types/src/translations/locales/en-US.d.ts +12 -1
  86. package/dist/types/src/translations/locales/en-US.d.ts.map +1 -1
  87. package/package.json +12 -11
  88. package/src/components/IdentityList/DeviceList.tsx +31 -0
  89. package/src/components/IdentityList/IdentityListItem.tsx +15 -3
  90. package/src/components/IdentityList/SpaceMemberList.tsx +10 -8
  91. package/src/components/IdentityList/SpaceMemberListContainer.tsx +5 -4
  92. package/src/components/IdentityList/index.ts +1 -0
  93. package/src/components/InvitationList/InvitationList.tsx +6 -2
  94. package/src/components/InvitationList/InvitationListItem.tsx +36 -25
  95. package/src/components/InvitationList/InvitationStatusAvatar.tsx +10 -5
  96. package/src/components/SpaceList/SpaceListItem.tsx +32 -0
  97. package/src/components/SpaceList/index.ts +5 -0
  98. package/src/components/index.ts +1 -0
  99. package/src/composites/DevicesDialog/DevicesDialog.tsx +38 -0
  100. package/src/composites/DevicesDialog/index.ts +5 -0
  101. package/src/composites/JoinDialog/JoinDialog.stories.tsx +59 -58
  102. package/src/composites/JoinDialog/JoinDialog.tsx +3 -1
  103. package/src/composites/Shell/Shell.tsx +92 -0
  104. package/src/composites/Shell/index.ts +5 -0
  105. package/src/composites/SpaceDialog/SpaceDialog.tsx +38 -0
  106. package/src/composites/SpaceDialog/index.ts +5 -0
  107. package/src/composites/index.ts +3 -0
  108. package/src/layouts/PanelAlertDialog/PanelAlertDialog.tsx +4 -8
  109. package/src/layouts/PanelDialog/PanelDialog.tsx +39 -0
  110. package/src/layouts/PanelDialog/index.ts +5 -0
  111. package/src/layouts/PanelSidebar/PanelSidebar.tsx +1 -1
  112. package/src/layouts/index.ts +1 -0
  113. package/src/panels/DevicesPanel/DevicesPanel.tsx +98 -0
  114. package/src/panels/DevicesPanel/index.ts +5 -0
  115. package/src/panels/JoinPanel/JoinHeading.tsx +44 -16
  116. package/src/panels/JoinPanel/JoinPanel.tsx +32 -11
  117. package/src/panels/JoinPanel/JoinPanelProps.ts +4 -8
  118. package/src/panels/JoinPanel/view-states/AdditionMethodSelector.tsx +5 -1
  119. package/src/panels/JoinPanel/view-states/IdentityAdded.tsx +63 -24
  120. package/src/panels/JoinPanel/view-states/IdentityInput.tsx +8 -1
  121. package/src/panels/JoinPanel/view-states/IdentitySelector.tsx +4 -1
  122. package/src/panels/JoinPanel/view-states/InvitationAccepted.tsx +1 -0
  123. package/src/panels/JoinPanel/view-states/InvitationAuthenticator.tsx +4 -1
  124. package/src/panels/JoinPanel/view-states/InvitationInput.tsx +8 -1
  125. package/src/panels/JoinPanel/view-states/InvitationRescuer.tsx +4 -1
  126. package/src/panels/SpacePanel/SpacePanel.stories.tsx +53 -0
  127. package/src/panels/SpacePanel/SpacePanel.tsx +174 -0
  128. package/src/panels/SpacePanel/index.ts +5 -0
  129. package/src/panels/index.ts +2 -0
  130. package/src/styles/dialogStyles.ts +8 -0
  131. package/src/styles/index.ts +1 -0
  132. package/src/translations/locales/en-US.ts +14 -3
@@ -8,7 +8,7 @@ import React, { useCallback } from 'react';
8
8
 
9
9
  import { CancellableInvitationObservable } from '@dxos/client';
10
10
  import { useInvitationStatus } from '@dxos/react-client';
11
- import { Button, getSize, mx, useTranslation } from '@dxos/react-components';
11
+ import { Button, getSize, mx, useId, useTranslation } from '@dxos/react-components';
12
12
 
13
13
  import { invitationStatusValue } from '../../util';
14
14
  import { SharedInvitationListProps } from './InvitationListProps';
@@ -27,18 +27,19 @@ export const InvitationListItem = ({
27
27
  createInvitationUrl
28
28
  }: InvitationListItemProps) => {
29
29
  const { t } = useTranslation('os');
30
+ const qrLabel = useId('qrLabel');
30
31
 
31
32
  const { cancel, status, haltedAt, invitationCode, authenticationCode } = useInvitationStatus(invitation);
32
33
  const statusValue = invitationStatusValue.get(status) ?? 0;
33
34
 
34
35
  const isCancellable = statusValue < 4 && statusValue >= 0;
35
36
  const showShare = statusValue < 3 && statusValue >= 0;
36
- const showPin = statusValue === 3;
37
+ const showAuthCode = statusValue === 3;
37
38
 
38
39
  const handleClickRemove = useCallback(() => onClickRemove(invitation), [invitation, onClickRemove]);
39
40
 
40
41
  const invitationUrl = invitationCode && createInvitationUrl(invitationCode);
41
- const handleCLickCopy = useCallback(() => {
42
+ const handleClickCopy = useCallback(() => {
42
43
  if (invitationUrl) {
43
44
  void navigator.clipboard.writeText(invitationUrl);
44
45
  }
@@ -47,15 +48,21 @@ export const InvitationListItem = ({
47
48
  return (
48
49
  <AccordionPrimitive.Item value={value}>
49
50
  <AccordionPrimitive.Header className='flex gap-2 items-center'>
50
- <InvitationStatusAvatar {...{ status, haltedAt }} />
51
+ <InvitationStatusAvatar {...{ status, haltedAt, size: 8 }} />
51
52
  {showShare && invitationUrl ? (
52
- <AccordionPrimitive.Trigger asChild>
53
- <Button className='grow flex gap-1'>
54
- <span>{t('open share panel label')}</span>
55
- <QrCode className={getSize(4)} weight='bold' />
53
+ <>
54
+ <AccordionPrimitive.Trigger asChild>
55
+ <Button className='grow flex gap-1' data-testid='show-qrcode'>
56
+ <span>{t('open share panel label')}</span>
57
+ <QrCode className={getSize(4)} weight='bold' />
58
+ </Button>
59
+ </AccordionPrimitive.Trigger>
60
+ <Button className='flex gap-1' onClick={handleClickCopy} data-testid='copy-invitation'>
61
+ <span className='pli-1'>{t('copy invitation code label')}</span>
62
+ <Copy className={getSize(4)} weight='bold' />
56
63
  </Button>
57
- </AccordionPrimitive.Trigger>
58
- ) : showPin ? (
64
+ </>
65
+ ) : showAuthCode ? (
59
66
  <p className='grow text-xl text-center text-success-500 dark:text-success-300 font-mono'>
60
67
  {authenticationCode}
61
68
  </p>
@@ -63,31 +70,35 @@ export const InvitationListItem = ({
63
70
  <span role='none' className='grow' />
64
71
  )}
65
72
  {isCancellable ? (
66
- <Button variant='ghost' compact className='flex gap-1' onClick={cancel}>
73
+ <Button variant='ghost' compact className='flex gap-1' onClick={cancel} data-testid='cancel-invitation'>
67
74
  <span className='sr-only'>{t('cancel invitation label')}</span>
68
75
  <ProhibitInset className={getSize(4)} weight='bold' />
69
76
  </Button>
70
77
  ) : (
71
- <Button variant='ghost' compact className='flex gap-1' onClick={handleClickRemove}>
78
+ <Button
79
+ variant='ghost'
80
+ compact
81
+ className='flex gap-1'
82
+ onClick={handleClickRemove}
83
+ data-testid='remove-invitation'
84
+ >
72
85
  <span className='sr-only'>{t('remove invitation label')}</span>
73
86
  <X className={getSize(4)} weight='bold' />
74
87
  </Button>
75
88
  )}
76
89
  </AccordionPrimitive.Header>
77
90
  {showShare && invitationUrl && (
78
- <AccordionPrimitive.Content>
79
- <Button className='flex gap-2 is-full p-2' onClick={handleCLickCopy}>
80
- <QRCodeSVG
81
- bgColor='transparent'
82
- fgColor='currentColor'
83
- value={invitationUrl}
84
- className={mx('grow aspect-square is-24 bs-auto')}
85
- />
86
- <span className='pli-1'>
87
- {t('copy invitation code label')}
88
- <Copy className={mx(getSize(4), 'inline mis-1')} weight='bold' />
89
- </span>
90
- </Button>
91
+ <AccordionPrimitive.Content className='flex gap-2 is-full radix-state-open:p-1 items-center'>
92
+ <QRCodeSVG
93
+ bgColor='transparent'
94
+ fgColor='currentColor'
95
+ value={invitationUrl}
96
+ className={mx('grow-[2] aspect-square is-24 bs-auto')}
97
+ aria-labelledby={qrLabel}
98
+ />
99
+ <span className='pli-1 flex-1 font-system-normal text-sm text-center' id={qrLabel}>
100
+ {t('qr label')}
101
+ </span>
91
102
  </AccordionPrimitive.Content>
92
103
  )}
93
104
  </AccordionPrimitive.Item>
@@ -5,19 +5,24 @@
5
5
  import React, { ComponentPropsWithoutRef } from 'react';
6
6
 
7
7
  import { Invitation } from '@dxos/client';
8
- import { getSize, Size } from '@dxos/react-components';
8
+ import { getSize, mx, Size } from '@dxos/react-components';
9
9
 
10
10
  import { inactiveStrokeColor, activeStrokeColor, resolvedStrokeColor } from '../../styles';
11
11
  import { invitationStatusValue } from '../../util';
12
12
 
13
+ export interface InvitationStatusAvatarSlots {
14
+ svg?: ComponentPropsWithoutRef<'svg'>;
15
+ }
16
+
13
17
  export interface InvitationStatusAvatarProps {
14
18
  size?: Size;
15
19
  status: Invitation.State;
16
20
  haltedAt?: Invitation.State;
21
+ slots?: InvitationStatusAvatarSlots;
17
22
  }
18
23
 
19
24
  const svgSize = 32;
20
- const strokeWidth = 4;
25
+ const strokeWidth = 5;
21
26
  const radius = (svgSize - strokeWidth) / 2;
22
27
  const circumference = Math.PI * 2 * radius;
23
28
  const gap = circumference / 12;
@@ -31,17 +36,17 @@ const circleProps: ComponentPropsWithoutRef<'circle'> = {
31
36
  cx: svgSize / 2,
32
37
  cy: svgSize / 2,
33
38
  r: radius,
34
- strokeWidth: 4,
39
+ strokeWidth,
35
40
  strokeDasharray: `${segment - gap} ${2 * segment + gap}`
36
41
  };
37
42
 
38
- export const InvitationStatusAvatar = ({ size = 10, status, haltedAt }: InvitationStatusAvatarProps) => {
43
+ export const InvitationStatusAvatar = ({ size = 10, status, haltedAt, slots = {} }: InvitationStatusAvatarProps) => {
39
44
  const resolvedColor = resolvedStrokeColor(status);
40
45
  const halted =
41
46
  status === Invitation.State.CANCELLED || status === Invitation.State.TIMEOUT || status === Invitation.State.ERROR;
42
47
  const cursor = invitationStatusValue.get(halted ? haltedAt! : status)!;
43
48
  return (
44
- <svg viewBox={`0 0 ${svgSize} ${svgSize}`} className={getSize(size)}>
49
+ <svg {...slots.svg} viewBox={`0 0 ${svgSize} ${svgSize}`} className={mx(getSize(size), slots.svg?.className)}>
45
50
  {[...Array(nSegments)].map((_, index) => (
46
51
  <circle
47
52
  key={index}
@@ -0,0 +1,32 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React, { ForwardedRef, forwardRef } from 'react';
6
+
7
+ import type { Space } from '@dxos/client';
8
+ import { Avatar, mx } from '@dxos/react-components';
9
+ import { humanize } from '@dxos/util';
10
+
11
+ export const SpaceListItem = forwardRef(
12
+ ({ space, onClick }: { space: Space; onClick?: () => void }, ref: ForwardedRef<HTMLLIElement>) => {
13
+ return (
14
+ <li
15
+ className={mx('flex gap-2 items-center', onClick && 'cursor-pointer')}
16
+ onClick={() => onClick?.()}
17
+ ref={ref}
18
+ data-testid='space-list-item'
19
+ >
20
+ <Avatar
21
+ {...{
22
+ variant: 'circle',
23
+ size: 9,
24
+ fallbackValue: space.key.toHex(),
25
+ label: <p className='text-sm truncate'>{space.properties.name ?? humanize(space.key)}</p>,
26
+ slots: { labels: { className: 'block shrink overflow-hidden' }, root: { className: 'shrink-0' } }
27
+ }}
28
+ />
29
+ </li>
30
+ );
31
+ }
32
+ );
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './SpaceListItem';
@@ -5,3 +5,4 @@
5
5
  export * from './IdentityList';
6
6
  export * from './InvitationList';
7
7
  export * from './PanelSeparator';
8
+ export * from './SpaceList';
@@ -0,0 +1,38 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { Close } from '@radix-ui/react-dialog';
6
+ import React from 'react';
7
+
8
+ import { ThemeContext, useId } from '@dxos/react-components';
9
+
10
+ import { PanelDialog, PanelDialogProps } from '../../layouts';
11
+ import { DevicesPanel, DevicesPanelProps } from '../../panels';
12
+
13
+ export interface DevicesDialogProps
14
+ extends Omit<PanelDialogProps, 'titleId' | 'children'>,
15
+ Omit<DevicesPanelProps, 'doneActionParent'> {}
16
+
17
+ export const DevicesDialog = ({ slots, ...devicesDialogProps }: DevicesDialogProps) => {
18
+ const titleId = useId('spaceDialog__title');
19
+
20
+ return (
21
+ <PanelDialog
22
+ {...{
23
+ slots: { ...slots, root: { onOpenChange: (open) => open || devicesDialogProps.onDone?.(), ...slots?.root } },
24
+ titleId
25
+ }}
26
+ >
27
+ <ThemeContext.Provider value={{ themeVariant: 'os' }}>
28
+ <DevicesPanel
29
+ {...{
30
+ ...devicesDialogProps,
31
+ titleId,
32
+ doneActionParent: <Close asChild />
33
+ }}
34
+ />
35
+ </ThemeContext.Provider>
36
+ </PanelDialog>
37
+ );
38
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './DevicesDialog';
@@ -4,7 +4,7 @@
4
4
 
5
5
  import '@dxosTheme';
6
6
 
7
- import { StoryFn } from '@storybook/react';
7
+ import { StoryContext, StoryFn } from '@storybook/react';
8
8
  import React, { useCallback, useMemo, useState } from 'react';
9
9
 
10
10
  import { Client, Invitation, InvitationEncoder } from '@dxos/client';
@@ -19,62 +19,63 @@ import { JoinDialog } from './JoinDialog';
19
19
  export default {
20
20
  component: JoinDialog
21
21
  };
22
- export const Default = {
23
- decorators: [
24
- (Story: StoryFn) => {
25
- const n = 2;
26
- const testBuilder = new TestBuilder();
27
- const clients = useMemo(() => {
28
- return [...Array(n)].map(() => new Client({ services: testBuilder.createClientServicesHost() }));
29
- }, []);
30
-
31
- const [invitationCode, setInvitationCode] = useState<string>();
32
-
33
- const onInvitationEvent = useCallback((invitation: Invitation) => {
34
- if (!invitationCode) {
35
- const nextInvitationCode = InvitationEncoder.encode(invitation);
36
- log.info('[next invitation code]', { nextInvitationCode });
37
- setInvitationCode(nextInvitationCode);
38
- }
39
- }, []);
40
-
41
- useAsyncEffect(async () => {
42
- await Promise.all(clients.map((client) => client.initialize()));
43
- log.info('[initialized]');
44
-
45
- await clients[0].halo.createProfile({ displayName: 'Os Mutantes' });
46
-
47
- const space = await clients[0].echo.createSpace();
48
- log.info('[space created]', space);
49
- space.properties.name = 'Q3 2022 Planning';
50
-
51
- const invitation = space.createInvitation({ type: Invitation.Type.INTERACTIVE });
52
- log.info('[invitation created]', invitation);
53
-
54
- invitation.subscribe({
55
- onAuthenticating: onInvitationEvent,
56
- onCancelled: (...args) => log.warn('[cancelled]', args),
57
- onConnected: onInvitationEvent,
58
- onConnecting: onInvitationEvent,
59
- onError: onInvitationEvent,
60
- onSuccess: onInvitationEvent,
61
- onTimeout: (...args) => log.warn('[timeout]', args)
62
- });
63
-
64
- return () => {
65
- void Promise.all(clients.map((client) => client.destroy()));
66
- };
67
- }, clients);
68
-
69
- if (!invitationCode) {
70
- return <Loading label='Setting things up…' />;
71
- }
72
-
73
- return (
74
- <ClientProvider client={clients[1]} fallback={() => <Loading label='Loading client…' />}>
75
- <Story args={{ initialInvitationCode: invitationCode }} />
76
- </ClientProvider>
77
- );
22
+
23
+ const JoinClientDecorator = (Story: StoryFn, { args }: StoryContext) => {
24
+ const n = 2;
25
+ const testBuilder = new TestBuilder();
26
+ const clients = useMemo(() => {
27
+ return [...Array(n)].map(() => new Client({ services: testBuilder.createClientServicesHost() }));
28
+ }, []);
29
+
30
+ const [invitationCode, setInvitationCode] = useState<string>();
31
+
32
+ const onInvitationEvent = useCallback((invitation: Invitation) => {
33
+ if (!invitationCode) {
34
+ const nextInvitationCode = InvitationEncoder.encode(invitation);
35
+ log.info('[next invitation code]', { nextInvitationCode });
36
+ setInvitationCode(nextInvitationCode);
78
37
  }
79
- ]
38
+ }, []);
39
+
40
+ useAsyncEffect(async () => {
41
+ await Promise.all(clients.map((client) => client.initialize()));
42
+ log.info('[initialized]');
43
+
44
+ await clients[0].halo.createProfile({ displayName: 'Os Mutantes' });
45
+
46
+ const space = await clients[0].echo.createSpace();
47
+ log.info('[space created]', space);
48
+ space.properties.name = 'Q3 2022 Planning';
49
+
50
+ const invitation = space.createInvitation({ type: Invitation.Type.INTERACTIVE });
51
+ log.info('[invitation created]', invitation);
52
+
53
+ invitation.subscribe({
54
+ onAuthenticating: onInvitationEvent,
55
+ onCancelled: (...args) => log.warn('[cancelled]', args),
56
+ onConnected: onInvitationEvent,
57
+ onConnecting: onInvitationEvent,
58
+ onError: onInvitationEvent,
59
+ onSuccess: onInvitationEvent,
60
+ onTimeout: (...args) => log.warn('[timeout]', args)
61
+ });
62
+
63
+ return () => {
64
+ void Promise.all(clients.map((client) => client.destroy()));
65
+ };
66
+ }, clients);
67
+
68
+ if (!invitationCode) {
69
+ return <Loading label='Setting things up…' />;
70
+ }
71
+
72
+ return (
73
+ <ClientProvider client={clients[1]} fallback={() => <Loading label='Loading client…' />}>
74
+ <Story args={{ initialInvitationCode: invitationCode, ...args }} />
75
+ </ClientProvider>
76
+ );
77
+ };
78
+
79
+ export const Default = {
80
+ decorators: [JoinClientDecorator]
80
81
  };
@@ -10,7 +10,9 @@ import { ThemeContext, useId } from '@dxos/react-components';
10
10
  import { PanelAlertDialog, PanelAlertDialogProps } from '../../layouts';
11
11
  import { JoinPanel, JoinPanelProps } from '../../panels';
12
12
 
13
- export interface JoinDialogProps extends Omit<PanelAlertDialogProps, 'titleId' | 'children'>, JoinPanelProps {}
13
+ export interface JoinDialogProps
14
+ extends Omit<PanelAlertDialogProps, 'titleId' | 'children'>,
15
+ Omit<JoinPanelProps, 'exitActionParent' | 'doneActionParent'> {}
14
16
 
15
17
  export const JoinDialog = ({ slots, ...joinPanelProps }: JoinDialogProps) => {
16
18
  const titleId = useId('joinDialog__title');
@@ -0,0 +1,92 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React, { useEffect, useState } from 'react';
6
+
7
+ import { ShellRuntime } from '@dxos/client';
8
+ import { LayoutRequest, ShellDisplay, ShellLayout } from '@dxos/protocols/proto/dxos/iframe';
9
+ import { useClient, useSpace, useSpaces } from '@dxos/react-client';
10
+
11
+ import { DevicesDialog } from '../DevicesDialog';
12
+ import { JoinDialog } from '../JoinDialog';
13
+ import { SpaceDialog } from '../SpaceDialog';
14
+
15
+ export const Shell = ({ runtime, origin }: { runtime: ShellRuntime; origin: string }) => {
16
+ const [{ layout, invitationCode, spaceKey }, setLayout] = useState<LayoutRequest>({
17
+ layout: runtime.layout,
18
+ invitationCode: runtime.invitationCode,
19
+ spaceKey: runtime.spaceKey
20
+ });
21
+
22
+ const client = useClient();
23
+ const spaces = useSpaces();
24
+ const space = useSpace(spaceKey);
25
+
26
+ useEffect(() => {
27
+ return runtime.layoutUpdate.on((request) => setLayout(request));
28
+ }, []);
29
+
30
+ switch (layout) {
31
+ case ShellLayout.AUTH:
32
+ return (
33
+ <JoinDialog
34
+ mode='halo-only'
35
+ initialInvitationCode={invitationCode}
36
+ onDone={async () => {
37
+ // TODO(wittjosiah): Is this app-specific?
38
+ const space = spaces[0] ?? (await client.echo.createSpace());
39
+ await runtime.setAppContext({ display: ShellDisplay.NONE, spaceKey: space.key });
40
+ runtime.setLayout(ShellLayout.DEFAULT);
41
+ }}
42
+ />
43
+ );
44
+
45
+ case ShellLayout.CURRENT_SPACE:
46
+ case ShellLayout.SPACE_LIST:
47
+ return (
48
+ <SpaceDialog
49
+ space={space}
50
+ initialState={layout === ShellLayout.SPACE_LIST ? 'space list' : 'current space'}
51
+ createInvitationUrl={(invitationCode) => `${origin}?spaceInvitationCode=${invitationCode}`}
52
+ onClickJoinSpace={() => runtime.setLayout(ShellLayout.JOIN_SPACE, { spaceKey: space?.key })}
53
+ onClickMember={() => runtime.setLayout(ShellLayout.DEVICES_LIST, { spaceKey: space?.key })}
54
+ onDone={async (space) => {
55
+ // TODO(wittjosiah): If space is newly created the app won't yet know about it.
56
+ // This is a bug that needs to be fixed when upgrading the space service.
57
+ await runtime.setAppContext({ display: ShellDisplay.NONE, spaceKey: space ? space.key : spaceKey });
58
+ runtime.setLayout(ShellLayout.DEFAULT);
59
+ }}
60
+ />
61
+ );
62
+
63
+ case ShellLayout.JOIN_SPACE:
64
+ return (
65
+ <JoinDialog
66
+ initialInvitationCode={invitationCode}
67
+ onDone={async (result) => {
68
+ await runtime.setAppContext({ display: ShellDisplay.NONE, spaceKey: result?.spaceKey ?? undefined });
69
+ runtime.setLayout(ShellLayout.DEFAULT);
70
+ }}
71
+ onExit={async () => {
72
+ await runtime.setAppContext({ display: ShellDisplay.NONE, spaceKey: runtime.spaceKey });
73
+ runtime.setLayout(ShellLayout.DEFAULT);
74
+ }}
75
+ />
76
+ );
77
+
78
+ case ShellLayout.DEVICES_LIST:
79
+ return (
80
+ <DevicesDialog
81
+ createInvitationUrl={(invitationCode) => `${origin}?haloInvitationCode=${invitationCode}`}
82
+ onDone={async () => {
83
+ await runtime.setAppContext({ display: ShellDisplay.NONE, spaceKey: runtime.spaceKey });
84
+ runtime.setLayout(ShellLayout.DEFAULT);
85
+ }}
86
+ />
87
+ );
88
+
89
+ default:
90
+ return null;
91
+ }
92
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './Shell';
@@ -0,0 +1,38 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { Close } from '@radix-ui/react-dialog';
6
+ import React from 'react';
7
+
8
+ import { ThemeContext, useId } from '@dxos/react-components';
9
+
10
+ import { PanelDialog, PanelDialogProps } from '../../layouts';
11
+ import { SpacePanel, SpacePanelProps } from '../../panels';
12
+
13
+ export interface SpaceDialogProps
14
+ extends Omit<PanelDialogProps, 'titleId' | 'children'>,
15
+ Omit<SpacePanelProps, 'doneActionParent'> {}
16
+
17
+ export const SpaceDialog = ({ slots, ...spacePanelProps }: SpaceDialogProps) => {
18
+ const titleId = useId('spaceDialog__title');
19
+
20
+ return (
21
+ <PanelDialog
22
+ {...{
23
+ slots: { ...slots, root: { onOpenChange: (open) => open || spacePanelProps.onDone?.(null), ...slots?.root } },
24
+ titleId
25
+ }}
26
+ >
27
+ <ThemeContext.Provider value={{ themeVariant: 'os' }}>
28
+ <SpacePanel
29
+ {...{
30
+ ...spacePanelProps,
31
+ titleId,
32
+ doneActionParent: <Close asChild />
33
+ }}
34
+ />
35
+ </ThemeContext.Provider>
36
+ </PanelDialog>
37
+ );
38
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './SpaceDialog';
@@ -1,5 +1,8 @@
1
1
  //
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
+
4
5
  export * from './IdentityPopover';
5
6
  export * from './JoinDialog';
7
+ export * from './Shell';
8
+ export * from './SpaceDialog';
@@ -7,6 +7,8 @@ import React, { ComponentPropsWithoutRef, PropsWithChildren } from 'react';
7
7
 
8
8
  import { mx } from '@dxos/react-components';
9
9
 
10
+ import { defaultDialogContent, defaultOverlay } from '../../styles';
11
+
10
12
  export interface PanelAlertDialogSlots {
11
13
  root: ComponentPropsWithoutRef<typeof AlertDialog.Root>;
12
14
  overlay: ComponentPropsWithoutRef<typeof AlertDialog.Overlay>;
@@ -23,18 +25,12 @@ export const PanelAlertDialog = ({ titleId, slots = {}, children }: PanelAlertDi
23
25
  return (
24
26
  <AlertDialog.Root defaultOpen {...slots.root}>
25
27
  {slots.trigger && <AlertDialog.Trigger {...slots.trigger} />}
26
- <AlertDialog.Overlay
27
- {...slots.overlay}
28
- className={mx(
29
- 'fixed inset-0 backdrop-blur z-50 overflow-auto grid place-items-center p-2 md:p-4 lg:p-8',
30
- slots.overlay?.className
31
- )}
32
- >
28
+ <AlertDialog.Overlay {...slots.overlay} className={mx(defaultOverlay, 'z-40', slots.overlay?.className)}>
33
29
  <AlertDialog.Content
34
30
  onEscapeKeyDown={(event) => event.preventDefault()}
35
31
  {...slots.content}
36
32
  aria-labelledby={titleId}
37
- className={mx('is-full min-is-[260px] max-is-[320px] shadow-md backdrop-blur-md', slots.content?.className)}
33
+ className={mx(defaultDialogContent, slots.content?.className)}
38
34
  >
39
35
  {children}
40
36
  </AlertDialog.Content>
@@ -0,0 +1,39 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import * as Dialog from '@radix-ui/react-dialog';
6
+ import React, { ComponentPropsWithoutRef, PropsWithChildren } from 'react';
7
+
8
+ import { mx } from '@dxos/react-components';
9
+
10
+ import { defaultDialogContent, defaultOverlay } from '../../styles';
11
+
12
+ export interface PanelDialogSlots {
13
+ root: ComponentPropsWithoutRef<typeof Dialog.Root>;
14
+ overlay: ComponentPropsWithoutRef<typeof Dialog.Overlay>;
15
+ content: ComponentPropsWithoutRef<typeof Dialog.Content>;
16
+ trigger: ComponentPropsWithoutRef<typeof Dialog.Trigger>;
17
+ }
18
+
19
+ export type PanelDialogProps = PropsWithChildren<{
20
+ titleId: string;
21
+ slots?: Partial<PanelDialogSlots>;
22
+ }>;
23
+
24
+ export const PanelDialog = ({ titleId, slots = {}, children }: PanelDialogProps) => {
25
+ return (
26
+ <Dialog.Root defaultOpen {...slots.root}>
27
+ {slots.trigger && <Dialog.Trigger {...slots.trigger} />}
28
+ <Dialog.Overlay {...slots.overlay} className={mx(defaultOverlay, 'z-40', slots.overlay?.className)}>
29
+ <Dialog.Content
30
+ {...slots.content}
31
+ aria-labelledby={titleId}
32
+ className={mx(defaultDialogContent, slots.content?.className)}
33
+ >
34
+ {children}
35
+ </Dialog.Content>
36
+ </Dialog.Overlay>
37
+ </Dialog.Root>
38
+ );
39
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './PanelDialog';
@@ -82,7 +82,7 @@ export const PanelSidebarProvider = ({ children, slots }: PropsWithChildren<Pane
82
82
  onCloseAutoFocus={(event) => isLg && event.preventDefault()}
83
83
  {...slots?.content}
84
84
  className={mx(
85
- 'fixed block-start-0 block-end-0 is-sidebar z-50 overscroll-contain overflow-x-hidden overflow-y-auto',
85
+ 'fixed block-start-0 block-end-0 is-sidebar z-30 overscroll-contain overflow-x-hidden overflow-y-auto',
86
86
  'transition-[inset-inline-start,inset-inline-end] duration-200 ease-in-out',
87
87
  transitionShow ? 'inline-start-0' : '-inline-start-sidebar',
88
88
  slots?.content?.className
@@ -5,3 +5,4 @@
5
5
  export * from './PanelPopover';
6
6
  export * from './PanelSidebar';
7
7
  export * from './PanelAlertDialog';
8
+ export * from './PanelDialog';