@dxos/react-ui 0.1.26 → 0.1.27-next.a0dff21

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 (142) hide show
  1. package/dist/lib/browser/index.mjs +1021 -472
  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 +4 -3
  7. package/dist/types/src/components/IdentityList/IdentityListItem.d.ts.map +1 -1
  8. package/dist/types/src/components/IdentityList/IdentityListItem.stories.d.ts +3 -2
  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/IdentityPopover/IdentityPopover.d.ts +2 -2
  32. package/dist/types/src/composites/IdentityPopover/IdentityPopover.d.ts.map +1 -1
  33. package/dist/types/src/composites/JoinDialog/JoinDialog.d.ts +1 -1
  34. package/dist/types/src/composites/JoinDialog/JoinDialog.d.ts.map +1 -1
  35. package/dist/types/src/composites/JoinDialog/JoinDialog.stories.d.ts +2 -2
  36. package/dist/types/src/composites/JoinDialog/JoinDialog.stories.d.ts.map +1 -1
  37. package/dist/types/src/composites/Shell/Shell.d.ts +6 -0
  38. package/dist/types/src/composites/Shell/Shell.d.ts.map +1 -0
  39. package/dist/types/src/composites/Shell/index.d.ts +2 -0
  40. package/dist/types/src/composites/Shell/index.d.ts.map +1 -0
  41. package/dist/types/src/composites/SpaceDialog/SpaceDialog.d.ts +6 -0
  42. package/dist/types/src/composites/SpaceDialog/SpaceDialog.d.ts.map +1 -0
  43. package/dist/types/src/composites/SpaceDialog/index.d.ts +2 -0
  44. package/dist/types/src/composites/SpaceDialog/index.d.ts.map +1 -0
  45. package/dist/types/src/composites/index.d.ts +2 -0
  46. package/dist/types/src/composites/index.d.ts.map +1 -1
  47. package/dist/types/src/layouts/PanelAlertDialog/PanelAlertDialog.d.ts.map +1 -1
  48. package/dist/types/src/layouts/PanelDialog/PanelDialog.d.ts +14 -0
  49. package/dist/types/src/layouts/PanelDialog/PanelDialog.d.ts.map +1 -0
  50. package/dist/types/src/layouts/PanelDialog/index.d.ts +2 -0
  51. package/dist/types/src/layouts/PanelDialog/index.d.ts.map +1 -0
  52. package/dist/types/src/layouts/index.d.ts +1 -0
  53. package/dist/types/src/layouts/index.d.ts.map +1 -1
  54. package/dist/types/src/panels/DevicesPanel/DevicesPanel.d.ts +10 -0
  55. package/dist/types/src/panels/DevicesPanel/DevicesPanel.d.ts.map +1 -0
  56. package/dist/types/src/panels/DevicesPanel/index.d.ts +2 -0
  57. package/dist/types/src/panels/DevicesPanel/index.d.ts.map +1 -0
  58. package/dist/types/src/panels/IdentityPanel/IdentityPanel.d.ts +2 -2
  59. package/dist/types/src/panels/IdentityPanel/IdentityPanel.d.ts.map +1 -1
  60. package/dist/types/src/panels/JoinPanel/JoinHeading.d.ts +3 -0
  61. package/dist/types/src/panels/JoinPanel/JoinHeading.d.ts.map +1 -1
  62. package/dist/types/src/panels/JoinPanel/JoinPanel.d.ts +1 -1
  63. package/dist/types/src/panels/JoinPanel/JoinPanel.d.ts.map +1 -1
  64. package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts +6 -5
  65. package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts.map +1 -1
  66. package/dist/types/src/panels/JoinPanel/view-states/AdditionMethodSelector.d.ts +2 -2
  67. package/dist/types/src/panels/JoinPanel/view-states/AdditionMethodSelector.d.ts.map +1 -1
  68. package/dist/types/src/panels/JoinPanel/view-states/IdentityAdded.d.ts +12 -4
  69. package/dist/types/src/panels/JoinPanel/view-states/IdentityAdded.d.ts.map +1 -1
  70. package/dist/types/src/panels/JoinPanel/view-states/IdentityInput.d.ts.map +1 -1
  71. package/dist/types/src/panels/JoinPanel/view-states/IdentitySelector.d.ts +2 -2
  72. package/dist/types/src/panels/JoinPanel/view-states/IdentitySelector.d.ts.map +1 -1
  73. package/dist/types/src/panels/JoinPanel/view-states/InvitationAccepted.d.ts.map +1 -1
  74. package/dist/types/src/panels/JoinPanel/view-states/InvitationAuthenticator.d.ts.map +1 -1
  75. package/dist/types/src/panels/JoinPanel/view-states/InvitationInput.d.ts.map +1 -1
  76. package/dist/types/src/panels/JoinPanel/view-states/InvitationRescuer.d.ts.map +1 -1
  77. package/dist/types/src/panels/JoinPanel/view-states/ViewState.d.ts +2 -2
  78. package/dist/types/src/panels/JoinPanel/view-states/ViewState.d.ts.map +1 -1
  79. package/dist/types/src/panels/SpacePanel/SpacePanel.d.ts +12 -0
  80. package/dist/types/src/panels/SpacePanel/SpacePanel.d.ts.map +1 -0
  81. package/dist/types/src/panels/SpacePanel/SpacePanel.stories.d.ts +9 -0
  82. package/dist/types/src/panels/SpacePanel/SpacePanel.stories.d.ts.map +1 -0
  83. package/dist/types/src/panels/SpacePanel/index.d.ts +2 -0
  84. package/dist/types/src/panels/SpacePanel/index.d.ts.map +1 -0
  85. package/dist/types/src/panels/index.d.ts +2 -0
  86. package/dist/types/src/panels/index.d.ts.map +1 -1
  87. package/dist/types/src/styles/dialogStyles.d.ts +3 -0
  88. package/dist/types/src/styles/dialogStyles.d.ts.map +1 -0
  89. package/dist/types/src/styles/index.d.ts +1 -0
  90. package/dist/types/src/styles/index.d.ts.map +1 -1
  91. package/dist/types/src/translations/locales/en-US.d.ts +12 -1
  92. package/dist/types/src/translations/locales/en-US.d.ts.map +1 -1
  93. package/package.json +12 -11
  94. package/src/components/IdentityList/DeviceList.tsx +31 -0
  95. package/src/components/IdentityList/IdentityListItem.tsx +17 -5
  96. package/src/components/IdentityList/SpaceMemberList.tsx +10 -8
  97. package/src/components/IdentityList/SpaceMemberListContainer.tsx +7 -6
  98. package/src/components/IdentityList/index.ts +1 -0
  99. package/src/components/InvitationList/InvitationList.stories.tsx +1 -1
  100. package/src/components/InvitationList/InvitationList.tsx +12 -6
  101. package/src/components/InvitationList/InvitationListItem.tsx +30 -25
  102. package/src/components/InvitationList/InvitationStatusAvatar.tsx +10 -5
  103. package/src/components/SpaceList/SpaceListItem.tsx +32 -0
  104. package/src/components/SpaceList/index.ts +5 -0
  105. package/src/components/index.ts +1 -0
  106. package/src/composites/DevicesDialog/DevicesDialog.tsx +38 -0
  107. package/src/composites/DevicesDialog/index.ts +5 -0
  108. package/src/composites/IdentityPopover/IdentityPopover.tsx +3 -3
  109. package/src/composites/JoinDialog/JoinDialog.stories.tsx +59 -58
  110. package/src/composites/JoinDialog/JoinDialog.tsx +3 -1
  111. package/src/composites/Shell/Shell.tsx +86 -0
  112. package/src/composites/Shell/index.ts +5 -0
  113. package/src/composites/SpaceDialog/SpaceDialog.tsx +38 -0
  114. package/src/composites/SpaceDialog/index.ts +5 -0
  115. package/src/composites/index.ts +3 -0
  116. package/src/layouts/PanelAlertDialog/PanelAlertDialog.tsx +4 -8
  117. package/src/layouts/PanelDialog/PanelDialog.tsx +39 -0
  118. package/src/layouts/PanelDialog/index.ts +5 -0
  119. package/src/layouts/PanelSidebar/PanelSidebar.tsx +1 -1
  120. package/src/layouts/index.ts +1 -0
  121. package/src/panels/DevicesPanel/DevicesPanel.tsx +101 -0
  122. package/src/panels/DevicesPanel/index.ts +5 -0
  123. package/src/panels/IdentityPanel/IdentityPanel.tsx +5 -5
  124. package/src/panels/JoinPanel/JoinHeading.tsx +38 -16
  125. package/src/panels/JoinPanel/JoinPanel.tsx +35 -14
  126. package/src/panels/JoinPanel/JoinPanelProps.ts +6 -10
  127. package/src/panels/JoinPanel/view-states/AdditionMethodSelector.tsx +8 -2
  128. package/src/panels/JoinPanel/view-states/IdentityAdded.tsx +67 -28
  129. package/src/panels/JoinPanel/view-states/IdentityInput.tsx +9 -2
  130. package/src/panels/JoinPanel/view-states/IdentitySelector.tsx +8 -6
  131. package/src/panels/JoinPanel/view-states/InvitationAccepted.tsx +1 -0
  132. package/src/panels/JoinPanel/view-states/InvitationAuthenticator.tsx +4 -1
  133. package/src/panels/JoinPanel/view-states/InvitationInput.tsx +8 -1
  134. package/src/panels/JoinPanel/view-states/InvitationRescuer.tsx +4 -1
  135. package/src/panels/JoinPanel/view-states/ViewState.tsx +3 -3
  136. package/src/panels/SpacePanel/SpacePanel.stories.tsx +53 -0
  137. package/src/panels/SpacePanel/SpacePanel.tsx +90 -0
  138. package/src/panels/SpacePanel/index.ts +5 -0
  139. package/src/panels/index.ts +2 -0
  140. package/src/styles/dialogStyles.ts +8 -0
  141. package/src/styles/index.ts +1 -0
  142. package/src/translations/locales/en-US.ts +14 -3
