Merge pull request #29972 from overleaf/td-ciam-onboarding
Unified access onboarding data collection pages GitOrigin-RevId: c56a3d52f749883eeb2302e22aaf6bdf1239160c
This commit is contained in:
@@ -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
|
||||
+26
-21
@@ -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
-1
@@ -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'
|
||||
),
|
||||
}
|
||||
+2
-2
@@ -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
-1
@@ -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;
|
||||
}
|
||||
@@ -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 |
Reference in New Issue
Block a user