Merge pull request #29972 from overleaf/td-ciam-onboarding

Unified access onboarding data collection pages

GitOrigin-RevId: c56a3d52f749883eeb2302e22aaf6bdf1239160c
This commit is contained in:
Tim Down
2025-12-05 09:53:53 +00:00
committed by Copybot
parent 7a57ef00cb
commit 452d854d5b
33 changed files with 1273 additions and 225 deletions
@@ -0,0 +1,91 @@
include terms_of_service
include recaptcha
include ../../../modules/saas-authentication/app/views/_mixins
mixin ciamLogo
header.ciam-logo
a.brand.overleaf-ds-logo.ciam-image-link(href='/')
span.visually-hidden Overleaf
mixin ciamCardSeparator
hr.ciam-card-separator
mixin ciamCardFooter
section.ciam-card-footer
+ciamCardSeparator
.ciam-footer-ds-logo
a.ciam-image-link(
href='https://www.digital-science.com/'
target='_blank'
rel='noopener noreferrer'
)
img(
src=buildImgPath('digital-science/digital-science.svg')
alt='Digital Science — home'
)
p
| !{translate('advancing_research_with', null, [{ name: 'a', attrs: { href: 'https://www.overleaf.com/', target: '_blank', rel: 'noopener noreferrer' }}, { name: 'a', attrs: { href: 'https://www.papersapp.com/', target: '_blank', rel: 'noopener noreferrer' }}])}
mixin ciamTermsOfServiceAgreement
p
+termsOfServiceAgreementContent
mixin ciamRecaptchaConditions
p
+recaptchaConditionsContent
mixin ciamCustomFormDangerMessage(key)
div(
class='notification ciam-notification notification-type-error'
hidden
data-ol-custom-form-message=key
role='alert'
aria-live='polite'
)
.notification-icon
ph-warning-circle(aria-hidden='true')
.notification-content.text-left
block
mixin ciamSamlErrorNotLoggedIn(error)
+samlErrorNotLoggedIn(error)
ph-warning-circle(aria-hidden='true')
mixin ciamFooter
footer
.footer-links
a(href='https://www.overleaf.com/legal#Privacy') Privacy
a(href='https://www.overleaf.com/legal#Terms') Terms
mixin ciamErrorNotification
.notification.notification-ds.notification-type-error(
role='alert'
aria-live='polite'
)
.notification-icon
ph-warning-circle(aria-hidden='true')
.notification-content-and-cta
.notification-content
block
mixin ciamInfoNotification
.notification.notification-ds.notification-type-info(
role='alert'
aria-live='polite'
)
.notification-icon
ph-info(aria-hidden='true')
.notification-content-and-cta
.notification-content
block
mixin ciamOrDivider
p.ciam-login-register-or-text-container= translate('login_register_or').toUpperCase()
mixin ciamButtonContentLoading(loadingLabel)
span.button-content
span(data-ol-inflight='idle')
block
span.spinner-container(hidden data-ol-inflight='pending')
.loading-spinner-large.spinner-border.spinner-border-sm(aria-hidden='true')
span.visually-hidden= loadingLabel
@@ -7,7 +7,6 @@ import {
ComponentProps,
FormEvent,
MouseEventHandler,
useEffect,
useState,
} from 'react'
import { Trans, useTranslation } from 'react-i18next'
@@ -21,6 +20,8 @@ import DSButton from '@/shared/components/ds/ds-button'
import CIAMSixDigitsInput from '@/features/settings/components/emails/ciam-six-digits-input'
import OLFormText from '@/shared/components/ol/ol-form-text'
import DSFormText from '@/shared/components/ds/ds-form-text'
import { CaretRight } from '@phosphor-icons/react'
import DSNotification from '@/shared/components/ds/ds-notification'
type Feedback = {
type: 'input' | 'alert'
@@ -177,11 +178,11 @@ export function ConfirmEmailForm({
if (successRedirectPath && successButtonText && successMessage) {
return (
<ConfirmEmailSuccessfullForm
<ConfirmEmailSuccessfulForm
successMessage={successMessage}
successButtonText={successButtonText}
redirectTo={successRedirectPath}
autoRedirect={isCiam ? 8000 : false}
isCiam={Boolean(isCiam)}
/>
)
}
@@ -196,6 +197,8 @@ export function ConfirmEmailForm({
const SixDigits = isCiam ? CIAMSixDigitsInput : OLSixDigitsInput
const FormText = isCiam ? DSFormText : OLFormText
const NotificationComponent = isCiam ? DSNotification : Notification
return (
<form
onSubmit={submitHandler}
@@ -205,7 +208,7 @@ export function ConfirmEmailForm({
>
<div className="confirm-email-form-inner">
{(feedback?.type === 'alert' || outerErrorDisplay) && (
<Notification
<NotificationComponent
ariaLive="polite"
className="confirm-email-alert"
type={outerErrorDisplay ? 'error' : feedback!.style}
@@ -328,41 +331,43 @@ function Title({
return <h5 className="h5">{t('confirm_your_email')}</h5>
}
function ConfirmEmailSuccessfullForm({
function ConfirmEmailSuccessfulForm({
successMessage,
successButtonText,
redirectTo,
autoRedirect = false,
isCiam,
}: {
successMessage: React.ReactNode
successButtonText: string
redirectTo: string
autoRedirect?: number | false
isCiam: boolean
}) {
const location = useLocation()
const submitHandler = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
location.assign(redirectTo)
}
useEffect(() => {
if (autoRedirect) {
const timer = setTimeout(() => location.assign(redirectTo), autoRedirect)
return () => clearTimeout(timer)
}
}, [autoRedirect, location, redirectTo])
const button = isCiam ? (
<DSButton
type="submit"
variant="primary"
size="lg"
className="w-100"
trailingIcon={<CaretRight size={24} />}
>
{successButtonText}
</DSButton>
) : (
<OLButton type="submit" variant="primary">
{successButtonText}
</OLButton>
)
return (
<form onSubmit={submitHandler} className="confirm-email-success-form">
<div aria-live="polite">{successMessage}</div>
{!autoRedirect && (
<div className="form-actions">
<OLButton type="submit" variant="primary">
{successButtonText}
</OLButton>
</div>
)}
<div className="form-actions">{button}</div>
</form>
)
}
@@ -5,6 +5,9 @@ import { escapeRegExp } from 'lodash'
import OLFormControl from '@/shared/components/ol/ol-form-control'
import { DropdownItem } from '@/shared/components/dropdown/dropdown-menu'
import OLFormLabel from '@/shared/components/ol/ol-form-label'
import DSFormLabel from '@/shared/components/ds/ds-form-label'
import DSFormControl from '@/shared/components/ds/ds-form-control'
import { Check } from '@phosphor-icons/react'
type DownshiftInputProps = {
highlightMatches?: boolean
@@ -16,6 +19,7 @@ type DownshiftInputProps = {
inputRef?: React.ForwardedRef<HTMLInputElement>
showLabel?: boolean
showSuggestedText?: boolean
isCiam?: boolean
} & React.InputHTMLAttributes<HTMLInputElement>
const filterItemsByInputValue = (
@@ -35,6 +39,7 @@ function Downshift({
inputRef,
showLabel = false,
showSuggestedText = false,
isCiam = false,
}: DownshiftInputProps) {
const [inputItems, setInputItems] = useState(items)
@@ -76,7 +81,73 @@ function Downshift({
)
}
const shouldOpen = isOpen && inputItems.length
const TickIcon = function () {
return isCiam ? <Check /> : 'check'
}
const shouldOpen = isOpen && inputItems.length > 0
const dropdown = (
<ul
{...getMenuProps()}
className={classnames('dropdown-menu', 'select-dropdown-menu', {
show: shouldOpen,
'ciam-dropdown-menu': isCiam,
})}
>
{showSuggestedText && inputItems.length > 0 && (
<li>
<DropdownItem as="span" role={undefined} disabled>
{itemsTitle}
</DropdownItem>
</li>
)}
{inputItems.map((item, index) => (
// eslint-disable-next-line jsx-a11y/role-supports-aria-props
<li
key={`${item}${index}`}
{...getItemProps({ item, index })}
aria-selected={selectedItem === item}
>
<DropdownItem
as="span"
role={undefined}
className={classnames({
active: selectedItem === item,
'dropdown-item-highlighted': highlightedIndex === index,
})}
trailingIcon={selectedItem === item ? <TickIcon /> : undefined}
>
{highlightMatchedCharacters(item, inputValue)}
</DropdownItem>
</li>
))}
</ul>
)
if (isCiam) {
return (
<div className="dropdown d-block">
<DSFormLabel
{...getLabelProps()}
className={showLabel ? '' : 'visually-hidden'}
>
{label}
</DSFormLabel>
<DSFormControl
{...getInputProps({
onChange: (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value)
},
ref: inputRef,
})}
placeholder={placeholder}
disabled={disabled}
/>
{dropdown}
</div>
)
}
return (
<div className={classnames('dropdown', 'd-block')}>
@@ -98,40 +169,7 @@ function Downshift({
disabled={disabled}
/>
</div>
<ul
{...getMenuProps()}
className={classnames('dropdown-menu', 'select-dropdown-menu', {
show: shouldOpen,
})}
>
{showSuggestedText && inputItems.length && (
<li>
<DropdownItem as="span" role={undefined} disabled>
{itemsTitle}
</DropdownItem>
</li>
)}
{inputItems.map((item, index) => (
// eslint-disable-next-line jsx-a11y/role-supports-aria-props
<li
key={`${item}${index}`}
{...getItemProps({ item, index })}
aria-selected={selectedItem === item}
>
<DropdownItem
as="span"
role={undefined}
className={classnames({
active: selectedItem === item,
'dropdown-item-highlighted': highlightedIndex === index,
})}
trailingIcon={selectedItem === item ? 'check' : undefined}
>
{highlightMatchedCharacters(item, inputValue)}
</DropdownItem>
</li>
))}
</ul>
{dropdown}
</div>
)
}
@@ -0,0 +1,35 @@
import classNames from 'classnames'
import { useTranslation } from 'react-i18next'
export function CiamStepper({
steps,
active,
}: {
steps: number
active: number
}) {
const { t } = useTranslation()
return (
<div className="ciam-stepper-container">
<div
className="ciam-stepper"
role="progressbar"
aria-label={t('progress_bar_percentage')}
aria-valuenow={active + 1}
aria-valuemax={steps}
tabIndex={0}
>
{Array.from({ length: steps }).map((_, i) => (
<div
key={i}
className={classNames({
step: true,
active: i === active,
completed: i < active,
})}
/>
))}
</div>
</div>
)
}
@@ -6,6 +6,7 @@ import classNames from 'classnames'
type DSButtonProps = Pick<
ButtonProps,
| 'children'
| 'className'
| 'disabled'
| 'href'
| 'id'
@@ -32,6 +33,7 @@ const DSButton = forwardRef<HTMLButtonElement, DSButtonProps>(
(
{
children,
className,
leadingIcon,
isLoading = false,
loadingLabel,
@@ -44,7 +46,7 @@ const DSButton = forwardRef<HTMLButtonElement, DSButtonProps>(
) => {
const { t } = useTranslation()
const buttonClassName = classNames('d-inline-grid btn-ds', {
const buttonClassName = classNames('d-inline-grid btn-ds', className, {
'button-loading': isLoading,
})
@@ -0,0 +1,27 @@
import { Form, FormCheckProps } from 'react-bootstrap'
import classNames from 'classnames'
type DSFormCheckboxProps = Pick<
FormCheckProps,
| 'disabled'
| 'checked'
| 'onChange'
| 'label'
| 'name'
| 'value'
| 'id'
| 'className'
>
function DSFormCheckbox(props: DSFormCheckboxProps) {
const { className, ...rest } = props
return (
<Form.Check
type="checkbox"
className={classNames('form-check-ds', className)}
{...rest}
/>
)
}
export default DSFormCheckbox
@@ -1,18 +1,48 @@
import { forwardRef } from 'react'
import React, { forwardRef } from 'react'
import { Form, FormControlProps } from 'react-bootstrap'
import classnames from 'classnames'
interface ButtonProps extends FormControlProps {
size?: 'lg'
interface DSFormControlProps extends FormControlProps {
prepend?: React.ReactNode
append?: React.ReactNode
}
const DSFormControl = forwardRef<HTMLInputElement, ButtonProps>(
({ className, ...props }, ref) => {
const DSFormControl = forwardRef<HTMLInputElement, DSFormControlProps>(
({ prepend, append, className, ...props }, ref) => {
if (prepend || append) {
const wrapperClassNames = classnames(
'form-control-wrapper-ds form-control-wrapper-lg-ds',
{
'form-control-wrapper-disabled-ds': props.disabled,
}
)
const formControlClassNames = classnames('form-control-ds', className, {
'form-control-offset-start-ds': prepend,
'form-control-offset-end-ds': append,
})
return (
<div className={wrapperClassNames}>
{prepend && (
<span className="form-control-start-icon-ds">{prepend}</span>
)}
<Form.Control
{...props}
className={formControlClassNames}
ref={ref}
/>
{append && <span className="form-control-end-icon-ds">{append}</span>}
</div>
)
}
return (
<Form.Control
ref={ref}
{...props}
size="lg"
className={classnames('form-control-ds', className)}
{...props}
/>
)
}
@@ -0,0 +1,27 @@
import { Form, FormCheckProps } from 'react-bootstrap'
import classNames from 'classnames'
type DSFormRadioProps = Pick<
FormCheckProps,
| 'disabled'
| 'checked'
| 'onChange'
| 'label'
| 'name'
| 'value'
| 'id'
| 'className'
>
function DSFormRadio(props: DSFormRadioProps) {
const { className, ...rest } = props
return (
<Form.Check
type="radio"
className={classNames('form-check-ds', className)}
{...rest}
/>
)
}
export default DSFormRadio
@@ -0,0 +1,28 @@
import Notification, {
NotificationProps,
} from '@/shared/components/notification'
import classNames from 'classnames'
import { WarningCircle, Info } from '@phosphor-icons/react'
type DSNotificationProps = Pick<
NotificationProps,
'content' | 'customIcon' | 'className'
> & {
type: 'info' | 'error'
}
function DSNotification(props: DSNotificationProps) {
const { type, className, ...rest } = props
const customIcon = type === 'info' ? <Info /> : <WarningCircle />
return (
<Notification
type={type}
customIcon={customIcon}
className={classNames('notification-ds', className)}
{...rest}
/>
)
}
export default DSNotification
@@ -0,0 +1,46 @@
import React from 'react'
type DSSelectionGroupItemProps<ValueType> = {
checked?: boolean
disabled?: boolean
onChange?: (value: ValueType) => void
required?: boolean
children: React.ReactNode
} & (
| {
type: 'radio'
name: string
value: ValueType
}
| {
type: 'checkbox'
name?: string
value?: ValueType
}
)
export default function DSSelectionGroupItem(
props: DSSelectionGroupItemProps<any>
) {
const handleChange = () => {
props.onChange?.(props.value)
}
return (
<li className="selection-group-ds-item">
<label>
<input
type={props.type}
className="check-input-ds"
name={props.name}
value={props.value}
checked={props.checked}
disabled={props.disabled}
required={props.required}
onChange={handleChange}
/>
{props.children}
</label>
</li>
)
}
@@ -0,0 +1,20 @@
import classNames from 'classnames'
export default function DSSelectionGroup({
legend,
className,
children,
}: {
legend?: React.ReactNode
className?: string
children: React.ReactNode
}) {
return (
<fieldset className={classNames('selection-group-ds', className)}>
{legend ? (
<legend className="selection-group-ds-legend">{legend}</legend>
) : null}
<ul className="selection-group-ds-list">{children}</ul>
</fieldset>
)
}
@@ -17,16 +17,31 @@ const CiamLayout: FC<Props> = ({ children }: Props) => (
<section className="ciam-card-footer">
<hr className="ciam-card-separator" />
<div className="ciam-footer-ds-logo">
<img src={dsLogo} alt="Digital Science — home" />
<a
href="https://www.digital-science.com/"
target="_blank"
rel="noopener noreferrer"
className="ciam-image-link"
>
<img src={dsLogo} alt="Digital Science — home" />
</a>
</div>
<p>
<Trans
i18nKey="advancing_research_with"
components={[
// eslint-disable-next-line jsx-a11y/anchor-has-content,react/jsx-key
<a href="https://www.overleaf.com" />,
<a
href="https://www.overleaf.com/"
target="_blank"
rel="noopener noreferrer"
/>,
// eslint-disable-next-line jsx-a11y/anchor-has-content,react/jsx-key
<a href="https://www.papersapp.com/" />,
<a
href="https://www.papersapp.com/"
target="_blank"
rel="noopener noreferrer"
/>,
]}
/>
</p>
@@ -12,8 +12,12 @@ import { useTranslation } from 'react-i18next'
import { Form } from 'react-bootstrap'
import FormControl from '@/shared/components/form/form-control'
import MaterialIcon from '@/shared/components/material-icon'
import { CaretUp, CaretDown, Check } from '@phosphor-icons/react'
import { DropdownItem } from '@/shared/components/dropdown/dropdown-menu'
import OLSpinner from './ol/ol-spinner'
import DSFormLabel from '@/shared/components/ds/ds-form-label'
import DSFormGroup from '@/shared/components/ds/ds-form-group'
import DSFormControl from '@/shared/components/ds/ds-form-control'
export type SelectProps<T> = {
// The items rendered as dropdown options.
@@ -51,6 +55,8 @@ export type SelectProps<T> = {
selectedIcon?: boolean
// testId for the input element
dataTestId?: string
// CIAM-specific layout
isCiam?: boolean
}
export const Select = <T,>({
@@ -70,6 +76,7 @@ export const Select = <T,>({
loading = false,
selectedIcon = false,
dataTestId,
isCiam,
}: SelectProps<T>) => {
const [selectedItem, setSelectedItem] = useState<T | undefined | null>(
defaultItem
@@ -145,6 +152,80 @@ export const Select = <T,>({
value = defaultText
}
const TickIcon = function () {
return isCiam ? <Check /> : 'check'
}
const dropdown = (
<ul
{...getMenuProps({ disabled })}
className={classNames('dropdown-menu', {
'w-100': !isCiam,
'ciam-dropdown-menu': isCiam,
show: isOpen,
})}
>
{isOpen &&
items?.map((item, index) => {
// We're using an actual disabled button so we don't need the
// aria-disabled prop
const { 'aria-disabled': disabled, ...itemProps } = getItemProps({
item,
index,
})
return (
<li role="none" key={itemToKey(item)}>
<DropdownItem
as="button"
type="button"
className={classNames({
'select-highlighted': highlightedIndex === index,
})}
active={selectedItem === item}
trailingIcon={
selectedIcon && selectedItem === item ? (
<TickIcon />
) : undefined
}
description={itemToSubtitle ? itemToSubtitle(item) : undefined}
{...itemProps}
disabled={disabled}
>
{itemToString(item)}
</DropdownItem>
</li>
)
})}
</ul>
)
if (isCiam) {
return (
<div className="select-wrapper" ref={rootRef}>
<DSFormGroup>
{label ? (
<DSFormLabel {...getLabelProps()}>
{label} {optionalLabel && <span>({t('optional')})</span>}{' '}
{loading && <OLSpinner size="sm" />}
</DSFormLabel>
) : null}
<DSFormControl
data-testid={dataTestId}
{...getToggleButtonProps({
disabled,
onKeyDown,
className: 'select-trigger',
})}
value={value}
readOnly
append={isOpen ? <CaretUp /> : <CaretDown />}
/>
{dropdown}
</DSFormGroup>
</div>
)
}
return (
<div className="select-wrapper" ref={rootRef}>
{label ? (
@@ -172,42 +253,7 @@ export const Select = <T,>({
/>
}
/>
<ul
{...getMenuProps({ disabled })}
className={classNames('dropdown-menu w-100', { show: isOpen })}
>
{isOpen &&
items?.map((item, index) => {
// We're using an actual disabled button so we don't need the
// aria-disabled prop
const { 'aria-disabled': disabled, ...itemProps } = getItemProps({
item,
index,
})
return (
<li role="none" key={itemToKey(item)}>
<DropdownItem
as="button"
type="button"
className={classNames({
'select-highlighted': highlightedIndex === index,
})}
active={selectedItem === item}
trailingIcon={
selectedIcon && selectedItem === item ? 'check' : undefined
}
description={
itemToSubtitle ? itemToSubtitle(item) : undefined
}
{...itemProps}
disabled={disabled}
>
{itemToString(item)}
</DropdownItem>
</li>
)
})}
</ul>
{dropdown}
</div>
)
}
@@ -0,0 +1,6 @@
import { useSplitTestContext } from '@/shared/context/split-test-context'
export default function useIsCiam() {
const { splitTestVariants } = useSplitTestContext()
return splitTestVariants.uniaccessphase1 === 'enabled'
}
@@ -1,5 +1,5 @@
import { Meta } from '@storybook/react'
import { figmaDesignUrl } from '../../../.storybook/utils/figma-design-url'
import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
import DSButton from '@/shared/components/ds/ds-button'
type Args = React.ComponentProps<typeof DSButton>
@@ -0,0 +1,27 @@
import type { Meta, StoryObj } from '@storybook/react'
import DSFormCheckbox from '@/shared/components/ds/ds-form-checkbox'
import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
const meta: Meta<typeof DSFormCheckbox> = {
title: 'Shared / DS Components / Form',
component: DSFormCheckbox,
parameters: {
controls: {
include: ['disabled'],
},
},
}
export default meta
type Story = StoryObj<typeof DSFormCheckbox>
export const Checkbox: Story = {
args: {
id: 'id-1',
label: 'Label',
disabled: false,
},
parameters: figmaDesignUrl(
'https://www.figma.com/design/aJQlecvqCS9Ry8b6JA1lQN/DS---Components?node-id=6135-5430&m=dev'
),
}
@@ -1,5 +1,5 @@
import { Meta } from '@storybook/react'
import { figmaDesignUrl } from '../../../.storybook/utils/figma-design-url'
import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
import DSFormControl from '@/shared/components/ds/ds-form-control'
import DSFormText from '@/shared/components/ds/ds-form-text'
import DSFormGroup from '@/shared/components/ds/ds-form-group'
@@ -26,7 +26,7 @@ export const FormControl = ({ textType, value, ...args }: Args) => {
}
const meta: Meta<typeof FormControl> = {
title: 'Shared / DS Components',
title: 'Shared / DS Components / Form',
component: FormControl,
argTypes: {
disabled: { control: 'boolean' },
@@ -0,0 +1,27 @@
import type { Meta, StoryObj } from '@storybook/react'
import DSFormRadio from '@/shared/components/ds/ds-form-radio'
import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
const meta: Meta<typeof DSFormRadio> = {
title: 'Shared / DS Components / Form',
component: DSFormRadio,
parameters: {
controls: {
include: ['disabled'],
},
},
}
export default meta
type Story = StoryObj<typeof DSFormRadio>
export const Radio: Story = {
args: {
id: 'id-1',
label: 'Label',
disabled: false,
},
parameters: figmaDesignUrl(
'https://www.figma.com/design/aJQlecvqCS9Ry8b6JA1lQN/DS---Components?node-id=6135-5510&m=dev'
),
}
@@ -0,0 +1,32 @@
import DSNotification from '@/shared/components/ds/ds-notification'
type Args = React.ComponentProps<typeof DSNotification>
export const Notification = (args: Args) => {
return <DSNotification {...args} />
}
export default {
title: 'Shared / DS Components / Notification',
component: DSNotification,
args: {
content: (
<p>
This can be <b>any HTML</b> passed to the component. For example,
paragraphs, headers, <code>code samples</code>,{' '}
<a href="/services/web/public">links</a>, etc are all supported.
</p>
),
},
argTypes: {
type: {
control: 'radio',
options: ['info', 'error'],
},
},
parameters: {
controls: {
include: ['content', 'title', 'type'],
},
},
}
@@ -0,0 +1,70 @@
import { Meta, type StoryObj } from '@storybook/react'
import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
import DSSelectionGroup from '@/shared/components/ds/ds-selection-group'
import DSSelectionGroupItem from '@/shared/components/ds/ds-selection-group-item'
type Story = StoryObj<typeof DSSelectionGroup>
export const SelectionGroupRadio: Story = {
args: {
legend: 'Select your favourite animal',
},
render: args => {
return (
<DSSelectionGroup {...args}>
<DSSelectionGroupItem type="radio" name="animal" value="wombat">
Wombat
</DSSelectionGroupItem>
<DSSelectionGroupItem type="radio" name="animal" value="capybara">
Capybara
</DSSelectionGroupItem>
<DSSelectionGroupItem type="radio" name="animal" value="badger">
Badger
</DSSelectionGroupItem>
</DSSelectionGroup>
)
},
}
export const SelectionGroupCheckbox: Story = {
args: {
legend: 'Which animals do you like?',
},
render: args => {
return (
<DSSelectionGroup {...args}>
<DSSelectionGroupItem type="checkbox" name="vole">
Vole
</DSSelectionGroupItem>
<DSSelectionGroupItem type="checkbox" name="spider">
Spider
</DSSelectionGroupItem>
<DSSelectionGroupItem type="checkbox" name="wombat">
Wombat
</DSSelectionGroupItem>
<DSSelectionGroupItem type="checkbox" name="capybara">
Capybara
</DSSelectionGroupItem>
<DSSelectionGroupItem type="checkbox" name="badger">
Badger
</DSSelectionGroupItem>
</DSSelectionGroup>
)
},
}
const meta: Meta<typeof DSSelectionGroup> = {
title: 'Shared / DS Components',
component: DSSelectionGroup,
parameters: {
controls: {
include: ['legend'],
},
...figmaDesignUrl(
'https://www.figma.com/design/aJQlecvqCS9Ry8b6JA1lQN/DS---Components?node-id=6276-265&m=dev'
),
},
}
export default meta
@@ -1,6 +1,6 @@
import { ComponentProps, useEffect, useState } from 'react'
import { Meta } from '@storybook/react'
import { figmaDesignUrl } from '../../../.storybook/utils/figma-design-url'
import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
import DSFormGroup from '@/shared/components/ds/ds-form-group'
import DSFormLabel from '@/shared/components/ds/ds-form-label'
import CIAMSixDigitsInput from '@/features/settings/components/emails/ciam-six-digits-input'
@@ -1,4 +1,10 @@
import { Select } from '../../js/shared/components/select'
import { Select } from '@/shared/components/select'
import { Meta } from '@storybook/react'
type Args = Pick<
React.ComponentProps<typeof Select>,
'disabled' | 'defaultText' | 'isCiam'
>
const items = [1, 2, 3, 4].map(index => ({
key: index,
@@ -6,9 +12,10 @@ const items = [1, 2, 3, 4].map(index => ({
group: index >= 3 ? 'Large numbers' : undefined,
}))
export const Base = () => {
export const Base = (args: Args) => {
return (
<Select
{...args}
items={items}
itemToString={x => String(x?.value)}
itemToKey={x => String(x.key)}
@@ -16,9 +23,10 @@ export const Base = () => {
)
}
export const WithSubtitles = () => {
export const WithSubtitles = (args: Args) => {
return (
<Select
{...args}
items={items}
itemToString={x => String(x?.value)}
itemToKey={x => String(x.key)}
@@ -27,9 +35,10 @@ export const WithSubtitles = () => {
)
}
export const WithSelectedIcon = () => {
export const WithSelectedIcon = (args: Args) => {
return (
<Select
{...args}
items={items}
itemToString={x => String(x?.value)}
itemToKey={x => String(x.key)}
@@ -39,9 +48,10 @@ export const WithSelectedIcon = () => {
)
}
export const WithDisabledItem = () => {
export const WithDisabledItem = (args: Args) => {
return (
<Select
{...args}
items={items}
itemToString={x => String(x?.value)}
itemToKey={x => String(x.key)}
@@ -51,16 +61,19 @@ export const WithDisabledItem = () => {
)
}
export default {
const meta: Meta<typeof Select> = {
title: 'Shared / Components / Select',
component: Select,
parameters: {
controls: {
include: ['disabled', 'defaultText'],
include: ['disabled', 'defaultText', 'isCiam'],
},
},
args: {
disabled: false,
isCiam: false,
defaultText: 'Choose an item',
},
}
export default meta
@@ -4,3 +4,5 @@
@import 'ciam-login';
@import 'ciam-register';
@import 'ciam-six-digits';
@import 'ciam-try-premium';
@import 'ciam-onboarding';
@@ -1,5 +1,3 @@
@use 'sass:math';
@mixin ciam-bottom-margin {
margin: 0 0 var(--ds-spacing-400);
}
@@ -10,6 +8,15 @@
gap: var(--ds-spacing-400);
}
@mixin ciam-h1 {
@include ds-heading-md-semibold;
font-family: var(--ds-font-family-sans), sans-serif;
color: var(--ds-color-text-primary);
padding-bottom: var(--ds-spacing-200);
margin: 0;
}
.overleaf-ds-logo {
background-image: url('../../../frontend/js/shared/svgs/overleaf-a-ds-solution-mallard.svg');
}
@@ -27,17 +34,11 @@
--password-visibility-toggle-width: calc(24px + 2 * var(--ds-spacing-250));
&,
h1 {
font-family: var(--ds-font-family-sans), sans-serif;
color: var(--ds-color-text-primary);
}
font-family: var(--ds-font-family-sans), sans-serif;
color: var(--ds-color-text-primary);
h1 {
@include ds-heading-md-semibold;
padding-bottom: var(--ds-spacing-200);
margin: 0;
@include ciam-h1;
}
footer {
@@ -112,35 +113,6 @@
@include media-breakpoint-up(sm) {
padding: var(--ds-spacing-1300);
}
.notification {
@include ds-body-sm-regular;
color: var(--ds-color-text-primary);
padding: 0 var(--ds-spacing-400);
border-width: 0;
border-radius: var(--ds-border-radius-200);
gap: var(--ds-spacing-300);
.notification-icon {
font-size: math.div(20em, 14);
display: flex;
align-items: center;
padding: var(--ds-spacing-50);
}
.notification-content {
padding: var(--ds-spacing-400) 0;
}
&.notification-type-error {
background-color: var(--ds-color-red-50);
}
&.notification-type-info {
background-color: var(--ds-color-blue-50);
}
}
}
.ciam-login-register-error-container .notification {
@@ -189,9 +161,13 @@
.ciam-footer-ds-logo {
text-align: center;
padding: var(--ds-spacing-200) 0;
a {
display: inline-block;
}
}
.ciam-stepper {
.ciam-password-strength-bar {
margin: 0;
height: 4px;
border-radius: var(--ds-border-radius-full);
@@ -225,3 +201,84 @@
background-color: var(--ds-color-neutral-200);
}
}
.ciam-list-style-check-green {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: var(--ds-spacing-200);
li {
background-image: url('../../../public/img/ciam-check-green.svg');
background-repeat: no-repeat;
background-position: left center;
background-size: 16px 16px;
padding-left: calc(16px + var(--ds-spacing-200));
}
}
.ciam-onboarding-steps-container {
text-align: center;
padding: var(--ds-spacing-250) var(--ds-spacing-400);
}
.ciam-dropdown-menu {
// Apply styling from DS design system to CIAM dropdowns
--dropdown-border-radius: var(--ds-border-radius-200);
--dropdown-text-active: var(--ds-color-text-primary);
--dropdown-background-active: var(--ds-color-neutral-50);
--dropdown-background-hover: var(--ds-color-neutral-50);
--bs-dropdown-item-padding-x: var(--ds-spacing-300);
--bs-dropdown-item-padding-y: var(--ds-spacing-250);
.dropdown-item {
border-radius: var(--ds-border-radius-200);
}
// Allow dropdowns to be wider than the form container where necessary
width: 100%;
overflow-x: hidden;
@include media-breakpoint-up(sm) {
width: auto;
min-width: 100%;
max-width: 640px;
}
}
.ciam-form-control-info {
@include ds-body-sm-regular;
padding-top: calc(var(--ds-spacing-200) - var(--ds-spacing-100));
margin: 0;
color: var(--ds-color-text-secondary);
}
.ciam-stepper-container {
padding: var(--ds-spacing-200) var(--ds-spacing-400) 0 var(--ds-spacing-400);
}
.ciam-stepper {
display: flex;
justify-content: center;
gap: var(--ds-spacing-400);
&:focus-visible {
@include ds-focus-outline;
}
.step {
display: inline-block;
border-radius: 50%;
background-color: var(--ds-color-text-disabled);
width: 8px;
height: 8px;
&.active,
&.completed {
background-color: var(--ds-color-text-primary);
}
}
}
@@ -0,0 +1,31 @@
@import 'ds-design-system';
.btn.btn-ds.ciam-onboarding-back-button {
--bs-btn-padding-x: 0;
--bs-btn-hover-bg: transparent;
}
.ciam-onboarding-email-updates p {
margin: 0;
}
.ciam-onboarding-privacy-note {
@include ds-body-sm-regular;
color: var(--ds-color-text-secondary);
padding-top: var(--ds-spacing-250);
}
.ciam-onboarding-step {
@include ciam-vertically-spaced;
padding-bottom: var(--ds-spacing-400);
legend {
@include ciam-h1;
}
}
.ciam-used-latex-options {
padding-bottom: var(--ds-spacing-200);
}
@@ -0,0 +1,13 @@
@import 'ds-design-system';
.ciam-try-premium-logo-container {
display: flex;
justify-content: center;
gap: var(--ds-spacing-600);
padding: var(--ds-spacing-200) 0 var(--ds-spacing-500) 0;
}
.ciam-try-premium-third-party-logo {
width: 36px;
height: 36px;
}
+152 -68
View File
@@ -1,83 +1,167 @@
// Sass variables
// Neutral/grey
$ds-color-neutral-50: #fafafa;
$ds-color-neutral-100: #f2f2f2;
$ds-color-neutral-200: #e6e6e6;
$ds-color-neutral-300: #d6d6d6;
$ds-color-neutral-400: #c7c7c7;
$ds-color-neutral-500: #b5b5b5;
$ds-color-neutral-600: #a1a1a1;
$ds-color-neutral-700: #8a8a8a;
$ds-color-neutral-800: #6b6b6b;
$ds-color-neutral-900: #383838;
$ds-color-neutral-950: #262626;
// Green
$ds-color-green-50: #f2f8f5;
$ds-color-green-100: #e3f2eb;
$ds-color-green-200: #c0e7d6;
$ds-color-green-300: #8adbb7;
$ds-color-green-400: #38cc89;
$ds-color-green-500: #26b072;
$ds-color-green-600: #158954;
$ds-color-green-700: #19754c;
$ds-color-green-800: #196241;
$ds-color-green-900: #164630;
$ds-color-green-950: #112c20;
// Yellow
$ds-color-yellow-50: #fffaeb;
$ds-color-yellow-100: #fff7db;
$ds-color-yellow-200: #ffeeb8;
$ds-color-yellow-300: #ffe58f;
$ds-color-yellow-400: #ffda61;
$ds-color-yellow-500: #ffcc20;
$ds-color-yellow-600: #f0b800;
$ds-color-yellow-700: #d1a000;
$ds-color-yellow-800: #ad8500;
$ds-color-yellow-900: #806200;
$ds-color-yellow-950: #574200;
// Red
$ds-color-red-50: #fff5f7;
$ds-color-red-100: #fee7eb;
$ds-color-red-200: #fdc9d3;
$ds-color-red-300: #fba7b7;
$ds-color-red-400: #f97b92;
$ds-color-red-500: #f51d43;
$ds-color-red-600: #e60a32;
$ds-color-red-700: #c3092b;
$ds-color-red-800: #a10723;
$ds-color-red-900: #75051a;
$ds-color-red-950: #530412;
// Blue
$ds-color-blue-50: #f7fafd;
$ds-color-blue-100: #ecf2f9;
$ds-color-blue-200: #d4e3f2;
$ds-color-blue-300: #bdd4ea;
$ds-color-blue-400: #a2c3e2;
$ds-color-blue-500: #7facd7;
$ds-color-blue-600: #5893cb;
$ds-color-blue-700: #3470a8;
$ds-color-blue-800: #2b5d8c;
$ds-color-blue-900: #1e4161;
$ds-color-blue-950: #17314a;
// Teal
$ds-color-teal-50: #f1f8f8;
$ds-color-teal-100: #e3f2f1;
$ds-color-teal-200: #c1e2df;
$ds-color-teal-300: #9ed1cd;
$ds-color-teal-400: #6dbab4;
$ds-color-teal-500: #4a9d96;
$ds-color-teal-600: #438e88;
$ds-color-teal-700: #3b7d77;
$ds-color-teal-800: #2f6460;
$ds-color-teal-900: #244c49;
$ds-color-teal-950: #193432;
// Black and white
$ds-color-white: #fff;
$ds-color-black: #000;
// CSS custom properties
:root {
// Neutral/grey
--ds-color-neutral-50: #fafafa;
--ds-color-neutral-100: #f2f2f2;
--ds-color-neutral-200: #e6e6e6;
--ds-color-neutral-300: #d6d6d6;
--ds-color-neutral-400: #c7c7c7;
--ds-color-neutral-500: #b5b5b5;
--ds-color-neutral-600: #a1a1a1;
--ds-color-neutral-700: #8a8a8a;
--ds-color-neutral-800: #6b6b6b;
--ds-color-neutral-900: #383838;
--ds-color-neutral-950: #262626;
--ds-color-neutral-50: #{$ds-color-neutral-50};
--ds-color-neutral-100: #{$ds-color-neutral-100};
--ds-color-neutral-200: #{$ds-color-neutral-200};
--ds-color-neutral-300: #{$ds-color-neutral-300};
--ds-color-neutral-400: #{$ds-color-neutral-400};
--ds-color-neutral-500: #{$ds-color-neutral-500};
--ds-color-neutral-600: #{$ds-color-neutral-600};
--ds-color-neutral-700: #{$ds-color-neutral-700};
--ds-color-neutral-800: #{$ds-color-neutral-800};
--ds-color-neutral-900: #{$ds-color-neutral-900};
--ds-color-neutral-950: #{$ds-color-neutral-950};
// Green
--ds-color-green-50: #f2f8f5;
--ds-color-green-100: #e3f2eb;
--ds-color-green-200: #c0e7d6;
--ds-color-green-300: #8adbb7;
--ds-color-green-400: #38cc89;
--ds-color-green-500: #26b072;
--ds-color-green-600: #158954;
--ds-color-green-700: #19754c;
--ds-color-green-800: #196241;
--ds-color-green-900: #164630;
--ds-color-green-950: #112c20;
--ds-color-green-50: #{$ds-color-green-50};
--ds-color-green-100: #{$ds-color-green-100};
--ds-color-green-200: #{$ds-color-green-200};
--ds-color-green-300: #{$ds-color-green-300};
--ds-color-green-400: #{$ds-color-green-400};
--ds-color-green-500: #{$ds-color-green-500};
--ds-color-green-600: #{$ds-color-green-600};
--ds-color-green-700: #{$ds-color-green-700};
--ds-color-green-800: #{$ds-color-green-800};
--ds-color-green-900: #{$ds-color-green-900};
--ds-color-green-950: #{$ds-color-green-950};
// Yellow
--ds-color-yellow-50: #fffaeb;
--ds-color-yellow-100: #fff7db;
--ds-color-yellow-200: #ffeeb8;
--ds-color-yellow-300: #ffe58f;
--ds-color-yellow-400: #ffda61;
--ds-color-yellow-500: #ffcc20;
--ds-color-yellow-600: #f0b800;
--ds-color-yellow-700: #d1a000;
--ds-color-yellow-800: #ad8500;
--ds-color-yellow-900: #806200;
--ds-color-yellow-950: #574200;
--ds-color-yellow-50: #{$ds-color-yellow-50};
--ds-color-yellow-100: #{$ds-color-yellow-100};
--ds-color-yellow-200: #{$ds-color-yellow-200};
--ds-color-yellow-300: #{$ds-color-yellow-300};
--ds-color-yellow-400: #{$ds-color-yellow-400};
--ds-color-yellow-500: #{$ds-color-yellow-500};
--ds-color-yellow-600: #{$ds-color-yellow-600};
--ds-color-yellow-700: #{$ds-color-yellow-700};
--ds-color-yellow-800: #{$ds-color-yellow-800};
--ds-color-yellow-900: #{$ds-color-yellow-900};
--ds-color-yellow-950: #{$ds-color-yellow-950};
// Red
--ds-color-red-50: #fff5f7;
--ds-color-red-100: #fee7eb;
--ds-color-red-200: #fdc9d3;
--ds-color-red-300: #fba7b7;
--ds-color-red-400: #f97b92;
--ds-color-red-500: #f51d43;
--ds-color-red-600: #e60a32;
--ds-color-red-700: #c3092b;
--ds-color-red-800: #a10723;
--ds-color-red-900: #75051a;
--ds-color-red-950: #530412;
--ds-color-red-50: #{$ds-color-red-50};
--ds-color-red-100: #{$ds-color-red-100};
--ds-color-red-200: #{$ds-color-red-200};
--ds-color-red-300: #{$ds-color-red-300};
--ds-color-red-400: #{$ds-color-red-400};
--ds-color-red-500: #{$ds-color-red-500};
--ds-color-red-600: #{$ds-color-red-600};
--ds-color-red-700: #{$ds-color-red-700};
--ds-color-red-800: #{$ds-color-red-800};
--ds-color-red-900: #{$ds-color-red-900};
--ds-color-red-950: #{$ds-color-red-950};
// Blue
--ds-color-blue-50: #f7fafd;
--ds-color-blue-100: #ecf2f9;
--ds-color-blue-200: #d4e3f2;
--ds-color-blue-300: #bdd4ea;
--ds-color-blue-400: #a2c3e2;
--ds-color-blue-500: #7facd7;
--ds-color-blue-600: #5893cb;
--ds-color-blue-700: #3470a8;
--ds-color-blue-800: #2b5d8c;
--ds-color-blue-900: #1e4161;
--ds-color-blue-950: #17314a;
--ds-color-blue-50: #{$ds-color-blue-50};
--ds-color-blue-100: #{$ds-color-blue-100};
--ds-color-blue-200: #{$ds-color-blue-200};
--ds-color-blue-300: #{$ds-color-blue-300};
--ds-color-blue-400: #{$ds-color-blue-400};
--ds-color-blue-500: #{$ds-color-blue-500};
--ds-color-blue-600: #{$ds-color-blue-600};
--ds-color-blue-700: #{$ds-color-blue-700};
--ds-color-blue-800: #{$ds-color-blue-800};
--ds-color-blue-900: #{$ds-color-blue-900};
--ds-color-blue-950: #{$ds-color-blue-950};
// Teal
--ds-color-teal-50: #f1f8f8;
--ds-color-teal-100: #e3f2f1;
--ds-color-teal-200: #c1e2df;
--ds-color-teal-300: #9ed1cd;
--ds-color-teal-400: #6dbab4;
--ds-color-teal-500: #4a9d96;
--ds-color-teal-600: #438e88;
--ds-color-teal-700: #3b7d77;
--ds-color-teal-800: #2f6460;
--ds-color-teal-900: #244c49;
--ds-color-teal-950: #193432;
--ds-color-teal-50: #{$ds-color-teal-50};
--ds-color-teal-100: #{$ds-color-teal-100};
--ds-color-teal-200: #{$ds-color-teal-200};
--ds-color-teal-300: #{$ds-color-teal-300};
--ds-color-teal-400: #{$ds-color-teal-400};
--ds-color-teal-500: #{$ds-color-teal-500};
--ds-color-teal-600: #{$ds-color-teal-600};
--ds-color-teal-700: #{$ds-color-teal-700};
--ds-color-teal-800: #{$ds-color-teal-800};
--ds-color-teal-900: #{$ds-color-teal-900};
--ds-color-teal-950: #{$ds-color-teal-950};
// Black and white
--ds-color-white: #fff;
--ds-color-black: #000;
--ds-color-white: #{$ds-color-white};
--ds-color-black: #{$ds-color-black};
}
@@ -1,2 +1,4 @@
@import 'button';
@import 'form-control';
@import 'notification';
@import 'selection-group';
@@ -1,6 +1,10 @@
@use 'sass:math';
@import '../mixins';
:root {
--ds-form-check-input-size: 1em;
}
.form-control-ds,
.form-group-ds,
.form-text-ds,
@@ -43,9 +47,15 @@ input.form-control.form-control-ds {
&:read-only {
background: var(--ds-color-neutral-200);
color: var(--ds-color-text-secondary);
&.select-trigger {
background: transparent;
color: var(--ds-color-neutral-950);
}
}
&:disabled {
&:disabled,
&:disabled.select-trigger {
color: var(--ds-color-text-disabled);
background: var(--ds-color-neutral-100);
}
@@ -111,6 +121,188 @@ input.form-control.form-control-ds {
position: relative;
}
.ciam-form-input-icon {
.form-input-icon-ds {
font-size: var(--ds-font-size-600);
}
.form-control-wrapper-ds {
position: relative;
&.form-control-wrapper-disabled-ds {
.form-control-start-icon,
.form-control-end-icon {
& > * {
color: var(--ds-color-text-disabled);
}
}
}
.form-control-start-icon-ds,
.form-control-end-icon-ds {
position: absolute;
top: 0;
height: 100%;
display: flex;
align-items: center;
&:has(*) {
pointer-events: none;
}
}
.form-control-start-icon-ds {
left: 0;
padding-left: calc(var(--form-control-padding-x) + var(--bs-border-width));
}
.form-control-end-icon-ds {
right: 0;
padding-right: calc(var(--form-control-padding-x) + var(--bs-border-width));
.form-control-search-clear-btn {
@include reset-button;
display: flex;
justify-content: center;
}
}
--icon-width: 20px;
--form-control-padding-x: var(--ds-spacing-250);
--form-control-icon-offset-y: var(--ds-spacing-200);
--form-control-icon-padding: calc(
var(--form-control-padding-x) + var(--form-control-icon-offset-y) +
var(--icon-width)
);
.form-control-ds.form-control-offset-start-ds {
padding-left: var(--form-control-icon-padding);
}
.form-control-ds.form-control-offset-end-ds {
padding-right: var(--form-control-icon-padding);
}
&:has(:disabled) {
color: var(--ds-color-text-disabled);
}
}
.form-check-ds input.form-check-input,
input.check-input-ds {
@function form-check-svg($type, $color) {
@if $type == 'checkbox' {
@return url("data:image/svg+xml,<svg width='8' height='8' viewBox='0 0 8 8' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M1 4.60011L3 7.00011L7 1.00011' fill='none' stroke='#{$color}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>");
}
@if $type == 'radio' {
@return url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'><circle r='4' cx='4' cy='4' fill='#{$color}'/></svg>");
}
}
border-style: solid;
border-width: 1px;
&,
&:hover {
border-color: var(--ds-color-neutral-900);
}
&:checked {
&,
&:hover {
background-color: var(--ds-color-neutral-950);
border-color: var(--ds-color-neutral-950);
}
}
&:disabled {
background-color: var(--ds-color-neutral-200);
border-color: var(--ds-color-neutral-200);
}
&:focus-visible {
@include ds-focus-outline;
}
@mixin input-type-override($type) {
width: var(--ds-form-check-input-size);
height: var(--ds-form-check-input-size);
&:checked {
background-size: 8px;
background-image: escape-svg(form-check-svg($type, '#fff'));
}
&[disabled],
&:disabled {
&:checked {
background-image: escape-svg(
form-check-svg($type, $ds-color-neutral-400)
);
}
}
}
&[type='checkbox'] {
@include input-type-override('checkbox');
border-radius: var(--ds-border-radius-100);
}
&[type='radio'] {
@include input-type-override('radio');
border-radius: 50%;
}
}
input.check-input-ds {
appearance: none;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
margin: var(--ds-spacing-100);
&[type='radio'] {
border-radius: 50%;
}
}
.form-check-ds {
--ds-form-check-input-margin: var(--ds-spacing-100);
--ds-form-check-label-gap: var(--ds-spacing-200);
--ds-form-check-box-size: 1em;
padding-left: calc(
var(--ds-form-check-box-size) + 2 * var(--ds-form-check-input-margin) +
var(--ds-form-check-label-gap)
);
&:has(.form-check-input:focus-visible) {
@include ds-focus-outline;
border-radius: var(--ds-border-radius-100);
}
.form-check-label {
@include ds-body-md-regular;
}
.form-check-input {
margin-left: calc(
-1 *
(
var(--ds-form-check-box-size) + var(--ds-form-check-input-margin) +
var(--ds-form-check-label-gap)
)
);
margin-top: var(--ds-form-check-input-margin);
margin-bottom: var(--ds-form-check-input-margin);
&:focus,
&:focus-visible {
box-shadow: none;
}
}
}
@@ -0,0 +1,30 @@
@use 'sass:math';
.notification.notification-ds {
@include ds-body-sm-regular;
color: var(--ds-color-text-primary);
padding: 0 var(--ds-spacing-400);
border-width: 0;
border-radius: var(--ds-border-radius-200);
gap: var(--ds-spacing-300);
.notification-icon {
font-size: math.div(20em, 14);
display: flex;
align-items: center;
padding: var(--ds-spacing-50);
}
.notification-content {
padding: var(--ds-spacing-400) 0;
}
&.notification-type-error {
background-color: var(--ds-color-red-50);
}
&.notification-type-info {
background-color: var(--ds-color-blue-50);
}
}
@@ -0,0 +1,40 @@
.selection-group-ds {
display: flex;
flex-direction: column;
gap: var(--ds-spacing-400);
}
.selection-group-ds-list {
display: flex;
flex-direction: column;
gap: var(--ds-spacing-200);
list-style: none;
padding: 0;
margin: 0;
}
.selection-group-ds-item label {
display: flex;
gap: var(--ds-spacing-200);
padding: var(--ds-spacing-250);
border: 1px solid var(--ds-color-neutral-300);
border-radius: var(--ds-border-radius-200);
cursor: pointer;
&:has(input:checked) {
border-color: var(--ds-color-neutral-950);
background-color: var(--ds-color-neutral-50);
}
&:has(input:focus-visible, textarea:focus-visible) {
@include ds-focus-outline;
border-radius: var(--ds-border-radius-100);
.form-check-ds,
input:focus-visible,
textarea:focus-visible {
outline: none;
}
}
}
@@ -12,10 +12,19 @@
.confirm-email-success-form {
display: flex;
flex: 1 1 auto;
align-items: center;
flex-direction: column;
justify-content: center;
text-align: center;
min-height: 400px;
p {
margin: 0;
}
.form-actions {
margin-top: var(--ds-spacing-600);
margin-bottom: var(--ds-spacing-400);
}
}
.confirm-email-form .confirm-email-form-inner {
@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.354 4.85403L6.35403 12.854C6.30759 12.9005 6.25245 12.9374 6.19175 12.9626C6.13105 12.9877 6.06599 13.0007 6.00028 13.0007C5.93457 13.0007 5.86951 12.9877 5.80881 12.9626C5.74811 12.9374 5.69296 12.9005 5.64653 12.854L2.14653 9.35403C2.05271 9.26021 2 9.13296 2 9.00028C2 8.8676 2.05271 8.74035 2.14653 8.64653C2.24035 8.55271 2.3676 8.5 2.50028 8.5C2.63296 8.5 2.76021 8.55271 2.85403 8.64653L6.00028 11.7934L13.6465 4.14653C13.7403 4.05271 13.8676 4 14.0003 4C14.133 4 14.2602 4.05271 14.354 4.14653C14.4478 4.24035 14.5006 4.3676 14.5006 4.50028C14.5006 4.63296 14.4478 4.76021 14.354 4.85403Z" fill="#26B072"/>
</svg>

After

Width:  |  Height:  |  Size: 733 B