diff --git a/services/web/app/views/_mixins/ciam_mixins.pug b/services/web/app/views/_mixins/ciam_mixins.pug new file mode 100644 index 0000000000..6cf7a9b986 --- /dev/null +++ b/services/web/app/views/_mixins/ciam_mixins.pug @@ -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 diff --git a/services/web/frontend/js/features/settings/components/emails/confirm-email-form.tsx b/services/web/frontend/js/features/settings/components/emails/confirm-email-form.tsx index ab9a4bfccd..40d2e683e9 100644 --- a/services/web/frontend/js/features/settings/components/emails/confirm-email-form.tsx +++ b/services/web/frontend/js/features/settings/components/emails/confirm-email-form.tsx @@ -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 ( - ) } @@ -196,6 +197,8 @@ export function ConfirmEmailForm({ const SixDigits = isCiam ? CIAMSixDigitsInput : OLSixDigitsInput const FormText = isCiam ? DSFormText : OLFormText + const NotificationComponent = isCiam ? DSNotification : Notification + return (
{(feedback?.type === 'alert' || outerErrorDisplay) && ( - {t('confirm_your_email')} } -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) => { e.preventDefault() location.assign(redirectTo) } - - useEffect(() => { - if (autoRedirect) { - const timer = setTimeout(() => location.assign(redirectTo), autoRedirect) - return () => clearTimeout(timer) - } - }, [autoRedirect, location, redirectTo]) + const button = isCiam ? ( + } + > + {successButtonText} + + ) : ( + + {successButtonText} + + ) return (
{successMessage}
- {!autoRedirect && ( -
- - {successButtonText} - -
- )} +
{button}
) } diff --git a/services/web/frontend/js/features/settings/components/emails/downshift-input.tsx b/services/web/frontend/js/features/settings/components/emails/downshift-input.tsx index b21c565665..247a89757d 100644 --- a/services/web/frontend/js/features/settings/components/emails/downshift-input.tsx +++ b/services/web/frontend/js/features/settings/components/emails/downshift-input.tsx @@ -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 showLabel?: boolean showSuggestedText?: boolean + isCiam?: boolean } & React.InputHTMLAttributes 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' + } + + const shouldOpen = isOpen && inputItems.length > 0 + + const dropdown = ( +
    + {showSuggestedText && inputItems.length > 0 && ( +
  • + + {itemsTitle} + +
  • + )} + {inputItems.map((item, index) => ( + // eslint-disable-next-line jsx-a11y/role-supports-aria-props +
  • + : undefined} + > + {highlightMatchedCharacters(item, inputValue)} + +
  • + ))} +
