@dxos/react-ui 0.1.24 → 0.1.25-next.3c6b542
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +1092 -452
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/IdentityList/DeviceList.d.ts +7 -0
- package/dist/types/src/components/IdentityList/DeviceList.d.ts.map +1 -0
- package/dist/types/src/components/IdentityList/IdentityListItem.d.ts +2 -1
- package/dist/types/src/components/IdentityList/IdentityListItem.d.ts.map +1 -1
- package/dist/types/src/components/IdentityList/IdentityListItem.stories.d.ts +2 -1
- package/dist/types/src/components/IdentityList/IdentityListItem.stories.d.ts.map +1 -1
- package/dist/types/src/components/IdentityList/SpaceMemberList.d.ts +3 -2
- package/dist/types/src/components/IdentityList/SpaceMemberList.d.ts.map +1 -1
- package/dist/types/src/components/IdentityList/SpaceMemberListContainer.d.ts +3 -1
- package/dist/types/src/components/IdentityList/SpaceMemberListContainer.d.ts.map +1 -1
- package/dist/types/src/components/IdentityList/index.d.ts +1 -0
- package/dist/types/src/components/IdentityList/index.d.ts.map +1 -1
- package/dist/types/src/components/InvitationList/InvitationList.d.ts.map +1 -1
- package/dist/types/src/components/InvitationList/InvitationListItem.d.ts.map +1 -1
- package/dist/types/src/components/InvitationList/InvitationStatusAvatar.d.ts +6 -1
- package/dist/types/src/components/InvitationList/InvitationStatusAvatar.d.ts.map +1 -1
- package/dist/types/src/components/InvitationList/InvitationStatusAvatar.stories.d.ts +1 -1
- package/dist/types/src/components/SpaceList/SpaceListItem.d.ts +7 -0
- package/dist/types/src/components/SpaceList/SpaceListItem.d.ts.map +1 -0
- package/dist/types/src/components/SpaceList/index.d.ts +2 -0
- package/dist/types/src/components/SpaceList/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/composites/DevicesDialog/DevicesDialog.d.ts +6 -0
- package/dist/types/src/composites/DevicesDialog/DevicesDialog.d.ts.map +1 -0
- package/dist/types/src/composites/DevicesDialog/index.d.ts +2 -0
- package/dist/types/src/composites/DevicesDialog/index.d.ts.map +1 -0
- package/dist/types/src/composites/JoinDialog/JoinDialog.d.ts +1 -1
- package/dist/types/src/composites/JoinDialog/JoinDialog.d.ts.map +1 -1
- package/dist/types/src/composites/JoinDialog/JoinDialog.stories.d.ts +2 -2
- package/dist/types/src/composites/JoinDialog/JoinDialog.stories.d.ts.map +1 -1
- package/dist/types/src/composites/Shell/Shell.d.ts +6 -0
- package/dist/types/src/composites/Shell/Shell.d.ts.map +1 -0
- package/dist/types/src/composites/Shell/index.d.ts +2 -0
- package/dist/types/src/composites/Shell/index.d.ts.map +1 -0
- package/dist/types/src/composites/SpaceDialog/SpaceDialog.d.ts +6 -0
- package/dist/types/src/composites/SpaceDialog/SpaceDialog.d.ts.map +1 -0
- package/dist/types/src/composites/SpaceDialog/index.d.ts +2 -0
- package/dist/types/src/composites/SpaceDialog/index.d.ts.map +1 -0
- package/dist/types/src/composites/index.d.ts +2 -0
- package/dist/types/src/composites/index.d.ts.map +1 -1
- package/dist/types/src/layouts/PanelAlertDialog/PanelAlertDialog.d.ts.map +1 -1
- package/dist/types/src/layouts/PanelDialog/PanelDialog.d.ts +14 -0
- package/dist/types/src/layouts/PanelDialog/PanelDialog.d.ts.map +1 -0
- package/dist/types/src/layouts/PanelDialog/index.d.ts +2 -0
- package/dist/types/src/layouts/PanelDialog/index.d.ts.map +1 -0
- package/dist/types/src/layouts/index.d.ts +1 -0
- package/dist/types/src/layouts/index.d.ts.map +1 -1
- package/dist/types/src/panels/DevicesPanel/DevicesPanel.d.ts +10 -0
- package/dist/types/src/panels/DevicesPanel/DevicesPanel.d.ts.map +1 -0
- package/dist/types/src/panels/DevicesPanel/index.d.ts +2 -0
- package/dist/types/src/panels/DevicesPanel/index.d.ts.map +1 -0
- package/dist/types/src/panels/JoinPanel/JoinHeading.d.ts +3 -0
- package/dist/types/src/panels/JoinPanel/JoinHeading.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/JoinPanel.d.ts +1 -1
- package/dist/types/src/panels/JoinPanel/JoinPanel.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts +4 -3
- package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/view-states/AdditionMethodSelector.d.ts +1 -1
- package/dist/types/src/panels/JoinPanel/view-states/AdditionMethodSelector.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/view-states/IdentityAdded.d.ts +10 -2
- package/dist/types/src/panels/JoinPanel/view-states/IdentityAdded.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/view-states/IdentityInput.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/view-states/IdentitySelector.d.ts +1 -1
- package/dist/types/src/panels/JoinPanel/view-states/IdentitySelector.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/view-states/InvitationAccepted.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/view-states/InvitationAuthenticator.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/view-states/InvitationInput.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/view-states/InvitationRescuer.d.ts.map +1 -1
- package/dist/types/src/panels/SpacePanel/SpacePanel.d.ts +18 -0
- package/dist/types/src/panels/SpacePanel/SpacePanel.d.ts.map +1 -0
- package/dist/types/src/panels/SpacePanel/SpacePanel.stories.d.ts +9 -0
- package/dist/types/src/panels/SpacePanel/SpacePanel.stories.d.ts.map +1 -0
- package/dist/types/src/panels/SpacePanel/index.d.ts +2 -0
- package/dist/types/src/panels/SpacePanel/index.d.ts.map +1 -0
- package/dist/types/src/panels/index.d.ts +2 -0
- package/dist/types/src/panels/index.d.ts.map +1 -1
- package/dist/types/src/styles/dialogStyles.d.ts +3 -0
- package/dist/types/src/styles/dialogStyles.d.ts.map +1 -0
- package/dist/types/src/styles/index.d.ts +1 -0
- package/dist/types/src/styles/index.d.ts.map +1 -1
- package/dist/types/src/translations/locales/en-US.d.ts +12 -1
- package/dist/types/src/translations/locales/en-US.d.ts.map +1 -1
- package/package.json +12 -11
- package/src/components/IdentityList/DeviceList.tsx +31 -0
- package/src/components/IdentityList/IdentityListItem.tsx +15 -3
- package/src/components/IdentityList/SpaceMemberList.tsx +10 -8
- package/src/components/IdentityList/SpaceMemberListContainer.tsx +5 -4
- package/src/components/IdentityList/index.ts +1 -0
- package/src/components/InvitationList/InvitationList.tsx +6 -2
- package/src/components/InvitationList/InvitationListItem.tsx +36 -25
- package/src/components/InvitationList/InvitationStatusAvatar.tsx +10 -5
- package/src/components/SpaceList/SpaceListItem.tsx +32 -0
- package/src/components/SpaceList/index.ts +5 -0
- package/src/components/index.ts +1 -0
- package/src/composites/DevicesDialog/DevicesDialog.tsx +38 -0
- package/src/composites/DevicesDialog/index.ts +5 -0
- package/src/composites/JoinDialog/JoinDialog.stories.tsx +59 -58
- package/src/composites/JoinDialog/JoinDialog.tsx +3 -1
- package/src/composites/Shell/Shell.tsx +92 -0
- package/src/composites/Shell/index.ts +5 -0
- package/src/composites/SpaceDialog/SpaceDialog.tsx +38 -0
- package/src/composites/SpaceDialog/index.ts +5 -0
- package/src/composites/index.ts +3 -0
- package/src/layouts/PanelAlertDialog/PanelAlertDialog.tsx +4 -8
- package/src/layouts/PanelDialog/PanelDialog.tsx +39 -0
- package/src/layouts/PanelDialog/index.ts +5 -0
- package/src/layouts/PanelSidebar/PanelSidebar.tsx +1 -1
- package/src/layouts/index.ts +1 -0
- package/src/panels/DevicesPanel/DevicesPanel.tsx +98 -0
- package/src/panels/DevicesPanel/index.ts +5 -0
- package/src/panels/JoinPanel/JoinHeading.tsx +44 -16
- package/src/panels/JoinPanel/JoinPanel.tsx +32 -11
- package/src/panels/JoinPanel/JoinPanelProps.ts +4 -8
- package/src/panels/JoinPanel/view-states/AdditionMethodSelector.tsx +5 -1
- package/src/panels/JoinPanel/view-states/IdentityAdded.tsx +63 -24
- package/src/panels/JoinPanel/view-states/IdentityInput.tsx +8 -1
- package/src/panels/JoinPanel/view-states/IdentitySelector.tsx +4 -1
- package/src/panels/JoinPanel/view-states/InvitationAccepted.tsx +1 -0
- package/src/panels/JoinPanel/view-states/InvitationAuthenticator.tsx +4 -1
- package/src/panels/JoinPanel/view-states/InvitationInput.tsx +8 -1
- package/src/panels/JoinPanel/view-states/InvitationRescuer.tsx +4 -1
- package/src/panels/SpacePanel/SpacePanel.stories.tsx +53 -0
- package/src/panels/SpacePanel/SpacePanel.tsx +174 -0
- package/src/panels/SpacePanel/index.ts +5 -0
- package/src/panels/index.ts +2 -0
- package/src/styles/dialogStyles.ts +8 -0
- package/src/styles/index.ts +1 -0
- package/src/translations/locales/en-US.ts +14 -3
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { UserPlus, X } from 'phosphor-react';
|
|
6
|
+
import React, { cloneElement, useReducer } from 'react';
|
|
7
|
+
|
|
8
|
+
import { useClient, useDevices, useHaloInvitations, useIdentity } from '@dxos/react-client';
|
|
9
|
+
import { Button, getSize, mx, Tooltip, useTranslation } from '@dxos/react-components';
|
|
10
|
+
|
|
11
|
+
import { DeviceList, InvitationList, PanelSeparator } from '../../components';
|
|
12
|
+
import { defaultSurface, subduedSurface } from '../../styles';
|
|
13
|
+
|
|
14
|
+
export type DevicesPanelProps = {
|
|
15
|
+
titleId?: string;
|
|
16
|
+
createInvitationUrl: (invitationCode: string) => string;
|
|
17
|
+
doneActionParent?: Parameters<typeof cloneElement>[0];
|
|
18
|
+
onDone?: () => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type DevicesView = 'device list';
|
|
22
|
+
|
|
23
|
+
const DeviceListView = ({ createInvitationUrl, titleId, onDone, doneActionParent }: DevicesPanelProps) => {
|
|
24
|
+
const { t } = useTranslation('os');
|
|
25
|
+
const client = useClient();
|
|
26
|
+
const identity = useIdentity();
|
|
27
|
+
const devices = useDevices();
|
|
28
|
+
const invitations = useHaloInvitations();
|
|
29
|
+
const displayName = identity?.displayName;
|
|
30
|
+
|
|
31
|
+
if (!identity) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const doneButton = (
|
|
36
|
+
<Button compact variant='ghost' onClick={() => onDone?.()} data-testid='show-all-spaces'>
|
|
37
|
+
<X className={getSize(4)} weight='bold' />
|
|
38
|
+
</Button>
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div role='none' className='flex flex-col'>
|
|
43
|
+
<div role='none' className={mx(subduedSurface, 'rounded-bs-md flex items-center p-2 gap-2')}>
|
|
44
|
+
{/* TODO(wittjosiah): Label this as the identity panel. */}
|
|
45
|
+
<h2 id={titleId} className={mx('grow font-system-medium', !displayName && 'font-mono')}>
|
|
46
|
+
{displayName ?? identity.identityKey.truncate()}
|
|
47
|
+
</h2>
|
|
48
|
+
<Tooltip content={t('close label')} zIndex='z-50'>
|
|
49
|
+
{doneActionParent ? cloneElement(doneActionParent, {}, doneButton) : doneButton}
|
|
50
|
+
</Tooltip>
|
|
51
|
+
</div>
|
|
52
|
+
<div role='region' className={mx(defaultSurface, 'rounded-be-md p-2')}>
|
|
53
|
+
<InvitationList
|
|
54
|
+
invitations={invitations}
|
|
55
|
+
onClickRemove={({ invitation }) => invitation && client.halo.removeInvitation(invitation.invitationId!)}
|
|
56
|
+
createInvitationUrl={createInvitationUrl}
|
|
57
|
+
/>
|
|
58
|
+
<Button
|
|
59
|
+
className='is-full flex gap-2 mbs-2'
|
|
60
|
+
compact
|
|
61
|
+
onClick={() => client.halo.createInvitation()}
|
|
62
|
+
data-testid='create-space-invitation'
|
|
63
|
+
>
|
|
64
|
+
<span>{t('create space invitation label')}</span>
|
|
65
|
+
<UserPlus className={getSize(4)} weight='bold' />
|
|
66
|
+
</Button>
|
|
67
|
+
<PanelSeparator />
|
|
68
|
+
<DeviceList devices={devices} />
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
type DevicesPanelState = {
|
|
75
|
+
activeView: DevicesView;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
type DevicesPanelAction = {
|
|
79
|
+
type: null;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// TODO(wittjosiah): Rename identity panel and include other views?
|
|
83
|
+
export const DevicesPanel = (props: DevicesPanelProps) => {
|
|
84
|
+
const reducer = (state: DevicesPanelState, action: DevicesPanelAction) => {
|
|
85
|
+
const nextState = { ...state };
|
|
86
|
+
switch (action.type) {
|
|
87
|
+
case null:
|
|
88
|
+
}
|
|
89
|
+
return nextState;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const [panelState] = useReducer(reducer, {
|
|
93
|
+
activeView: 'device list'
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// TODO(wittjosiah): Use ViewState or similar.
|
|
97
|
+
return <>{panelState.activeView === 'device list' ? <DeviceListView {...props} /> : null}</>;
|
|
98
|
+
};
|
|
@@ -9,25 +9,41 @@ import { useSpace } from '@dxos/react-client';
|
|
|
9
9
|
import { Avatar, Button, getSize, Heading, mx, Trans, useId, useTranslation } from '@dxos/react-components';
|
|
10
10
|
|
|
11
11
|
import { subduedSurface } from '../../styles';
|
|
12
|
+
import { JoinPanelMode } from './JoinPanelProps';
|
|
12
13
|
|
|
13
14
|
export interface JoinSpaceHeadingProps {
|
|
15
|
+
mode?: JoinPanelMode;
|
|
14
16
|
titleId: string;
|
|
15
17
|
invitation?: AuthenticatingInvitationObservable;
|
|
16
18
|
onExit?: () => void;
|
|
17
19
|
exitActionParent?: Parameters<typeof cloneElement>[0];
|
|
20
|
+
preventExit?: boolean;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
export const JoinHeading = forwardRef(
|
|
21
|
-
(
|
|
24
|
+
(
|
|
25
|
+
{ mode, titleId, invitation, onExit, exitActionParent, preventExit }: JoinSpaceHeadingProps,
|
|
26
|
+
ref: ForwardedRef<HTMLDivElement>
|
|
27
|
+
) => {
|
|
22
28
|
const { t } = useTranslation('os');
|
|
23
29
|
|
|
24
30
|
const space = useSpace(invitation?.invitation?.spaceKey);
|
|
25
|
-
const
|
|
31
|
+
const name = mode === 'halo-only' ? '(Unknown identity)' : space?.properties.name ?? '(Space name not available)';
|
|
32
|
+
const nameId = useId(mode === 'halo-only' ? 'identityDisplayName' : 'spaceDisplayName');
|
|
26
33
|
|
|
27
|
-
const
|
|
34
|
+
const invitationKey =
|
|
35
|
+
mode === 'halo-only'
|
|
36
|
+
? invitation?.invitation?.identityKey?.toHex()
|
|
37
|
+
: invitation?.invitation?.identityKey?.toHex();
|
|
28
38
|
|
|
29
39
|
const exitButton = (
|
|
30
|
-
<Button
|
|
40
|
+
<Button
|
|
41
|
+
compact
|
|
42
|
+
variant='ghost'
|
|
43
|
+
{...(onExit && { onClick: onExit })}
|
|
44
|
+
className='grow-0 shrink-0'
|
|
45
|
+
data-testid='join-exit'
|
|
46
|
+
>
|
|
31
47
|
<ProhibitInset className={getSize(5)} />
|
|
32
48
|
<span className='sr-only'>{t('exit label')}</span>
|
|
33
49
|
</Button>
|
|
@@ -36,20 +52,32 @@ export const JoinHeading = forwardRef(
|
|
|
36
52
|
return (
|
|
37
53
|
<div role='none' className={mx(subduedSurface, 'p-2 rounded-bs-md')} ref={ref}>
|
|
38
54
|
<div role='group' className='flex items-center gap-2'>
|
|
39
|
-
|
|
55
|
+
{invitationKey ? (
|
|
56
|
+
<Avatar fallbackValue={invitationKey} labelId={nameId} />
|
|
57
|
+
) : (
|
|
58
|
+
<span role='none' className={mx(getSize(10), 'bg-neutral-300 dark:bg-neutral-700 rounded-full')} />
|
|
59
|
+
)}
|
|
40
60
|
<Heading level={1} className='font-body font-normal text-base grow' id={titleId}>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
{invitation ? (
|
|
62
|
+
<Trans
|
|
63
|
+
{...{
|
|
64
|
+
defaults: t('joining heading'),
|
|
65
|
+
components: {
|
|
66
|
+
small: <span className='block leading-none mbe-1 font-system-medium text-sm' />,
|
|
67
|
+
large: <span className='block leading-none' id={nameId} />
|
|
68
|
+
},
|
|
69
|
+
values: { name }
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
) : (
|
|
73
|
+
<span className='block leading-none font-system-medium text-sm'>
|
|
74
|
+
{mode === 'halo-only' ? t('halo heading') : t('join space heading')}
|
|
75
|
+
</span>
|
|
76
|
+
)}
|
|
51
77
|
</Heading>
|
|
52
|
-
{
|
|
78
|
+
{!preventExit &&
|
|
79
|
+
mode !== 'halo-only' &&
|
|
80
|
+
(exitActionParent ? cloneElement(exitActionParent, {}, exitButton) : exitButton)}
|
|
53
81
|
</div>
|
|
54
82
|
</div>
|
|
55
83
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
|
-
import React, { useEffect, useReducer } from 'react';
|
|
4
|
+
import React, { useEffect, useReducer, useState } from 'react';
|
|
5
5
|
|
|
6
6
|
import { InvitationEncoder } from '@dxos/client';
|
|
7
7
|
import { log } from '@dxos/log';
|
|
@@ -22,18 +22,20 @@ import {
|
|
|
22
22
|
} from './view-states';
|
|
23
23
|
|
|
24
24
|
export const JoinPanel = ({
|
|
25
|
+
mode,
|
|
25
26
|
initialInvitationCode,
|
|
26
27
|
titleId: propsTitleId,
|
|
27
28
|
exitActionParent,
|
|
28
29
|
onExit,
|
|
29
30
|
doneActionParent,
|
|
30
|
-
onDone
|
|
31
|
+
onDone,
|
|
32
|
+
preventExit
|
|
31
33
|
}: JoinPanelProps) => {
|
|
32
34
|
const client = useClient();
|
|
33
35
|
const internalTitleId = useId('joinPanel__title');
|
|
34
36
|
const titleId = propsTitleId ?? internalTitleId;
|
|
35
37
|
const identity = useIdentity();
|
|
36
|
-
|
|
38
|
+
const [prevIdentity, setPrevIdentity] = useState(identity);
|
|
37
39
|
const availableIdentities = identity ? [identity] : [];
|
|
38
40
|
|
|
39
41
|
const reducer = (state: JoinState, action: JoinAction) => {
|
|
@@ -50,7 +52,7 @@ export const JoinPanel = ({
|
|
|
50
52
|
if (action.method === 'accept device invitation') {
|
|
51
53
|
nextState.activeView = 'halo invitation acceptor';
|
|
52
54
|
if (state.unredeemedHaloInvitationCode) {
|
|
53
|
-
nextState.haloInvitation = client.
|
|
55
|
+
nextState.haloInvitation = client.halo.acceptInvitation(
|
|
54
56
|
InvitationEncoder.decode(state.unredeemedHaloInvitationCode)
|
|
55
57
|
);
|
|
56
58
|
nextState.unredeemedHaloInvitationCode = undefined;
|
|
@@ -120,13 +122,14 @@ export const JoinPanel = ({
|
|
|
120
122
|
nextState[action.from === 'halo' ? 'haloViewState' : 'spaceViewState'] = 'invitation input';
|
|
121
123
|
break;
|
|
122
124
|
}
|
|
123
|
-
log
|
|
125
|
+
log('[join panel reducer]', { action, nextState });
|
|
124
126
|
return nextState;
|
|
125
127
|
};
|
|
126
128
|
|
|
129
|
+
const unredeemedHaloInvitationCode = mode === 'halo-only' ? initialInvitationCode : undefined;
|
|
127
130
|
const [joinState, dispatch] = useReducer(reducer, {
|
|
128
|
-
unredeemedHaloInvitationCode
|
|
129
|
-
unredeemedSpaceInvitationCode: initialInvitationCode,
|
|
131
|
+
unredeemedHaloInvitationCode,
|
|
132
|
+
unredeemedSpaceInvitationCode: mode === 'halo-only' ? undefined : initialInvitationCode,
|
|
130
133
|
spaceInvitation: undefined,
|
|
131
134
|
haloInvitation: undefined,
|
|
132
135
|
activeView: availableIdentities.length > 0 ? 'identity selector' : 'addition method selector',
|
|
@@ -136,6 +139,17 @@ export const JoinPanel = ({
|
|
|
136
139
|
haloViewState: 'invitation input'
|
|
137
140
|
});
|
|
138
141
|
|
|
142
|
+
if (identity !== prevIdentity) {
|
|
143
|
+
setPrevIdentity(identity);
|
|
144
|
+
identity && !prevIdentity && dispatch({ type: 'added identity', identity });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
if (unredeemedHaloInvitationCode) {
|
|
149
|
+
dispatch({ type: 'select addition method', method: 'accept device invitation' });
|
|
150
|
+
}
|
|
151
|
+
}, [unredeemedHaloInvitationCode]);
|
|
152
|
+
|
|
139
153
|
useEffect(() => {
|
|
140
154
|
// TODO (thure): Validate if this is sufficiently synchronous for iOS to move focus. It might not be!
|
|
141
155
|
const attrValue =
|
|
@@ -146,7 +160,7 @@ export const JoinPanel = ({
|
|
|
146
160
|
: joinState.activeView === 'halo invitation acceptor'
|
|
147
161
|
? `${joinState.activeView}; ${joinState.haloViewState}`
|
|
148
162
|
: joinState.activeView;
|
|
149
|
-
log
|
|
163
|
+
log('[autofocus value]', { attrValue });
|
|
150
164
|
const $nextAutofocus: HTMLElement | null = document.querySelector(`[data-autofocus="${attrValue}"]`);
|
|
151
165
|
if ($nextAutofocus) {
|
|
152
166
|
$nextAutofocus.focus();
|
|
@@ -177,7 +191,9 @@ export const JoinPanel = ({
|
|
|
177
191
|
|
|
178
192
|
return (
|
|
179
193
|
<>
|
|
180
|
-
<JoinHeading
|
|
194
|
+
<JoinHeading
|
|
195
|
+
{...{ mode, titleId, invitation: joinState.spaceInvitation, onExit, exitActionParent, preventExit }}
|
|
196
|
+
/>
|
|
181
197
|
<div role='none' className='is-full overflow-hidden'>
|
|
182
198
|
<div role='none' className='flex is-[1300%]' aria-live='polite'>
|
|
183
199
|
<IdentitySelector
|
|
@@ -240,14 +256,19 @@ export const JoinPanel = ({
|
|
|
240
256
|
active:
|
|
241
257
|
joinState.activeView === 'halo invitation acceptor' &&
|
|
242
258
|
joinState.haloViewState === 'invitation accepted',
|
|
243
|
-
invitationType: 'halo'
|
|
259
|
+
invitationType: 'halo',
|
|
260
|
+
doneActionParent,
|
|
261
|
+
onDone
|
|
244
262
|
}}
|
|
245
263
|
/>
|
|
246
264
|
<IdentityAdded
|
|
247
265
|
{...{
|
|
266
|
+
mode,
|
|
248
267
|
dispatch,
|
|
249
268
|
addedIdentity: joinState.selectedIdentity,
|
|
250
|
-
active: joinState.activeView === 'identity added'
|
|
269
|
+
active: joinState.activeView === 'identity added',
|
|
270
|
+
doneActionParent,
|
|
271
|
+
onDone
|
|
251
272
|
}}
|
|
252
273
|
/>
|
|
253
274
|
<InvitationInput
|
|
@@ -4,22 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
import { cloneElement, Dispatch } from 'react';
|
|
6
6
|
|
|
7
|
-
import type {
|
|
8
|
-
AuthenticatingInvitationObservable,
|
|
9
|
-
Profile as NaturalProfile,
|
|
10
|
-
Space as NaturalSpace
|
|
11
|
-
} from '@dxos/client';
|
|
7
|
+
import type { AuthenticatingInvitationObservable, Profile } from '@dxos/client';
|
|
12
8
|
import { InvitationResult } from '@dxos/react-client';
|
|
13
9
|
|
|
14
|
-
export type
|
|
15
|
-
|
|
16
|
-
export type Profile = Pick<NaturalProfile, 'displayName' | 'identityKey'>;
|
|
10
|
+
export type JoinPanelMode = 'default' | 'halo-only';
|
|
17
11
|
|
|
18
12
|
export interface JoinPanelProps {
|
|
13
|
+
mode?: JoinPanelMode;
|
|
19
14
|
initialInvitationCode?: string;
|
|
20
15
|
titleId?: string;
|
|
21
16
|
exitActionParent?: Parameters<typeof cloneElement>[0];
|
|
22
17
|
onExit?: () => void;
|
|
18
|
+
preventExit?: boolean;
|
|
23
19
|
doneActionParent?: Parameters<typeof cloneElement>[0];
|
|
24
20
|
onDone?: (result: InvitationResult | null) => void;
|
|
25
21
|
parseInvitationCodeInput?: (invitationCodeInput: string) => string;
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
import { CaretLeft, CaretRight, Plus, QrCode, Textbox } from 'phosphor-react';
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
8
|
+
import type { Profile } from '@dxos/client';
|
|
8
9
|
import { Button, CompoundButton, getSize, mx, useTranslation } from '@dxos/react-components';
|
|
9
10
|
|
|
10
|
-
import { Profile } from '../JoinPanelProps';
|
|
11
11
|
import { ViewState, ViewStateHeading, ViewStateProps } from './ViewState';
|
|
12
12
|
|
|
13
13
|
export interface AdditionMethodSelectorProps extends ViewStateProps {
|
|
@@ -36,6 +36,7 @@ export const AdditionMethodSelector = ({ availableIdentities, ...viewStateProps
|
|
|
36
36
|
before={<Plus className={getSize(6)} />}
|
|
37
37
|
onClick={() => dispatch({ type: 'select addition method', method: 'create identity' })}
|
|
38
38
|
data-autofocus='addition method selector'
|
|
39
|
+
data-testid='create-identity'
|
|
39
40
|
>
|
|
40
41
|
{t('create identity label')}
|
|
41
42
|
</CompoundButton>
|
|
@@ -44,6 +45,7 @@ export const AdditionMethodSelector = ({ availableIdentities, ...viewStateProps
|
|
|
44
45
|
description={t('join identity description')}
|
|
45
46
|
before={<QrCode className={getSize(6)} />}
|
|
46
47
|
onClick={() => dispatch({ type: 'select addition method', method: 'accept device invitation' })}
|
|
48
|
+
data-testid='join-identity'
|
|
47
49
|
>
|
|
48
50
|
{t('join identity label')}
|
|
49
51
|
</CompoundButton>
|
|
@@ -52,6 +54,7 @@ export const AdditionMethodSelector = ({ availableIdentities, ...viewStateProps
|
|
|
52
54
|
description={t('recover identity description')}
|
|
53
55
|
before={<Textbox className={getSize(6)} />}
|
|
54
56
|
onClick={() => dispatch({ type: 'select addition method', method: 'recover identity' })}
|
|
57
|
+
data-testid='recover-identity'
|
|
55
58
|
>
|
|
56
59
|
{t('recover identity label')}
|
|
57
60
|
</CompoundButton>
|
|
@@ -59,6 +62,7 @@ export const AdditionMethodSelector = ({ availableIdentities, ...viewStateProps
|
|
|
59
62
|
<Button
|
|
60
63
|
disabled={disabled || availableIdentities.length < 1}
|
|
61
64
|
onClick={() => dispatch({ type: 'deselect identity' })}
|
|
65
|
+
data-testid='deselect-identity'
|
|
62
66
|
>
|
|
63
67
|
<CaretLeft className={getSize(4)} weight='bold' />
|
|
64
68
|
<span className='grow'>{t('deselect identity label')}</span>
|
|
@@ -2,19 +2,54 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { CaretLeft, CaretRight } from 'phosphor-react';
|
|
6
|
-
import React from 'react';
|
|
5
|
+
import { CaretLeft, CaretRight, Check } from 'phosphor-react';
|
|
6
|
+
import React, { cloneElement } from 'react';
|
|
7
7
|
|
|
8
8
|
import type { Profile } from '@dxos/client';
|
|
9
|
+
import { InvitationResult } from '@dxos/react-client';
|
|
9
10
|
import { Avatar, Button, getSize, mx, useTranslation } from '@dxos/react-components';
|
|
10
11
|
|
|
12
|
+
import { JoinPanelMode } from '../JoinPanelProps';
|
|
11
13
|
import { ViewState, ViewStateHeading, ViewStateProps } from './ViewState';
|
|
12
14
|
|
|
13
|
-
export interface IdentityAddedProps extends ViewStateProps {
|
|
15
|
+
export interface IdentityAddedProps extends ViewStateProps, DoneProps {
|
|
16
|
+
mode?: JoinPanelMode;
|
|
14
17
|
addedIdentity?: Profile;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
|
-
export
|
|
20
|
+
export interface DoneProps extends ViewStateProps {
|
|
21
|
+
doneActionParent?: Parameters<typeof cloneElement>[0];
|
|
22
|
+
onDone?: (result: InvitationResult | null) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const Done = ({ onDone, doneActionParent, active }: DoneProps) => {
|
|
26
|
+
const disabled = !active;
|
|
27
|
+
const { t } = useTranslation('os');
|
|
28
|
+
|
|
29
|
+
const doneButton = (
|
|
30
|
+
<Button
|
|
31
|
+
{...(onDone && { onClick: () => onDone(null) })}
|
|
32
|
+
disabled={disabled}
|
|
33
|
+
className='grow flex items-center gap-2 pli-2'
|
|
34
|
+
data-autofocus='identity added'
|
|
35
|
+
data-testid='identity-added-done'
|
|
36
|
+
>
|
|
37
|
+
<CaretLeft weight='bold' className={mx(getSize(2), 'invisible')} />
|
|
38
|
+
<span className='grow'>{t('done label')}</span>
|
|
39
|
+
<Check weight='bold' className={getSize(4)} />
|
|
40
|
+
</Button>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return doneActionParent ? cloneElement(doneActionParent, {}, doneButton) : doneButton;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const IdentityAdded = ({
|
|
47
|
+
mode,
|
|
48
|
+
addedIdentity,
|
|
49
|
+
onDone,
|
|
50
|
+
doneActionParent,
|
|
51
|
+
...viewStateProps
|
|
52
|
+
}: IdentityAddedProps) => {
|
|
18
53
|
const disabled = !viewStateProps.active;
|
|
19
54
|
const { dispatch } = viewStateProps;
|
|
20
55
|
const { t } = useTranslation('os');
|
|
@@ -35,26 +70,30 @@ export const IdentityAdded = ({ addedIdentity, ...viewStateProps }: IdentityAdde
|
|
|
35
70
|
status='active'
|
|
36
71
|
/>
|
|
37
72
|
</div>
|
|
38
|
-
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
73
|
+
{mode === 'halo-only' ? (
|
|
74
|
+
<Done onDone={onDone} doneActionParent={doneActionParent} {...viewStateProps} />
|
|
75
|
+
) : (
|
|
76
|
+
<div className='flex gap-2'>
|
|
77
|
+
<Button
|
|
78
|
+
disabled={disabled || !addedIdentity}
|
|
79
|
+
className='grow flex items-center gap-2 pli-2 order-2'
|
|
80
|
+
onClick={() => addedIdentity && dispatch({ type: 'select identity', identity: addedIdentity })}
|
|
81
|
+
data-autofocus='identity added'
|
|
82
|
+
>
|
|
83
|
+
<CaretLeft weight='bold' className={mx(getSize(2), 'invisible')} />
|
|
84
|
+
<span className='grow'>{t('continue label')}</span>
|
|
85
|
+
<CaretRight weight='bold' className={getSize(4)} />
|
|
86
|
+
</Button>
|
|
87
|
+
<Button
|
|
88
|
+
disabled={disabled}
|
|
89
|
+
onClick={() => dispatch({ type: 'deselect identity' })}
|
|
90
|
+
className='flex items-center gap-2 pis-2 pie-4'
|
|
91
|
+
>
|
|
92
|
+
<CaretLeft weight='bold' className={getSize(4)} />
|
|
93
|
+
<span>{t('deselect identity label')}</span>
|
|
94
|
+
</Button>
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
58
97
|
</ViewState>
|
|
59
98
|
);
|
|
60
99
|
};
|
|
@@ -50,10 +50,16 @@ export const IdentityInput = ({ method, ...viewStateProps }: IdentityCreatorProp
|
|
|
50
50
|
} as ComponentPropsWithoutRef<'input'>
|
|
51
51
|
}}
|
|
52
52
|
{...(validationMessage.length && { validationValence: 'error', validationMessage })}
|
|
53
|
+
data-testid='identity-input'
|
|
53
54
|
/>
|
|
54
55
|
<div role='none' className='grow' />
|
|
55
56
|
<div className='flex gap-2'>
|
|
56
|
-
<Button
|
|
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
|
+
>
|
|
57
63
|
<CaretLeft weight='bold' className={mx(getSize(2), 'invisible')} />
|
|
58
64
|
<span className='grow'>{t('continue label')}</span>
|
|
59
65
|
<CaretRight weight='bold' className={getSize(4)} />
|
|
@@ -62,6 +68,7 @@ export const IdentityInput = ({ method, ...viewStateProps }: IdentityCreatorProp
|
|
|
62
68
|
disabled={disabled}
|
|
63
69
|
onClick={() => dispatch({ type: 'add identity' })}
|
|
64
70
|
className='flex items-center gap-2 pis-2 pie-4'
|
|
71
|
+
data-testid={`${method === 'recover identity' ? 'recover' : 'create'}-identity-input-back`}
|
|
65
72
|
>
|
|
66
73
|
<CaretLeft weight='bold' className={getSize(4)} />
|
|
67
74
|
<span>{t('back label')}</span>
|
|
@@ -6,9 +6,9 @@ import * as RadioGroup from '@radix-ui/react-radio-group';
|
|
|
6
6
|
import { CaretLeft, CaretRight, UserPlus } from 'phosphor-react';
|
|
7
7
|
import React, { useCallback, useState } from 'react';
|
|
8
8
|
|
|
9
|
+
import type { Profile } from '@dxos/client';
|
|
9
10
|
import { Avatar, Button, themeVariantFocus, getSize, mx, useTranslation } from '@dxos/react-components';
|
|
10
11
|
|
|
11
|
-
import { Profile } from '../JoinPanelProps';
|
|
12
12
|
import { ViewState, ViewStateHeading, ViewStateProps } from './ViewState';
|
|
13
13
|
|
|
14
14
|
export interface IdentitySelectorProps extends ViewStateProps {
|
|
@@ -57,6 +57,7 @@ export const IdentitySelector = ({ availableIdentities, ...viewStateProps }: Ide
|
|
|
57
57
|
'radix-state-unchecked:bg-neutral-100 dark:radix-state-unchecked:bg-neutral-900',
|
|
58
58
|
themeVariantFocus('os')
|
|
59
59
|
)}
|
|
60
|
+
data-testid='identity-selector-item'
|
|
60
61
|
>
|
|
61
62
|
<RadioGroup.Indicator className='absolute inset-0 flex items-center justify-center leading-0'>
|
|
62
63
|
<div className='w-1.5 h-1.5 rounded-full bg-white' />
|
|
@@ -75,6 +76,7 @@ export const IdentitySelector = ({ availableIdentities, ...viewStateProps }: Ide
|
|
|
75
76
|
compact
|
|
76
77
|
onClick={() => dispatch({ type: 'add identity' })}
|
|
77
78
|
className='flex items-center gap-2 pli-2'
|
|
79
|
+
data-testid='add-identity'
|
|
78
80
|
>
|
|
79
81
|
<span>{t('add identity label')}</span>
|
|
80
82
|
<UserPlus weight='bold' className={getSize(3.5)} />
|
|
@@ -88,6 +90,7 @@ export const IdentitySelector = ({ availableIdentities, ...viewStateProps }: Ide
|
|
|
88
90
|
identity: activeIdentity
|
|
89
91
|
})
|
|
90
92
|
}
|
|
93
|
+
data-testid='select-identity'
|
|
91
94
|
>
|
|
92
95
|
<CaretLeft weight='bold' className={mx(getSize(4), 'invisible')} />
|
|
93
96
|
<span className='grow'>{t('continue label')}</span>
|
|
@@ -33,6 +33,7 @@ const PureInvitationAcceptedContent = ({
|
|
|
33
33
|
disabled={disabled}
|
|
34
34
|
className='grow flex items-center gap-2 pli-2'
|
|
35
35
|
data-autofocus={`${invitationType} invitation acceptor; invitation accepted`}
|
|
36
|
+
data-testid={`${invitationType}-invitation-accepted-done`}
|
|
36
37
|
>
|
|
37
38
|
<CaretLeft weight='bold' className={mx(getSize(2), 'invisible')} />
|
|
38
39
|
<span className='grow'>{t('done label')}</span>
|
|
@@ -37,7 +37,7 @@ const PureInvitationAuthenticatorContent = ({
|
|
|
37
37
|
return (
|
|
38
38
|
<>
|
|
39
39
|
<Input
|
|
40
|
-
label={t('
|
|
40
|
+
label={t('auth code input label')}
|
|
41
41
|
size='pin'
|
|
42
42
|
length={pinLength}
|
|
43
43
|
onChange={onChange}
|
|
@@ -57,6 +57,7 @@ const PureInvitationAuthenticatorContent = ({
|
|
|
57
57
|
validationValence: 'error',
|
|
58
58
|
validationMessage: t('failed to authenticate message')
|
|
59
59
|
})}
|
|
60
|
+
data-testid='auth-code-input'
|
|
60
61
|
/>
|
|
61
62
|
<div role='none' className='grow' />
|
|
62
63
|
<div className='flex gap-2'>
|
|
@@ -65,6 +66,7 @@ const PureInvitationAuthenticatorContent = ({
|
|
|
65
66
|
className='grow flex items-center gap-2 pli-2 order-2'
|
|
66
67
|
onClick={onAuthenticate}
|
|
67
68
|
data-autofocus-pinlength={invitationType}
|
|
69
|
+
data-testid={`${invitationType}-invitation-authenticator-next`}
|
|
68
70
|
>
|
|
69
71
|
<CaretLeft weight='bold' className={mx(getSize(2), 'invisible')} />
|
|
70
72
|
<span className='grow'>{t('next label')}</span>
|
|
@@ -74,6 +76,7 @@ const PureInvitationAuthenticatorContent = ({
|
|
|
74
76
|
disabled={disabled}
|
|
75
77
|
className='flex items-center gap-2 pis-2 pie-4'
|
|
76
78
|
onClick={() => dispatch({ type: 'cancel invitation', from: invitationType })}
|
|
79
|
+
data-testid={`${invitationType}-invitation-authenticator-cancel`}
|
|
77
80
|
>
|
|
78
81
|
<CaretLeft weight='bold' className={getSize(4)} />
|
|
79
82
|
<span>{t('cancel label')}</span>
|
|
@@ -46,10 +46,16 @@ export const InvitationInput = ({ invitationType, ...viewStateProps }: Invitatio
|
|
|
46
46
|
onKeyUp: ({ key }) => key === 'Enter' && handleNext()
|
|
47
47
|
} as ComponentPropsWithoutRef<'input'>
|
|
48
48
|
}}
|
|
49
|
+
data-testid='invitation-input'
|
|
49
50
|
/>
|
|
50
51
|
<div role='none' className='grow' />
|
|
51
52
|
<div className='flex gap-2'>
|
|
52
|
-
<Button
|
|
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
|
+
>
|
|
53
59
|
<CaretLeft weight='bold' className={mx(getSize(2), 'invisible')} />
|
|
54
60
|
<span className='grow'>{t('continue label')}</span>
|
|
55
61
|
<CaretRight weight='bold' className={getSize(4)} />
|
|
@@ -58,6 +64,7 @@ export const InvitationInput = ({ invitationType, ...viewStateProps }: Invitatio
|
|
|
58
64
|
disabled={disabled}
|
|
59
65
|
onClick={() => dispatch({ type: 'add identity' })}
|
|
60
66
|
className='flex items-center gap-2 pis-2 pie-4'
|
|
67
|
+
data-testid={`${invitationType}-invitation-input-back`}
|
|
61
68
|
>
|
|
62
69
|
<CaretLeft weight='bold' className={getSize(4)} />
|
|
63
70
|
<span>{t('back label')}</span>
|
|
@@ -35,7 +35,7 @@ const InvitationActions = ({
|
|
|
35
35
|
<>
|
|
36
36
|
<div role='none' className='grow' />
|
|
37
37
|
<div className='flex gap-2'>
|
|
38
|
-
<Button disabled className='grow flex items-center gap-2 pli-2 order-2'>
|
|
38
|
+
<Button disabled className='grow flex items-center gap-2 pli-2 order-2' data-testid='next'>
|
|
39
39
|
<CaretLeft weight='bold' className={mx(getSize(2), 'invisible')} />
|
|
40
40
|
<span className='grow'>{t('next label')}</span>
|
|
41
41
|
<CaretRight weight='bold' className={getSize(4)} />
|
|
@@ -45,6 +45,7 @@ const InvitationActions = ({
|
|
|
45
45
|
className='flex items-center gap-2 pis-2 pie-4'
|
|
46
46
|
onClick={cancel}
|
|
47
47
|
data-autofocus={`${invitationType} invitation acceptor; invitation rescuer`}
|
|
48
|
+
data-testid='invitation-rescuer-cancel'
|
|
48
49
|
>
|
|
49
50
|
<CaretLeft weight='bold' className={getSize(4)} />
|
|
50
51
|
<span>{t('cancel label')}</span>
|
|
@@ -62,6 +63,7 @@ const InvitationActions = ({
|
|
|
62
63
|
className='grow flex items-center gap-2 pli-2'
|
|
63
64
|
onClick={() => dispatch({ type: 'reset invitation', from: invitationType })}
|
|
64
65
|
data-autofocus={`${invitationType} invitation acceptor; invitation rescuer`}
|
|
66
|
+
data-testid='invitation-rescuer-reset'
|
|
65
67
|
>
|
|
66
68
|
<CaretLeft weight='bold' className={mx(getSize(5), 'invisible')} />
|
|
67
69
|
<span className='grow'>{t('reset label')}</span>
|
|
@@ -82,6 +84,7 @@ export const InvitationRescuer = ({ invitationType, ...viewStateProps }: Invitat
|
|
|
82
84
|
disabled={disabled}
|
|
83
85
|
className='grow flex items-center gap-2 pli-2'
|
|
84
86
|
data-autofocus='space invitation acceptor; invitation rescuer'
|
|
87
|
+
data-testid='invitation-rescuer-connect'
|
|
85
88
|
>
|
|
86
89
|
<CaretLeft weight='bold' className={mx(getSize(5), 'invisible')} />
|
|
87
90
|
<span className='grow'>{t('connect label')}</span>
|