@dxos/react-ui 0.1.26 → 0.1.27-next.a0dff21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +1021 -472
- 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 +4 -3
- package/dist/types/src/components/IdentityList/IdentityListItem.d.ts.map +1 -1
- package/dist/types/src/components/IdentityList/IdentityListItem.stories.d.ts +3 -2
- 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/IdentityPopover/IdentityPopover.d.ts +2 -2
- package/dist/types/src/composites/IdentityPopover/IdentityPopover.d.ts.map +1 -1
- 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/IdentityPanel/IdentityPanel.d.ts +2 -2
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.d.ts.map +1 -1
- 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 +6 -5
- package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/view-states/AdditionMethodSelector.d.ts +2 -2
- 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 +12 -4
- 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 +2 -2
- 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/JoinPanel/view-states/ViewState.d.ts +2 -2
- package/dist/types/src/panels/JoinPanel/view-states/ViewState.d.ts.map +1 -1
- package/dist/types/src/panels/SpacePanel/SpacePanel.d.ts +12 -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 +17 -5
- package/src/components/IdentityList/SpaceMemberList.tsx +10 -8
- package/src/components/IdentityList/SpaceMemberListContainer.tsx +7 -6
- package/src/components/IdentityList/index.ts +1 -0
- package/src/components/InvitationList/InvitationList.stories.tsx +1 -1
- package/src/components/InvitationList/InvitationList.tsx +12 -6
- package/src/components/InvitationList/InvitationListItem.tsx +30 -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/IdentityPopover/IdentityPopover.tsx +3 -3
- package/src/composites/JoinDialog/JoinDialog.stories.tsx +59 -58
- package/src/composites/JoinDialog/JoinDialog.tsx +3 -1
- package/src/composites/Shell/Shell.tsx +86 -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 +101 -0
- package/src/panels/DevicesPanel/index.ts +5 -0
- package/src/panels/IdentityPanel/IdentityPanel.tsx +5 -5
- package/src/panels/JoinPanel/JoinHeading.tsx +38 -16
- package/src/panels/JoinPanel/JoinPanel.tsx +35 -14
- package/src/panels/JoinPanel/JoinPanelProps.ts +6 -10
- package/src/panels/JoinPanel/view-states/AdditionMethodSelector.tsx +8 -2
- package/src/panels/JoinPanel/view-states/IdentityAdded.tsx +67 -28
- package/src/panels/JoinPanel/view-states/IdentityInput.tsx +9 -2
- package/src/panels/JoinPanel/view-states/IdentitySelector.tsx +8 -6
- 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/JoinPanel/view-states/ViewState.tsx +3 -3
- package/src/panels/SpacePanel/SpacePanel.stories.tsx +53 -0
- package/src/panels/SpacePanel/SpacePanel.tsx +90 -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,39 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Dialog from '@radix-ui/react-dialog';
|
|
6
|
+
import React, { ComponentPropsWithoutRef, PropsWithChildren } from 'react';
|
|
7
|
+
|
|
8
|
+
import { mx } from '@dxos/react-components';
|
|
9
|
+
|
|
10
|
+
import { defaultDialogContent, defaultOverlay } from '../../styles';
|
|
11
|
+
|
|
12
|
+
export interface PanelDialogSlots {
|
|
13
|
+
root: ComponentPropsWithoutRef<typeof Dialog.Root>;
|
|
14
|
+
overlay: ComponentPropsWithoutRef<typeof Dialog.Overlay>;
|
|
15
|
+
content: ComponentPropsWithoutRef<typeof Dialog.Content>;
|
|
16
|
+
trigger: ComponentPropsWithoutRef<typeof Dialog.Trigger>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type PanelDialogProps = PropsWithChildren<{
|
|
20
|
+
titleId: string;
|
|
21
|
+
slots?: Partial<PanelDialogSlots>;
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
export const PanelDialog = ({ titleId, slots = {}, children }: PanelDialogProps) => {
|
|
25
|
+
return (
|
|
26
|
+
<Dialog.Root defaultOpen {...slots.root}>
|
|
27
|
+
{slots.trigger && <Dialog.Trigger {...slots.trigger} />}
|
|
28
|
+
<Dialog.Overlay {...slots.overlay} className={mx(defaultOverlay, 'z-40', slots.overlay?.className)}>
|
|
29
|
+
<Dialog.Content
|
|
30
|
+
{...slots.content}
|
|
31
|
+
aria-labelledby={titleId}
|
|
32
|
+
className={mx(defaultDialogContent, slots.content?.className)}
|
|
33
|
+
>
|
|
34
|
+
{children}
|
|
35
|
+
</Dialog.Content>
|
|
36
|
+
</Dialog.Overlay>
|
|
37
|
+
</Dialog.Root>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -82,7 +82,7 @@ export const PanelSidebarProvider = ({ children, slots }: PropsWithChildren<Pane
|
|
|
82
82
|
onCloseAutoFocus={(event) => isLg && event.preventDefault()}
|
|
83
83
|
{...slots?.content}
|
|
84
84
|
className={mx(
|
|
85
|
-
'fixed block-start-0 block-end-0 is-sidebar z-
|
|
85
|
+
'fixed block-start-0 block-end-0 is-sidebar z-30 overscroll-contain overflow-x-hidden overflow-y-auto',
|
|
86
86
|
'transition-[inset-inline-start,inset-inline-end] duration-200 ease-in-out',
|
|
87
87
|
transitionShow ? 'inline-start-0' : '-inline-start-sidebar',
|
|
88
88
|
slots?.content?.className
|
package/src/layouts/index.ts
CHANGED
|
@@ -0,0 +1,101 @@
|
|
|
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, DensityProvider, 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?.profile?.displayName;
|
|
30
|
+
|
|
31
|
+
if (!identity) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const doneButton = (
|
|
36
|
+
<Button 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
|
+
onClick={() => client.halo.createInvitation()}
|
|
61
|
+
data-testid='create-space-invitation'
|
|
62
|
+
>
|
|
63
|
+
<span>{t('create space invitation label')}</span>
|
|
64
|
+
<UserPlus className={getSize(4)} weight='bold' />
|
|
65
|
+
</Button>
|
|
66
|
+
<PanelSeparator />
|
|
67
|
+
<DeviceList devices={devices} />
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
type DevicesPanelState = {
|
|
74
|
+
activeView: DevicesView;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
type DevicesPanelAction = {
|
|
78
|
+
type: null;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// TODO(wittjosiah): Rename identity panel and include other views?
|
|
82
|
+
export const DevicesPanel = (props: DevicesPanelProps) => {
|
|
83
|
+
const reducer = (state: DevicesPanelState, action: DevicesPanelAction) => {
|
|
84
|
+
const nextState = { ...state };
|
|
85
|
+
switch (action.type) {
|
|
86
|
+
case null:
|
|
87
|
+
}
|
|
88
|
+
return nextState;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const [panelState] = useReducer(reducer, {
|
|
92
|
+
activeView: 'device list'
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// TODO(wittjosiah): Use ViewState or similar.
|
|
96
|
+
return (
|
|
97
|
+
<DensityProvider density='fine'>
|
|
98
|
+
{panelState.activeView === 'device list' ? <DeviceListView {...props} /> : null}
|
|
99
|
+
</DensityProvider>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
import React from 'react';
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type { Identity } from '@dxos/client';
|
|
7
7
|
import { useClient } from '@dxos/react-client';
|
|
8
8
|
import { Avatar, Button, ThemeContext, useTranslation } from '@dxos/react-components';
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@ export const IdentityPanel = ({
|
|
|
11
11
|
identity,
|
|
12
12
|
onClickManageProfile
|
|
13
13
|
}: {
|
|
14
|
-
identity:
|
|
14
|
+
identity: Identity;
|
|
15
15
|
onClickManageProfile?: () => void;
|
|
16
16
|
}) => {
|
|
17
17
|
const { t } = useTranslation('os');
|
|
@@ -22,15 +22,15 @@ export const IdentityPanel = ({
|
|
|
22
22
|
tab?.focus();
|
|
23
23
|
};
|
|
24
24
|
return (
|
|
25
|
-
<ThemeContext.Provider value={{ themeVariant: 'os' }}>
|
|
25
|
+
<ThemeContext.Provider value={{ themeVariant: 'os', rootDensity: 'fine' }}>
|
|
26
26
|
<div className='flex flex-col gap-2 justify-center items-center'>
|
|
27
27
|
<Avatar
|
|
28
28
|
size={16}
|
|
29
29
|
variant='circle'
|
|
30
30
|
fallbackValue={identity.identityKey.toHex()}
|
|
31
|
-
label={identity.displayName ?? ''}
|
|
31
|
+
label={identity.profile?.displayName ?? ''}
|
|
32
32
|
/>
|
|
33
|
-
<Button
|
|
33
|
+
<Button onClick={onClickManageProfile ?? defaultManageProfile} className='is-full'>
|
|
34
34
|
{t('manage profile label')}
|
|
35
35
|
</Button>
|
|
36
36
|
</div>
|
|
@@ -9,25 +9,35 @@ 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 variant='ghost' {...(onExit && { onClick: onExit })} className='grow-0 shrink-0' data-testid='join-exit'>
|
|
31
41
|
<ProhibitInset className={getSize(5)} />
|
|
32
42
|
<span className='sr-only'>{t('exit label')}</span>
|
|
33
43
|
</Button>
|
|
@@ -36,20 +46,32 @@ export const JoinHeading = forwardRef(
|
|
|
36
46
|
return (
|
|
37
47
|
<div role='none' className={mx(subduedSurface, 'p-2 rounded-bs-md')} ref={ref}>
|
|
38
48
|
<div role='group' className='flex items-center gap-2'>
|
|
39
|
-
|
|
49
|
+
{invitationKey ? (
|
|
50
|
+
<Avatar fallbackValue={invitationKey} labelId={nameId} />
|
|
51
|
+
) : (
|
|
52
|
+
<span role='none' className={mx(getSize(10), 'bg-neutral-300 dark:bg-neutral-700 rounded-full')} />
|
|
53
|
+
)}
|
|
40
54
|
<Heading level={1} className='font-body font-normal text-base grow' id={titleId}>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
{invitation ? (
|
|
56
|
+
<Trans
|
|
57
|
+
{...{
|
|
58
|
+
defaults: t('joining heading'),
|
|
59
|
+
components: {
|
|
60
|
+
small: <span className='block leading-none mbe-1 font-system-medium text-sm' />,
|
|
61
|
+
large: <span className='block leading-none' id={nameId} />
|
|
62
|
+
},
|
|
63
|
+
values: { name }
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
) : (
|
|
67
|
+
<span className='block leading-none font-system-medium text-sm'>
|
|
68
|
+
{mode === 'halo-only' ? t('halo heading') : t('join space heading')}
|
|
69
|
+
</span>
|
|
70
|
+
)}
|
|
51
71
|
</Heading>
|
|
52
|
-
{
|
|
72
|
+
{!preventExit &&
|
|
73
|
+
mode !== 'halo-only' &&
|
|
74
|
+
(exitActionParent ? cloneElement(exitActionParent, {}, exitButton) : exitButton)}
|
|
53
75
|
</div>
|
|
54
76
|
</div>
|
|
55
77
|
);
|
|
@@ -1,12 +1,12 @@
|
|
|
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';
|
|
8
8
|
import { useClient, useIdentity } from '@dxos/react-client';
|
|
9
|
-
import { useId } from '@dxos/react-components';
|
|
9
|
+
import { DensityProvider, useId } from '@dxos/react-components';
|
|
10
10
|
|
|
11
11
|
import { JoinHeading } from './JoinHeading';
|
|
12
12
|
import { JoinAction, JoinPanelProps, JoinState } from './JoinPanelProps';
|
|
@@ -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();
|
|
@@ -176,8 +190,10 @@ export const JoinPanel = ({
|
|
|
176
190
|
}, [joinState.haloInvitation]);
|
|
177
191
|
|
|
178
192
|
return (
|
|
179
|
-
|
|
180
|
-
<JoinHeading
|
|
193
|
+
<DensityProvider density='fine'>
|
|
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
|
|
@@ -298,6 +319,6 @@ export const JoinPanel = ({
|
|
|
298
319
|
/>
|
|
299
320
|
</div>
|
|
300
321
|
</div>
|
|
301
|
-
|
|
322
|
+
</DensityProvider>
|
|
302
323
|
);
|
|
303
324
|
};
|
|
@@ -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, Identity } 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;
|
|
@@ -27,7 +23,7 @@ export interface JoinPanelProps {
|
|
|
27
23
|
|
|
28
24
|
export interface IdentityAction {
|
|
29
25
|
type: 'select identity' | 'added identity';
|
|
30
|
-
identity:
|
|
26
|
+
identity: Identity;
|
|
31
27
|
}
|
|
32
28
|
|
|
33
29
|
export interface EmptyJoinAction {
|
|
@@ -89,6 +85,6 @@ export interface JoinState {
|
|
|
89
85
|
haloInvitation?: AuthenticatingInvitationObservable;
|
|
90
86
|
haloViewState: InvitationView;
|
|
91
87
|
haloInvitationAnnotation?: 'authenticating' | 'authentication failed';
|
|
92
|
-
selectedIdentity?:
|
|
88
|
+
selectedIdentity?: Identity;
|
|
93
89
|
additionMethod?: AdditionMethodAction['method'];
|
|
94
90
|
}
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
import { CaretLeft, CaretRight, Plus, QrCode, Textbox } from 'phosphor-react';
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
8
|
+
import type { Identity } 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 {
|
|
14
|
-
availableIdentities:
|
|
14
|
+
availableIdentities: Identity[];
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export const AdditionMethodSelector = ({ availableIdentities, ...viewStateProps }: AdditionMethodSelectorProps) => {
|
|
@@ -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,14 +45,18 @@ 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>
|
|
50
52
|
<CompoundButton
|
|
51
53
|
{...sharedButtonProps}
|
|
54
|
+
// TODO(mykola): Implement recover.
|
|
55
|
+
disabled={true}
|
|
52
56
|
description={t('recover identity description')}
|
|
53
57
|
before={<Textbox className={getSize(6)} />}
|
|
54
58
|
onClick={() => dispatch({ type: 'select addition method', method: 'recover identity' })}
|
|
59
|
+
data-testid='recover-identity'
|
|
55
60
|
>
|
|
56
61
|
{t('recover identity label')}
|
|
57
62
|
</CompoundButton>
|
|
@@ -59,6 +64,7 @@ export const AdditionMethodSelector = ({ availableIdentities, ...viewStateProps
|
|
|
59
64
|
<Button
|
|
60
65
|
disabled={disabled || availableIdentities.length < 1}
|
|
61
66
|
onClick={() => dispatch({ type: 'deselect identity' })}
|
|
67
|
+
data-testid='deselect-identity'
|
|
62
68
|
>
|
|
63
69
|
<CaretLeft className={getSize(4)} weight='bold' />
|
|
64
70
|
<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
|
-
import type {
|
|
8
|
+
import type { Identity } 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 {
|
|
14
|
-
|
|
15
|
+
export interface IdentityAddedProps extends ViewStateProps, DoneProps {
|
|
16
|
+
mode?: JoinPanelMode;
|
|
17
|
+
addedIdentity?: Identity;
|
|
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');
|
|
@@ -27,34 +62,38 @@ export const IdentityAdded = ({ addedIdentity, ...viewStateProps }: IdentityAdde
|
|
|
27
62
|
size={20}
|
|
28
63
|
fallbackValue={addedIdentity?.identityKey.toHex() ?? ''}
|
|
29
64
|
label={
|
|
30
|
-
<p className={mx('text-lg', !addedIdentity?.displayName && 'font-mono')}>
|
|
31
|
-
{addedIdentity?.displayName ?? addedIdentity?.identityKey.truncate() ?? ' '}
|
|
65
|
+
<p className={mx('text-lg', !addedIdentity?.profile?.displayName && 'font-mono')}>
|
|
66
|
+
{addedIdentity?.profile?.displayName ?? addedIdentity?.identityKey.truncate() ?? ' '}
|
|
32
67
|
</p>
|
|
33
68
|
}
|
|
34
69
|
variant='circle'
|
|
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
|
};
|