@@ -35,7 +35,7 @@ export const Default = {
35
35
  await Promise.all(clients.map((client) => client.initialize()));
36
36
  log.info('[initialized]');
37
37
 
38
- await clients[0].halo.createProfile({ displayName: 'Os Mutantes' });
38
+ await clients[0].halo.createIdentity({ displayName: 'Os Mutantes' });
39
39
 
40
40
  const space = await clients[0].echo.createSpace();
41
41
  log.info('[space created]', space);
@@ -5,6 +5,7 @@ import { Root as AccordionRoot } from '@radix-ui/react-accordion';
5
5
  import React from 'react';
6
6
 
7
7
  import type { CancellableInvitationObservable } from '@dxos/client';
8
+ import { defaultDescription, DensityProvider, mx, useTranslation } from '@dxos/react-components';
8
9
 
9
10
  import { InvitationListItem, InvitationListItemProps } from './InvitationListItem';
10
11
 
@@ -13,12 +14,17 @@ export interface InvitationListProps extends Omit<InvitationListItemProps, 'invi
13
14
  }
14
15
 
15
16
  export const InvitationList = ({ invitations, ...invitationProps }: InvitationListProps) => {
16
- return (
17
- <AccordionRoot type='single' collapsible>
18
- {invitations.map((invitation, index) => {
19
- const value = invitation.invitation?.invitationId ?? `inv_${index}`;
20
- return <InvitationListItem key={value} value={value} invitation={invitation} {...invitationProps} />;
21
- })}
17
+ const { t } = useTranslation('os');
18
+ return invitations.length ? (
19
+ <AccordionRoot type='single' collapsible className='flex flex-col gap-1'>
20
+ <DensityProvider density='fine'>
21
+ {invitations.map((invitation, index) => {
22
+ const value = invitation.invitation?.invitationId ?? `inv_${index}`;
23
+ return <InvitationListItem key={value} value={value} invitation={invitation} {...invitationProps} />;
24
+ })}
25
+ </DensityProvider>
22
26
  </AccordionRoot>
27
+ ) : (
28
+ <p className={mx(defaultDescription, 'text-center p-2')}>{t('empty invitations message')}</p>
23
29
  );
24
30
  };
@@ -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,29 @@ 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' 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 variant='ghost' className='flex gap-1' onClick={handleClickRemove} data-testid='remove-invitation'>
72
79
  <span className='sr-only'>{t('remove invitation label')}</span>
73
80
  <X className={getSize(4)} weight='bold' />
74
81
  </Button>
75
82
  )}