+ ) + + if (isCiam) { + return ( +
+ + {label} + + ) => { + setValue(event.target.value) + }, + ref: inputRef, + })} + placeholder={placeholder} + disabled={disabled} + /> + {dropdown} +
+ ) + } return (
@@ -98,40 +169,7 @@ function Downshift({ disabled={disabled} />
-
    - {showSuggestedText && inputItems.length && ( -
  • - - {itemsTitle} - -
  • - )} - {inputItems.map((item, index) => ( - // eslint-disable-next-line jsx-a11y/role-supports-aria-props -
  • - - {highlightMatchedCharacters(item, inputValue)} - -
  • - ))} -
+ {dropdown}
) } diff --git a/services/web/frontend/js/shared/components/ciam-stepper.tsx b/services/web/frontend/js/shared/components/ciam-stepper.tsx new file mode 100644 index 0000000000..950de10c55 --- /dev/null +++ b/services/web/frontend/js/shared/components/ciam-stepper.tsx @@ -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 ( +
+
+ {Array.from({ length: steps }).map((_, i) => ( +
+ ))} +
+
+ ) +} diff --git a/services/web/frontend/js/shared/components/ds/ds-button.tsx b/services/web/frontend/js/shared/components/ds/ds-button.tsx index 6a45aabae9..1b2a56a70d 100644 --- a/services/web/frontend/js/shared/components/ds/ds-button.tsx +++ b/services/web/frontend/js/shared/components/ds/ds-button.tsx @@ -6,6 +6,7 @@ import classNames from 'classnames' type DSButtonProps = Pick< ButtonProps, | 'children' + | 'className' | 'disabled' | 'href' | 'id' @@ -32,6 +33,7 @@ const DSButton = forwardRef( ( { children, + className, leadingIcon, isLoading = false, loadingLabel, @@ -44,7 +46,7 @@ const DSButton = forwardRef( ) => { const { t } = useTranslation() - const buttonClassName = classNames('d-inline-grid btn-ds', { + const buttonClassName = classNames('d-inline-grid btn-ds', className, { 'button-loading': isLoading, }) diff --git a/services/web/frontend/js/shared/components/ds/ds-form-checkbox.tsx b/services/web/frontend/js/shared/components/ds/ds-form-checkbox.tsx new file mode 100644 index 0000000000..57f0736a34 --- /dev/null +++ b/services/web/frontend/js/shared/components/ds/ds-form-checkbox.tsx @@ -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 ( + + ) +} + +export default DSFormCheckbox diff --git a/services/web/frontend/js/shared/components/ds/ds-form-control.tsx b/services/web/frontend/js/shared/components/ds/ds-form-control.tsx index d41bb3fdbc..4003d95d4f 100644 --- a/services/web/frontend/js/shared/components/ds/ds-form-control.tsx +++ b/services/web/frontend/js/shared/components/ds/ds-form-control.tsx @@ -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( - ({ className, ...props }, ref) => { +const DSFormControl = forwardRef( + ({ 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 ( +
+ {prepend && ( + {prepend} + )} + + {append && {append}} +
+ ) + } + return ( ) } diff --git a/services/web/frontend/js/shared/components/ds/ds-form-radio.tsx b/services/web/frontend/js/shared/components/ds/ds-form-radio.tsx new file mode 100644 index 0000000000..4994e07616 --- /dev/null +++ b/services/web/frontend/js/shared/components/ds/ds-form-radio.tsx @@ -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 ( + + ) +} + +export default DSFormRadio diff --git a/services/web/frontend/js/shared/components/ds/ds-notification.tsx b/services/web/frontend/js/shared/components/ds/ds-notification.tsx new file mode 100644 index 0000000000..3fffe60c40 --- /dev/null +++ b/services/web/frontend/js/shared/components/ds/ds-notification.tsx @@ -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' ? : + + return ( + + ) +} + +export default DSNotification diff --git a/services/web/frontend/js/shared/components/ds/ds-selection-group-item.tsx b/services/web/frontend/js/shared/components/ds/ds-selection-group-item.tsx new file mode 100644 index 0000000000..8bf855ed29 --- /dev/null +++ b/services/web/frontend/js/shared/components/ds/ds-selection-group-item.tsx @@ -0,0 +1,46 @@ +import React from 'react' + +type DSSelectionGroupItemProps = { + 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 +) { + const handleChange = () => { + props.onChange?.(props.value) + } + + return ( +
  • + +
  • + ) +} diff --git a/services/web/frontend/js/shared/components/ds/ds-selection-group.tsx b/services/web/frontend/js/shared/components/ds/ds-selection-group.tsx new file mode 100644 index 0000000000..08f30f1f73 --- /dev/null +++ b/services/web/frontend/js/shared/components/ds/ds-selection-group.tsx @@ -0,0 +1,20 @@ +import classNames from 'classnames' + +export default function DSSelectionGroup({ + legend, + className, + children, +}: { + legend?: React.ReactNode + className?: string + children: React.ReactNode +}) { + return ( +
    + {legend ? ( + {legend} + ) : null} +
      {children}
    +
    + ) +} diff --git a/services/web/frontend/js/shared/components/layouts/ciam-layout.tsx b/services/web/frontend/js/shared/components/layouts/ciam-layout.tsx index 72964b93bb..a4b637eba3 100644 --- a/services/web/frontend/js/shared/components/layouts/ciam-layout.tsx +++ b/services/web/frontend/js/shared/components/layouts/ciam-layout.tsx @@ -17,16 +17,31 @@ const CiamLayout: FC = ({ children }: Props) => (

    - Digital Science — home + + Digital Science — home +

    , + , // eslint-disable-next-line jsx-a11y/anchor-has-content,react/jsx-key - , + , ]} />

    diff --git a/services/web/frontend/js/shared/components/select.tsx b/services/web/frontend/js/shared/components/select.tsx index 3bf8519b8e..e501ae82fb 100644 --- a/services/web/frontend/js/shared/components/select.tsx +++ b/services/web/frontend/js/shared/components/select.tsx @@ -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 = { // The items rendered as dropdown options. @@ -51,6 +55,8 @@ export type SelectProps = { selectedIcon?: boolean // testId for the input element dataTestId?: string + // CIAM-specific layout + isCiam?: boolean } export const Select = ({ @@ -70,6 +76,7 @@ export const Select = ({ loading = false, selectedIcon = false, dataTestId, + isCiam, }: SelectProps) => { const [selectedItem, setSelectedItem] = useState( defaultItem @@ -145,6 +152,80 @@ export const Select = ({ value = defaultText } + const TickIcon = function () { + return isCiam ? : 'check' + } + + const dropdown = ( +
      + {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 ( +
    • + + ) : undefined + } + description={itemToSubtitle ? itemToSubtitle(item) : undefined} + {...itemProps} + disabled={disabled} + > + {itemToString(item)} + +
    • + ) + })} +
    + ) + + if (isCiam) { + return ( +
    + + {label ? ( + + {label} {optionalLabel && ({t('optional')})}{' '} + {loading && } + + ) : null} + : } + /> + {dropdown} + +
    + ) + } + return (
    {label ? ( @@ -172,42 +253,7 @@ export const Select = ({ /> } /> -
      - {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 ( -
    • - - {itemToString(item)} - -
    • - ) - })} -
    + {dropdown}
    ) } diff --git a/services/web/frontend/js/shared/hooks/use-is-ciam.ts b/services/web/frontend/js/shared/hooks/use-is-ciam.ts new file mode 100644 index 0000000000..1220675f10 --- /dev/null +++ b/services/web/frontend/js/shared/hooks/use-is-ciam.ts @@ -0,0 +1,6 @@ +import { useSplitTestContext } from '@/shared/context/split-test-context' + +export default function useIsCiam() { + const { splitTestVariants } = useSplitTestContext() + return splitTestVariants.uniaccessphase1 === 'enabled' +} diff --git a/services/web/frontend/stories/shared/ds-button.stories.tsx b/services/web/frontend/stories/shared/ds/ds-button.stories.tsx similarity index 91% rename from services/web/frontend/stories/shared/ds-button.stories.tsx rename to services/web/frontend/stories/shared/ds/ds-button.stories.tsx index 8b77bb4f1d..a7881e1c5f 100644 --- a/services/web/frontend/stories/shared/ds-button.stories.tsx +++ b/services/web/frontend/stories/shared/ds/ds-button.stories.tsx @@ -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 diff --git a/services/web/frontend/stories/shared/ds/ds-form-check.stories.tsx b/services/web/frontend/stories/shared/ds/ds-form-check.stories.tsx new file mode 100644 index 0000000000..a7097f04c7 --- /dev/null +++ b/services/web/frontend/stories/shared/ds/ds-form-check.stories.tsx @@ -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 = { + title: 'Shared / DS Components / Form', + component: DSFormCheckbox, + parameters: { + controls: { + include: ['disabled'], + }, + }, +} +export default meta + +type Story = StoryObj + +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' + ), +} diff --git a/services/web/frontend/stories/shared/ds-form-control.stories.tsx b/services/web/frontend/stories/shared/ds/ds-form-control.stories.tsx similarity index 93% rename from services/web/frontend/stories/shared/ds-form-control.stories.tsx rename to services/web/frontend/stories/shared/ds/ds-form-control.stories.tsx index ee42914dd2..7b6bc27712 100644 --- a/services/web/frontend/stories/shared/ds-form-control.stories.tsx +++ b/services/web/frontend/stories/shared/ds/ds-form-control.stories.tsx @@ -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 = { - title: 'Shared / DS Components', + title: 'Shared / DS Components / Form', component: FormControl, argTypes: { disabled: { control: 'boolean' }, diff --git a/services/web/frontend/stories/shared/ds/ds-form-radio.stories.tsx b/services/web/frontend/stories/shared/ds/ds-form-radio.stories.tsx new file mode 100644 index 0000000000..be58034312 --- /dev/null +++ b/services/web/frontend/stories/shared/ds/ds-form-radio.stories.tsx @@ -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 = { + title: 'Shared / DS Components / Form', + component: DSFormRadio, + parameters: { + controls: { + include: ['disabled'], + }, + }, +} +export default meta + +type Story = StoryObj + +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' + ), +} diff --git a/services/web/frontend/stories/shared/ds/ds-notification.stories.tsx b/services/web/frontend/stories/shared/ds/ds-notification.stories.tsx new file mode 100644 index 0000000000..98b13c8c41 --- /dev/null +++ b/services/web/frontend/stories/shared/ds/ds-notification.stories.tsx @@ -0,0 +1,32 @@ +import DSNotification from '@/shared/components/ds/ds-notification' + +type Args = React.ComponentProps + +export const Notification = (args: Args) => { + return +} + +export default { + title: 'Shared / DS Components / Notification', + component: DSNotification, + args: { + content: ( +

    + This can be any HTML passed to the component. For example, + paragraphs, headers, code samples,{' '} + links, etc are all supported. +

    + ), + }, + argTypes: { + type: { + control: 'radio', + options: ['info', 'error'], + }, + }, + parameters: { + controls: { + include: ['content', 'title', 'type'], + }, + }, +} diff --git a/services/web/frontend/stories/shared/ds/ds-selection-group.stories.tsx b/services/web/frontend/stories/shared/ds/ds-selection-group.stories.tsx new file mode 100644 index 0000000000..54fb20b5ed --- /dev/null +++ b/services/web/frontend/stories/shared/ds/ds-selection-group.stories.tsx @@ -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 + +export const SelectionGroupRadio: Story = { + args: { + legend: 'Select your favourite animal', + }, + render: args => { + return ( + + + Wombat + + + Capybara + + + Badger + + + ) + }, +} + +export const SelectionGroupCheckbox: Story = { + args: { + legend: 'Which animals do you like?', + }, + render: args => { + return ( + + + Vole + + + Spider + + + Wombat + + + Capybara + + + Badger + + + ) + }, +} + +const meta: Meta = { + 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 diff --git a/services/web/frontend/stories/shared/ds-six-digits-input.stories.tsx b/services/web/frontend/stories/shared/ds/ds-six-digits-input.stories.tsx similarity index 94% rename from services/web/frontend/stories/shared/ds-six-digits-input.stories.tsx rename to services/web/frontend/stories/shared/ds/ds-six-digits-input.stories.tsx index 827e8582b8..ad893bdcf0 100644 --- a/services/web/frontend/stories/shared/ds-six-digits-input.stories.tsx +++ b/services/web/frontend/stories/shared/ds/ds-six-digits-input.stories.tsx @@ -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' diff --git a/services/web/frontend/stories/shared/select.stories.tsx b/services/web/frontend/stories/shared/select.stories.tsx index f31a99aa11..311fb34c22 100644 --- a/services/web/frontend/stories/shared/select.stories.tsx +++ b/services/web/frontend/stories/shared/select.stories.tsx @@ -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, + '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 ( String(x?.value)} itemToKey={x => String(x.key)} @@ -27,9 +35,10 @@ export const WithSubtitles = () => { ) } -export const WithSelectedIcon = () => { +export const WithSelectedIcon = (args: Args) => { return ( String(x?.value)} itemToKey={x => String(x.key)} @@ -51,16 +61,19 @@ export const WithDisabledItem = () => { ) } -export default { +const meta: Meta = { 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 diff --git a/services/web/frontend/stylesheets/ciam/all.scss b/services/web/frontend/stylesheets/ciam/all.scss index efe9c3e859..c5eb0fd43e 100644 --- a/services/web/frontend/stylesheets/ciam/all.scss +++ b/services/web/frontend/stylesheets/ciam/all.scss @@ -4,3 +4,5 @@ @import 'ciam-login'; @import 'ciam-register'; @import 'ciam-six-digits'; +@import 'ciam-try-premium'; +@import 'ciam-onboarding'; diff --git a/services/web/frontend/stylesheets/ciam/ciam-layout.scss b/services/web/frontend/stylesheets/ciam/ciam-layout.scss index 4fd96f659e..4e6d84faa7 100644 --- a/services/web/frontend/stylesheets/ciam/ciam-layout.scss +++ b/services/web/frontend/stylesheets/ciam/ciam-layout.scss @@ -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); + } + } +} diff --git a/services/web/frontend/stylesheets/ciam/ciam-onboarding.scss b/services/web/frontend/stylesheets/ciam/ciam-onboarding.scss new file mode 100644 index 0000000000..0715ba0211 --- /dev/null +++ b/services/web/frontend/stylesheets/ciam/ciam-onboarding.scss @@ -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); +} diff --git a/services/web/frontend/stylesheets/ciam/ciam-try-premium.scss b/services/web/frontend/stylesheets/ciam/ciam-try-premium.scss new file mode 100644 index 0000000000..5c11de99c6 --- /dev/null +++ b/services/web/frontend/stylesheets/ciam/ciam-try-premium.scss @@ -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; +} diff --git a/services/web/frontend/stylesheets/ds/colors.scss b/services/web/frontend/stylesheets/ds/colors.scss index 6f52e2d0c1..067a564a1e 100644 --- a/services/web/frontend/stylesheets/ds/colors.scss +++ b/services/web/frontend/stylesheets/ds/colors.scss @@ -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}; } diff --git a/services/web/frontend/stylesheets/ds/components/all.scss b/services/web/frontend/stylesheets/ds/components/all.scss index 65516dff8d..93f0db785a 100644 --- a/services/web/frontend/stylesheets/ds/components/all.scss +++ b/services/web/frontend/stylesheets/ds/components/all.scss @@ -1,2 +1,4 @@ @import 'button'; @import 'form-control'; +@import 'notification'; +@import 'selection-group'; diff --git a/services/web/frontend/stylesheets/ds/components/form-control.scss b/services/web/frontend/stylesheets/ds/components/form-control.scss index c367754706..6b0cc13f73 100644 --- a/services/web/frontend/stylesheets/ds/components/form-control.scss +++ b/services/web/frontend/stylesheets/ds/components/form-control.scss @@ -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,"); + } + + @if $type == 'radio' { + @return url("data:image/svg+xml,"); + } + } + + 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; + } + } +} diff --git a/services/web/frontend/stylesheets/ds/components/notification.scss b/services/web/frontend/stylesheets/ds/components/notification.scss new file mode 100644 index 0000000000..f462adc62c --- /dev/null +++ b/services/web/frontend/stylesheets/ds/components/notification.scss @@ -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); + } +} diff --git a/services/web/frontend/stylesheets/ds/components/selection-group.scss b/services/web/frontend/stylesheets/ds/components/selection-group.scss new file mode 100644 index 0000000000..fd72981640 --- /dev/null +++ b/services/web/frontend/stylesheets/ds/components/selection-group.scss @@ -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; + } + } +} diff --git a/services/web/frontend/stylesheets/pages/onboarding-confirm-email-ciam.scss b/services/web/frontend/stylesheets/pages/onboarding-confirm-email-ciam.scss index f680a4174d..97e6fd994b 100644 --- a/services/web/frontend/stylesheets/pages/onboarding-confirm-email-ciam.scss +++ b/services/web/frontend/stylesheets/pages/onboarding-confirm-email-ciam.scss @@ -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 { diff --git a/services/web/public/img/ciam-check-green.svg b/services/web/public/img/ciam-check-green.svg new file mode 100644 index 0000000000..da04b41cac --- /dev/null +++ b/services/web/public/img/ciam-check-green.svg @@ -0,0 +1,3 @@ + + +