From 5c21e98f65d968a633656529117145dfdc4b280c Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Thu, 11 Jan 2024 18:19:21 +0200 Subject: [PATCH] feat(clerk-js): Introduce Gauge component (#2563) * feat(clerk-js): Add Gauge component * chore(repo): Add Changeset * fix(clerk-js): Use the provided member limit * fix(clerk-js): Remove additional types from Gauge component * fix(clerk-js): Show gauge when the member limit is greater than zero --- .changeset/nasty-mirrors-arrive.md | 2 + .../OrganizationProfile/MembershipWidget.tsx | 71 ++++++++----- packages/clerk-js/src/ui/elements/Gauge.tsx | 100 ++++++++++++++++++ packages/clerk-js/src/ui/elements/index.ts | 1 + 4 files changed, 146 insertions(+), 28 deletions(-) create mode 100644 .changeset/nasty-mirrors-arrive.md create mode 100644 packages/clerk-js/src/ui/elements/Gauge.tsx diff --git a/.changeset/nasty-mirrors-arrive.md b/.changeset/nasty-mirrors-arrive.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/nasty-mirrors-arrive.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/MembershipWidget.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/MembershipWidget.tsx index 1a5def49cb..6773881812 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/MembershipWidget.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/MembershipWidget.tsx @@ -2,8 +2,11 @@ import { useOrganization } from '@clerk/shared/react'; import { runIfFunctionOrReturn } from '../../../utils'; import { useOrganizationProfileContext } from '../../contexts'; -import { Col, Flex, Link, Text } from '../../customizables'; +import { Flex, Icon, Link, Text } from '../../customizables'; +import { Gauge } from '../../elements'; +import { ArrowRightIcon } from '../../icons'; import { useRouter } from '../../router'; +import { mqu } from '../../styledSystem'; export const MembershipWidget = () => { const { organization } = useOrganization(); @@ -17,45 +20,57 @@ export const MembershipWidget = () => { } const totalCount = organization?.membersCount + organization?.pendingInvitationsCount; + const limit = runIfFunctionOrReturn(__unstable_manageBillingMembersLimit); return ( ({ background: theme.colors.$blackAlpha50, - padding: theme.space.$4, - justifyContent: 'space-between', - alignItems: 'flex-start', + padding: theme.space.$2, borderRadius: theme.radii.$md, + gap: theme.space.$4, })} > - - Members can be given access to applications. - - {runIfFunctionOrReturn(__unstable_manageBillingMembersLimit) > 0 && ( - ({ - alignSelf: 'flex-start', - color: t.colors.$primary500, - marginTop: t.space.$1, - })} - onClick={() => router.navigate(runIfFunctionOrReturn(__unstable_manageBillingUrl))} - > - {runIfFunctionOrReturn(__unstable_manageBillingLabel) || 'Manage billing'} - + ({ + [mqu.sm]: { + gap: t.space.$3, + }, + gap: t.space.$2, + })} + > + {limit > 0 && ( + )} - - - ({ - color: t.colors.$blackAlpha600, + [mqu.sm]: { + flexDirection: 'column', + }, + gap: t.space.$0x5, })} > - {totalCount} of{' '} - {__unstable_manageBillingMembersLimit - ? `${runIfFunctionOrReturn(__unstable_manageBillingMembersLimit)} members` - : 'unlimited'} - - + You can invite {__unstable_manageBillingMembersLimit ? `up to ${limit}` : 'unlimited'} members. + {limit > 0 && ( + ({ + fontWeight: t.fontWeights.$medium, + })} + variant='body' + onClick={() => router.navigate(runIfFunctionOrReturn(__unstable_manageBillingUrl))} + > + {runIfFunctionOrReturn(__unstable_manageBillingLabel) || 'Manage billing'} + + + + )} + + ); }; diff --git a/packages/clerk-js/src/ui/elements/Gauge.tsx b/packages/clerk-js/src/ui/elements/Gauge.tsx new file mode 100644 index 0000000000..7cbe73b5e0 --- /dev/null +++ b/packages/clerk-js/src/ui/elements/Gauge.tsx @@ -0,0 +1,100 @@ +import React from 'react'; + +import { Col, Text } from '../customizables'; +import { defaultInternalTheme } from '../foundations'; + +export type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; + +const sizes: Record = { + xs: { + svgSize: 24, + textSize: defaultInternalTheme.fontSizes.$xs, + }, + sm: { + svgSize: 32, + textSize: defaultInternalTheme.fontSizes.$sm, + }, + md: { + svgSize: 52, + textSize: defaultInternalTheme.fontSizes.$md, + }, + lg: { + svgSize: 64, + textSize: defaultInternalTheme.fontSizes.$lg, + }, + xl: { + svgSize: 96, + textSize: defaultInternalTheme.fontSizes.$xl, + }, +}; + +export type GaugeProps = { + value: number; + limit: number; + size?: Size; + strokeWidth?: number; +}; + +export const Gauge = React.memo((props: GaugeProps) => { + const { value, limit, size = 'sm' } = props; + const { textSize, svgSize } = sizes[size]; + const radius = svgSize / 2; + const strokeWidth = props.strokeWidth || svgSize / 10; + const normalizedRadius = radius - strokeWidth / 2; + const circumference = 2 * Math.PI * normalizedRadius; + const strokeDashoffset = (value / limit) * circumference; + const offset = circumference - strokeDashoffset; + + return ( + ({ + '--cl-gauge-inner-stroke-color': theme.colors.$blackAlpha900, + '--cl-gauge-outter-stroke-color': theme.colors.$blackAlpha300, + '> svg': { + transform: 'rotate(-90deg)', + }, + })} + > + + + {value >= 0 ? ( + + ) : null} + + ({ + position: 'absolute', + display: 'flex', + fontSize: textSize, + color: theme.colors.$blackAlpha900, + })} + > + {value} + + + ); +}); diff --git a/packages/clerk-js/src/ui/elements/index.ts b/packages/clerk-js/src/ui/elements/index.ts index eb515db8fc..7e82a4d827 100644 --- a/packages/clerk-js/src/ui/elements/index.ts +++ b/packages/clerk-js/src/ui/elements/index.ts @@ -56,3 +56,4 @@ export * from './InformationBox'; export * from './withAvatarShimmer'; export * from './Card'; export * from './ProfileCard'; +export * from './Gauge';