diff --git a/CHANGELOG.md b/CHANGELOG.md index fa04811..adaddb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to ### Changed +- ✨(uiv2) change mail domains - 🛂(dimail) simplify interop with dimail - ✨(mailbox) remove secondary email as required field diff --git a/docker-compose.yml b/docker-compose.yml index d97856b..9e7bf9e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,7 @@ services: postgresql: image: postgres:16 + platform: linux/amd64 env_file: - env.d/development/postgresql ports: diff --git a/src/frontend/apps/desk/.env.development b/src/frontend/apps/desk/.env.development index 45ee232..19f3fba 100644 --- a/src/frontend/apps/desk/.env.development +++ b/src/frontend/apps/desk/.env.development @@ -1 +1 @@ -NEXT_PUBLIC_API_ORIGIN=http://localhost:8071 +NEXT_PUBLIC_API_ORIGIN=http://localhost:8071 \ No newline at end of file diff --git a/src/frontend/apps/desk/cunningham.ts b/src/frontend/apps/desk/cunningham.ts index a179392..0169910 100644 --- a/src/frontend/apps/desk/cunningham.ts +++ b/src/frontend/apps/desk/cunningham.ts @@ -1,373 +1,5 @@ -const config = { - themes: { - default: { - theme: { - colors: { - 'card-border': '#DDDDDD', - 'primary-bg': '#FAFAFA', - 'primary-100': '#EDF5FA', - 'primary-150': '#E5EEFA', - 'info-150': '#E5EEFA', - }, - font: { - sizes: { - ml: '0.938rem', - xl: '1.50rem', - t: '0.6875rem', - s: '0.75rem', - h1: '2.2rem', - h2: '1.7rem', - h3: '1.37rem', - h4: '1.15rem', - h5: '1rem', - h6: '0.87rem', - }, - weights: { - thin: 100, - extrabold: 800, - black: 900, - }, - }, - spacings: { - '0': '0', - none: '0', - auto: 'auto', - bx: '2.2rem', - full: '100%', - }, - breakpoints: { - xxs: '320px', - xs: '480px', - }, - }, - components: { - datagrid: { - header: { - weight: 'var(--c--theme--font--weights--extrabold)', - size: 'var(--c--theme--font--sizes--ml)', - }, - cell: { - color: 'var(--c--theme--colors--primary-500)', - size: 'var(--c--theme--font--sizes--ml)', - }, - }, - 'forms-checkbox': { - 'background-color': { - hover: '#055fd214', - }, - color: 'var(--c--theme--colors--primary-500)', - 'font-size': 'var(--c--theme--font--sizes--ml)', - }, - 'forms-datepicker': { - 'border-color': 'var(--c--theme--colors--primary-500)', - 'value-color': 'var(--c--theme--colors--primary-500)', - 'border-radius': { - hover: 'var(--c--components--forms-datepicker--border-radius)', - focus: 'var(--c--components--forms-datepicker--border-radius)', - }, - }, - 'forms-field': { - color: 'var(--c--theme--colors--primary-500)', - 'value-color': 'var(--c--theme--colors--primary-500)', - width: 'auto', - }, - 'forms-input': { - 'value-color': 'var(--c--theme--colors--primary-500)', - 'border-color': 'var(--c--theme--colors--primary-500)', - color: { - error: 'var(--c--theme--colors--danger-500)', - 'error-hover': 'var(--c--theme--colors--danger-500)', - 'box-shadow-error-hover': 'var(--c--theme--colors--danger-500)', - }, - }, - 'forms-labelledbox': { - 'label-color': { - small: 'var(--c--theme--colors--primary-500)', - 'small-disabled': 'var(--c--theme--colors--greyscale-400)', - big: { - disabled: 'var(--c--theme--colors--greyscale-400)', - }, - }, - }, - 'forms-select': { - 'border-color': 'var(--c--theme--colors--primary-500)', - 'border-color-disabled-hover': - 'var(--c--theme--colors--greyscale-200)', - 'border-radius': { - hover: 'var(--c--components--forms-select--border-radius)', - focus: 'var(--c--components--forms-select--border-radius)', - }, - 'font-size': 'var(--c--theme--font--sizes--ml)', - 'menu-background-color': '#ffffff', - 'item-background-color': { - hover: 'var(--c--theme--colors--primary-300)', - }, - }, - 'forms-switch': { - 'accent-color': 'var(--c--theme--colors--primary-400)', - }, - 'forms-textarea': { - 'border-color': 'var(--c--components--forms-textarea--border-color)', - 'border-color-hover': - 'var(--c--components--forms-textarea--border-color)', - 'border-radius': { - hover: 'var(--c--components--forms-textarea--border-radius)', - focus: 'var(--c--components--forms-textarea--border-radius)', - }, - color: 'var(--c--theme--colors--primary-500)', - disabled: { - 'border-color-hover': 'var(--c--theme--colors--greyscale-200)', - }, - }, - modal: { - 'background-color': '#ffffff', - }, - button: { - 'border-radius': { - active: 'var(--c--components--button--border-radius)', - }, - 'medium-height': 'auto', - 'small-height': 'auto', - success: { - color: 'white', - 'color-disabled': 'white', - 'color-hover': 'white', - background: { - color: 'var(--c--theme--colors--success-600)', - 'color-disabled': 'var(--c--theme--colors--greyscale-300)', - 'color-hover': 'var(--c--theme--colors--success-800)', - }, - }, - danger: { - 'color-hover': 'white', - background: { - color: 'var(--c--theme--colors--danger-400)', - 'color-hover': 'var(--c--theme--colors--danger-500)', - 'color-disabled': 'var(--c--theme--colors--danger-100)', - }, - }, - primary: { - color: 'var(--c--theme--colors--primary-text)', - 'color-active': 'var(--c--theme--colors--primary-text)', - background: { - color: 'var(--c--theme--colors--primary-400)', - 'color-active': 'var(--c--theme--colors--primary-500)', - }, - border: { - 'color-active': 'transparent', - }, - }, - secondary: { - color: 'var(--c--theme--colors--primary-500)', - 'color-hover': 'var(--c--theme--colors--primary-text)', - background: { - color: 'white', - 'color-hover': 'var(--c--theme--colors--primary-700)', - }, - border: { - color: 'var(--c--theme--colors--primary-200)', - }, - }, - tertiary: { - color: 'var(--c--theme--colors--primary-text)', - 'color-disabled': 'var(--c--theme--colors--greyscale-600)', - background: { - 'color-hover': 'var(--c--theme--colors--primary-100)', - 'color-disabled': 'var(--c--theme--colors--greyscale-200)', - }, - }, - disabled: { - color: 'white', - background: { - color: '#b3cef0', - }, - }, - }, - }, - }, - dsfr: { - theme: { - colors: { - 'card-border': '#DDDDDD', - 'primary-text': '#000091', - 'primary-100': '#f5f5fe', - 'primary-150': '#F4F4FD', - 'primary-200': '#ececfe', - 'primary-300': '#e3e3fd', - 'primary-400': '#cacafb', - 'primary-500': '#6a6af4', - 'primary-600': '#000091', - 'primary-700': '#272747', - 'primary-800': '#21213f', - 'primary-900': '#1c1a36', - 'secondary-text': '#FFFFFF', - 'secondary-100': '#fee9ea', - 'secondary-200': '#fedfdf', - 'secondary-300': '#fdbfbf', - 'secondary-400': '#e1020f', - 'secondary-500': '#c91a1f', - 'secondary-600': '#5e2b2b', - 'secondary-700': '#3b2424', - 'secondary-800': '#341f1f', - 'secondary-900': '#2b1919', - 'greyscale-text': '#303C4B', - 'greyscale-000': '#f6f6f6', - 'greyscale-100': '#eeeeee', - 'greyscale-200': '#e5e5e5', - 'greyscale-300': '#e1e1e1', - 'greyscale-400': '#dddddd', - 'greyscale-500': '#cecece', - 'greyscale-600': '#7b7b7b', - 'greyscale-700': '#666666', - 'greyscale-800': '#2a2a2a', - 'greyscale-900': '#1e1e1e', - 'success-text': '#1f8d49', - 'success-100': '#dffee6', - 'success-200': '#b8fec9', - 'success-300': '#88fdaa', - 'success-400': '#3bea7e', - 'success-500': '#1f8d49', - 'success-600': '#18753c', - 'success-700': '#204129', - 'success-800': '#1e2e22', - 'success-900': '#19281d', - 'info-text': '#0078f3', - 'info-100': '#f4f6ff', - 'info-200': '#e8edff', - 'info-300': '#dde5ff', - 'info-400': '#bdcdff', - 'info-500': '#0078f3', - 'info-600': '#0063cb', - 'info-700': '#f4f6ff', - 'info-800': '#222a3f', - 'info-900': '#1d2437', - 'warning-text': '#d64d00', - 'warning-100': '#fff4f3', - 'warning-200': '#ffe9e6', - 'warning-300': '#ffded9', - 'warning-400': '#ffbeb4', - 'warning-500': '#d64d00', - 'warning-600': '#b34000', - 'warning-700': '#5e2c21', - 'warning-800': '#3e241e', - 'warning-900': '#361e19', - 'danger-text': '#e1000f', - 'danger-100': '#fef4f4', - 'danger-200': '#fee9e9', - 'danger-300': '#fddede', - 'danger-400': '#fcbfbf', - 'danger-500': '#e1000f', - 'danger-600': '#c9191e', - 'danger-700': '#642727', - 'danger-800': '#412121', - 'danger-900': '#3a1c1c', - }, - font: { - families: { - accent: 'Marianne', - base: 'Marianne', - }, - }, - }, - components: { - alert: { - 'border-radius': '0', - }, - button: { - 'medium-height': '48px', - 'border-radius': '4px', - primary: { - background: { - color: 'var(--c--theme--colors--primary-text)', - 'color-hover': '#1212ff', - 'color-active': '#2323ff', - }, - color: '#ffffff', - 'color-hover': '#ffffff', - 'color-active': '#ffffff', - }, - 'primary-text': { - background: { - 'color-hover': 'var(--c--theme--colors--primary-100)', - 'color-active': 'var(--c--theme--colors--primary-100)', - }, - 'color-hover': 'var(--c--theme--colors--primary-text)', - }, - secondary: { - background: { - 'color-hover': '#F6F6F6', - 'color-active': '#EDEDED', - }, - border: { - color: 'var(--c--theme--colors--primary-600)', - 'color-hover': 'var(--c--theme--colors--primary-600)', - }, - color: 'var(--c--theme--colors--primary-text)', - }, - 'tertiary-text': { - background: { - 'color-hover': 'var(--c--theme--colors--primary-100)', - }, - 'color-hover': 'var(--c--theme--colors--primary-text)', - }, - }, - datagrid: { - header: { - color: 'var(--c--theme--colors--primary-text)', - size: 'var(--c--theme--font--sizes--s)', - }, - body: { - 'background-color': 'transparent', - 'background-color-hover': '#F4F4FD', - }, - pagination: { - 'background-color': 'transparent', - 'background-color-active': 'var(--c--theme--colors--primary-300)', - }, - }, - 'forms-checkbox': { - 'border-radius': '0', - color: 'var(--c--theme--colors--primary-text)', - }, - 'forms-datepicker': { - 'border-radius': '0', - }, - 'forms-fileuploader': { - 'border-radius': '0', - }, - 'forms-field': { - color: 'var(--c--theme--colors--primary-text)', - }, - 'forms-input': { - 'border-radius': '4px', - 'background-color': '#ffffff', - 'border-color': 'var(--c--theme--colors--primary-text)', - 'box-shadow-color': 'var(--c--theme--colors--primary-text)', - 'value-color': 'var(--c--theme--colors--primary-text)', - }, - 'forms-labelledbox': { - 'label-color': { - big: 'var(--c--theme--colors--primary-text)', - }, - }, - 'forms-select': { - 'border-radius': '4px', - 'border-radius-hover': '4px', - 'background-color': '#ffffff', - 'border-color': 'var(--c--theme--colors--primary-text)', - 'border-color-hover': 'var(--c--theme--colors--primary-text)', - 'box-shadow-color': 'var(--c--theme--colors--primary-text)', - }, - 'forms-switch': { - 'handle-border-radius': '2px', - 'rail-border-radius': '4px', - }, - 'forms-textarea': { - 'border-radius': '0', - }, - }, - }, - }, -}; +import { cunninghamConfig } from '@gouvfr-lasuite/ui-kit'; -export default config; +export default { + ...cunninghamConfig, +}; diff --git a/src/frontend/apps/desk/package.json b/src/frontend/apps/desk/package.json index 2783714..117081b 100644 --- a/src/frontend/apps/desk/package.json +++ b/src/frontend/apps/desk/package.json @@ -16,6 +16,7 @@ }, "dependencies": { "@gouvfr-lasuite/integration": "1.0.2", + "@gouvfr-lasuite/ui-kit": "^0.4.1", "@hookform/resolvers": "4.0.0", "@openfun/cunningham-react": "3.0.0", "@tanstack/react-query": "5.72.1", @@ -58,5 +59,9 @@ "stylelint-config-standard": "38.0.0", "stylelint-prettier": "5.0.3", "typescript": "*" + }, + "resolutions": { + "react": "19.0.0", + "react-dom": "19.0.0" } } diff --git a/src/frontend/apps/desk/public/favicon.ico b/src/frontend/apps/desk/public/favicon.ico index a2c76f6..dee6e3a 100644 Binary files a/src/frontend/apps/desk/public/favicon.ico and b/src/frontend/apps/desk/public/favicon.ico differ diff --git a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Bold.woff b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Bold.woff index 335e348..bb193f2 100644 Binary files a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Bold.woff and b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Bold.woff differ diff --git a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Bold_Italic.woff b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Bold_Italic.woff index 72878fc..fe7f744 100644 Binary files a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Bold_Italic.woff and b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Bold_Italic.woff differ diff --git a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-ExtraBold.woff b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-ExtraBold.woff index ad85a55..592410b 100644 Binary files a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-ExtraBold.woff and b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-ExtraBold.woff differ diff --git a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-ExtraBold_Italic.woff b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-ExtraBold_Italic.woff index af92ea7..e55bf1a 100644 Binary files a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-ExtraBold_Italic.woff and b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-ExtraBold_Italic.woff differ diff --git a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Light.woff b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Light.woff index 9ef49df..0935b72 100644 Binary files a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Light.woff and b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Light.woff differ diff --git a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Light_Italic.woff b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Light_Italic.woff index 5ae0dbe..3d28387 100644 Binary files a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Light_Italic.woff and b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Light_Italic.woff differ diff --git a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Medium.woff b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Medium.woff index 692476e..ca00151 100644 Binary files a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Medium.woff and b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Medium.woff differ diff --git a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Medium_Italic.woff b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Medium_Italic.woff index 8e32b88..998dddb 100644 Binary files a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Medium_Italic.woff and b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Medium_Italic.woff differ diff --git a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Regular.woff b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Regular.woff index a95a2d0..e5b04f5 100644 Binary files a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Regular.woff and b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Regular.woff differ diff --git a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Regular_Italic.woff b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Regular_Italic.woff index 1880c5d..0a370ed 100644 Binary files a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Regular_Italic.woff and b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Regular_Italic.woff differ diff --git a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Thin.woff b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Thin.woff index 6c8bc5a..e71057e 100644 Binary files a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Thin.woff and b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Thin.woff differ diff --git a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Thin_Italic.woff b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Thin_Italic.woff index 4c23d6b..9a56136 100644 Binary files a/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Thin_Italic.woff and b/src/frontend/apps/desk/src/assets/fonts/Marianne/Marianne-Thin_Italic.woff differ diff --git a/src/frontend/apps/desk/src/assets/logo-regie.svg b/src/frontend/apps/desk/src/assets/logo-regie.svg new file mode 100644 index 0000000..0cc95ff --- /dev/null +++ b/src/frontend/apps/desk/src/assets/logo-regie.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/frontend/apps/desk/src/components/Box.tsx b/src/frontend/apps/desk/src/components/Box.tsx index f799e8a..9727079 100644 --- a/src/frontend/apps/desk/src/components/Box.tsx +++ b/src/frontend/apps/desk/src/components/Box.tsx @@ -1,6 +1,6 @@ import { ComponentPropsWithRef } from 'react'; import styled from 'styled-components'; -import { CSSProperties } from 'styled-components/dist/types'; +import { CSSProperties, RuleSet } from 'styled-components/dist/types'; import { MarginPadding, @@ -8,26 +8,35 @@ import { stylesPadding, } from '@/utils/styleBuilder'; +import { hideEffect, showEffect } from './Effect'; + export interface BoxProps { as?: keyof HTMLElementTagNameMap; $align?: CSSProperties['alignItems']; $background?: CSSProperties['background']; $color?: CSSProperties['color']; - $css?: string; + $css?: string | RuleSet; $direction?: CSSProperties['flexDirection']; $display?: CSSProperties['display']; - $flex?: boolean; + $effect?: 'show' | 'hide'; + $flex?: CSSProperties['flex']; $gap?: CSSProperties['gap']; + $hasTransition?: boolean | 'slow'; $height?: CSSProperties['height']; $justify?: CSSProperties['justifyContent']; $overflow?: CSSProperties['overflow']; - $maxWidth?: CSSProperties['maxWidth']; $margin?: MarginPadding; + $maxHeight?: CSSProperties['maxHeight']; + $minHeight?: CSSProperties['minHeight']; + $maxWidth?: CSSProperties['maxWidth']; $minWidth?: CSSProperties['minWidth']; $padding?: MarginPadding; $position?: CSSProperties['position']; $radius?: CSSProperties['borderRadius']; + $shrink?: CSSProperties['flexShrink']; + $transition?: CSSProperties['transition']; $width?: CSSProperties['width']; + $wrap?: CSSProperties['flexWrap']; $zIndex?: CSSProperties['zIndex']; } @@ -41,18 +50,48 @@ export const Box = styled('div')` ${({ $color }) => $color && `color: ${$color};`} ${({ $direction }) => $direction && `flex-direction: ${$direction};`} ${({ $display }) => $display && `display: ${$display};`} - ${({ $flex }) => $flex === false && `display: block;`} + ${({ $flex }) => $flex && `flex: ${$flex};`} ${({ $gap }) => $gap && `gap: ${$gap};`} ${({ $height }) => $height && `height: ${$height};`} + ${({ $hasTransition }) => + $hasTransition && $hasTransition === 'slow' + ? `transition: all 0.5s ease-in-out;` + : $hasTransition + ? `transition: all 0.3s ease-in-out;` + : ''} ${({ $justify }) => $justify && `justify-content: ${$justify};`} ${({ $margin }) => $margin && stylesMargin($margin)} + ${({ $maxHeight }) => $maxHeight && `max-height: ${$maxHeight};`} + ${({ $minHeight }) => $minHeight && `min-height: ${$minHeight};`} + ${({ $maxWidth }) => $maxWidth && `max-width: ${$maxWidth};`} + ${({ $minWidth }) => $minWidth && `min-width: ${$minWidth};`} ${({ $overflow }) => $overflow && `overflow: ${$overflow};`} ${({ $padding }) => $padding && stylesPadding($padding)} ${({ $position }) => $position && `position: ${$position};`} ${({ $radius }) => $radius && `border-radius: ${$radius};`} + ${({ $shrink }) => $shrink && `flex-shrink: ${$shrink};`} + ${({ $transition }) => $transition && `transition: ${$transition};`} ${({ $width }) => $width && `width: ${$width};`} - ${({ $maxWidth }) => $maxWidth && `max-width: ${$maxWidth};`} - ${({ $minWidth }) => $minWidth && `min-width: ${$minWidth};`} - ${({ $css }) => $css && `${$css};`} + ${({ $wrap }) => $wrap && `flex-wrap: ${$wrap};`} + ${({ $css }) => $css && (typeof $css === 'string' ? `${$css};` : $css)} ${({ $zIndex }) => $zIndex && `z-index: ${$zIndex};`} + ${({ $effect }) => { + let effect; + switch ($effect) { + case 'show': + effect = showEffect; + break; + case 'hide': + effect = hideEffect; + break; + } + + return ( + effect && + ` + transition: all 0.3s ease-in-out; + ${effect} + ` + ); + }} `; diff --git a/src/frontend/apps/desk/src/components/BoxButton.tsx b/src/frontend/apps/desk/src/components/BoxButton.tsx index 893af9d..c40d574 100644 --- a/src/frontend/apps/desk/src/components/BoxButton.tsx +++ b/src/frontend/apps/desk/src/components/BoxButton.tsx @@ -1,8 +1,13 @@ -import { ComponentPropsWithRef, forwardRef } from 'react'; +import { forwardRef } from 'react'; +import { css } from 'styled-components'; import { Box, BoxType } from './Box'; -export type BoxButtonType = ComponentPropsWithRef; +export type BoxButtonType = BoxType & { + disabled?: boolean; +}; + +/** /** * Styleless button that extends the Box component. @@ -17,21 +22,34 @@ export type BoxButtonType = ComponentPropsWithRef; * * ``` */ -const BoxButton = forwardRef( +const BoxButton = forwardRef( ({ $css, ...props }, ref) => { return ( ) => { + if (props.disabled) { + return; + } + props.onClick?.(event); + }} /> ); }, diff --git a/src/frontend/apps/desk/src/components/Card.tsx b/src/frontend/apps/desk/src/components/Card.tsx index e59ed74..639b17c 100644 --- a/src/frontend/apps/desk/src/components/Card.tsx +++ b/src/frontend/apps/desk/src/components/Card.tsx @@ -4,11 +4,7 @@ import { useCunninghamTheme } from '@/cunningham'; import { Box, BoxType } from '.'; -export const Card = ({ - children, - $css, - ...props -}: PropsWithChildren) => { +export const Card = ({ children, ...props }: PropsWithChildren) => { const { colorsTokens } = useCunninghamTheme(); return ( @@ -16,9 +12,7 @@ export const Card = ({ $background="white" $radius="4px" $css={` - box-shadow: 2px 2px 5px ${colorsTokens()['primary-300']}88; - border: 1px solid ${colorsTokens()['card-border']}; - ${$css} + border: 1px solid ${colorsTokens()['greyscale-050']}; `} {...props} > diff --git a/src/frontend/apps/desk/src/components/Effect.tsx b/src/frontend/apps/desk/src/components/Effect.tsx new file mode 100644 index 0000000..53cffc2 --- /dev/null +++ b/src/frontend/apps/desk/src/components/Effect.tsx @@ -0,0 +1,11 @@ +export const showEffect = ` + transform: scaleY(1); + opacity: 1; + max-height: 150px; +`; + +export const hideEffect = ` + transform: scaleY(0); + opacity: 0; + max-height: 0; +`; diff --git a/src/frontend/apps/desk/src/components/Icon.tsx b/src/frontend/apps/desk/src/components/Icon.tsx new file mode 100644 index 0000000..94b39bb --- /dev/null +++ b/src/frontend/apps/desk/src/components/Icon.tsx @@ -0,0 +1,63 @@ +import { css } from 'styled-components'; + +import { Text, TextType } from '@/components'; +import { useCunninghamTheme } from '@/cunningham'; + +type IconProps = TextType & { + iconName: string; +}; +export const Icon = ({ iconName, ...textProps }: IconProps) => { + return ( + + {iconName} + + ); +}; + +interface IconBGProps extends TextType { + iconName: string; +} + +export const IconBG = ({ iconName, ...textProps }: IconBGProps) => { + const { colorsTokens } = useCunninghamTheme(); + + return ( + + {iconName} + + ); +}; + +type IconOptionsProps = TextType & { + isHorizontal?: boolean; +}; + +export const IconOptions = ({ isHorizontal, ...props }: IconOptionsProps) => { + return ( + + {isHorizontal ? 'more_horiz' : 'more_vert'} + + ); +}; diff --git a/src/frontend/apps/desk/src/components/IconOptions.tsx b/src/frontend/apps/desk/src/components/IconOptions.tsx deleted file mode 100644 index a6b2656..0000000 --- a/src/frontend/apps/desk/src/components/IconOptions.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Text } from '@/components'; - -interface IconOptionsProps { - isOpen: boolean; - 'aria-label': string; -} - -export const IconOptions = ({ isOpen, ...props }: IconOptionsProps) => { - return ( - - more_vert - - ); -}; diff --git a/src/frontend/apps/desk/src/components/Input.tsx b/src/frontend/apps/desk/src/components/Input.tsx new file mode 100644 index 0000000..0e4e3e4 --- /dev/null +++ b/src/frontend/apps/desk/src/components/Input.tsx @@ -0,0 +1,44 @@ +import React, { InputHTMLAttributes } from 'react'; + +import { Box, Text } from '@/components'; +import { useCunninghamTheme } from '@/cunningham'; + +type InputProps = InputHTMLAttributes & { + label: string; + error?: string; +}; + +export const Input = ({ label, error, required, ...props }: InputProps) => { + const { colorsTokens } = useCunninghamTheme(); + + return ( + + + + {error && ( + + {error} + + )} + + ); +}; diff --git a/src/frontend/apps/desk/src/components/Tag.tsx b/src/frontend/apps/desk/src/components/Tag.tsx new file mode 100644 index 0000000..d4c2c12 --- /dev/null +++ b/src/frontend/apps/desk/src/components/Tag.tsx @@ -0,0 +1,88 @@ +import { Tooltip } from '@openfun/cunningham-react'; +import { useTranslation } from 'react-i18next'; + +import { Box } from '@/components'; +import { useCunninghamTheme } from '@/cunningham'; + +interface TagContentProps { + status: 'pending' | 'enabled' | 'disabled' | 'failed' | 'action_required'; + showTooltip?: boolean; + tooltipType?: 'domain' | 'mail'; + placement?: 'top' | 'bottom'; +} + +const TagContent = ({ status }: TagContentProps) => { + const { colorsTokens } = useCunninghamTheme(); + const { t } = useTranslation(); + + const textColor = { + pending: colorsTokens()['info-600'], + enabled: colorsTokens()['success-600'], + disabled: colorsTokens()['greyscale-600'], + action_required: colorsTokens()['warning-600'], + failed: colorsTokens()['danger-600'], + }; + + const backgroundColor = { + pending: colorsTokens()['info-100'], + enabled: colorsTokens()['success-100'], + disabled: colorsTokens()['greyscale-100'], + action_required: colorsTokens()['warning-100'], + failed: colorsTokens()['danger-100'], + }; + + return ( + + {t(status).replace('_', ' ')} + + ); +}; + +export const Tag = ({ ...props }: TagContentProps) => { + const { t } = useTranslation(); + + const tooltipText: Record< + NonNullable, + Partial> + > = { + domain: { + pending: 'Domain pending validation by an administrator', + enabled: 'Active domain', + disabled: 'Disabled domain', + failed: 'Domain error, contact an administrator', + action_required: + 'A configuration action from the domain manager (outside Régie) is required', + }, + mail: { + pending: 'Email address pending validation by an administrator', + enabled: 'Functional email address', + failed: 'Email address error, contact an administrator', + disabled: 'Disabled email address', + }, + }; + + const rawTooltip = + props.tooltipType && tooltipText[props.tooltipType]?.[props.status]; + + const tooltipContent = rawTooltip ? t(rawTooltip) : ''; + + return props.showTooltip ? ( + + + + + + ) : ( + + ); +}; diff --git a/src/frontend/apps/desk/src/components/Text.tsx b/src/frontend/apps/desk/src/components/Text.tsx index 86c20fe..71dedff 100644 --- a/src/frontend/apps/desk/src/components/Text.tsx +++ b/src/frontend/apps/desk/src/components/Text.tsx @@ -1,4 +1,4 @@ -import { CSSProperties, ComponentPropsWithRef } from 'react'; +import { CSSProperties, ComponentPropsWithRef, forwardRef } from 'react'; import styled from 'styled-components'; import { tokens } from '@/cunningham'; @@ -13,6 +13,8 @@ export interface TextProps extends BoxProps { HTMLElementTagNameMap, 'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' >; + $elipsis?: boolean; + $isMaterialIcon?: boolean; $weight?: CSSProperties['fontWeight']; $textAlign?: CSSProperties['textAlign']; $size?: TextSizes | (string & {}); @@ -26,6 +28,7 @@ export interface TextProps extends BoxProps { | 'greyscale'; $variation?: | 'text' + | '000' | '100' | '200' | '300' @@ -34,7 +37,8 @@ export interface TextProps extends BoxProps { | '600' | '700' | '800' - | '900'; + | '900' + | '1000'; } export type TextType = ComponentPropsWithRef; @@ -48,12 +52,26 @@ export const TextStyled = styled(Box)` ${({ $theme, $variation }) => `color: var(--c--theme--colors--${$theme}-${$variation});`} ${({ $color }) => $color && `color: ${$color};`} + ${({ $elipsis }) => + $elipsis && + `white-space: nowrap; overflow: hidden; text-overflow: ellipsis;`} `; -export const Text = ({ - ...props -}: ComponentPropsWithRef) => { - return ( - - ); -}; +const Text = forwardRef>( + ({ className, $isMaterialIcon, ...props }, ref) => { + return ( + + ); + }, +); + +Text.displayName = 'Text'; + +export { Text }; diff --git a/src/frontend/apps/desk/src/components/TextErrors.tsx b/src/frontend/apps/desk/src/components/TextErrors.tsx index 7419b99..76f31eb 100644 --- a/src/frontend/apps/desk/src/components/TextErrors.tsx +++ b/src/frontend/apps/desk/src/components/TextErrors.tsx @@ -20,8 +20,7 @@ export const TextErrors = ({ causes.map((cause, i) => ( diff --git a/src/frontend/apps/desk/src/components/__tests__/Box.spec.tsx b/src/frontend/apps/desk/src/components/__tests__/Box.spec.tsx deleted file mode 100644 index e1e27ce..0000000 --- a/src/frontend/apps/desk/src/components/__tests__/Box.spec.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { render, screen } from '@testing-library/react'; - -import { Box } from '../Box'; - -describe('', () => { - it('has the padding from prop', () => { - const { unmount } = render(My Box); - - expect(screen.getByText('My Box')).toHaveStyle('padding: 10px'); - - unmount(); - - render( - - My Box - , - ); - - expect(screen.getByText('My Box')).toHaveStyle(` - padding-left: 4rem; - padding-right: 4rem; - padding-top: 3rem; - padding-bottom: 0.5rem;`); - }); - - it('has the margin from prop', () => { - const { unmount } = render(My Box); - expect(screen.getByText('My Box')).toHaveStyle('margin: 10px'); - - unmount(); - - render( - - My Box - , - ); - - expect(screen.getByText('My Box')).toHaveStyle(` - margin-left: auto; - margin-right: auto; - margin-top: 1.625rem; - margin-bottom: 100%;`); - }); -}); diff --git a/src/frontend/apps/desk/src/components/__tests__/Modal.test.tsx b/src/frontend/apps/desk/src/components/__tests__/Modal.test.tsx deleted file mode 100644 index 1d7b2ae..0000000 --- a/src/frontend/apps/desk/src/components/__tests__/Modal.test.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { ModalSize } from '@openfun/cunningham-react'; -import { render, screen, waitFor } from '@testing-library/react'; -import React from 'react'; - -import { AppWrapper } from '@/tests/utils'; - -import { Modal, usePreventFocusVisible } from '../Modal'; - -describe('usePreventFocusVisible hook', () => { - const TestComponent = () => { - usePreventFocusVisible(['.test-element']); - - return ( -
-
Test Element
-
- ); - }; - - const originalMutationObserver = global.MutationObserver; - - const mockDisconnect = jest.fn(); - const mutationObserverMock = jest.fn(function MutationObserver( - callback: MutationCallback, - ) { - this.observe = () => { - callback([{ type: 'childList' }] as MutationRecord[], this); - }; - - this.disconnect = mockDisconnect; - }); - - afterEach(() => jest.clearAllMocks()); - - beforeAll( - () => - (global.MutationObserver = - mutationObserverMock as unknown as typeof MutationObserver), - ); - - afterAll(() => (global.MutationObserver = originalMutationObserver)); - - test('sets tabindex to -1 on the target elements', () => { - const { unmount } = render(); - - const targetElement = screen.getByText('Test Element'); - - expect(targetElement).toHaveAttribute('tabindex', '-1'); - - unmount(); - - expect(mockDisconnect).toHaveBeenCalled(); - }); -}); - -describe('Modal', () => { - test('applies usePreventFocusVisible and sets tabindex', async () => { - render( - {}} - size={ModalSize.MEDIUM} - title={

Test Modal Title

} - leftActions={} - rightActions={} - > -

Modal content

-
, - { wrapper: AppWrapper }, - ); - - /* eslint-disable testing-library/no-node-access */ - const modalContent = document.querySelector('.c__modal__content'); - /* eslint-enable testing-library/no-node-access */ - - await waitFor(() => { - expect(modalContent).toHaveAttribute('tabindex', '-1'); - }); - }); -}); diff --git a/src/frontend/apps/desk/src/components/index.ts b/src/frontend/apps/desk/src/components/index.ts index 78a4b43..4bbe5fe 100644 --- a/src/frontend/apps/desk/src/components/index.ts +++ b/src/frontend/apps/desk/src/components/index.ts @@ -1,9 +1,13 @@ export * from './Box'; export * from './BoxButton'; export * from './Card'; +export * from './Effect'; export * from './DropButton'; -export * from './IconOptions'; +export * from './Icon'; +export * from './Input'; export * from './Link'; export * from './LogoGouv'; +export * from './Tag'; export * from './Text'; export * from './TextErrors'; +export * from './separators'; diff --git a/src/frontend/apps/desk/src/components/modal/CustomModal.tsx b/src/frontend/apps/desk/src/components/modal/CustomModal.tsx new file mode 100644 index 0000000..70dda29 --- /dev/null +++ b/src/frontend/apps/desk/src/components/modal/CustomModal.tsx @@ -0,0 +1,75 @@ +import { + Modal as CunninghamModal, + ModalProps, +} from '@openfun/cunningham-react'; +import React, { useEffect } from 'react'; + +import { HorizontalSeparator } from '@/components'; + +import style from './custom-modal.module.scss'; + +export const CustomModal: React.FC< + ModalProps & { step?: number; totalSteps?: number } +> = ({ children, step = 0, totalSteps = 1, ...props }) => { + // Apply the hook here once for all modals + usePreventFocusVisible(['.c__modal__content']); + + return ( + + {/*
e.stopPropagation()}> */} + {/* modal header */} + {totalSteps > 1 && ( +
+
+ {Array.from({ length: totalSteps }).map((_, index) => ( +
+ ))} +
+
+ )} + +
+ {children} + {/* modal content */} + {/*
{children}
*/} + + {/* modal footer */} +
+
+ + {/*
*/} +
+ ); +}; + +/** + * @description used to prevent elements to be navigable by keyboard when only a DOM mutation causes the elements to be + * in the document + * @see https://github.com/suitenumerique/people/pull/379 + */ +export const usePreventFocusVisible = (elements: string[]) => { + useEffect(() => { + const observer = new MutationObserver((mutationsList) => { + mutationsList.forEach(() => { + elements.forEach((selector) => + document.querySelector(selector)?.setAttribute('tabindex', '-1'), + ); + observer.disconnect(); + }); + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + }); + + return () => { + observer.disconnect(); + }; + }, [elements]); + + return null; +}; diff --git a/src/frontend/apps/desk/src/components/modal/custom-modal.module.scss b/src/frontend/apps/desk/src/components/modal/custom-modal.module.scss new file mode 100644 index 0000000..4474627 --- /dev/null +++ b/src/frontend/apps/desk/src/components/modal/custom-modal.module.scss @@ -0,0 +1,79 @@ +.modalCustom { + padding: 0; + border-radius: 4px; + box-shadow: 0 6px 18px rgba(0, 0, 18, 0.16); +} + +.header { + position: relative; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 var(--c--theme--spacings--md); + padding-top: var(--c--theme--spacings--sm); + padding-bottom: var(--c--theme--spacings--2xs); +} + +.closeButton { + position: absolute; + display: block; + right: 10px; + top: 5px; + background: none; + border: none; + font-size: 34px; + cursor: pointer; + color: var(--c--theme--colors--greyscale-700); +} + +.content { + padding: var(--c--theme--spacings--md); +} + +.c__modal__footer { + padding: 0 var(--c--theme--spacings--md); + flex-direction: column; + justify-content: space-between; +} + +.footer { + display: flex; + justify-content: space-between; + padding: 0 var(--c--theme--spacings--md); + padding-top: 0; + align-items: center; + border-top: 1px solid var(--c--theme--colors--greyscale-200); +} + +.progressBar { + display: flex; + width: 100%; + padding-top: 0; + align-items: stretch; + align-content: stretch; + flex-wrap: nowrap; + height: 4px; + gap: var(--c--theme--spacings--2xs); +} + +.progressBarStep { + flex: 1; + height: 4px; + display: block; + width: auto; + flex-grow: 1; + border-radius: 4px; + background-color: var(--c--theme--colors--primary-200); + &.active { + background-color: var(--c--theme--colors--primary-800); + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/src/frontend/apps/desk/src/components/separators/HorizontalSeparator.tsx b/src/frontend/apps/desk/src/components/separators/HorizontalSeparator.tsx new file mode 100644 index 0000000..cd61798 --- /dev/null +++ b/src/frontend/apps/desk/src/components/separators/HorizontalSeparator.tsx @@ -0,0 +1,33 @@ +import { useCunninghamTheme } from '@/cunningham'; + +import { Box } from '../Box'; + +export enum SeparatorVariant { + LIGHT = 'light', + DARK = 'dark', +} + +type Props = { + variant?: SeparatorVariant; + $withPadding?: boolean; +}; + +export const HorizontalSeparator = ({ + variant = SeparatorVariant.LIGHT, + $withPadding = true, +}: Props) => { + const { colorsTokens } = useCunninghamTheme(); + + return ( + + ); +}; diff --git a/src/frontend/apps/desk/src/components/separators/SeparatedSection.tsx b/src/frontend/apps/desk/src/components/separators/SeparatedSection.tsx new file mode 100644 index 0000000..65e0f58 --- /dev/null +++ b/src/frontend/apps/desk/src/components/separators/SeparatedSection.tsx @@ -0,0 +1,31 @@ +import { PropsWithChildren } from 'react'; +import { css } from 'styled-components'; + +import { useCunninghamTheme } from '@/cunningham'; + +import { Box } from '../Box'; + +type Props = { + showSeparator?: boolean; +}; + +export const SeparatedSection = ({ + showSeparator = true, + children, +}: PropsWithChildren) => { + const theme = useCunninghamTheme(); + const colors = theme.colorsTokens(); + return ( + + {children} + + ); +}; diff --git a/src/frontend/apps/desk/src/components/separators/index.ts b/src/frontend/apps/desk/src/components/separators/index.ts new file mode 100644 index 0000000..f8d2100 --- /dev/null +++ b/src/frontend/apps/desk/src/components/separators/index.ts @@ -0,0 +1,2 @@ +export * from './HorizontalSeparator'; +export * from './SeparatedSection'; diff --git a/src/frontend/apps/desk/src/core/MainLayout.tsx b/src/frontend/apps/desk/src/core/MainLayout.tsx deleted file mode 100644 index 9b9db73..0000000 --- a/src/frontend/apps/desk/src/core/MainLayout.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { PropsWithChildren } from 'react'; - -import { Box } from '@/components'; -import { Footer } from '@/features/footer/Footer'; -import { HEADER_HEIGHT, Header } from '@/features/header'; -import { Menu } from '@/features/menu'; - -import { useConfigStore } from './config'; - -export function MainLayout({ children }: PropsWithChildren) { - const { config } = useConfigStore(); - - return ( - - -
- - {config?.FEATURES.TEAMS_DISPLAY && } - - {children} - - - -