Skip to content

Commit

Permalink
feat(clerk-js): First iteration of OrganizationSwitcher retheme (#2311)
Browse files Browse the repository at this point in the history
* fix(clerk-js): Refactor Action and SmallAction elements

* feat(clerk-js): Retheme OrganizationSwitcher

* fix(clerk-js): Add the correct Add icon on both UB and OS

* fix(clerk-js): Retheme the InvitationPreview UI

* fix(clerk-js): Add changeset
  • Loading branch information
anagstef authored Dec 12, 2023
1 parent 69ce3e1 commit a1efd29
Show file tree
Hide file tree
Showing 20 changed files with 277 additions and 188 deletions.
2 changes: 2 additions & 0 deletions .changeset/popular-parents-hope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import { useDelayedVisibility, usePrefersReducedMotion } from '../hooks';
import type { ThemableCssProp } from '../styledSystem';
import { animations } from '../styledSystem';

export const NotificationCountBadge = ({
notificationCount,
containerSx,
}: {
type NotificationCountBadgeProps = {
notificationCount: number;
containerSx?: ThemableCssProp;
}) => {
};

export const NotificationCountBadge = ({ notificationCount, containerSx }: NotificationCountBadgeProps) => {
const prefersReducedMotion = usePrefersReducedMotion();
const showNotification = useDelayedVisibility(notificationCount > 0, 350) || false;

Expand All @@ -25,9 +24,11 @@ export const NotificationCountBadge = ({
<Box
sx={[
t => ({
position: 'relative',
width: t.sizes.$4,
height: t.sizes.$4,
position: 'absolute',
top: `-${t.space.$2}`,
right: `-${t.space.$1}`,
width: t.sizes.$2,
height: t.sizes.$2,
}),
containerSx,
]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@ import React from 'react';
import { runIfFunctionOrReturn } from '../../../utils';
import { NotificationCountBadge, withGate } from '../../common';
import { useEnvironment, useOrganizationSwitcherContext } from '../../contexts';
import { descriptors, localizationKeys } from '../../customizables';
import { descriptors, Flex, localizationKeys } from '../../customizables';
import {
Action,
Actions,
ExtraSmallAction,
OrganizationPreview,
PersonalWorkspacePreview,
PopoverCard,
SmallAction,
useCardState,
} from '../../elements';
import { RootBox } from '../../elements/RootBox';
import { Billing, CogFilled } from '../../icons';
import { useRouter } from '../../router';
import type { PropsOfComponent } from '../../styledSystem';
import type { PropsOfComponent, ThemableCssProp } from '../../styledSystem';
import { OrganizationActionList } from './OtherOrganizationActions';

type OrganizationSwitcherPopoverProps = { close: () => void } & PropsOfComponent<typeof PopoverCard.Root>;
Expand Down Expand Up @@ -101,31 +102,91 @@ export const OrganizationSwitcherPopover = React.forwardRef<HTMLDivElement, Orga
});
};

const manageOrganizationSmallIconButton = (
<ExtraSmallAction
elementDescriptor={descriptors.organizationSwitcherPopoverActionButton}
elementId={descriptors.organizationSwitcherPopoverActionButton.setId('manageOrganization')}
iconBoxElementDescriptor={descriptors.organizationSwitcherPopoverActionButtonIconBox}
iconBoxElementId={descriptors.organizationSwitcherPopoverActionButtonIconBox.setId('manageOrganization')}
iconElementDescriptor={descriptors.organizationSwitcherPopoverActionButtonIcon}
iconElementId={descriptors.organizationSwitcherPopoverActionButtonIcon.setId('manageOrganization')}
icon={CogFilled}
onClick={handleManageOrganizationClicked}
trailing={<NotificationCountBadgeManageButton />}
/>
);

const manageOrganizationButton = (
<Action
<SmallAction
elementDescriptor={descriptors.organizationSwitcherPopoverActionButton}
elementId={descriptors.organizationSwitcherPopoverActionButton.setId('manageOrganization')}
iconBoxElementDescriptor={descriptors.organizationSwitcherPopoverActionButtonIconBox}
iconBoxElementId={descriptors.organizationSwitcherPopoverActionButtonIconBox.setId('manageOrganization')}
iconElementDescriptor={descriptors.organizationSwitcherPopoverActionButtonIcon}
iconElementId={descriptors.organizationSwitcherPopoverActionButtonIcon.setId('manageOrganization')}
textElementDescriptor={descriptors.organizationSwitcherPopoverActionButtonText}
textElementId={descriptors.organizationSwitcherPopoverActionButtonText.setId('manageOrganization')}
icon={CogFilled}
label={localizationKeys('organizationSwitcher.action__manageOrganization')}
onClick={handleManageOrganizationClicked}
trailing={<NotificationCountBadgeManageButton />}
trailing={<NotificationCountBadgeManageButton sx={{ right: 0 }} />}
/>
);

const billingOrganizationButton = (
<Action
<SmallAction
icon={Billing}
label={runIfFunctionOrReturn(__unstable_manageBillingLabel) || 'Manage billing'}
label={runIfFunctionOrReturn(__unstable_manageBillingLabel) || 'Upgrade'}
onClick={() => router.navigate(runIfFunctionOrReturn(__unstable_manageBillingUrl))}
/>
);

const selectedOrganizationPreview = (currentOrg: OrganizationResource) =>
__unstable_manageBillingUrl ? (
<>
<OrganizationPreview
elementId={'organizationSwitcherActiveOrganization'}
organization={currentOrg}
user={user}
mainIdentifierVariant='buttonLarge'
sx={t => ({
padding: `${t.space.$4} ${t.space.$5}`,
})}
/>
<Actions
role='menu'
sx={t => ({ borderBottom: `${t.borders.$normal} ${t.colors.$blackAlpha100}` })}
>
<Flex
justify='between'
sx={t => ({ marginLeft: t.space.$12, padding: `0 ${t.space.$5} ${t.space.$4}`, gap: t.space.$2 })}
>
{manageOrganizationButton}
{billingOrganizationButton}
</Flex>
</Actions>
</>
) : (
<Flex
justify='between'
align='center'
sx={t => ({
width: '100%',
paddingRight: t.space.$5,
borderBottom: `${t.borders.$normal} ${t.colors.$blackAlpha100}`,
})}
>
<OrganizationPreview
elementId={'organizationSwitcherActiveOrganization'}
organization={currentOrg}
user={user}
mainIdentifierVariant='buttonLarge'
sx={t => ({
padding: `${t.space.$4} ${t.space.$5}`,
})}
/>
<Actions role='menu'>{manageOrganizationSmallIconButton}</Actions>
</Flex>
);

return (
<RootBox elementDescriptor={descriptors.organizationSwitcherPopoverRootBox}>
<PopoverCard.Root
Expand All @@ -136,30 +197,18 @@ export const OrganizationSwitcherPopover = React.forwardRef<HTMLDivElement, Orga
{...rest}
>
<PopoverCard.Main elementDescriptor={descriptors.organizationSwitcherPopoverMain}>
{currentOrg ? (
<>
<OrganizationPreview
elementId={'organizationSwitcherActiveOrganization'}
gap={4}
organization={currentOrg}
user={user}
sx={theme => t => ({ padding: `0 ${theme.space.$6}`, marginBottom: t.space.$2 })}
/>
<Actions role='menu'>
{manageOrganizationButton}
{__unstable_manageBillingUrl && billingOrganizationButton}
</Actions>
</>
) : (
!hidePersonal && (
<PersonalWorkspacePreview
gap={4}
user={userWithoutIdentifiers}
sx={theme => ({ padding: `0 ${theme.space.$6}`, marginBottom: theme.space.$6 })}
title={localizationKeys('organizationSwitcher.personalWorkspace')}
/>
)
)}
{currentOrg
? selectedOrganizationPreview(currentOrg)
: !hidePersonal && (
<PersonalWorkspacePreview
user={userWithoutIdentifiers}
sx={t => ({
padding: `${t.space.$4} ${t.space.$5}`,
borderBottom: `${t.borders.$normal} ${t.colors.$blackAlpha100}`,
})}
title={localizationKeys('organizationSwitcher.personalWorkspace')}
/>
)}
<OrganizationActionList
onCreateOrganizationClick={handleCreateOrganizationClicked}
onPersonalWorkspaceClick={handlePersonalWorkspaceClicked}
Expand All @@ -174,7 +223,7 @@ export const OrganizationSwitcherPopover = React.forwardRef<HTMLDivElement, Orga
);

const NotificationCountBadgeManageButton = withGate(
() => {
({ sx }: { sx?: ThemableCssProp }) => {
const { organizationSettings } = useEnvironment();

const isDomainsEnabled = organizationSettings?.domains?.enabled;
Expand All @@ -183,7 +232,12 @@ const NotificationCountBadgeManageButton = withGate(
membershipRequests: isDomainsEnabled || undefined,
});

return <NotificationCountBadge notificationCount={membershipRequests?.count || 0} />;
return (
<NotificationCountBadge
notificationCount={membershipRequests?.count || 0}
containerSx={sx}
/>
);
},
{
// if the user is not able to accept a request we should not notify them
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { NotificationCountBadge, useGate } from '../../common';
import { useEnvironment, useOrganizationSwitcherContext } from '../../contexts';
import { Button, descriptors, Icon, localizationKeys } from '../../customizables';
import { OrganizationPreview, PersonalWorkspacePreview, withAvatarShimmer } from '../../elements';
import { Selector } from '../../icons';
import { ChevronDown } from '../../icons';
import type { PropsOfComponent } from '../../styledSystem';
import { organizationListParams } from './utils';

Expand All @@ -31,7 +31,7 @@ export const OrganizationSwitcherTrigger = withAvatarShimmer(
<Button
elementDescriptor={descriptors.organizationSwitcherTrigger}
variant='ghost'
sx={[t => ({ minHeight: 0, padding: `0 ${t.space.$2} 0 0`, position: 'relative' }), sx]}
sx={[t => ({ minHeight: 0, padding: `${t.space.$1} ${t.space.$2}`, position: 'relative' }), sx]}
ref={ref}
aria-label={`${props.isOpen ? 'Close' : 'Open'} organization switcher`}
aria-expanded={props.isOpen}
Expand All @@ -42,17 +42,18 @@ export const OrganizationSwitcherTrigger = withAvatarShimmer(
<OrganizationPreview
elementId={'organizationSwitcherTrigger'}
gap={3}
size={'sm'}
size='xs'
organization={organization}
sx={{ maxWidth: '30ch' }}
sx={t => ({ maxWidth: '30ch', color: t.colors.$blackAlpha600 })}
/>
)}
{!organization && (
<PersonalWorkspacePreview
size={'sm'}
size='xs'
gap={3}
user={userWithoutIdentifiers}
showAvatar={!hidePersonal}
sx={t => ({ color: t.colors.$blackAlpha600 })}
title={
hidePersonal
? localizationKeys('organizationSwitcher.notSelected')
Expand All @@ -65,8 +66,8 @@ export const OrganizationSwitcherTrigger = withAvatarShimmer(

<Icon
elementDescriptor={descriptors.organizationSwitcherTriggerIcon}
icon={Selector}
sx={t => ({ opacity: t.opacity.$sm, marginLeft: `${t.space.$2}` })}
icon={ChevronDown}
sx={t => ({ color: t.colors.$blackAlpha400, marginLeft: `${t.space.$2}` })}
/>
</Button>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';

import { descriptors, localizationKeys } from '../../customizables';
import { Action, SecondaryActions } from '../../elements';
import { Plus } from '../../icons';
import { Add } from '../../icons';
import { UserInvitationSuggestionList } from './UserInvitationSuggestionList';
import type { UserMembershipListProps } from './UserMembershipList';
import { UserMembershipList } from './UserMembershipList';
Expand All @@ -29,11 +29,19 @@ const CreateOrganizationButton = ({
iconBoxElementId={descriptors.organizationSwitcherPopoverActionButtonIconBox.setId('createOrganization')}
iconElementDescriptor={descriptors.organizationSwitcherPopoverActionButtonIcon}
iconElementId={descriptors.organizationSwitcherPopoverActionButtonIcon.setId('createOrganization')}
textElementDescriptor={descriptors.organizationSwitcherPopoverActionButtonText}
textElementId={descriptors.organizationSwitcherPopoverActionButtonText.setId('createOrganization')}
icon={Plus}
icon={Add}
label={localizationKeys('organizationSwitcher.action__createOrganization')}
onClick={onCreateOrganizationClick}
sx={t => ({
color: t.colors.$blackAlpha600,
':hover': {
color: t.colors.$blackAlpha600,
},
})}
iconSx={t => ({
width: t.sizes.$9,
height: t.sizes.$6,
})}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const AcceptRejectInvitationButtons = (props: UserOrganizationInvitationResource
elementDescriptor={descriptors.organizationSwitcherInvitationAcceptButton}
textVariant='buttonSmall'
variant='secondary'
size='sm'
size='xs'
isLoading={card.isLoading}
onClick={handleAccept}
localizationKey={localizationKeys('organizationSwitcher.action__invitationAccept')}
Expand All @@ -110,17 +110,19 @@ const InvitationPreview = withCardStateProvider(
align='center'
gap={2}
sx={t => ({
minHeight: 'unset',
height: t.space.$12,
justifyContent: 'space-between',
padding: `0 ${t.space.$6}`,
padding: `${t.space.$4} ${t.space.$5}`,
})}
>
<OrganizationPreview
elementId='organizationSwitcherListedOrganization'
avatarSx={t => ({ margin: `0 calc(${t.space.$3}/2)` })}
organization={publicOrganizationData}
size='sm'
sx={t => ({
color: t.colors.$blackAlpha600,
':hover': {
color: t.colors.$blackAlpha600,
},
})}
/>

{children}
Expand All @@ -134,7 +136,7 @@ const SwitcherInvitationActions = (props: PropsOfComponent<typeof Flex> & { show
return (
<Actions
sx={t => ({
borderTop: showBorder ? `${t.borders.$normal} ${t.colors.$blackAlpha200}` : 'none',
borderBottom: showBorder ? `${t.borders.$normal} ${t.colors.$blackAlpha200}` : 'none',
})}
role='menu'
{...restProps}
Expand Down
Loading

0 comments on commit a1efd29

Please sign in to comment.