Repository navigation
feat(mosaic): mock the combined sign-in flow #10339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
dd4c1c1
82aee0a
e34e156
54a337e
8d11951
7f3c394
8025735
d651734
a55e622
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| --- | ||
| --- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| --- | ||
| --- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -78,6 +78,7 @@ export const header = stylex.create({ | |
| flexDirection: 'column', | ||
| flexGrow: '1', | ||
| rowGap: HEADER_ROW_GAP, | ||
| minWidth: 0, | ||
| }, | ||
| contentBesideCloseButton: { | ||
| paddingInlineStart: `calc(${CLOSE_BUTTON_WIDTH} + ${HEADER_COLUMN_GAP})`, | ||
|
|
@@ -104,11 +105,17 @@ export const header = stylex.create({ | |
| export const headerAlign = stylex.create({ | ||
| start: {}, | ||
| center: { | ||
| alignItems: 'center', | ||
| textAlign: 'center', | ||
| }, | ||
| }); | ||
|
|
||
| export const mediaAlign = stylex.create({ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep fixed-width header children centered. Removing
|
||
| start: {}, | ||
| center: { | ||
| justifyContent: 'center', | ||
| }, | ||
| }); | ||
|
|
||
| const INSET = space['4']; | ||
|
|
||
| export const banner = stylex.create({ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,13 +24,17 @@ type CardElevation = 'card' | 'flush' | 'overlay'; | |
|
|
||
| export type CardSize = keyof typeof slots.sizes; | ||
|
|
||
| export type CardHeaderAlign = keyof typeof slots.headerAlign; | ||
| export type CardAlign = keyof typeof slots.headerAlign; | ||
|
|
||
| const DEFAULT_ELEVATION: CardElevation = 'card'; | ||
|
|
||
| // The enclosing card and the dialog it was rendered in. Context crosses portals, so a card in a | ||
| // dialog opened from inside another card is still the dialog's outermost one. | ||
| const CardContext = React.createContext<{ elevation: CardElevation; dialog: DialogContextValue | null } | null>(null); | ||
| const CardContext = React.createContext<{ | ||
| elevation: CardElevation; | ||
| align: CardAlign; | ||
| dialog: DialogContextValue | null; | ||
| } | null>(null); | ||
|
|
||
| function CardBranding() { | ||
| return ( | ||
|
|
@@ -45,6 +49,8 @@ export interface CardProps extends MosaicComponentProps<'div'> { | |
| elevation?: CardElevation; | ||
| /** The card's width. `lg` is for forms that need the room, like a tag input. @default 'md' */ | ||
| size?: CardSize; | ||
| /** Aligns the content of every `Card.Header` in the card. @default 'start' */ | ||
| align?: CardAlign; | ||
| /** | ||
| * Signs the foot of the card with "Secured by Clerk". An instance that has paid the branding off | ||
| * carries none of it, so a connected surface passes `displayConfig.branded` here. | ||
|
|
@@ -55,20 +61,29 @@ export interface CardProps extends MosaicComponentProps<'div'> { | |
| } | ||
|
|
||
| const Root = React.forwardRef<HTMLDivElement, CardProps>(function CardRoot( | ||
| { elevation = DEFAULT_ELEVATION, size = 'md', renderBranding = true, render, xstyle, children, ...rest }, | ||
| { | ||
| elevation = DEFAULT_ELEVATION, | ||
| size = 'md', | ||
| align = 'start', | ||
| renderBranding = true, | ||
| render, | ||
| xstyle, | ||
| children, | ||
| ...rest | ||
| }, | ||
| ref, | ||
| ) { | ||
| const dialog = React.useContext(DialogContext); | ||
| const enclosing = React.useContext(CardContext); | ||
| const isSurface = (enclosing === null || enclosing.dialog !== dialog) && elevation !== 'flush'; | ||
| const context = React.useMemo(() => ({ elevation, dialog }), [elevation, dialog]); | ||
| const context = React.useMemo(() => ({ elevation, align, dialog }), [elevation, align, dialog]); | ||
| const element = useRender({ | ||
| defaultTagName: 'div', | ||
| render, | ||
| ref, | ||
| props: { | ||
| ...mergeStyleProps( | ||
| themeProps('card-root', { elevation, size }), | ||
| themeProps('card-root', { elevation, size, align }), | ||
| stylex.props( | ||
| reset.base, | ||
| slots.root.base, | ||
|
|
@@ -115,27 +130,19 @@ function HeaderCloseButton() { | |
| ); | ||
| } | ||
|
|
||
| export interface CardHeaderProps extends MosaicComponentProps<'div'> { | ||
| /** Aligns the header's content. @default 'start' */ | ||
| align?: CardHeaderAlign; | ||
| } | ||
|
|
||
| const Header = React.forwardRef<HTMLDivElement, CardHeaderProps>(function CardHeader( | ||
| { align = 'start', render, xstyle, children, ...rest }, | ||
| const Header = React.forwardRef<HTMLDivElement, MosaicComponentProps<'div'>>(function CardHeader( | ||
| { render, xstyle, children, ...rest }, | ||
| ref, | ||
| ) { | ||
| const align = React.useContext(CardContext)?.align ?? 'start'; | ||
| const dialog = React.useContext(DialogContext); | ||
| const hasCloseButton = isInDialog(dialog) && dialog.showCloseButton; | ||
| return useRender({ | ||
| defaultTagName: 'div', | ||
| render, | ||
| ref, | ||
| props: { | ||
| ...mergeStyleProps( | ||
| themeProps('card-header', { align }), | ||
| stylex.props(reset.base, slots.header.base, xstyle), | ||
| rest, | ||
| ), | ||
| ...mergeStyleProps(themeProps('card-header'), stylex.props(reset.base, slots.header.base, xstyle), rest), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preserve the header alignment attribute. Existing themes can target As per coding guidelines, every Mosaic part “reflects its variants and state as data attributes.”
|
||
| children: ( | ||
| <> | ||
| {/* First in the DOM, so it is the first tabbable element and takes the dialog's opening | ||
|
|
@@ -168,12 +175,17 @@ const Media = React.forwardRef<HTMLDivElement, MosaicComponentProps<'div'>>(func | |
| { render, xstyle, ...rest }, | ||
| ref, | ||
| ) { | ||
| const align = React.useContext(CardContext)?.align ?? 'start'; | ||
| return useRender({ | ||
| defaultTagName: 'div', | ||
| render, | ||
| ref, | ||
| props: { | ||
| ...mergeStyleProps(themeProps('card-media'), stylex.props(reset.base, slots.header.media, xstyle), rest), | ||
| ...mergeStyleProps( | ||
| themeProps('card-media'), | ||
| stylex.props(reset.base, slots.header.media, slots.mediaAlign[align], xstyle), | ||
| rest, | ||
| ), | ||
| }, | ||
| }); | ||
| }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,2 @@ | ||
| export { Card } from './card'; | ||
| export type { CardBannerProps, CardHeaderAlign, CardHeaderProps, CardProps, CardSize } from './card'; | ||
| export type { CardAlign, CardBannerProps, CardProps, CardSize } from './card'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| export { SignInView } from './sign-in.view'; | ||
| export type { | ||
| SignInMethod, | ||
| SignInOtpChannel, | ||
| SignInSocialProvider, | ||
| SignInStep, | ||
| SignInViewProps, | ||
| SignUpDetails, | ||
| } from './sign-in.types'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| export const signInMessages = { | ||
| start: { | ||
| title: 'Sign in to {applicationName}', | ||
| titleCombined: 'Continue to {applicationName}', | ||
| subtitle: 'Welcome back! Please sign in to continue', | ||
| subtitleCombined: 'Welcome! Sign in or create an account to continue', | ||
| actionText: 'Don’t have an account?', | ||
| actionLink: 'Sign up', | ||
| }, | ||
| socialButton: 'Continue with {provider}', | ||
| dividerText: 'or', | ||
| password: { | ||
| title: 'Enter your password', | ||
| subtitle: 'Enter the password associated with your account', | ||
| actionLink: 'Forgot password?', | ||
| }, | ||
| emailCode: { | ||
| title: 'Check your email', | ||
| subtitle: 'Enter the verification code sent to {identifier}', | ||
| formTitle: 'Verification code', | ||
| resendText: 'Didn’t receive a code?', | ||
| resend: 'Resend', | ||
| resendCountdown: 'Resend ({seconds})', | ||
| }, | ||
| totpMfa: { | ||
| title: 'Two-step verification', | ||
| subtitle: 'To continue, please enter the verification code generated by your authenticator app', | ||
| formTitle: 'Verification code', | ||
| }, | ||
| backupCodeMfa: { | ||
| title: 'Enter a backup code', | ||
| subtitle: 'Your backup code is the one you got when setting up two-step authentication.', | ||
| }, | ||
| alternativeMethods: { | ||
| title: 'Use another method', | ||
| subtitle: 'Facing issues? You can use any of these methods to sign in.', | ||
| actionText: 'Don’t have any of these?', | ||
| actionLink: 'Get help', | ||
| blockButtonEmailCode: 'Email code to {identifier}', | ||
| blockButtonPhoneCode: 'Send SMS code to {identifier}', | ||
| blockButtonPassword: 'Sign in with your password', | ||
| blockButtonPasskey: 'Sign in with your passkey', | ||
| blockButtonTotp: 'Use your authenticator app', | ||
| blockButtonBackupCode: 'Use a backup code', | ||
| getHelp: { | ||
| title: 'Get help', | ||
| content: | ||
| 'If you’re experiencing difficulty signing into your account, email us and we will work with you to restore access as soon as possible.', | ||
| blockButtonEmailSupport: 'Email support', | ||
| }, | ||
| }, | ||
| signUp: { | ||
| title: 'Create your account', | ||
| subtitle: 'Welcome! Please fill in the details to get started.', | ||
| actionText: 'Already have an account?', | ||
| actionLink: 'Sign in', | ||
| }, | ||
| verifyEmail: { | ||
| title: 'Verify your email', | ||
| subtitle: 'Enter the verification code sent to {identifier}', | ||
| formTitle: 'Verification code', | ||
| resendText: 'Didn’t receive a code?', | ||
| resend: 'Resend', | ||
| resendCountdown: 'Resend ({seconds})', | ||
| }, | ||
| backButton: 'Back', | ||
| footerActionLinkUseAnotherMethod: 'Use another method', | ||
| formButtonPrimary: 'Continue', | ||
| formFieldActionHidePassword: 'Hide password', | ||
| formFieldActionShowPassword: 'Show password', | ||
| formFieldInputPlaceholderEmailAddress: 'Enter your email address', | ||
| formFieldInputPlaceholderPassword: 'Enter your password', | ||
| formFieldInputPlaceholderFirstName: 'First name', | ||
| formFieldInputPlaceholderLastName: 'Last name', | ||
| formFieldLabelEmailAddress: 'Email address', | ||
| formFieldLabelPassword: 'Password', | ||
| formFieldLabelFirstName: 'First name', | ||
| formFieldLabelLastName: 'Last name', | ||
| formFieldLabelBackupCode: 'Backup code', | ||
| identityPreviewEditButton: 'Edit email address', | ||
| verifying: 'Verifying', | ||
| } as const; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import * as stylex from '@stylexjs/stylex'; | ||
|
|
||
| import { colorVars, space } from '../../tokens.stylex'; | ||
|
|
||
| export const styles = stylex.create({ | ||
| logo: { | ||
| display: 'block', | ||
| objectFit: 'contain', | ||
| height: space['8'], | ||
| maxWidth: '100%', | ||
| }, | ||
| providerIcon: { | ||
| display: 'block', | ||
| objectFit: 'contain', | ||
| height: space['4'], | ||
| width: space['4'], | ||
| }, | ||
| divider: { | ||
| gap: space['4'], | ||
| alignItems: 'center', | ||
| display: 'flex', | ||
| }, | ||
| dividerLine: { | ||
| backgroundColor: colorVars['--cl-color-border'], | ||
| flexGrow: 1, | ||
| height: '1px', | ||
| }, | ||
| identity: { | ||
| gap: space['1'], | ||
| alignItems: 'center', | ||
| display: 'flex', | ||
| justifyContent: 'center', | ||
| minWidth: 0, | ||
| }, | ||
| identityText: { | ||
| minWidth: 0, | ||
| }, | ||
| fieldStack: { | ||
| gap: space['4'], | ||
| display: 'grid', | ||
| }, | ||
| nameRow: { | ||
| gap: space['4'], | ||
| display: 'grid', | ||
| gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', | ||
| }, | ||
| forgotPassword: { | ||
| alignSelf: 'start', | ||
| justifySelf: 'start', | ||
| }, | ||
| footerText: { | ||
| textAlign: 'center', | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a supported styling prop in this test.
Card.DescriptionusesMosaicComponentProps, which excludesstyle. This line fails type-checking in the test TypeScript project. Use anxstyleatom or style a native rendered element instead. (raw.githubusercontent.com)As per coding guidelines, “A part exposes no
classNameorstyleprop.”Source: Coding guidelines