@dxos/react-ui 0.1.23 → 0.1.24

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 (147) hide show
  1. package/dist/lib/browser/index.mjs +1781 -57
  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/{IdentityListItem → IdentityList}/IdentityListItem.d.ts +0 -0
  5. package/dist/types/src/components/IdentityList/IdentityListItem.d.ts.map +1 -0
  6. package/dist/types/src/components/{IdentityListItem → IdentityList}/IdentityListItem.stories.d.ts +0 -0
  7. package/dist/types/src/components/IdentityList/IdentityListItem.stories.d.ts.map +1 -0
  8. package/dist/types/src/components/IdentityList/SpaceMemberList.d.ts +6 -0
  9. package/dist/types/src/components/IdentityList/SpaceMemberList.d.ts.map +1 -0
  10. package/dist/types/src/components/IdentityList/SpaceMemberListContainer.d.ts +7 -0
  11. package/dist/types/src/components/IdentityList/SpaceMemberListContainer.d.ts.map +1 -0
  12. package/dist/types/src/components/IdentityList/index.d.ts +4 -0
  13. package/dist/types/src/components/IdentityList/index.d.ts.map +1 -0
  14. package/dist/types/src/components/InvitationList/InvitationList.d.ts +7 -0
  15. package/dist/types/src/components/InvitationList/InvitationList.d.ts.map +1 -0
  16. package/dist/types/src/components/InvitationList/InvitationList.stories.d.ts +10 -0
  17. package/dist/types/src/components/InvitationList/InvitationList.stories.d.ts.map +1 -0
  18. package/dist/types/src/components/InvitationList/InvitationListContainer.d.ts +7 -0
  19. package/dist/types/src/components/InvitationList/InvitationListContainer.d.ts.map +1 -0
  20. package/dist/types/src/components/InvitationList/InvitationListItem.d.ts +9 -0
  21. package/dist/types/src/components/InvitationList/InvitationListItem.d.ts.map +1 -0
  22. package/dist/types/src/components/InvitationList/InvitationListProps.d.ts +4 -0
  23. package/dist/types/src/components/InvitationList/InvitationListProps.d.ts.map +1 -0
  24. package/dist/types/src/components/InvitationList/InvitationStatusAvatar.d.ts +9 -0
  25. package/dist/types/src/components/InvitationList/InvitationStatusAvatar.d.ts.map +1 -0
  26. package/dist/types/src/components/InvitationList/InvitationStatusAvatar.stories.d.ts +9 -0
  27. package/dist/types/src/components/InvitationList/InvitationStatusAvatar.stories.d.ts.map +1 -0
  28. package/dist/types/src/components/InvitationList/index.d.ts +5 -0
  29. package/dist/types/src/components/InvitationList/index.d.ts.map +1 -0
  30. package/dist/types/src/components/PanelSeparator/PanelSeparator.d.ts +3 -0
  31. package/dist/types/src/components/PanelSeparator/PanelSeparator.d.ts.map +1 -0
  32. package/dist/types/src/components/PanelSeparator/index.d.ts +2 -0
  33. package/dist/types/src/components/PanelSeparator/index.d.ts.map +1 -0
  34. package/dist/types/src/components/index.d.ts +4 -0
  35. package/dist/types/src/components/index.d.ts.map +1 -0
  36. package/dist/types/src/composites/JoinDialog/JoinDialog.d.ts +6 -0
  37. package/dist/types/src/composites/JoinDialog/JoinDialog.d.ts.map +1 -0
  38. package/dist/types/src/composites/JoinDialog/JoinDialog.stories.d.ts +10 -0
  39. package/dist/types/src/composites/JoinDialog/JoinDialog.stories.d.ts.map +1 -0
  40. package/dist/types/src/composites/JoinDialog/index.d.ts +2 -0
  41. package/dist/types/src/composites/JoinDialog/index.d.ts.map +1 -0
  42. package/dist/types/src/composites/index.d.ts +1 -0
  43. package/dist/types/src/composites/index.d.ts.map +1 -1
  44. package/dist/types/src/index.d.ts +1 -0
  45. package/dist/types/src/index.d.ts.map +1 -1
  46. package/dist/types/src/layouts/PanelAlertDialog/PanelAlertDialog.d.ts +14 -0
  47. package/dist/types/src/layouts/PanelAlertDialog/PanelAlertDialog.d.ts.map +1 -0
  48. package/dist/types/src/layouts/PanelAlertDialog/index.d.ts +2 -0
  49. package/dist/types/src/layouts/PanelAlertDialog/index.d.ts.map +1 -0
  50. package/dist/types/src/layouts/PanelSidebar/PanelSidebar.d.ts +2 -3
  51. package/dist/types/src/layouts/PanelSidebar/PanelSidebar.d.ts.map +1 -1
  52. package/dist/types/src/layouts/index.d.ts +1 -0
  53. package/dist/types/src/layouts/index.d.ts.map +1 -1
  54. package/dist/types/src/panels/JoinPanel/JoinHeading.d.ts +10 -0
  55. package/dist/types/src/panels/JoinPanel/JoinHeading.d.ts.map +1 -0
  56. package/dist/types/src/panels/JoinPanel/JoinPanel.d.ts +3 -0
  57. package/dist/types/src/panels/JoinPanel/JoinPanel.d.ts.map +1 -0
  58. package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts +53 -0
  59. package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts.map +1 -0
  60. package/dist/types/src/panels/JoinPanel/index.d.ts +3 -0
  61. package/dist/types/src/panels/JoinPanel/index.d.ts.map +1 -0
  62. package/dist/types/src/panels/JoinPanel/view-states/AdditionMethodSelector.d.ts +7 -0
  63. package/dist/types/src/panels/JoinPanel/view-states/AdditionMethodSelector.d.ts.map +1 -0
  64. package/dist/types/src/panels/JoinPanel/view-states/IdentityAdded.d.ts +7 -0
  65. package/dist/types/src/panels/JoinPanel/view-states/IdentityAdded.d.ts.map +1 -0
  66. package/dist/types/src/panels/JoinPanel/view-states/IdentityInput.d.ts +6 -0
  67. package/dist/types/src/panels/JoinPanel/view-states/IdentityInput.d.ts.map +1 -0
  68. package/dist/types/src/panels/JoinPanel/view-states/IdentitySelector.d.ts +7 -0
  69. package/dist/types/src/panels/JoinPanel/view-states/IdentitySelector.d.ts.map +1 -0
  70. package/dist/types/src/panels/JoinPanel/view-states/InvitationAccepted.d.ts +10 -0
  71. package/dist/types/src/panels/JoinPanel/view-states/InvitationAccepted.d.ts.map +1 -0
  72. package/dist/types/src/panels/JoinPanel/view-states/InvitationAuthenticator.d.ts +7 -0
  73. package/dist/types/src/panels/JoinPanel/view-states/InvitationAuthenticator.d.ts.map +1 -0
  74. package/dist/types/src/panels/JoinPanel/view-states/InvitationInput.d.ts +6 -0
  75. package/dist/types/src/panels/JoinPanel/view-states/InvitationInput.d.ts.map +1 -0
  76. package/dist/types/src/panels/JoinPanel/view-states/InvitationRescuer.d.ts +6 -0
  77. package/dist/types/src/panels/JoinPanel/view-states/InvitationRescuer.d.ts.map +1 -0
  78. package/dist/types/src/panels/JoinPanel/view-states/ViewState.d.ts +12 -0
  79. package/dist/types/src/panels/JoinPanel/view-states/ViewState.d.ts.map +1 -0
  80. package/dist/types/src/panels/JoinPanel/view-states/index.d.ts +9 -0
  81. package/dist/types/src/panels/JoinPanel/view-states/index.d.ts.map +1 -0
  82. package/dist/types/src/panels/index.d.ts +1 -0
  83. package/dist/types/src/panels/index.d.ts.map +1 -1
  84. package/dist/types/src/styles/index.d.ts +1 -0
  85. package/dist/types/src/styles/index.d.ts.map +1 -1
  86. package/dist/types/src/styles/invitationStatusStyles.d.ts +20 -0
  87. package/dist/types/src/styles/invitationStatusStyles.d.ts.map +1 -0
  88. package/dist/types/src/styles/panelStyles.d.ts +2 -1
  89. package/dist/types/src/styles/panelStyles.d.ts.map +1 -1
  90. package/dist/types/src/translations/locales/en-US.d.ts +41 -0
  91. package/dist/types/src/translations/locales/en-US.d.ts.map +1 -1
  92. package/dist/types/src/util/index.d.ts +2 -0
  93. package/dist/types/src/util/index.d.ts.map +1 -0
  94. package/dist/types/src/util/invitationStatusValue.d.ts +8 -0
  95. package/dist/types/src/util/invitationStatusValue.d.ts.map +1 -0
  96. package/package.json +18 -11
  97. package/src/components/{IdentityListItem → IdentityList}/IdentityListItem.stories.tsx +0 -0
  98. package/src/components/{IdentityListItem → IdentityList}/IdentityListItem.tsx +4 -3
  99. package/src/components/IdentityList/SpaceMemberList.tsx +31 -0
  100. package/src/components/IdentityList/SpaceMemberListContainer.tsx +27 -0
  101. package/src/components/IdentityList/index.ts +7 -0
  102. package/src/components/InvitationList/InvitationList.stories.tsx +65 -0
  103. package/src/components/InvitationList/InvitationList.tsx +24 -0
  104. package/src/components/InvitationList/InvitationListContainer.tsx +27 -0
  105. package/src/components/InvitationList/InvitationListItem.tsx +95 -0
  106. package/src/components/InvitationList/InvitationListProps.ts +7 -0
  107. package/src/components/InvitationList/InvitationStatusAvatar.stories.tsx +52 -0
  108. package/src/components/InvitationList/InvitationStatusAvatar.tsx +75 -0
  109. package/src/components/InvitationList/index.ts +8 -0
  110. package/src/components/PanelSeparator/PanelSeparator.tsx +17 -0
  111. package/src/components/PanelSeparator/index.ts +5 -0
  112. package/src/components/index.ts +7 -0
  113. package/src/composites/JoinDialog/JoinDialog.stories.tsx +80 -0
  114. package/src/composites/JoinDialog/JoinDialog.tsx +31 -0
  115. package/src/composites/JoinDialog/index.ts +5 -0
  116. package/src/composites/index.ts +1 -0
  117. package/src/index.ts +1 -0
  118. package/src/layouts/PanelAlertDialog/PanelAlertDialog.tsx +44 -0
  119. package/src/layouts/PanelAlertDialog/index.ts +5 -0
  120. package/src/layouts/PanelPopover/PanelPopover.tsx +2 -2
  121. package/src/layouts/PanelSidebar/PanelSidebar.tsx +17 -25
  122. package/src/layouts/index.ts +1 -0
  123. package/src/panels/JoinPanel/JoinHeading.tsx +57 -0
  124. package/src/panels/JoinPanel/JoinPanel.tsx +303 -0
  125. package/src/panels/JoinPanel/JoinPanelProps.ts +94 -0
  126. package/src/panels/JoinPanel/index.ts +6 -0
  127. package/src/panels/JoinPanel/view-states/AdditionMethodSelector.tsx +69 -0
  128. package/src/panels/JoinPanel/view-states/IdentityAdded.tsx +60 -0
  129. package/src/panels/JoinPanel/view-states/IdentityInput.tsx +72 -0
  130. package/src/panels/JoinPanel/view-states/IdentitySelector.tsx +98 -0
  131. package/src/panels/JoinPanel/view-states/InvitationAccepted.tsx +69 -0
  132. package/src/panels/JoinPanel/view-states/InvitationAuthenticator.tsx +139 -0
  133. package/src/panels/JoinPanel/view-states/InvitationInput.tsx +68 -0
  134. package/src/panels/JoinPanel/view-states/InvitationRescuer.tsx +95 -0
  135. package/src/panels/JoinPanel/view-states/ViewState.tsx +204 -0
  136. package/src/panels/JoinPanel/view-states/index.ts +12 -0
  137. package/src/panels/index.ts +1 -0
  138. package/src/styles/index.ts +1 -0
  139. package/src/styles/invitationStatusStyles.ts +42 -0
  140. package/src/styles/panelStyles.ts +2 -1
  141. package/src/testing/ClientProvider.tsx +1 -1
  142. package/src/translations/locales/en-US.ts +42 -1
  143. package/src/util/index.ts +5 -0
  144. package/src/util/invitationStatusValue.ts +21 -0
  145. package/theme-extensions.js +11 -0
  146. package/dist/types/src/components/IdentityListItem/IdentityListItem.d.ts.map +0 -1
  147. package/dist/types/src/components/IdentityListItem/IdentityListItem.stories.d.ts.map +0 -1
