@dxos/react-ui 0.1.22 → 0.1.24-next.6b1f434

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 (199) hide show
  1. package/dist/lib/browser/index.mjs +2434 -77
  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 +7 -0
  7. package/dist/types/src/components/IdentityList/IdentityListItem.d.ts.map +1 -0
  8. package/dist/types/src/components/{IdentityListItem → IdentityList}/IdentityListItem.stories.d.ts +2 -1
  9. package/dist/types/src/components/IdentityList/IdentityListItem.stories.d.ts.map +1 -0
  10. package/dist/types/src/components/IdentityList/SpaceMemberList.d.ts +7 -0
  11. package/dist/types/src/components/IdentityList/SpaceMemberList.d.ts.map +1 -0
  12. package/dist/types/src/components/IdentityList/SpaceMemberListContainer.d.ts +9 -0
  13. package/dist/types/src/components/IdentityList/SpaceMemberListContainer.d.ts.map +1 -0
  14. package/dist/types/src/components/IdentityList/index.d.ts +5 -0
  15. package/dist/types/src/components/IdentityList/index.d.ts.map +1 -0
  16. package/dist/types/src/components/InvitationList/InvitationList.d.ts +7 -0
  17. package/dist/types/src/components/InvitationList/InvitationList.d.ts.map +1 -0
  18. package/dist/types/src/components/InvitationList/InvitationList.stories.d.ts +10 -0
  19. package/dist/types/src/components/InvitationList/InvitationList.stories.d.ts.map +1 -0
  20. package/dist/types/src/components/InvitationList/InvitationListContainer.d.ts +7 -0
  21. package/dist/types/src/components/InvitationList/InvitationListContainer.d.ts.map +1 -0
  22. package/dist/types/src/components/InvitationList/InvitationListItem.d.ts +9 -0
  23. package/dist/types/src/components/InvitationList/InvitationListItem.d.ts.map +1 -0
  24. package/dist/types/src/components/InvitationList/InvitationListProps.d.ts +4 -0
  25. package/dist/types/src/components/InvitationList/InvitationListProps.d.ts.map +1 -0
  26. package/dist/types/src/components/InvitationList/InvitationStatusAvatar.d.ts +14 -0
  27. package/dist/types/src/components/InvitationList/InvitationStatusAvatar.d.ts.map +1 -0
  28. package/dist/types/src/components/InvitationList/InvitationStatusAvatar.stories.d.ts +9 -0
  29. package/dist/types/src/components/InvitationList/InvitationStatusAvatar.stories.d.ts.map +1 -0
  30. package/dist/types/src/components/InvitationList/index.d.ts +5 -0
  31. package/dist/types/src/components/InvitationList/index.d.ts.map +1 -0
  32. package/dist/types/src/components/PanelSeparator/PanelSeparator.d.ts +3 -0
  33. package/dist/types/src/components/PanelSeparator/PanelSeparator.d.ts.map +1 -0
  34. package/dist/types/src/components/PanelSeparator/index.d.ts +2 -0
  35. package/dist/types/src/components/PanelSeparator/index.d.ts.map +1 -0
  36. package/dist/types/src/components/SpaceList/SpaceListItem.d.ts +7 -0
  37. package/dist/types/src/components/SpaceList/SpaceListItem.d.ts.map +1 -0
  38. package/dist/types/src/components/SpaceList/index.d.ts +2 -0
  39. package/dist/types/src/components/SpaceList/index.d.ts.map +1 -0
  40. package/dist/types/src/components/index.d.ts +5 -0
  41. package/dist/types/src/components/index.d.ts.map +1 -0
  42. package/dist/types/src/composites/DevicesDialog/DevicesDialog.d.ts +6 -0
  43. package/dist/types/src/composites/DevicesDialog/DevicesDialog.d.ts.map +1 -0
  44. package/dist/types/src/composites/DevicesDialog/index.d.ts +2 -0
  45. package/dist/types/src/composites/DevicesDialog/index.d.ts.map +1 -0
  46. package/dist/types/src/composites/JoinDialog/JoinDialog.d.ts +6 -0
  47. package/dist/types/src/composites/JoinDialog/JoinDialog.d.ts.map +1 -0
  48. package/dist/types/src/composites/JoinDialog/JoinDialog.stories.d.ts +10 -0
  49. package/dist/types/src/composites/JoinDialog/JoinDialog.stories.d.ts.map +1 -0
  50. package/dist/types/src/composites/JoinDialog/index.d.ts +2 -0
  51. package/dist/types/src/composites/JoinDialog/index.d.ts.map +1 -0
  52. package/dist/types/src/composites/Shell/Shell.d.ts +6 -0
  53. package/dist/types/src/composites/Shell/Shell.d.ts.map +1 -0
  54. package/dist/types/src/composites/Shell/index.d.ts +2 -0
  55. package/dist/types/src/composites/Shell/index.d.ts.map +1 -0
  56. package/dist/types/src/composites/SpaceDialog/SpaceDialog.d.ts +6 -0
  57. package/dist/types/src/composites/SpaceDialog/SpaceDialog.d.ts.map +1 -0
  58. package/dist/types/src/composites/SpaceDialog/index.d.ts +2 -0
  59. package/dist/types/src/composites/SpaceDialog/index.d.ts.map +1 -0
  60. package/dist/types/src/composites/index.d.ts +3 -0
  61. package/dist/types/src/composites/index.d.ts.map +1 -1
  62. package/dist/types/src/index.d.ts +1 -0
  63. package/dist/types/src/index.d.ts.map +1 -1
  64. package/dist/types/src/layouts/PanelAlertDialog/PanelAlertDialog.d.ts +14 -0
  65. package/dist/types/src/layouts/PanelAlertDialog/PanelAlertDialog.d.ts.map +1 -0
  66. package/dist/types/src/layouts/PanelAlertDialog/index.d.ts +2 -0
  67. package/dist/types/src/layouts/PanelAlertDialog/index.d.ts.map +1 -0
  68. package/dist/types/src/layouts/PanelDialog/PanelDialog.d.ts +14 -0
  69. package/dist/types/src/layouts/PanelDialog/PanelDialog.d.ts.map +1 -0
  70. package/dist/types/src/layouts/PanelDialog/index.d.ts +2 -0
  71. package/dist/types/src/layouts/PanelDialog/index.d.ts.map +1 -0
  72. package/dist/types/src/layouts/PanelSidebar/PanelSidebar.d.ts +4 -5
  73. package/dist/types/src/layouts/PanelSidebar/PanelSidebar.d.ts.map +1 -1
  74. package/dist/types/src/layouts/index.d.ts +2 -0
  75. package/dist/types/src/layouts/index.d.ts.map +1 -1
  76. package/dist/types/src/panels/DevicesPanel/DevicesPanel.d.ts +10 -0
  77. package/dist/types/src/panels/DevicesPanel/DevicesPanel.d.ts.map +1 -0
  78. package/dist/types/src/panels/DevicesPanel/index.d.ts +2 -0
  79. package/dist/types/src/panels/DevicesPanel/index.d.ts.map +1 -0
  80. package/dist/types/src/panels/JoinPanel/JoinHeading.d.ts +13 -0
  81. package/dist/types/src/panels/JoinPanel/JoinHeading.d.ts.map +1 -0
  82. package/dist/types/src/panels/JoinPanel/JoinPanel.d.ts +3 -0
  83. package/dist/types/src/panels/JoinPanel/JoinPanel.d.ts.map +1 -0
  84. package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts +54 -0
  85. package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts.map +1 -0
  86. package/dist/types/src/panels/JoinPanel/index.d.ts +3 -0
  87. package/dist/types/src/panels/JoinPanel/index.d.ts.map +1 -0
  88. package/dist/types/src/panels/JoinPanel/view-states/AdditionMethodSelector.d.ts +7 -0
  89. package/dist/types/src/panels/JoinPanel/view-states/AdditionMethodSelector.d.ts.map +1 -0
  90. package/dist/types/src/panels/JoinPanel/view-states/IdentityAdded.d.ts +15 -0
  91. package/dist/types/src/panels/JoinPanel/view-states/IdentityAdded.d.ts.map +1 -0
  92. package/dist/types/src/panels/JoinPanel/view-states/IdentityInput.d.ts +6 -0
  93. package/dist/types/src/panels/JoinPanel/view-states/IdentityInput.d.ts.map +1 -0
  94. package/dist/types/src/panels/JoinPanel/view-states/IdentitySelector.d.ts +7 -0
  95. package/dist/types/src/panels/JoinPanel/view-states/IdentitySelector.d.ts.map +1 -0
  96. package/dist/types/src/panels/JoinPanel/view-states/InvitationAccepted.d.ts +10 -0
  97. package/dist/types/src/panels/JoinPanel/view-states/InvitationAccepted.d.ts.map +1 -0
  98. package/dist/types/src/panels/JoinPanel/view-states/InvitationAuthenticator.d.ts +7 -0
  99. package/dist/types/src/panels/JoinPanel/view-states/InvitationAuthenticator.d.ts.map +1 -0
  100. package/dist/types/src/panels/JoinPanel/view-states/InvitationInput.d.ts +6 -0
  101. package/dist/types/src/panels/JoinPanel/view-states/InvitationInput.d.ts.map +1 -0
  102. package/dist/types/src/panels/JoinPanel/view-states/InvitationRescuer.d.ts +6 -0
  103. package/dist/types/src/panels/JoinPanel/view-states/InvitationRescuer.d.ts.map +1 -0
  104. package/dist/types/src/panels/JoinPanel/view-states/ViewState.d.ts +12 -0
  105. package/dist/types/src/panels/JoinPanel/view-states/ViewState.d.ts.map +1 -0
  106. package/dist/types/src/panels/JoinPanel/view-states/index.d.ts +9 -0
  107. package/dist/types/src/panels/JoinPanel/view-states/index.d.ts.map +1 -0
  108. package/dist/types/src/panels/SpacePanel/SpacePanel.d.ts +18 -0
  109. package/dist/types/src/panels/SpacePanel/SpacePanel.d.ts.map +1 -0
  110. package/dist/types/src/panels/SpacePanel/SpacePanel.stories.d.ts +9 -0
  111. package/dist/types/src/panels/SpacePanel/SpacePanel.stories.d.ts.map +1 -0
  112. package/dist/types/src/panels/SpacePanel/index.d.ts +2 -0
  113. package/dist/types/src/panels/SpacePanel/index.d.ts.map +1 -0
  114. package/dist/types/src/panels/index.d.ts +3 -0
  115. package/dist/types/src/panels/index.d.ts.map +1 -1
  116. package/dist/types/src/styles/dialogStyles.d.ts +3 -0
  117. package/dist/types/src/styles/dialogStyles.d.ts.map +1 -0
  118. package/dist/types/src/styles/index.d.ts +2 -0
  119. package/dist/types/src/styles/index.d.ts.map +1 -1
  120. package/dist/types/src/styles/invitationStatusStyles.d.ts +20 -0
  121. package/dist/types/src/styles/invitationStatusStyles.d.ts.map +1 -0
  122. package/dist/types/src/styles/panelStyles.d.ts +2 -1
  123. package/dist/types/src/styles/panelStyles.d.ts.map +1 -1
  124. package/dist/types/src/translations/locales/en-US.d.ts +52 -0
  125. package/dist/types/src/translations/locales/en-US.d.ts.map +1 -1
  126. package/dist/types/src/util/index.d.ts +2 -0
  127. package/dist/types/src/util/index.d.ts.map +1 -0
  128. package/dist/types/src/util/invitationStatusValue.d.ts +8 -0
  129. package/dist/types/src/util/invitationStatusValue.d.ts.map +1 -0
  130. package/package.json +19 -11
  131. package/src/components/IdentityList/DeviceList.tsx +31 -0
  132. package/src/components/{IdentityListItem → IdentityList}/IdentityListItem.stories.tsx +0 -0
  133. package/src/components/{IdentityListItem → IdentityList}/IdentityListItem.tsx +18 -5
  134. package/src/components/IdentityList/SpaceMemberList.tsx +33 -0
  135. package/src/components/IdentityList/SpaceMemberListContainer.tsx +28 -0
  136. package/src/components/IdentityList/index.ts +8 -0
  137. package/src/components/InvitationList/InvitationList.stories.tsx +65 -0
  138. package/src/components/InvitationList/InvitationList.tsx +28 -0
  139. package/src/components/InvitationList/InvitationListContainer.tsx +27 -0
  140. package/src/components/InvitationList/InvitationListItem.tsx +106 -0
  141. package/src/components/InvitationList/InvitationListProps.ts +7 -0
  142. package/src/components/InvitationList/InvitationStatusAvatar.stories.tsx +52 -0
  143. package/src/components/InvitationList/InvitationStatusAvatar.tsx +80 -0
  144. package/src/components/InvitationList/index.ts +8 -0
  145. package/src/components/PanelSeparator/PanelSeparator.tsx +17 -0
  146. package/src/components/PanelSeparator/index.ts +5 -0
  147. package/src/components/SpaceList/SpaceListItem.tsx +32 -0
  148. package/src/components/SpaceList/index.ts +5 -0
  149. package/src/components/index.ts +8 -0
  150. package/src/composites/DevicesDialog/DevicesDialog.tsx +38 -0
  151. package/src/composites/DevicesDialog/index.ts +5 -0
  152. package/src/composites/JoinDialog/JoinDialog.stories.tsx +81 -0
  153. package/src/composites/JoinDialog/JoinDialog.tsx +33 -0
  154. package/src/composites/JoinDialog/index.ts +5 -0
  155. package/src/composites/Shell/Shell.tsx +92 -0
  156. package/src/composites/Shell/index.ts +5 -0
  157. package/src/composites/SpaceDialog/SpaceDialog.tsx +38 -0
  158. package/src/composites/SpaceDialog/index.ts +5 -0
  159. package/src/composites/index.ts +4 -0
  160. package/src/index.ts +1 -0
  161. package/src/layouts/PanelAlertDialog/PanelAlertDialog.tsx +40 -0
  162. package/src/layouts/PanelAlertDialog/index.ts +5 -0
  163. package/src/layouts/PanelDialog/PanelDialog.tsx +39 -0
  164. package/src/layouts/PanelDialog/index.ts +5 -0
  165. package/src/layouts/PanelPopover/PanelPopover.tsx +2 -2
  166. package/src/layouts/PanelSidebar/PanelSidebar.tsx +27 -31
  167. package/src/layouts/index.ts +2 -0
  168. package/src/panels/DevicesPanel/DevicesPanel.tsx +98 -0
  169. package/src/panels/DevicesPanel/index.ts +5 -0
  170. package/src/panels/JoinPanel/JoinHeading.tsx +85 -0
  171. package/src/panels/JoinPanel/JoinPanel.tsx +319 -0
  172. package/src/panels/JoinPanel/JoinPanelProps.ts +90 -0
  173. package/src/panels/JoinPanel/index.ts +6 -0
  174. package/src/panels/JoinPanel/view-states/AdditionMethodSelector.tsx +73 -0
  175. package/src/panels/JoinPanel/view-states/IdentityAdded.tsx +99 -0
  176. package/src/panels/JoinPanel/view-states/IdentityInput.tsx +79 -0
  177. package/src/panels/JoinPanel/view-states/IdentitySelector.tsx +101 -0
  178. package/src/panels/JoinPanel/view-states/InvitationAccepted.tsx +70 -0
  179. package/src/panels/JoinPanel/view-states/InvitationAuthenticator.tsx +142 -0
  180. package/src/panels/JoinPanel/view-states/InvitationInput.tsx +75 -0
  181. package/src/panels/JoinPanel/view-states/InvitationRescuer.tsx +98 -0
  182. package/src/panels/JoinPanel/view-states/ViewState.tsx +204 -0
  183. package/src/panels/JoinPanel/view-states/index.ts +12 -0
  184. package/src/panels/SpacePanel/SpacePanel.stories.tsx +53 -0
  185. package/src/panels/SpacePanel/SpacePanel.tsx +174 -0
  186. package/src/panels/SpacePanel/index.ts +5 -0
  187. package/src/panels/index.ts +3 -0
  188. package/src/styles/dialogStyles.ts +8 -0
  189. package/src/styles/index.ts +2 -0
  190. package/src/styles/invitationStatusStyles.ts +42 -0
  191. package/src/styles/panelStyles.ts +2 -1
  192. package/src/testing/ClientProvider.tsx +1 -1
  193. package/src/translations/locales/en-US.ts +53 -1
  194. package/src/util/index.ts +5 -0
  195. package/src/util/invitationStatusValue.ts +21 -0
  196. package/theme-extensions.js +11 -0
  197. package/dist/types/src/components/IdentityListItem/IdentityListItem.d.ts +0 -6
  198. package/dist/types/src/components/IdentityListItem/IdentityListItem.d.ts.map +0 -1
  199. package/dist/types/src/components/IdentityListItem/IdentityListItem.stories.d.ts.map +0 -1
