Skip to content

Commit

Permalink
feat(clerk-js): Introduce Gauge component (#2563)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
octoper authored Jan 11, 2024
1 parent db2d829 commit 5c21e98
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 28 deletions.
2 changes: 2 additions & 0 deletions .changeset/nasty-mirrors-arrive.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 @@ -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();
Expand All @@ -17,45 +20,57 @@ export const MembershipWidget = () => {
}

const totalCount = organization?.membersCount + organization?.pendingInvitationsCount;
const limit = runIfFunctionOrReturn(__unstable_manageBillingMembersLimit);

return (
<Flex
sx={theme => ({
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,
})}
>
<Col>
<Text>Members can be given access to applications.</Text>

{runIfFunctionOrReturn(__unstable_manageBillingMembersLimit) > 0 && (
<Link
sx={t => ({
alignSelf: 'flex-start',
color: t.colors.$primary500,
marginTop: t.space.$1,
})}
onClick={() => router.navigate(runIfFunctionOrReturn(__unstable_manageBillingUrl))}
>
{runIfFunctionOrReturn(__unstable_manageBillingLabel) || 'Manage billing'}
</Link>
<Flex
align='center'
sx={t => ({
[mqu.sm]: {
gap: t.space.$3,
},
gap: t.space.$2,
})}
>
{limit > 0 && (
<Gauge
limit={limit}
value={totalCount}
size='xs'
/>
)}
</Col>
<Col>
<Text
<Flex
sx={t => ({
color: t.colors.$blackAlpha600,
[mqu.sm]: {
flexDirection: 'column',
},
gap: t.space.$0x5,
})}
>
{totalCount} of{' '}
{__unstable_manageBillingMembersLimit
? `${runIfFunctionOrReturn(__unstable_manageBillingMembersLimit)} members`
: 'unlimited'}
</Text>
</Col>
<Text>You can invite {__unstable_manageBillingMembersLimit ? `up to ${limit}` : 'unlimited'} members.</Text>
{limit > 0 && (
<Link
sx={t => ({
fontWeight: t.fontWeights.$medium,
})}
variant='body'
onClick={() => router.navigate(runIfFunctionOrReturn(__unstable_manageBillingUrl))}
>
{runIfFunctionOrReturn(__unstable_manageBillingLabel) || 'Manage billing'}

<Icon icon={ArrowRightIcon} />
</Link>
)}
</Flex>
</Flex>
</Flex>
);
};
100 changes: 100 additions & 0 deletions packages/clerk-js/src/ui/elements/Gauge.tsx
Original file line number Diff line number Diff line change
@@ -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<Size, { svgSize: number; textSize: string }> = {
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 (
<Col
center
sx={theme => ({
'--cl-gauge-inner-stroke-color': theme.colors.$blackAlpha900,
'--cl-gauge-outter-stroke-color': theme.colors.$blackAlpha300,
'> svg': {
transform: 'rotate(-90deg)',
},
})}
>
<svg
width={svgSize}
height={svgSize}
viewBox={`0 0 ${svgSize} ${svgSize}`}
>
<circle
r={normalizedRadius}
cx={radius}
cy={radius}
strokeWidth={strokeWidth}
fill='transparent'
stroke='var(--cl-gauge-outter-stroke-color)'
strokeLinecap='round'
/>
{value >= 0 ? (
<circle
r={normalizedRadius}
cx={radius}
cy={radius}
strokeWidth={strokeWidth}
strokeDasharray={circumference + ' ' + circumference}
strokeDashoffset={offset}
fill='transparent'
stroke='var(--cl-gauge-inner-stroke-color)'
strokeLinecap='round'
/>
) : null}
</svg>
<Text
as='div'
sx={theme => ({
position: 'absolute',
display: 'flex',
fontSize: textSize,
color: theme.colors.$blackAlpha900,
})}
>
{value}
</Text>
</Col>
);
});
1 change: 1 addition & 0 deletions packages/clerk-js/src/ui/elements/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,4 @@ export * from './InformationBox';
export * from './withAvatarShimmer';
export * from './Card';
export * from './ProfileCard';
export * from './Gauge';

0 comments on commit 5c21e98

Please sign in to comment.