@@ -0,0 +1,80 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import '@dxosTheme';
6
+
7
+ import { StoryFn } from '@storybook/react';
8
+ import React, { useCallback, useMemo, useState } from 'react';
9
+
10
+ import { Client, Invitation, InvitationEncoder } from '@dxos/client';
11
+ import { TestBuilder } from '@dxos/client/testing';
12
+ import { log } from '@dxos/log';
13
+ import { useAsyncEffect } from '@dxos/react-async';
14
+ import { ClientProvider } from '@dxos/react-client';
15
+ import { Loading } from '@dxos/react-components';
16
+
17
+ import { JoinDialog } from './JoinDialog';
18
+
19
+ export default {
20
+ component: JoinDialog
21
+ };
22
+ export const Default = {
23
+ decorators: [
24
+ (Story: StoryFn) => {
25
+ const n = 2;
26
+ const testBuilder = new TestBuilder();
27
+ const clients = useMemo(() => {
28
+ return [...Array(n)].map(() => new Client({ services: testBuilder.createClientServicesHost() }));
29
+ }, []);
30
+
31
+ const [invitationCode, setInvitationCode] = useState<string>();
32
+
33
+ const onInvitationEvent = useCallback((invitation: Invitation) => {
34
+ if (!invitationCode) {
35
+ const nextInvitationCode = InvitationEncoder.encode(invitation);
36
+ log.info('[next invitation code]', { nextInvitationCode });
37
+ setInvitationCode(nextInvitationCode);
38
+ }
39
+ }, []);
40
+
41
+ useAsyncEffect(async () => {
42
+ await Promise.all(clients.map((client) => client.initialize()));
43
+ log.info('[initialized]');
44
+
45
+ await clients[0].halo.createProfile({ displayName: 'Os Mutantes' });
46
+
47
+ const space = await clients[0].echo.createSpace();
48
+ log.info('[space created]', space);
49
+ space.properties.name = 'Q3 2022 Planning';
50
+
51
+ const invitation = space.createInvitation({ type: Invitation.Type.INTERACTIVE });
52
+ log.info('[invitation created]', invitation);
53
+
54
+ invitation.subscribe({
55
+ onAuthenticating: onInvitationEvent,
56
+ onCancelled: (...args) => log.warn('[cancelled]', args),
57
+ onConnected: onInvitationEvent,
58
+ onConnecting: onInvitationEvent,
59
+ onError: onInvitationEvent,
60
+ onSuccess: onInvitationEvent,
61
+ onTimeout: (...args) => log.warn('[timeout]', args)
62
+ });
63
+
64
+ return () => {
65
+ void Promise.all(clients.map((client) => client.destroy()));
66
+ };
67
+ }, clients);
68
+
69
+ if (!invitationCode) {
70
+ return <Loading label='Setting things up…' />;
71
+ }
72
+
73
+ return (
74
+ <ClientProvider client={clients[1]} fallback={() => <Loading label='Loading client…' />}>
75
+ <Story args={{ initialInvitationCode: invitationCode }} />
76
+ </ClientProvider>
77
+ );
78
+ }
79
+ ]
80
+ };
@@ -0,0 +1,31 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { Action, Cancel } from '@radix-ui/react-alert-dialog';
6
+ import React from 'react';
7
+
8
+ import { ThemeContext, useId } from '@dxos/react-components';
9
+
10
+ import { PanelAlertDialog, PanelAlertDialogProps } from '../../layouts';
11
+ import { JoinPanel, JoinPanelProps } from '../../panels';
12
+
13
+ export interface JoinDialogProps extends Omit<PanelAlertDialogProps, 'titleId' | 'children'>, JoinPanelProps {}
14
+
15
+ export const JoinDialog = ({ slots, ...joinPanelProps }: JoinDialogProps) => {
16
+ const titleId = useId('joinDialog__title');
17
+ return (
18
+ <PanelAlertDialog {...{ slots, titleId }}>
19
+ <ThemeContext.Provider value={{ themeVariant: 'os' }}>
20
+ <JoinPanel
21
+ {...{
22
+ ...joinPanelProps,
23
+ titleId,
24
+ exitActionParent: <Cancel asChild />,
25
+ doneActionParent: <Action asChild />
26
+ }}
27
+ />
28
+ </ThemeContext.Provider>
29
+ </PanelAlertDialog>
30
+ );
31
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './JoinDialog';
@@ -2,3 +2,4 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
  export * from './IdentityPopover';
5
+ export * from './JoinDialog';
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
+ export * from './components';
5
6
  export * from './composites';
6
7
  export * from './layouts';
7
8
  export * from './panels';
@@ -0,0 +1,44 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import * as AlertDialog from '@radix-ui/react-alert-dialog';
6
+ import React, { ComponentPropsWithoutRef, PropsWithChildren } from 'react';
7
+
8
+ import { mx } from '@dxos/react-components';
9
+
10
+ export interface PanelAlertDialogSlots {
11
+ root: ComponentPropsWithoutRef<typeof AlertDialog.Root>;
12
+ overlay: ComponentPropsWithoutRef<typeof AlertDialog.Overlay>;
13
+ content: ComponentPropsWithoutRef<typeof AlertDialog.Content>;
14
+ trigger: ComponentPropsWithoutRef<typeof AlertDialog.Trigger>;
15
+ }
16
+
17
+ export type PanelAlertDialogProps = PropsWithChildren<{
18
+ titleId: string;
19
+ slots?: Partial<PanelAlertDialogSlots>;
20
+ }>;
21
+
22
+ export const PanelAlertDialog = ({ titleId, slots = {}, children }: PanelAlertDialogProps) => {
23
+ return (
24
+ <AlertDialog.Root defaultOpen {...slots.root}>
25
+ {slots.trigger && <AlertDialog.Trigger {...slots.trigger} />}
26
+ <AlertDialog.Overlay
27
+ {...slots.overlay}
28
+ className={mx(
29
+ 'fixed inset-0 backdrop-blur z-50 overflow-auto grid place-items-center p-2 md:p-4 lg:p-8',
30
+ slots.overlay?.className
31
+ )}
32
+ >
33
+ <AlertDialog.Content
34
+ onEscapeKeyDown={(event) => event.preventDefault()}
35
+ {...slots.content}
36
+ aria-labelledby={titleId}
37
+ className={mx('is-full min-is-[260px] max-is-[320px] shadow-md backdrop-blur-md', slots.content?.className)}
38
+ >
39
+ {children}
40
+ </AlertDialog.Content>
41
+ </AlertDialog.Overlay>
42
+ </AlertDialog.Root>
43
+ );
44
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './PanelAlertDialog';
@@ -6,7 +6,7 @@ import React, { ComponentProps } from 'react';
6
6
 
7
7
  import { mx, Popover } from '@dxos/react-components';
8
8
 
9
- import { defaultPanel, defaultArrow } from '../../styles';
9
+ import { defaultSurface, defaultArrow } from '../../styles';
10
10
 
11
11
  /**
12
12
  * This component essentially sets default styles for popovers which have panels as their content
@@ -24,7 +24,7 @@ export const PanelPopover = ({ slots, children, ...popoverProps }: ComponentProp
24
24
  collisionPadding: 8,
25
25
  sideOffset: 4,
26
26
  ...slots?.content,
27
- className: mx(defaultPanel, slots?.content?.className)
27
+ className: mx(defaultSurface, slots?.content?.className)
28
28
  },
29
29
  trigger: {
30
30
  ...slots?.trigger,
@@ -37,8 +37,7 @@ export const useTogglePanelSidebar = () => {
37
37
 
38
38
  export interface PanelSidebarProviderSlots {
39
39
  content?: ComponentProps<typeof DialogPrimitive.Content>;
40
- fixedBlockStart?: ComponentProps<'div'>;
41
- fixedBlockEnd?: ComponentProps<'div'>;
40
+ main?: ComponentProps<'div'>;
42
41
  }
43
42
 
44
43
  export interface PanelSidebarProviderProps {
@@ -46,13 +45,9 @@ export interface PanelSidebarProviderProps {
46
45
  slots?: PanelSidebarProviderSlots;
47
46
  }
48
47
 
49
- export const PanelSidebarProvider = ({
50
- children,
51
- inlineStart,
52
- slots
53
- }: PropsWithChildren<PanelSidebarProviderProps>) => {
48
+ export const PanelSidebarProvider = ({ children, slots }: PropsWithChildren<PanelSidebarProviderProps>) => {
54
49
  const { t } = useTranslation('os');
55
- const [isLg] = useMediaQuery('lg');
50
+ const [isLg] = useMediaQuery('lg', { ssr: false });
56
51
  const [displayState, setInternalDisplayState] = useState<PanelSidebarState>(isLg ? 'show' : 'hide');
57
52
  const isOpen = displayState === 'show';
58
53
  const [transitionShow, setTransitionShow] = useState(isOpen);
@@ -81,30 +76,23 @@ export const PanelSidebarProvider = ({
81
76
  return (
82
77
  <PanelSidebarContext.Provider value={{ setDisplayState, displayState }}>
83
78
  <DialogPrimitive.Root open={domShow} modal={!isLg}>
79
+ {/* Sidebar. */}
84
80
  <DialogPrimitive.Content
81
+ onOpenAutoFocus={(event) => isLg && event.preventDefault()}
82
+ onCloseAutoFocus={(event) => isLg && event.preventDefault()}
85
83
  {...slots?.content}
86
84
  className={mx(
87
- 'fixed block-start-0 block-end-0 is-[272px] z-50 transition-[inset-inline-start,inset-inline-end] duration-200 ease-in-out overflow-x-hidden overflow-y-auto',
88
- transitionShow ? 'inline-start-0' : 'inline-start-[-272px]',
85
+ 'fixed block-start-0 block-end-0 is-sidebar z-50 overscroll-contain overflow-x-hidden overflow-y-auto',
86
+ 'transition-[inset-inline-start,inset-inline-end] duration-200 ease-in-out',
87
+ transitionShow ? 'inline-start-0' : '-inline-start-sidebar',
89
88
  slots?.content?.className
90
89
  )}
91
90
  >
92
91
  <DialogPrimitive.Title className='sr-only'>{t('sidebar label')}</DialogPrimitive.Title>
93
92
  {slots?.content?.children}
94
93
  </DialogPrimitive.Content>
95
- {slots?.fixedBlockStart && (
96
- <div
97
- role='none'
98
- {...slots?.fixedBlockStart}
99
- className={mx(
100
- 'fixed inline-end-0 block-start-0 z-[49] transition-[inset-inline-start,inset-inline-end] duration-200 ease-in-out',
101
- transitionShow ? 'inline-start-[272px]' : 'inline-start-0',
102
- slots?.fixedBlockStart?.className
103
- )}
104
- >
105
- {slots?.fixedBlockStart?.children}
106
- </div>
107
- )}
94
+
95
+ {/* TODO(burdon): Simple comment required. */}
108
96
  {!isLg && (
109
97
  <DialogPrimitive.Overlay
110
98
  className={mx(
@@ -115,11 +103,15 @@ export const PanelSidebarProvider = ({
115
103
  onClick={internalHide}
116
104
  />
117
105
  )}
106
+
107
+ {/* Main content. */}
118
108
  <div
119
109
  role='none'
110
+ {...slots?.main}
120
111
  className={mx(
121
- 'bs-full transition-[padding-inline-start] duration-200 ease-in-out',
122
- isLg && isOpen ? 'pis-[272px]' : 'pis-0'
112
+ 'transition-[padding-inline-start] duration-200 ease-in-out',
113
+ isLg && isOpen ? 'pis-sidebar' : 'pis-0',
114
+ slots?.main?.className
123
115
  )}
124
116
  >
125
117
  {children}
@@ -4,3 +4,4 @@
4
4
 
5
5
  export * from './PanelPopover';
6
6
  export * from './PanelSidebar';
7
+ export * from './PanelAlertDialog';
@@ -0,0 +1,57 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+ import { ProhibitInset } from 'phosphor-react';
5
+ import React, { cloneElement, ForwardedRef, forwardRef } from 'react';
6
+
7
+ import { AuthenticatingInvitationObservable } from '@dxos/client';
8
+ import { useSpace } from '@dxos/react-client';
9
+ import { Avatar, Button, getSize, Heading, mx, Trans, useId, useTranslation } from '@dxos/react-components';
10
+
11
+ import { subduedSurface } from '../../styles';
12
+
13
+ export interface JoinSpaceHeadingProps {
14
+ titleId: string;
15
+ invitation?: AuthenticatingInvitationObservable;
16
+ onExit?: () => void;
17
+ exitActionParent?: Parameters<typeof cloneElement>[0];
18
+ }
19
+
20
+ export const JoinHeading = forwardRef(
21
+ ({ titleId, invitation, onExit, exitActionParent }: JoinSpaceHeadingProps, ref: ForwardedRef<HTMLDivElement>) => {
22
+ const { t } = useTranslation('os');
23
+
24
+ const space = useSpace(invitation?.invitation?.spaceKey);
25
+ const spaceTitle = space?.properties.name ?? '(Space not available)';
26
+
27
+ const nameId = useId('spaceDisplayName');
28
+
29
+ const exitButton = (
30
+ <Button compact variant='ghost' {...(onExit && { onClick: onExit })} className='grow-0 shrink-0'>
31
+ <ProhibitInset className={getSize(5)} />
32
+ <span className='sr-only'>{t('exit label')}</span>
33
+ </Button>
34
+ );
35
+
36
+ return (
37
+ <div role='none' className={mx(subduedSurface, 'p-2 rounded-bs-md')} ref={ref}>
38
+ <div role='group' className='flex items-center gap-2'>
39
+ <Avatar fallbackValue={invitation?.invitation?.spaceKey?.toHex() ?? ''} labelId={nameId} />
40
+ <Heading level={1} className='font-body font-normal text-base grow' id={titleId}>
41
+ <Trans
42
+ {...{
43
+ defaults: t('join space heading'),
44
+ components: {
45
+ small: <span className='block leading-none mbe-1 font-system-medium text-sm' />,
46
+ large: <span className='block leading-none' id={nameId} />
47
+ },
48
+ values: { spaceTitle }
49
+ }}
50
+ />
51
+ </Heading>
52
+ {exitActionParent ? cloneElement(exitActionParent, {}, exitButton) : exitButton}
53
+ </div>
54
+ </div>
55
+ );
56
+ }
57
+ );
@@ -0,0 +1,303 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+ import React, { useEffect, useReducer } from 'react';
5
+
6
+ import { InvitationEncoder } from '@dxos/client';
7
+ import { log } from '@dxos/log';
8
+ import { useClient, useIdentity } from '@dxos/react-client';
9
+ import { useId } from '@dxos/react-components';
10
+
11
+ import { JoinHeading } from './JoinHeading';
12
+ import { JoinAction, JoinPanelProps, JoinState } from './JoinPanelProps';
13
+ import {
14
+ AdditionMethodSelector,
15
+ IdentitySelector,
16
+ IdentityInput,
17
+ IdentityAdded,
18
+ InvitationAuthenticator,
19
+ InvitationRescuer,
20
+ InvitationInput,
21
+ InvitationAccepted
22
+ } from './view-states';
23
+
24
+ export const JoinPanel = ({
25
+ initialInvitationCode,
26
+ titleId: propsTitleId,
27
+ exitActionParent,
28
+ onExit,
29
+ doneActionParent,
30
+ onDone
31
+ }: JoinPanelProps) => {
32
+ const client = useClient();
33
+ const internalTitleId = useId('joinPanel__title');
34
+ const titleId = propsTitleId ?? internalTitleId;
35
+ const identity = useIdentity();
36
+
37
+ const availableIdentities = identity ? [identity] : [];
38
+
39
+ const reducer = (state: JoinState, action: JoinAction) => {
40
+ const nextState = { ...state };
41
+ switch (action.type) {
42
+ case 'added identity':
43
+ nextState.activeView = 'identity added';
44
+ nextState.selectedIdentity = action.identity;
45
+ break;
46
+ case 'add identity':
47
+ nextState.activeView = 'addition method selector';
48
+ break;
49
+ case 'select addition method':
50
+ if (action.method === 'accept device invitation') {
51
+ nextState.activeView = 'halo invitation acceptor';
52
+ if (state.unredeemedHaloInvitationCode) {
53
+ nextState.haloInvitation = client.echo.acceptInvitation(
54
+ InvitationEncoder.decode(state.unredeemedHaloInvitationCode)
55
+ );
56
+ nextState.unredeemedHaloInvitationCode = undefined;
57
+ nextState.haloViewState = 'invitation authenticator';
58
+ } else {
59
+ nextState.haloViewState = 'invitation input';
60
+ }
61
+ } else {
62
+ nextState.activeView = 'identity input';
63
+ nextState.additionMethod = action.method;
64
+ }
65
+ break;
66
+ case 'select identity':
67
+ nextState.selectedIdentity = action.identity;
68
+ nextState.activeView = 'space invitation acceptor';
69
+ if (state.unredeemedSpaceInvitationCode) {
70
+ nextState.spaceInvitation = client.echo.acceptInvitation(
71
+ InvitationEncoder.decode(state.unredeemedSpaceInvitationCode)
72
+ );
73
+ nextState.unredeemedSpaceInvitationCode = undefined;
74
+ nextState.spaceViewState = 'invitation authenticator';
75
+ } else {
76
+ nextState.spaceViewState = 'invitation input';
77
+ }
78
+ break;
79
+ case 'deselect identity':
80
+ nextState.selectedIdentity = undefined;
81
+ nextState.activeView = 'identity selector';
82
+ break;
83
+ case 'cancel invitation':
84
+ if (action.from === 'space' && state.spaceInvitation?.invitation) {
85
+ void state.spaceInvitation?.cancel();
86
+ }
87
+ if (action.from === 'halo' && state.haloInvitation?.invitation) {
88
+ void state.haloInvitation?.cancel();
89
+ }
90
+ break;
91
+ case 'authenticating invitation':
92
+ nextState[action.from === 'halo' ? 'haloInvitationAnnotation' : 'spaceInvitationAnnotation'] = 'authenticating';
93
+ break;
94
+ case 'connecting invitation':
95
+ nextState[action.from === 'halo' ? 'haloInvitation' : 'spaceInvitation'] = action.invitation;
96
+ // eslint-disable-next-line no-fallthrough
97
+ case 'cancelled invitation':
98
+ case 'fail invitation':
99
+ case 'timeout invitation':
100
+ nextState[action.from === 'halo' ? 'haloViewState' : 'spaceViewState'] = 'invitation rescuer';
101
+ break;
102
+ case 'connect invitation':
103
+ case 'authenticate invitation':
104
+ if (action.from === 'halo' && state.haloInvitationAnnotation === 'authenticating') {
105
+ nextState.haloInvitationAnnotation = 'authentication failed';
106
+ }
107
+ if (action.from === 'space' && state.spaceInvitationAnnotation === 'authenticating') {
108
+ nextState.spaceInvitationAnnotation = 'authentication failed';
109
+ }
110
+ nextState[action.from === 'halo' ? 'haloViewState' : 'spaceViewState'] = 'invitation authenticator';
111
+ break;
112
+ case 'accepted invitation':
113
+ nextState[action.from === 'halo' ? 'haloViewState' : 'spaceViewState'] = 'invitation accepted';
114
+ break;
115
+ case 'reset invitation':
116
+ nextState[action.from === 'halo' ? 'haloInvitation' : 'spaceInvitation'] = undefined;
117
+ nextState[action.from === 'halo' ? 'haloInvitationAnnotation' : 'spaceInvitationAnnotation'] = undefined;
118
+ nextState[action.from === 'halo' ? 'unredeemedHaloInvitationCode' : 'unredeemedSpaceInvitationCode'] =
119
+ undefined;
120
+ nextState[action.from === 'halo' ? 'haloViewState' : 'spaceViewState'] = 'invitation input';
121
+ break;
122
+ }
123
+ log.debug('[join panel reducer]', { action, nextState });
124
+ return nextState;
125
+ };
126
+
127
+ const [joinState, dispatch] = useReducer(reducer, {
128
+ unredeemedHaloInvitationCode: undefined,
129
+ unredeemedSpaceInvitationCode: initialInvitationCode,
130
+ spaceInvitation: undefined,
131
+ haloInvitation: undefined,
132
+ activeView: availableIdentities.length > 0 ? 'identity selector' : 'addition method selector',
133
+ selectedIdentity: undefined,
134
+ additionMethod: undefined,
135
+ spaceViewState: 'invitation input',
136
+ haloViewState: 'invitation input'
137
+ });
138
+
139
+ useEffect(() => {
140
+ // TODO (thure): Validate if this is sufficiently synchronous for iOS to move focus. It might not be!
141
+ const attrValue =
142
+ joinState.activeView === 'identity input'
143
+ ? `${joinState.activeView}; ${joinState.additionMethod}`
144
+ : joinState.activeView === 'space invitation acceptor'
145
+ ? `${joinState.activeView}; ${joinState.spaceViewState}`
146
+ : joinState.activeView === 'halo invitation acceptor'
147
+ ? `${joinState.activeView}; ${joinState.haloViewState}`
148
+ : joinState.activeView;
149
+ log.debug('[autofocus value]', { attrValue });
150
+ const $nextAutofocus: HTMLElement | null = document.querySelector(`[data-autofocus="${attrValue}"]`);
151
+ if ($nextAutofocus) {
152
+ $nextAutofocus.focus();
153
+ }
154
+ }, [joinState.activeView, joinState.spaceViewState, joinState.haloViewState]);
155
+
156
+ useEffect(() => {
157
+ return joinState.spaceInvitation?.subscribe({
158
+ onAuthenticating: () => dispatch({ type: 'authenticate invitation', from: 'space' }),
159
+ onCancelled: () => dispatch({ type: 'cancelled invitation', from: 'space' }),
160
+ onConnected: () => dispatch({ type: 'connect invitation', from: 'space' }),
161
+ onError: () => dispatch({ type: 'fail invitation', from: 'space' }),
162
+ onSuccess: () => dispatch({ type: 'accepted invitation', from: 'space' }),
163
+ onTimeout: () => dispatch({ type: 'timeout invitation', from: 'space' })
164
+ });
165
+ }, [joinState.spaceInvitation]);
166
+
167
+ useEffect(() => {
168
+ return joinState.haloInvitation?.subscribe({
169
+ onAuthenticating: () => dispatch({ type: 'authenticate invitation', from: 'halo' }),
170
+ onCancelled: () => dispatch({ type: 'cancelled invitation', from: 'halo' }),
171
+ onConnected: () => dispatch({ type: 'connect invitation', from: 'halo' }),
172
+ onError: () => dispatch({ type: 'fail invitation', from: 'halo' }),
173
+ onSuccess: () => dispatch({ type: 'accepted invitation', from: 'halo' }),
174
+ onTimeout: () => dispatch({ type: 'timeout invitation', from: 'halo' })
175
+ });
176
+ }, [joinState.haloInvitation]);
177
+
178
+ return (
179
+ <>
180
+ <JoinHeading {...{ titleId, invitation: joinState.spaceInvitation, onExit, exitActionParent }} />
181
+ <div role='none' className='is-full overflow-hidden'>
182
+ <div role='none' className='flex is-[1300%]' aria-live='polite'>
183
+ <IdentitySelector
184
+ {...{ dispatch, availableIdentities, active: joinState.activeView === 'identity selector' }}
185
+ />
186
+ <AdditionMethodSelector
187
+ {...{
188
+ dispatch,
189
+ availableIdentities,
190
+ active: joinState.activeView === 'addition method selector'
191
+ }}
192
+ />
193
+ <IdentityInput
194
+ {...{
195
+ dispatch,
196
+ active: joinState.activeView === 'identity input' && joinState.additionMethod === 'create identity',
197
+ method: 'create identity'
198
+ }}
199
+ />
200
+ <IdentityInput
201
+ {...{
202
+ dispatch,
203
+ active: joinState.activeView === 'identity input' && joinState.additionMethod === 'recover identity',
204
+ method: 'recover identity'
205
+ }}
206
+ />
207
+ <InvitationInput
208
+ {...{
209
+ dispatch,
210
+ activeInvitation: joinState.haloInvitation || true,
211
+ active:
212
+ joinState.activeView === 'halo invitation acceptor' && joinState.haloViewState === 'invitation input',
213
+ invitationType: 'halo'
214
+ }}
215
+ />
216
+ <InvitationRescuer
217
+ {...{
218
+ dispatch,
219
+ activeInvitation: joinState.haloInvitation || true,
220
+ active:
221
+ joinState.activeView === 'halo invitation acceptor' && joinState.haloViewState === 'invitation rescuer',
222
+ invitationType: 'halo'
223
+ }}
224
+ />
225
+ <InvitationAuthenticator
226
+ {...{
227
+ dispatch,
228
+ activeInvitation: joinState.haloInvitation || true,
229
+ active:
230
+ joinState.activeView === 'halo invitation acceptor' &&
231
+ joinState.haloViewState === 'invitation authenticator',
232
+ invitationType: 'halo',
233
+ ...(joinState.spaceInvitationAnnotation === 'authentication failed' && { failed: true })
234
+ }}
235
+ />
236
+ <InvitationAccepted
237
+ {...{
238
+ dispatch,
239
+ activeInvitation: joinState.haloInvitation || true,
240
+ active:
241
+ joinState.activeView === 'halo invitation acceptor' &&
242
+ joinState.haloViewState === 'invitation accepted',
243
+ invitationType: 'halo'
244
+ }}
245
+ />
246
+ <IdentityAdded
247
+ {...{
248
+ dispatch,
249
+ addedIdentity: joinState.selectedIdentity,
250
+ active: joinState.activeView === 'identity added'
251
+ }}
252
+ />
253
+ <InvitationInput
254
+ {...{
255
+ dispatch,
256
+ activeInvitation: joinState.spaceInvitation || true,
257
+ selectedIdentity: joinState.selectedIdentity,
258
+ active:
259
+ joinState.activeView === 'space invitation acceptor' && joinState.spaceViewState === 'invitation input',
260
+ invitationType: 'space'
261
+ }}
262
+ />
263
+ <InvitationRescuer
264
+ {...{
265
+ dispatch,
266
+ activeInvitation: joinState.spaceInvitation || true,
267
+ selectedIdentity: joinState.selectedIdentity,
268
+ active:
269
+ joinState.activeView === 'space invitation acceptor' &&
270
+ joinState.spaceViewState === 'invitation rescuer',
271
+ invitationType: 'space'
272
+ }}
273
+ />
274
+ <InvitationAuthenticator
275
+ {...{
276
+ dispatch,
277
+ activeInvitation: joinState.spaceInvitation || true,
278
+ selectedIdentity: joinState.selectedIdentity,
279
+ active:
280
+ joinState.activeView === 'space invitation acceptor' &&
281
+ joinState.spaceViewState === 'invitation authenticator',
282
+ invitationType: 'space',
283
+ ...(joinState.spaceInvitationAnnotation === 'authentication failed' && { failed: true })
284
+ }}
285
+ />
286
+ <InvitationAccepted
287
+ {...{
288
+ dispatch,
289
+ activeInvitation: joinState.spaceInvitation || true,
290
+ selectedIdentity: joinState.selectedIdentity,
291
+ active:
292
+ joinState.activeView === 'space invitation acceptor' &&
293
+ joinState.spaceViewState === 'invitation accepted',
294
+ invitationType: 'space',
295
+ doneActionParent,
296
+ onDone
297
+ }}
298
+ />
299
+ </div>
300
+ </div>
301
+ </>
302
+ );
303
+ };