@@ -0,0 +1,31 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { DeviceInfo } from '@dxos/protocols/proto/dxos/halo/credentials/identity';
8
+
9
+ import { IdentityListItem } from './IdentityListItem';
10
+
11
+ export interface DeviceListProps {
12
+ devices: DeviceInfo[];
13
+ onSelect?: (device: DeviceInfo) => void;
14
+ }
15
+
16
+ export const DeviceList = ({ devices, onSelect }: DeviceListProps) => {
17
+ return (
18
+ <ul className='flex flex-col gap-2'>
19
+ {devices.map((device) => {
20
+ const identity = { identityKey: device.publicKey, displayName: device.displayName };
21
+ return (
22
+ <IdentityListItem
23
+ key={device.publicKey.toHex()}
24
+ identity={identity}
25
+ onClick={onSelect && (() => onSelect(device))}
26
+ />
27
+ );
28
+ })}
29
+ </ul>
30
+ );
31
+ };
@@ -4,17 +4,30 @@
4
4
  import React from 'react';
5
5
 
6
6
  import { Profile, SpaceMember } from '@dxos/client';
7
- import { Avatar, useTranslation } from '@dxos/react-components';
7
+ import { Avatar, mx, useTranslation } from '@dxos/react-components';
8
8
 
9
- export const IdentityListItem = ({ identity, presence }: { identity: Profile; presence?: SpaceMember['presence'] }) => {
9
+ export const IdentityListItem = ({
10
+ identity,
11
+ presence,
12
+ onClick
13
+ }: {
14
+ identity: Profile;
15
+ presence?: SpaceMember['presence'];
16
+ onClick?: () => void;
17
+ }) => {
10
18
  const { t } = useTranslation('os');
11
19
  return (
12
- <li className='flex gap-2'>
20
+ <li
21
+ className={mx('flex gap-2 items-center', onClick && 'cursor-pointer')}
22
+ onClick={() => onClick?.()}
23
+ data-testid='identity-list-item'
24
+ >
13
25
  <Avatar
14
26
  {...{
27
+ variant: 'circle',
15
28
  size: 9,
16
29
  fallbackValue: identity.identityKey.toHex(),
17
- label: <p className='text-sm'>{identity.displayName ?? identity.identityKey.truncate()}</p>,
30
+ label: <p className='text-sm truncate'>{identity.displayName ?? identity.identityKey.truncate()}</p>,
18
31
  ...(presence === SpaceMember.PresenceState.OFFLINE && {
19
32
  status: 'inactive',
20
33
  description: (
@@ -24,7 +37,7 @@ export const IdentityListItem = ({ identity, presence }: { identity: Profile; pr
24
37
  )
25
38
  }),
26
39
  ...(presence === SpaceMember.PresenceState.ONLINE && { status: 'active' }),
27
- slots: { labels: { className: 'block' } }
40
+ slots: { labels: { className: 'block shrink overflow-hidden' }, root: { className: 'shrink-0' } }
28
41
  }}
29
42
  />
30
43
  </li>
@@ -0,0 +1,33 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import type { SpaceMember } from '@dxos/client';
8
+
9
+ import { IdentityListItem } from './IdentityListItem';
10
+
11
+ export interface SpaceMemberListProps {
12
+ members: SpaceMember[];
13
+ onSelect?: (member: SpaceMember) => void;
14
+ }
15
+
16
+ export const SpaceMemberList = ({ members, onSelect }: SpaceMemberListProps) => {
17
+ return (
18
+ <ul className='flex flex-col gap-2'>
19
+ {members
20
+ .filter((member) => member.profile)
21
+ .map((member) => {
22
+ return (
23
+ <IdentityListItem
24
+ key={member.identityKey.toHex()}
25
+ identity={member.profile!}
26
+ presence={member.presence}
27
+ onClick={onSelect && (() => onSelect(member))}
28
+ />
29
+ );
30
+ })}
31
+ </ul>
32
+ );
33
+ };
@@ -0,0 +1,28 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+ import React, { useMemo } from 'react';
5
+
6
+ import { PublicKey } from '@dxos/keys';
7
+ import { SpaceMember, useClient, useMembers } from '@dxos/react-client';
8
+
9
+ import { SpaceMemberList } from './SpaceMemberList';
10
+
11
+ export interface SpaceMemberListContainerProps {
12
+ spaceKey: PublicKey;
13
+ includeSelf?: boolean;
14
+ onSelect?: (member: SpaceMember) => void;
15
+ }
16
+
17
+ export const SpaceMemberListContainer = ({ spaceKey, includeSelf, onSelect }: SpaceMemberListContainerProps) => {
18
+ const client = useClient();
19
+ const allUnsortedMembers = useMembers(spaceKey);
20
+ const members = useMemo(
21
+ () =>
22
+ includeSelf
23
+ ? allUnsortedMembers.sort((a) => (a.identityKey.equals(client.halo.profile!.identityKey) ? -1 : 1))
24
+ : allUnsortedMembers.filter((member) => !member.identityKey.equals(client.halo.profile!.identityKey)),
25
+ [allUnsortedMembers]
26
+ );
27
+ return <SpaceMemberList members={members} onSelect={onSelect} />;
28
+ };
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './DeviceList';
6
+ export * from './IdentityListItem';
7
+ export * from './SpaceMemberList';
8
+ export * from './SpaceMemberListContainer';
@@ -0,0 +1,65 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import '@dxosTheme';
6
+
7
+ import { StoryFn } from '@storybook/react';
8
+ import React, { useMemo, useState } from 'react';
9
+
10
+ import { CancellableInvitationObservable, Client, Invitation } from '@dxos/client';
11
+ import { TestBuilder } from '@dxos/client/testing';
12
+ import { log } from '@dxos/log';
13
+ import { useAsyncEffect } from '@dxos/react-async';
14
+ import { ClientProvider } from '@dxos/react-client';
15
+ import { Loading, mx, ThemeContext } from '@dxos/react-components';
16
+
17
+ import { defaultSurface } from '../../styles';
18
+ import { InvitationList } from './InvitationList';
19
+
20
+ export default {
21
+ component: InvitationList
22
+ };
23
+
24
+ export const Default = {
25
+ decorators: [
26
+ (Story: StoryFn) => {
27
+ const testBuilder = new TestBuilder();
28
+ const clients = useMemo(() => {
29
+ return [...Array(1)].map(() => new Client({ services: testBuilder.createClientServicesHost() }));
30
+ }, []);
31
+
32
+ const [invitations, setInvitations] = useState<CancellableInvitationObservable[]>([]);
33
+
34
+ useAsyncEffect(async () => {
35
+ await Promise.all(clients.map((client) => client.initialize()));
36
+ log.info('[initialized]');
37
+
38
+ await clients[0].halo.createProfile({ displayName: 'Os Mutantes' });
39
+
40
+ const space = await clients[0].echo.createSpace();
41
+ log.info('[space created]', space);
42
+ space.properties.name = 'Q3 2022 Planning';
43
+ log.info('[space title set]', space?.properties.name);
44
+
45
+ setInvitations([...Array(4)].map(() => space.createInvitation({ type: Invitation.Type.INTERACTIVE })));
46
+
47
+ log.info('[invitations created]');
48
+
49
+ return () => {
50
+ void Promise.all(clients.map((client) => client.destroy()));
51
+ };
52
+ }, clients);
53
+
54
+ return (
55
+ <ClientProvider client={clients[0]} fallback={() => <Loading label='Loading client…' />}>
56
+ <ThemeContext.Provider value={{ themeVariant: 'os' }}>
57
+ <div className={mx(defaultSurface, 'max-is-xs mli-auto rounded-md p-2 backdrop-blur-md')}>
58
+ <Story args={{ invitations, createInvitationUrl: (code: string) => code }} />
59
+ </div>
60
+ </ThemeContext.Provider>
61
+ </ClientProvider>
62
+ );
63
+ }
64
+ ]
65
+ };
@@ -0,0 +1,28 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+ import { Root as AccordionRoot } from '@radix-ui/react-accordion';
5
+ import React from 'react';
6
+
7
+ import type { CancellableInvitationObservable } from '@dxos/client';
8
+ import { defaultDescription, mx, useTranslation } from '@dxos/react-components';
9
+
10
+ import { InvitationListItem, InvitationListItemProps } from './InvitationListItem';
11
+
12
+ export interface InvitationListProps extends Omit<InvitationListItemProps, 'invitation' | 'value'> {
13
+ invitations: CancellableInvitationObservable[];
14
+ }
15
+
16
+ export const InvitationList = ({ invitations, ...invitationProps }: InvitationListProps) => {
17
+ const { t } = useTranslation('os');
18
+ return invitations.length ? (
19
+ <AccordionRoot type='single' collapsible className='flex flex-col gap-1'>
20
+ {invitations.map((invitation, index) => {
21
+ const value = invitation.invitation?.invitationId ?? `inv_${index}`;
22
+ return <InvitationListItem key={value} value={value} invitation={invitation} {...invitationProps} />;
23
+ })}
24
+ </AccordionRoot>
25
+ ) : (
26
+ <p className={mx(defaultDescription, 'text-center p-2')}>{t('empty invitations message')}</p>
27
+ );
28
+ };
@@ -0,0 +1,27 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+ import React, { useCallback } from 'react';
5
+
6
+ import type { CancellableInvitationObservable } from '@dxos/client';
7
+ import { PublicKey } from '@dxos/keys';
8
+ import { useSpace, useSpaceInvitations } from '@dxos/react-client';
9
+
10
+ import { InvitationList } from './InvitationList';
11
+ import { SharedInvitationListProps } from './InvitationListProps';
12
+
13
+ export interface InvitationListContainerProps extends SharedInvitationListProps {
14
+ spaceKey: PublicKey;
15
+ }
16
+
17
+ export const InvitationListContainer = ({ spaceKey, createInvitationUrl }: InvitationListContainerProps) => {
18
+ const space = useSpace(spaceKey);
19
+ const invitations = useSpaceInvitations(spaceKey);
20
+ const onClickRemove = useCallback(
21
+ ({ invitation }: CancellableInvitationObservable) => {
22
+ invitation?.invitationId && space?.removeInvitation(invitation.invitationId);
23
+ },
24
+ [space]
25
+ );
26
+ return <InvitationList invitations={invitations} {...{ onClickRemove, createInvitationUrl }} />;
27
+ };
@@ -0,0 +1,106 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+ import * as AccordionPrimitive from '@radix-ui/react-accordion';
5
+ import { Copy, ProhibitInset, QrCode, X } from 'phosphor-react';
6
+ import { QRCodeSVG } from 'qrcode.react';
7
+ import React, { useCallback } from 'react';
8
+
9
+ import { CancellableInvitationObservable } from '@dxos/client';
10
+ import { useInvitationStatus } from '@dxos/react-client';
11
+ import { Button, getSize, mx, useId, useTranslation } from '@dxos/react-components';
12
+
13
+ import { invitationStatusValue } from '../../util';
14
+ import { SharedInvitationListProps } from './InvitationListProps';
15
+ import { InvitationStatusAvatar } from './InvitationStatusAvatar';
16
+
17
+ export interface InvitationListItemProps extends SharedInvitationListProps {
18
+ invitation: CancellableInvitationObservable;
19
+ value: string;
20
+ onClickRemove: (invitation: CancellableInvitationObservable) => void;
21
+ }
22
+
23
+ export const InvitationListItem = ({
24
+ invitation,
25
+ value,
26
+ onClickRemove,
27
+ createInvitationUrl
28
+ }: InvitationListItemProps) => {
29
+ const { t } = useTranslation('os');
30
+ const qrLabel = useId('qrLabel');
31
+
32
+ const { cancel, status, haltedAt, invitationCode, authenticationCode } = useInvitationStatus(invitation);
33
+ const statusValue = invitationStatusValue.get(status) ?? 0;
34
+
35
+ const isCancellable = statusValue < 4 && statusValue >= 0;
36
+ const showShare = statusValue < 3 && statusValue >= 0;
37
+ const showAuthCode = statusValue === 3;
38
+
39
+ const handleClickRemove = useCallback(() => onClickRemove(invitation), [invitation, onClickRemove]);
40
+
41
+ const invitationUrl = invitationCode && createInvitationUrl(invitationCode);
42
+ const handleClickCopy = useCallback(() => {
43
+ if (invitationUrl) {
44
+ void navigator.clipboard.writeText(invitationUrl);
45
+ }
46
+ }, [invitationUrl]);
47
+
48
+ return (
49
+ <AccordionPrimitive.Item value={value}>
50
+ <AccordionPrimitive.Header className='flex gap-2 items-center'>
51
+ <InvitationStatusAvatar {...{ status, haltedAt, size: 8 }} />
52
+ {showShare && invitationUrl ? (
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' />
63
+ </Button>
64
+ </>
65
+ ) : showAuthCode ? (
66
+ <p className='grow text-xl text-center text-success-500 dark:text-success-300 font-mono'>
67
+ {authenticationCode}
68
+ </p>
69
+ ) : (
70
+ <span role='none' className='grow' />
71
+ )}
72
+ {isCancellable ? (
73
+ <Button variant='ghost' compact className='flex gap-1' onClick={cancel} data-testid='cancel-invitation'>
74
+ <span className='sr-only'>{t('cancel invitation label')}</span>
75
+ <ProhibitInset className={getSize(4)} weight='bold' />
76
+ </Button>
77
+ ) : (
78
+ <Button
79
+ variant='ghost'
80
+ compact
81
+ className='flex gap-1'
82
+ onClick={handleClickRemove}
83
+ data-testid='remove-invitation'
84
+ >
85
+ <span className='sr-only'>{t('remove invitation label')}</span>
86
+ <X className={getSize(4)} weight='bold' />
87
+ </Button>
88
+ )}
89
+ </AccordionPrimitive.Header>
90
+ {showShare && invitationUrl && (
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>
102
+ </AccordionPrimitive.Content>
103
+ )}
104
+ </AccordionPrimitive.Item>
105
+ );
106
+ };
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export interface SharedInvitationListProps {
6
+ createInvitationUrl: (invitationCode: string) => string;
7
+ }
@@ -0,0 +1,52 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+ import { StoryFn } from '@storybook/react';
5
+ import React from 'react';
6
+
7
+ import { Invitation } from '@dxos/client';
8
+ import { mx, ThemeContext } from '@dxos/react-components';
9
+
10
+ import { defaultSurface } from '../../styles';
11
+ import { InvitationStatusAvatar } from './InvitationStatusAvatar';
12
+
13
+ const haltedAts = [null, Invitation.State.CONNECTED, Invitation.State.AUTHENTICATING, Invitation.State.SUCCESS];
14
+
15
+ const statuses = [
16
+ Invitation.State.INIT,
17
+ Invitation.State.CONNECTING,
18
+ Invitation.State.CONNECTED,
19
+ Invitation.State.AUTHENTICATING,
20
+ Invitation.State.SUCCESS,
21
+ Invitation.State.CANCELLED,
22
+ Invitation.State.TIMEOUT,
23
+ Invitation.State.ERROR
24
+ ];
25
+
26
+ export default {
27
+ component: InvitationStatusAvatar
28
+ };
29
+
30
+ export const Default = {
31
+ decorators: [
32
+ (Story: StoryFn) => {
33
+ return (
34
+ <ThemeContext.Provider value={{ themeVariant: 'os' }}>
35
+ <div
36
+ className={mx(defaultSurface, 'max-is-md mli-auto rounded-md p-2 backdrop-blur-md flex flex-wrap gap-4')}
37
+ >
38
+ {haltedAts.map((haltedAt) => (
39
+ <>
40
+ {statuses.map((status) => {
41
+ return (
42
+ <Story key={`${status}__${haltedAt}`} args={{ status, ...(haltedAt !== null && { haltedAt }) }} />
43
+ );
44
+ })}
45
+ </>
46
+ ))}
47
+ </div>
48
+ </ThemeContext.Provider>
49
+ );
50
+ }
51
+ ]
52
+ };
@@ -0,0 +1,80 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React, { ComponentPropsWithoutRef } from 'react';
6
+
7
+ import { Invitation } from '@dxos/client';
8
+ import { getSize, mx, Size } from '@dxos/react-components';
9
+
10
+ import { inactiveStrokeColor, activeStrokeColor, resolvedStrokeColor } from '../../styles';
11
+ import { invitationStatusValue } from '../../util';
12
+
13
+ export interface InvitationStatusAvatarSlots {
14
+ svg?: ComponentPropsWithoutRef<'svg'>;
15
+ }
16
+
17
+ export interface InvitationStatusAvatarProps {
18
+ size?: Size;
19
+ status: Invitation.State;
20
+ haltedAt?: Invitation.State;
21
+ slots?: InvitationStatusAvatarSlots;
22
+ }
23
+
24
+ const svgSize = 32;
25
+ const strokeWidth = 5;
26
+ const radius = (svgSize - strokeWidth) / 2;
27
+ const circumference = Math.PI * 2 * radius;
28
+ const gap = circumference / 12;
29
+ const nSegments = 3;
30
+ const segment = circumference / nSegments;
31
+ const baseOffset = (2 * circumference) / 3 - (segment - gap) / 2;
32
+
33
+ const circleProps: ComponentPropsWithoutRef<'circle'> = {
34
+ fill: 'none',
35
+ strokeLinecap: 'round',
36
+ cx: svgSize / 2,
37
+ cy: svgSize / 2,
38
+ r: radius,
39
+ strokeWidth,
40
+ strokeDasharray: `${segment - gap} ${2 * segment + gap}`
41
+ };
42
+
43
+ export const InvitationStatusAvatar = ({ size = 10, status, haltedAt, slots = {} }: InvitationStatusAvatarProps) => {
44
+ const resolvedColor = resolvedStrokeColor(status);
45
+ const halted =
46
+ status === Invitation.State.CANCELLED || status === Invitation.State.TIMEOUT || status === Invitation.State.ERROR;
47
+ const cursor = invitationStatusValue.get(halted ? haltedAt! : status)!;
48
+ return (
49
+ <svg {...slots.svg} viewBox={`0 0 ${svgSize} ${svgSize}`} className={mx(getSize(size), slots.svg?.className)}>
50
+ {[...Array(nSegments)].map((_, index) => (
51
+ <circle
52
+ key={index}
53
+ {...circleProps}
54
+ strokeDashoffset={index * segment + baseOffset}
55
+ className={
56
+ index === 0
57
+ ? cursor === 1
58
+ ? halted
59
+ ? resolvedColor
60
+ : activeStrokeColor
61
+ : cursor > 1
62
+ ? resolvedColor
63
+ : inactiveStrokeColor
64
+ : index === 1
65
+ ? cursor === 3
66
+ ? halted
67
+ ? resolvedColor
68
+ : activeStrokeColor
69
+ : cursor > 3
70
+ ? resolvedColor
71
+ : inactiveStrokeColor
72
+ : cursor > 3
73
+ ? resolvedColor
74
+ : inactiveStrokeColor
75
+ }
76
+ />
77
+ ))}
78
+ </svg>
79
+ );
80
+ };
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './InvitationStatusAvatar';
6
+ export * from './InvitationListItem';
7
+ export * from './InvitationListContainer';
8
+ export * from './InvitationList';
@@ -0,0 +1,17 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React, { ComponentProps } from 'react';
6
+
7
+ import { mx } from '@dxos/react-components';
8
+
9
+ export const PanelSeparator = ({ className, ...props }: ComponentProps<'span'>) => {
10
+ return (
11
+ <span
12
+ role='none'
13
+ {...props}
14
+ className={mx('block bs-px mlb-1 bg-neutral-800/10 dark:bg-neutral-200/10', className)}
15
+ />
16
+ );
17
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './PanelSeparator';
@@ -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';
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './IdentityList';
6
+ export * from './InvitationList';
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';