76
83
  </AccordionPrimitive.Header>
77
84
  {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>
85
+ <AccordionPrimitive.Content className='flex gap-2 is-full radix-state-open:p-1 items-center'>
86
+ <QRCodeSVG
87
+ bgColor='transparent'
88
+ fgColor='currentColor'
89
+ value={invitationUrl}
90
+ className={mx('grow-[2] aspect-square is-24 bs-auto')}
91
+ aria-labelledby={qrLabel}
92
+ />
93
+ <span className='pli-1 flex-1 font-system-normal text-sm text-center' id={qrLabel}>
94
+ {t('qr label')}
95
+ </span>
91
96
  </AccordionPrimitive.Content>
92
97
  )}
93
98
  </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,14 +4,14 @@
4
4
 
5
5
  import React, { ComponentProps } from 'react';
6
6
 
7
- import { Profile } from '@dxos/client';
7
+ import { Identity } from '@dxos/client';
8
8
  import { Avatar, mx } from '@dxos/react-components';
9
9
 
10
10
  import { PanelPopover } from '../../layouts';
11
11
  import { IdentityPanel } from '../../panels';
12
12
 
13
13
  export interface IdentityPopoverProps extends Partial<ComponentProps<typeof PanelPopover>> {
14
- identity: Profile;
14
+ identity: Identity;
15
15
  onClickManageProfile?: () => void;
16
16
  }
