Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/localizations/src/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,23 @@ export const enUS: LocalizationResource = {
title: 'Choose an account',
titleWithoutPersonal: 'Choose an organization',
},
oauthConsent: {
action__allow: 'Allow',
action__deny: 'Deny',
offlineAccessNotice: " You'll stay signed in until you sign out or revoke access.",
redirectNotice: 'If you allow access, this app will redirect you to {{domainAction}}.',
redirectUriModal: {
subtitle: 'Make sure you trust {{applicationName}} and that this URL belongs to {{applicationName}}.',
title: 'Redirect URL',
},
scopeList: {
title: 'This will allow {{applicationName}} access to:',
},
subtitle: 'wants to access {{applicationName}} on behalf of {{identifier}}',
viewFullUrl: 'View full URL',
warning:
'Make sure that you trust {{applicationName}} ({{domainAction}}). You may be sharing sensitive data with this site or app.',
},
organizationProfile: {
apiKeysPage: {
title: 'API keys',
Expand Down
16 changes: 16 additions & 0 deletions packages/shared/src/types/localization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1252,6 +1252,22 @@ export type __internal_LocalizationResource = {
suggestionsAcceptedLabel: LocalizationValue;
action__createOrganization: LocalizationValue;
};
oauthConsent: {
subtitle: LocalizationValue<'applicationName' | 'identifier'>;
scopeList: {
title: LocalizationValue<'applicationName'>;
};
action__deny: LocalizationValue;
action__allow: LocalizationValue;
warning: LocalizationValue<'applicationName' | 'domainAction'>;
redirectNotice: LocalizationValue<'domainAction'>;
offlineAccessNotice: LocalizationValue;
viewFullUrl: LocalizationValue;
redirectUriModal: {
title: LocalizationValue;
subtitle: LocalizationValue<'applicationName'>;
};
};
unstable__errors: UnstableErrors;
dates: {
previous6Days: LocalizationValue<'date'>;
Expand Down
83 changes: 83 additions & 0 deletions packages/ui/src/components/OAuthConsent/InlineAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';

import { Text } from '@/ui/customizables';
import { Tooltip } from '@/ui/elements/Tooltip';

type InlineActionProps = {
text: string;
actionText: string;
onClick: () => void;
tooltipText: string;
};

export function InlineAction({ text, actionText, onClick, tooltipText }: InlineActionProps) {
const idx = text.indexOf(actionText);
if (idx === -1) {
return <>{text}</>;
}

let before = text.slice(0, idx);
let after = text.slice(idx + actionText.length);

// Pull adjacent parentheses into the action span so they don't wrap separately
let prefix = '';
let suffix = '';
if (before.endsWith('(')) {
before = before.slice(0, -1);
prefix = '(';
}
if (after.startsWith(')')) {
after = after.slice(1);
suffix = ')';
}

const actionContent = (
<Tooltip.Root>
<Tooltip.Trigger>
<Text
as='span'
role='button'
tabIndex={0}
aria-label={tooltipText}
variant='caption'
onClick={onClick}
onKeyDown={(e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onClick();
}
}}
sx={{
textDecoration: 'underline',
textDecorationStyle: 'dotted',
cursor: 'pointer',
outline: 'none',
display: 'inline-block',
}}
>
{actionText}
</Text>
</Tooltip.Trigger>
<Tooltip.Content text={tooltipText} />
</Tooltip.Root>
);

return (
<>
{before}
{prefix || suffix ? (
<Text
as='span'
sx={{ whiteSpace: 'nowrap' }}
>
{prefix}
{actionContent}
{suffix}
</Text>
) : (
actionContent
)}
{after}
</>
);
}
123 changes: 123 additions & 0 deletions packages/ui/src/components/OAuthConsent/ListGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { Box, Text, descriptors } from '@/ui/customizables';
import { common } from '@/ui/styledSystem';
import { colors } from '@/ui/utils/colors';
import type { ComponentProps } from 'react';

export function ListGroup({ children, sx, ...props }: Omit<ComponentProps<typeof Box>, 'elementDescriptor'>) {
return (
<Box
{...props}
sx={[
t => ({
textAlign: 'start',
borderWidth: t.borderWidths.$normal,
borderStyle: t.borderStyles.$solid,
borderColor: t.colors.$borderAlpha100,
borderRadius: t.radii.$lg,
overflow: 'hidden',
}),
sx,
]}
elementDescriptor={descriptors.listGroup}
>
{children}
</Box>
);
}

export function ListGroupHeader({ children, sx, ...props }: Omit<ComponentProps<typeof Box>, 'elementDescriptor'>) {
return (
<Box
{...props}
sx={[
t => ({
padding: t.space.$3,
background: common.mergedColorsBackground(
colors.setAlpha(t.colors.$colorBackground, 1),
t.colors.$neutralAlpha50,
),
}),
sx,
]}
elementDescriptor={descriptors.listGroupHeader}
>
{children}
</Box>
);
}

export function ListGroupHeaderTitle(props: Omit<ComponentProps<typeof Text>, 'elementDescriptor'>) {
return (
<Text
{...props}
variant='subtitle'
elementDescriptor={descriptors.listGroupHeaderTitle}
/>
);
}

export function ListGroupContent({
children,
sx,
...props
}: Omit<ComponentProps<typeof Box>, 'as' | 'elementDescriptor'>) {
return (
<Box
{...props}
as='ul'
sx={[t => ({ margin: t.sizes.$none, padding: t.sizes.$none }), sx]}
elementDescriptor={descriptors.listGroupContent}
>
{children}
</Box>
);
}

export function ListGroupItem({
children,
sx,
...props
}: Omit<ComponentProps<typeof Box>, 'as' | 'elementDescriptor'>) {
return (
<Box
{...props}
as='li'
sx={[
t => ({
display: 'flex',
alignItems: 'baseline',
paddingInline: t.space.$3,
paddingBlock: t.space.$2,
borderTopWidth: t.borderWidths.$normal,
borderTopStyle: t.borderStyles.$solid,
borderTopColor: t.colors.$borderAlpha100,
'&::before': {
content: '""',
display: 'inline-block',
width: t.space.$1,
height: t.space.$1,
background: t.colors.$colorMutedForeground,
borderRadius: t.radii.$circle,
transform: 'translateY(-0.1875rem)',
marginInlineEnd: t.space.$2,
flexShrink: 0,
},
}),
sx,
]}
elementDescriptor={descriptors.listGroupItem}
>
{children}
</Box>
);
}

export function ListGroupItemLabel(props: Omit<ComponentProps<typeof Text>, 'elementDescriptor'>) {
return (
<Text
{...props}
variant='subtitle'
elementDescriptor={descriptors.listGroupItemLabel}
/>
);
}
100 changes: 100 additions & 0 deletions packages/ui/src/components/OAuthConsent/LogoGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { descriptors, Flex } from '@/ui/customizables';
import type { ComponentProps } from 'react';
import type { ThemableCssProp } from '@/ui/styledSystem';
import { Box, Icon } from '@/ui/customizables';
import { LockDottedCircle } from '@/ui/icons';
import { colors } from '@/ui/utils/colors';
import { common } from '@/ui/styledSystem';

export function LogoGroup({ children }: { children: React.ReactNode }) {
return (
<Flex
justify='center'
align='center'
gap={4}
sx={t => ({
marginBlockEnd: t.space.$6,
})}
elementDescriptor={descriptors.logoGroup}
>
{children}
</Flex>
);
}

export function LogoGroupItem({ children, sx, ...props }: ComponentProps<typeof Flex>) {
return (
<Flex
{...props}
sx={[{ flex: 1 }, sx]}
elementDescriptor={descriptors.logoGroupItem}
>
{children}
</Flex>
);
}

export function LogoGroupIcon({ size = 'md', sx }: { size?: 'sm' | 'md'; sx?: ThemableCssProp }) {
const scale: ThemableCssProp = t => {
const value = size === 'sm' ? t.space.$6 : t.space.$12;
return {
width: value,
height: value,
};
};

return (
<Box
sx={t => [
{
background: common.mergedColorsBackground(
colors.setAlpha(t.colors.$colorBackground, 1),
t.colors.$neutralAlpha50,
),
borderRadius: t.radii.$circle,
borderWidth: t.borderWidths.$normal,
borderStyle: t.borderStyles.$solid,
borderColor: t.colors.$borderAlpha100,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
scale,
sx,
]}
elementDescriptor={descriptors.logoGroupIcon}
>
<Icon
icon={LockDottedCircle}
sx={t => ({
color: t.colors.$primary500,
})}
/>
</Box>
);
}

export function LogoGroupSeparator() {
return (
<Box
as='svg'
// @ts-ignore - valid SVG attribute
fill='none'
viewBox='0 0 16 2'
height={2}
aria-hidden
sx={t => ({
color: t.colors.$colorMutedForeground,
})}
elementDescriptor={descriptors.logoGroupSeparator}
>
<path
stroke='currentColor'
strokeDasharray='0.1 4'
strokeLinecap='round'
strokeWidth='2'
d='M1 1h14'
/>
</Box>
);
}
Loading
Loading