@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,79 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { CaretLeft, CaretRight } from 'phosphor-react';
6
+ import React, { ComponentPropsWithoutRef, useState } from 'react';
7
+
8
+ import { useClient } from '@dxos/react-client';
9
+ import { Button, getSize, Input, mx, useTranslation } from '@dxos/react-components';
10
+
11
+ import { ViewState, ViewStateHeading, ViewStateProps } from './ViewState';
12
+
13
+ export interface IdentityCreatorProps extends ViewStateProps {
14
+ method: 'recover identity' | 'create identity';
15
+ }
16
+
17
+ export const IdentityInput = ({ method, ...viewStateProps }: IdentityCreatorProps) => {
18
+ const disabled = !viewStateProps.active;
19
+ const { dispatch } = viewStateProps;
20
+ const { t } = useTranslation('os');
21
+ const [inputValue, setInputValue] = useState('');
22
+ const client = useClient();
23
+ const [validationMessage, setValidationMessage] = useState('');
24
+ const isRecover = method === 'recover identity';
25
+ const handleNext = () => {
26
+ void client.halo.createProfile({ [isRecover ? 'seedphrase' : 'displayName']: inputValue }).then(
27
+ (identity) => {
28
+ dispatch({ type: 'added identity', identity });
29
+ },
30
+ (_error) => {
31
+ setValidationMessage(t(isRecover ? 'failed to recover identity message' : 'failed to create identity message'));
32
+ }
33
+ );
34
+ };
35
+ return (
36
+ <ViewState {...viewStateProps}>
37
+ <Input
38
+ disabled={disabled}
39
+ label={
40
+ <ViewStateHeading>
41
+ {t(isRecover ? 'recover identity input label' : 'new identity input label')}
42
+ </ViewStateHeading>
43
+ }
44
+ onChange={({ target: { value } }) => setInputValue(value)}
45
+ slots={{
46
+ root: { className: 'm-0' },
47
+ input: {
48
+ 'data-autofocus': isRecover ? 'identity input; recover identity' : 'identity input; create identity',
49
+ onKeyUp: ({ key }) => key === 'Enter' && handleNext()
50
+ } as ComponentPropsWithoutRef<'input'>
51
+ }}
52
+ {...(validationMessage.length && { validationValence: 'error', validationMessage })}
53
+ data-testid='identity-input'
54
+ />
55
+ <div role='none' className='grow' />
56
+ <div className='flex gap-2'>
57
+ <Button
58
+ disabled={disabled}
59
+ className='grow flex items-center gap-2 pli-2 order-2'
60
+ onClick={handleNext}
61
+ data-testid={`${method === 'recover identity' ? 'recover' : 'create'}-identity-input-continue`}
62
+ >
63
+ <CaretLeft weight='bold' className={mx(getSize(2), 'invisible')} />
64
+ <span className='grow'>{t('continue label')}</span>
65
+ <CaretRight weight='bold' className={getSize(4)} />
66
+ </Button>
67
+ <Button
68
+ disabled={disabled}
69
+ onClick={() => dispatch({ type: 'add identity' })}
70
+ className='flex items-center gap-2 pis-2 pie-4'
71
+ data-testid={`${method === 'recover identity' ? 'recover' : 'create'}-identity-input-back`}
72
+ >
73
+ <CaretLeft weight='bold' className={getSize(4)} />
74
+ <span>{t('back label')}</span>
75
+ </Button>
76
+ </div>
77
+ </ViewState>
78
+ );
79
+ };
@@ -0,0 +1,101 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import * as RadioGroup from '@radix-ui/react-radio-group';
6
+ import { CaretLeft, CaretRight, UserPlus } from 'phosphor-react';
7
+ import React, { useCallback, useState } from 'react';
8
+
9
+ import type { Profile } from '@dxos/client';
10
+ import { Avatar, Button, themeVariantFocus, getSize, mx, useTranslation } from '@dxos/react-components';
11
+
12
+ import { ViewState, ViewStateHeading, ViewStateProps } from './ViewState';
13
+
14
+ export interface IdentitySelectorProps extends ViewStateProps {
15
+ availableIdentities: Profile[];
16
+ }
17
+
18
+ export const IdentitySelector = ({ availableIdentities, ...viewStateProps }: IdentitySelectorProps) => {
19
+ const { dispatch } = viewStateProps;
20
+ const disabled = !viewStateProps.active;
21
+
22
+ const { t } = useTranslation('os');
23
+ const [activeIdentity, setActiveIdentity] = useState(availableIdentities[0]);
24
+ const onValueChange = useCallback(
25
+ (nextHex: string) =>
26
+ setActiveIdentity(
27
+ availableIdentities.find((identity) => identity.identityKey.toHex() === nextHex) || activeIdentity
28
+ ),
29
+ [availableIdentities]
30
+ );
31
+
32
+ return (
33
+ <ViewState {...viewStateProps}>
34
+ <ViewStateHeading>{t('identity selector title')}</ViewStateHeading>
35
+ <RadioGroup.Root
36
+ disabled={disabled}
37
+ value={activeIdentity?.identityKey.toHex()}
38
+ onValueChange={onValueChange}
39
+ className='overflow-y-auto grow shrink min-bs-10 pli-1 -mli-1'
40
+ aria-label={t('identity radio group title')}
41
+ data-autofocus='identity selector'
42
+ >
43
+ {availableIdentities.map(({ displayName, identityKey }) => {
44
+ const hex = identityKey.toHex();
45
+ const inputId = `identitySelector__item--${hex}`;
46
+ const labelId = `identitySelector__itemLabel--${hex}`;
47
+ return (
48
+ <label htmlFor={inputId} key={hex} className='flex items-center mbe-2 gap-2 cursor-pointer'>
49
+ <RadioGroup.Item
50
+ id={inputId}
51
+ aria-labelledby={labelId}
52
+ value={hex}
53
+ className={mx(
54
+ 'peer relative w-4 h-4 rounded-full',
55
+ 'border border-transparent text-white',
56
+ 'radix-state-checked:bg-primary-600',
57
+ 'radix-state-unchecked:bg-neutral-100 dark:radix-state-unchecked:bg-neutral-900',
58
+ themeVariantFocus('os')
59
+ )}
60
+ data-testid='identity-selector-item'
61
+ >
62
+ <RadioGroup.Indicator className='absolute inset-0 flex items-center justify-center leading-0'>
63
+ <div className='w-1.5 h-1.5 rounded-full bg-white' />
64
+ </RadioGroup.Indicator>
65
+ </RadioGroup.Item>
66
+ <Avatar fallbackValue={hex} labelId={labelId} variant='circle' />
67
+ <span id={labelId} className={mx(!displayName && 'font-mono')}>
68
+ {displayName ?? identityKey.truncate() ?? ''}
69
+ </span>
70
+ </label>
71
+ );
72
+ })}
73
+ </RadioGroup.Root>
74
+ <Button
75
+ disabled={disabled}
76
+ compact
77
+ onClick={() => dispatch({ type: 'add identity' })}
78
+ className='flex items-center gap-2 pli-2'
79
+ data-testid='add-identity'
80
+ >
81
+ <span>{t('add identity label')}</span>
82
+ <UserPlus weight='bold' className={getSize(3.5)} />
83
+ </Button>
84
+ <Button
85
+ disabled={disabled || !activeIdentity}
86
+ className='flex items-center gap-2 pli-2'
87
+ onClick={() =>
88
+ dispatch({
89
+ type: 'select identity',
90
+ identity: activeIdentity
91
+ })
92
+ }
93
+ data-testid='select-identity'
94
+ >
95
+ <CaretLeft weight='bold' className={mx(getSize(4), 'invisible')} />
96
+ <span className='grow'>{t('continue label')}</span>
97
+ <CaretRight weight='bold' className={getSize(4)} />
98
+ </Button>
99
+ </ViewState>
100
+ );
101
+ };
@@ -0,0 +1,70 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { CaretLeft, Check } from 'phosphor-react';
6
+ import React, { cloneElement } from 'react';
7
+
8
+ import { AuthenticatingInvitationObservable } from '@dxos/client';
9
+ import { InvitationResult, useInvitationStatus } from '@dxos/react-client';
10
+ import { Button, getSize, mx, useTranslation } from '@dxos/react-components';
11
+
12
+ import { ViewState, ViewStateProps } from './ViewState';
13
+
14
+ export interface InvitationAcceptedProps extends ViewStateProps {
15
+ invitationType: 'space' | 'halo';
16
+ doneActionParent?: Parameters<typeof cloneElement>[0];
17
+ onDone?: (result: InvitationResult | null) => void;
18
+ }
19
+
20
+ const PureInvitationAcceptedContent = ({
21
+ onDone,
22
+ result,
23
+ invitationType,
24
+ doneActionParent,
25
+ active
26
+ }: InvitationAcceptedProps & { result: InvitationResult | null }) => {
27
+ const disabled = !active;
28
+ const { t } = useTranslation('os');
29
+
30
+ const doneButton = (
31
+ <Button
32
+ {...(onDone && { onClick: () => onDone(result) })}
33
+ disabled={disabled}
34
+ className='grow flex items-center gap-2 pli-2'
35
+ data-autofocus={`${invitationType} invitation acceptor; invitation accepted`}
36
+ data-testid={`${invitationType}-invitation-accepted-done`}
37
+ >
38
+ <CaretLeft weight='bold' className={mx(getSize(2), 'invisible')} />
39
+ <span className='grow'>{t('done label')}</span>
40
+ <Check weight='bold' className={getSize(4)} />
41
+ </Button>
42
+ );
43
+
44
+ return doneActionParent ? cloneElement(doneActionParent, {}, doneButton) : doneButton;
45
+ };
46
+
47
+ const InvitationAcceptedContent = (props: InvitationAcceptedProps) => {
48
+ const { result } = useInvitationStatus(props.activeInvitation as AuthenticatingInvitationObservable);
49
+ return <PureInvitationAcceptedContent {...props} result={result} />;
50
+ };
51
+
52
+ export const InvitationAccepted = (props: InvitationAcceptedProps) => {
53
+ const {
54
+ invitationType: _invitationType,
55
+ doneActionParent: _doneActionParent,
56
+ onDone: _onDone,
57
+ ...viewStateProps
58
+ } = props;
59
+ const { activeInvitation } = viewStateProps;
60
+
61
+ return (
62
+ <ViewState {...viewStateProps}>
63
+ {!activeInvitation || activeInvitation === true ? (
64
+ <PureInvitationAcceptedContent {...props} result={null} />
65
+ ) : (
66
+ <InvitationAcceptedContent {...props} />
67
+ )}
68
+ </ViewState>
69
+ );
70
+ };
@@ -0,0 +1,142 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { CaretLeft, CaretRight } from 'phosphor-react';
6
+ import React, { ChangeEvent, ComponentProps, ComponentPropsWithoutRef, useCallback, useState } from 'react';
7
+
8
+ import { AuthenticatingInvitationObservable } from '@dxos/client';
9
+ import { useInvitationStatus } from '@dxos/react-client';
10
+ import { Button, getSize, Input, mx, useTranslation } from '@dxos/react-components';
11
+
12
+ import { ViewState, ViewStateProps } from './ViewState';
13
+
14
+ const pinLength = 6;
15
+
16
+ export interface InvitationAuthenticatorProps extends ViewStateProps {
17
+ invitationType: 'space' | 'halo';
18
+ failed?: boolean;
19
+ }
20
+
21
+ const PureInvitationAuthenticatorContent = ({
22
+ disabled,
23
+ failed,
24
+ dispatch,
25
+ invitationType,
26
+ onChange,
27
+ onAuthenticate
28
+ }: {
29
+ disabled?: boolean;
30
+ dispatch: ViewStateProps['dispatch'];
31
+ invitationType: InvitationAuthenticatorProps['invitationType'];
32
+ failed: InvitationAuthenticatorProps['failed'];
33
+ onChange: ComponentProps<typeof Input>['onChange'];
34
+ onAuthenticate: ComponentProps<typeof Button>['onClick'];
35
+ }) => {
36
+ const { t } = useTranslation('os');
37
+ return (
38
+ <>
39
+ <Input
40
+ label={t('auth code input label')}
41
+ size='pin'
42
+ length={pinLength}
43
+ onChange={onChange}
44
+ disabled={disabled}
45
+ slots={{
46
+ root: { className: 'm-0' },
47
+ label: { className: 'sr-only' },
48
+ description: { className: 'text-center' },
49
+ input: {
50
+ disabled,
51
+ inputMode: 'numeric',
52
+ pattern: '\\d*',
53
+ 'data-autofocus': `${invitationType} invitation acceptor; invitation authenticator`
54
+ } as ComponentPropsWithoutRef<'input'>
55
+ }}
56
+ {...(failed && {
57
+ validationValence: 'error',
58
+ validationMessage: t('failed to authenticate message')
59
+ })}
60
+ data-testid='auth-code-input'
61
+ />
62
+ <div role='none' className='grow' />
63
+ <div className='flex gap-2'>
64
+ <Button
65
+ disabled={disabled}
66
+ className='grow flex items-center gap-2 pli-2 order-2'
67
+ onClick={onAuthenticate}
68
+ data-autofocus-pinlength={invitationType}
69
+ data-testid={`${invitationType}-invitation-authenticator-next`}
70
+ >
71
+ <CaretLeft weight='bold' className={mx(getSize(2), 'invisible')} />
72
+ <span className='grow'>{t('next label')}</span>
73
+ <CaretRight weight='bold' className={getSize(4)} />
74
+ </Button>
75
+ <Button
76
+ disabled={disabled}
77
+ className='flex items-center gap-2 pis-2 pie-4'
78
+ onClick={() => dispatch({ type: 'cancel invitation', from: invitationType })}
79
+ data-testid={`${invitationType}-invitation-authenticator-cancel`}
80
+ >
81
+ <CaretLeft weight='bold' className={getSize(4)} />
82
+ <span>{t('cancel label')}</span>
83
+ </Button>
84
+ </div>
85
+ </>
86
+ );
87
+ };
88
+
89
+ const InvitationAuthenticatorContent = ({
90
+ disabled,
91
+ invitation,
92
+ dispatch,
93
+ invitationType,
94
+ failed
95
+ }: {
96
+ disabled?: boolean;
97
+ invitation: AuthenticatingInvitationObservable;
98
+ dispatch: ViewStateProps['dispatch'];
99
+ invitationType: InvitationAuthenticatorProps['invitationType'];
100
+ failed: InvitationAuthenticatorProps['failed'];
101
+ }) => {
102
+ const [pinValue, setPinValue] = useState('');
103
+ const { authenticate } = useInvitationStatus(invitation);
104
+ const onAuthenticate = useCallback(() => {
105
+ dispatch({ type: 'authenticating invitation', from: invitationType });
106
+ void authenticate(pinValue);
107
+ }, [dispatch, invitationType, authenticate, pinValue]);
108
+ const onChange = useCallback(
109
+ ({ target: { value } }: ChangeEvent<HTMLInputElement>) => {
110
+ setPinValue(value);
111
+ if (value.length === pinLength) {
112
+ (document.querySelector(`[data-autofocus-pinlength="${invitationType}"]`) as HTMLElement | null)?.focus();
113
+ }
114
+ },
115
+ [authenticate, pinValue]
116
+ );
117
+ return (
118
+ <PureInvitationAuthenticatorContent {...{ disabled, failed, dispatch, invitationType, onChange, onAuthenticate }} />
119
+ );
120
+ };
121
+
122
+ export const InvitationAuthenticator = ({
123
+ invitationType,
124
+ failed,
125
+ ...viewStateProps
126
+ }: InvitationAuthenticatorProps) => {
127
+ const disabled = !viewStateProps.active;
128
+ const { activeInvitation, dispatch } = viewStateProps;
129
+ return (
130
+ <ViewState {...viewStateProps}>
131
+ {!activeInvitation || activeInvitation === true ? (
132
+ <PureInvitationAuthenticatorContent
133
+ {...{ disabled, failed, dispatch, invitationType, onChange: () => {}, onAuthenticate: () => {} }}
134
+ />
135
+ ) : (
136
+ <InvitationAuthenticatorContent
137
+ {...{ disabled, failed, invitation: activeInvitation, dispatch, invitationType }}
138
+ />
139
+ )}
140
+ </ViewState>
141
+ );
142
+ };
@@ -0,0 +1,75 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { CaretLeft, CaretRight } from 'phosphor-react';
6
+ import React, { ComponentPropsWithoutRef, useState } from 'react';
7
+
8
+ import { InvitationEncoder } from '@dxos/client';
9
+ import { useClient } from '@dxos/react-client';
10
+ import { Button, getSize, Input, mx, useTranslation } from '@dxos/react-components';
11
+
12
+ import { ViewState, ViewStateHeading, ViewStateProps } from './ViewState';
13
+
14
+ export interface InvitationInputProps extends ViewStateProps {
15
+ invitationType: 'space' | 'halo';
16
+ }
17
+
18
+ export const InvitationInput = ({ invitationType, ...viewStateProps }: InvitationInputProps) => {
19
+ const client = useClient();
20
+ const disabled = !viewStateProps.active;
21
+ const { dispatch } = viewStateProps;
22
+ const { t } = useTranslation('os');
23
+
24
+ const [inputValue, setInputValue] = useState('');
25
+
26
+ const handleNext = () =>
27
+ dispatch({
28
+ type: 'connecting invitation',
29
+ from: invitationType,
30
+ invitation: client[invitationType === 'halo' ? 'halo' : 'echo'].acceptInvitation(
31
+ InvitationEncoder.decode(inputValue)
32
+ )
33
+ });
34
+
35
+ return (
36
+ <ViewState {...viewStateProps}>
37
+ <Input
38
+ disabled={disabled}
39
+ label={<ViewStateHeading>{t('invitation input label')}</ViewStateHeading>}
40
+ onChange={({ target: { value } }) => setInputValue(value)}
41
+ slots={{
42
+ root: { className: 'm-0' },
43
+ label: { className: 'sr-only' },
44
+ input: {
45
+ 'data-autofocus': `${invitationType} invitation acceptor; invitation input`,
46
+ onKeyUp: ({ key }) => key === 'Enter' && handleNext()
47
+ } as ComponentPropsWithoutRef<'input'>
48
+ }}
49
+ data-testid='invitation-input'
50
+ />
51
+ <div role='none' className='grow' />
52
+ <div className='flex gap-2'>
53
+ <Button
54
+ disabled={disabled}
55
+ className='grow flex items-center gap-2 pli-2 order-2'
56
+ onClick={handleNext}
57
+ data-testid={`${invitationType}-invitation-input-continue`}
58
+ >
59
+ <CaretLeft weight='bold' className={mx(getSize(2), 'invisible')} />
60
+ <span className='grow'>{t('continue label')}</span>
61
+ <CaretRight weight='bold' className={getSize(4)} />
62
+ </Button>
63
+ <Button
64
+ disabled={disabled}
65
+ onClick={() => dispatch({ type: 'add identity' })}
66
+ className='flex items-center gap-2 pis-2 pie-4'
67
+ data-testid={`${invitationType}-invitation-input-back`}
68
+ >
69
+ <CaretLeft weight='bold' className={getSize(4)} />
70
+ <span>{t('back label')}</span>
71
+ </Button>
72
+ </div>
73
+ </ViewState>
74
+ );
75
+ };
@@ -0,0 +1,98 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { ArrowsClockwise, CaretLeft, CaretRight } from 'phosphor-react';
6
+ import React from 'react';
7
+
8
+ import { AuthenticatingInvitationObservable, Invitation } from '@dxos/client';
9
+ import { useInvitationStatus } from '@dxos/react-client';
10
+ import { Button, getSize, mx, useTranslation } from '@dxos/react-components';
11
+
12
+ import { ViewState, ViewStateProps } from './ViewState';
13
+
14
+ export interface InvitationConnectorProps extends ViewStateProps {
15
+ invitationType: 'space' | 'halo';
16
+ }
17
+
18
+ const InvitationActions = ({
19
+ activeInvitation,
20
+ disabled,
21
+ dispatch,
22
+ invitationType
23
+ }: {
24
+ activeInvitation: AuthenticatingInvitationObservable;
25
+ disabled?: boolean;
26
+ dispatch: ViewStateProps['dispatch'];
27
+ invitationType: InvitationConnectorProps['invitationType'];
28
+ }) => {
29
+ const { status, cancel } = useInvitationStatus(activeInvitation);
30
+ const { t } = useTranslation('os');
31
+
32
+ switch (status) {
33
+ case Invitation.State.CONNECTING:
34
+ return (
35
+ <>
36
+ <div role='none' className='grow' />
37
+ <div className='flex gap-2'>
38
+ <Button disabled className='grow flex items-center gap-2 pli-2 order-2' data-testid='next'>
39
+ <CaretLeft weight='bold' className={mx(getSize(2), 'invisible')} />
40
+ <span className='grow'>{t('next label')}</span>
41
+ <CaretRight weight='bold' className={getSize(4)} />
42
+ </Button>
43
+ <Button
44
+ disabled={disabled}
45
+ className='flex items-center gap-2 pis-2 pie-4'
46
+ onClick={cancel}
47
+ data-autofocus={`${invitationType} invitation acceptor; invitation rescuer`}
48
+ data-testid='invitation-rescuer-cancel'
49
+ >
50
+ <CaretLeft weight='bold' className={getSize(4)} />
51
+ <span>{t('cancel label')}</span>
52
+ </Button>
53
+ </div>
54
+ </>
55
+ );
56
+ case Invitation.State.TIMEOUT:
57
+ case Invitation.State.CANCELLED:
58
+ case Invitation.State.ERROR:
59
+ default:
60
+ return (
61
+ <Button
62
+ disabled={disabled}
63
+ className='grow flex items-center gap-2 pli-2'
64
+ onClick={() => dispatch({ type: 'reset invitation', from: invitationType })}
65
+ data-autofocus={`${invitationType} invitation acceptor; invitation rescuer`}
66
+ data-testid='invitation-rescuer-reset'
67
+ >
68
+ <CaretLeft weight='bold' className={mx(getSize(5), 'invisible')} />
69
+ <span className='grow'>{t('reset label')}</span>
70
+ <ArrowsClockwise weight='bold' className={getSize(5)} />
71
+ </Button>
72
+ );
73
+ }
74
+ };
75
+
76
+ export const InvitationRescuer = ({ invitationType, ...viewStateProps }: InvitationConnectorProps) => {
77
+ const disabled = !viewStateProps.active;
78
+ const { dispatch, activeInvitation } = viewStateProps;
79
+ const { t } = useTranslation('os');
80
+ return (
81
+ <ViewState {...viewStateProps}>
82
+ {activeInvitation === true || !activeInvitation ? (
83
+ <Button
84
+ disabled={disabled}
85
+ className='grow flex items-center gap-2 pli-2'
86
+ data-autofocus='space invitation acceptor; invitation rescuer'
87
+ data-testid='invitation-rescuer-connect'
88
+ >
89
+ <CaretLeft weight='bold' className={mx(getSize(5), 'invisible')} />
90
+ <span className='grow'>{t('connect label')}</span>
91
+ <ArrowsClockwise weight='bold' className={getSize(5)} />
92
+ </Button>
93
+ ) : (
94
+ <InvitationActions {...{ activeInvitation, disabled, dispatch, invitationType }} />
95
+ )}
96
+ </ViewState>
97
+ );
98
+ };