17
17
 
@@ -32,7 +32,7 @@ export const IdentityPopover = ({
32
32
  size={10}
33
33
  variant='circle'
34
34
  fallbackValue={identity.identityKey.toHex()}
35
- label={identity.displayName ?? ''}
35
+ label={identity.profile?.displayName ?? ''}
36
36
  />
37
37
  )
38
38
  }
@@ -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.createIdentity({ 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,86 @@
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
+ }, [runtime]);
29
+
30
+ switch (layout) {
31
+ case ShellLayout.INITIALIZE_IDENTITY:
32
+ return (
33
+ <JoinDialog
34
+ mode='halo-only'
35
+ initialInvitationCode={invitationCode}
36
+ onDone={async () => {
37
+ // TODO(wittjosiah): Is this app-specific?
38
+ spaces.length > 0 || (await client.echo.createSpace());
39
+ await runtime.setAppContext({ display: ShellDisplay.NONE });
40
+ runtime.setLayout(ShellLayout.DEFAULT);
41
+ }}
42
+ />
43
+ );
44
+
45
+ case ShellLayout.DEVICE_INVITATIONS:
46
+ return (
47
+ <DevicesDialog
48
+ createInvitationUrl={(invitationCode) => `${origin}?haloInvitationCode=${invitationCode}`}
49
+ onDone={async () => {
50
+ await runtime.setAppContext({ display: ShellDisplay.NONE });
51
+ runtime.setLayout(ShellLayout.DEFAULT);
52
+ }}
53
+ />
54
+ );
55
+
56
+ case ShellLayout.SPACE_INVITATIONS:
57
+ return (
58
+ <SpaceDialog
59
+ space={space}
60
+ createInvitationUrl={(invitationCode) => `${origin}?spaceInvitationCode=${invitationCode}`}
61
+ onDone={async () => {
62
+ await runtime.setAppContext({ display: ShellDisplay.NONE });
63
+ runtime.setLayout(ShellLayout.DEFAULT);
64
+ }}
65
+ />
66
+ );
67
+
68
+ case ShellLayout.JOIN_SPACE:
69
+ return (
70
+ <JoinDialog
71
+ initialInvitationCode={invitationCode}
72
+ onDone={async (result) => {
73
+ await runtime.setAppContext({ display: ShellDisplay.NONE, spaceKey: result?.spaceKey ?? undefined });
74
+ runtime.setLayout(ShellLayout.DEFAULT);
75
+ }}
76
+ onExit={async () => {
77
+ await runtime.setAppContext({ display: ShellDisplay.NONE });
78
+ runtime.setLayout(ShellLayout.DEFAULT);
79
+ }}
80
+ />
81
+ );
82
+
83
+ default:
84
+ return null;
85
+ }
86
+ };
@@ -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?.(), ...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>