From f8f72c442580a0c956f551587d6232aec507b1f7 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:14:01 +0200 Subject: [PATCH 01/27] Add align prop to Tooltip arrow --- src/components/tooltip/tooltip.styled.ts | 5 ++++- src/components/tooltip/tooltipStandAlone.tsx | 2 +- src/components/tooltip/tooltipUnControlled.tsx | 3 +-- src/components/tooltip/types/tooltip.ts | 2 +- src/components/tooltip/types/tooltipTheme.ts | 10 +++++++++- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/tooltip/tooltip.styled.ts b/src/components/tooltip/tooltip.styled.ts index 8033ea63..97c31e95 100644 --- a/src/components/tooltip/tooltip.styled.ts +++ b/src/components/tooltip/tooltip.styled.ts @@ -3,13 +3,14 @@ import styled, { css } from 'styled-components'; import { focusVisibleAlt } from '@/styles/mixins'; import { getStyles } from '@/utils/getStyles/getStyles'; -import { TooltipVariantStylesProps } from './types'; +import { TooltipAlignType, TooltipVariantStylesProps } from './types'; type TooltipStylesPropsTypes = { styles: TooltipVariantStylesProps; hasCloseIcon?: boolean; hasTitle?: boolean; hasBorder?: boolean; + align?: TooltipAlignType; }; export const TooltipStyled = styled.div<{ tooltipAsModal?: boolean }>` @@ -62,6 +63,8 @@ export const TooltipCloseIconStyled = styled.div` export const TooltipArrowStyled = styled.div` position: absolute; ${({ styles }) => getStyles(styles.arrowContainer)} + ${({ styles, align = TooltipAlignType.TOP }) => + getStyles(styles.arrowContainer?.tooltipAlignStyles?.[align])} width: ${props => props.styles.arrowContainer?.arrow_size}; height: ${props => props.styles.arrowContainer?.arrow_size}; transform: rotate(45deg); diff --git a/src/components/tooltip/tooltipStandAlone.tsx b/src/components/tooltip/tooltipStandAlone.tsx index fcf36520..59da6597 100644 --- a/src/components/tooltip/tooltipStandAlone.tsx +++ b/src/components/tooltip/tooltipStandAlone.tsx @@ -142,7 +142,7 @@ const TooltipStandAlone = ({ )} - + diff --git a/src/components/tooltip/tooltipUnControlled.tsx b/src/components/tooltip/tooltipUnControlled.tsx index baae4c3b..aa7dfb01 100644 --- a/src/components/tooltip/tooltipUnControlled.tsx +++ b/src/components/tooltip/tooltipUnControlled.tsx @@ -19,7 +19,6 @@ const TooltipUnControlledComponent = React.forwardRef( { ctv, tooltipAsModal, - align, onOpenClose, variant, tooltipAriaLabel, @@ -49,7 +48,7 @@ const TooltipUnControlledComponent = React.forwardRef( tooltipRef, variant, onOpenClose, - align, + align: props.align, ctv, }); diff --git a/src/components/tooltip/types/tooltip.ts b/src/components/tooltip/types/tooltip.ts index b5dc47ca..4e5a187c 100644 --- a/src/components/tooltip/types/tooltip.ts +++ b/src/components/tooltip/types/tooltip.ts @@ -31,6 +31,7 @@ export type TooltipPopoverType = Omit; export interface ITooltipStandAlone { disabled?: boolean; mediaDevice: DeviceBreakpointsType; + align?: TooltipAlignType; title?: TooltipTitleType; content?: TooltipContentType; onFocus?: React.FocusEventHandler; @@ -94,5 +95,4 @@ type propsToOmitUnControlled = export interface ITooltipUnControlled extends Omit, propsToOmitUnControlled> { onOpenClose?: (open: boolean) => void; - align?: TooltipAlignType; } diff --git a/src/components/tooltip/types/tooltipTheme.ts b/src/components/tooltip/types/tooltipTheme.ts index 6b8b0377..2fce17f8 100644 --- a/src/components/tooltip/types/tooltipTheme.ts +++ b/src/components/tooltip/types/tooltipTheme.ts @@ -1,5 +1,7 @@ import { CommonStyleType, DeviceBreakpointsType, IconTypes, TypographyTypes } from '@/types'; +import { TooltipAlignType } from './tooltipAlign'; + export type TooltipVariantStylesProps = { tooltipExternalContainer?: CommonStyleType; tooltipInternalContainer?: CommonStyleType; @@ -21,7 +23,13 @@ export type TooltipVariantStylesProps = { [DeviceBreakpointsType.TABLET]?: boolean; [DeviceBreakpointsType.MOBILE]?: boolean; }; - arrowContainer?: CommonStyleType & { arrow_size?: string; arrow_position?: string }; + arrowContainer?: CommonStyleType & { + arrow_size?: string; + arrow_position?: string; + tooltipAlignStyles?: { + [key in TooltipAlignType]?: CommonStyleType; + }; + }; arrow?: CommonStyleType; dragIconContainer?: CommonStyleType; dragIcon?: IconTypes; From d4938437883ee7f50c9a75fa34a46034d383ef75 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:16:03 +0200 Subject: [PATCH 02/27] Improve minwidth styles props on Drawer Component --- src/components/drawer/drawer.styled.ts | 42 +------------------ src/components/drawer/drawerControlled.tsx | 21 ++++++---- .../stories/drawerControlled.stories.tsx | 12 +++++- .../kubit/components/drawer/styles.ts | 22 ++++++++++ 4 files changed, 47 insertions(+), 50 deletions(-) diff --git a/src/components/drawer/drawer.styled.ts b/src/components/drawer/drawer.styled.ts index 66842bb9..58d34b40 100644 --- a/src/components/drawer/drawer.styled.ts +++ b/src/components/drawer/drawer.styled.ts @@ -33,30 +33,7 @@ export const DrawerTitleStyled = styled.div.withConfig({ })``; export const DrawerStyled = styled.div` - background-color: #fff; - max-height: 100vh; - max-height: var(--100svh, 100vh); - max-height: 100svh; - min-width: 50vw; - max-width: 50vw; - overflow-y: auto; - ${() => - ({ - theme: { - MEDIA_QUERIES: { onlyDesktop, onlyTablet }, - }, - }) => css` - ${onlyDesktop} { - min-height: 100vh; - min-height: var(--100svh, 100vh); - min-height: 100svh; - } - ${onlyTablet} { - min-height: 100vh; - min-height: var(--100svh, 100vh); - min-height: 100svh; - } - `}; + ${props => props.position && getStyles(props.styles.container?.[props.position])} ${DrawerTitleStyled} { transition: box-shadow 300ms; @@ -66,23 +43,6 @@ export const DrawerStyled = styled.div` ${props => getStyles(props.styles.titleContainer)} ${props => getTypographyStyles(props.styles.titleContainer)} } - - ${() => - ({ - theme: { - MEDIA_QUERIES: { onlyMobile, onlyTablet }, - }, - }) => css` - ${onlyMobile} { - max-width: inherit; - max-height: inherit; - } - ${onlyTablet} { - max-width: 100vw; - min-width: 100vw; - } - `}; - ${props => props.position && getStyles(props.styles.container?.[props.position])} `; export const DrawerNavigationStyled = styled.div` diff --git a/src/components/drawer/drawerControlled.tsx b/src/components/drawer/drawerControlled.tsx index d4f75802..f7e88978 100644 --- a/src/components/drawer/drawerControlled.tsx +++ b/src/components/drawer/drawerControlled.tsx @@ -1,8 +1,13 @@ import * as React from 'react'; import { STYLES_NAME } from '@/constants'; -import { useDeviceHeight, useMediaDevice, useScrollEffect, useZoomEffect } from '@/hooks'; -import { useStyles } from '@/hooks/useStyles/useStyles'; +import { + useDeviceHeight, + useMediaDevice, + useScrollEffect, + useStylesV2, + useZoomEffect, +} from '@/hooks'; import { ErrorBoundary, FallbackComponent } from '@/provider/errorBoundary'; import { CssProperty } from '@/utils'; @@ -25,13 +30,13 @@ const DrawerControlledComponent = React.forwardRef( ref: React.ForwardedRef | undefined | null ): JSX.Element => { useDeviceHeight(); - const styles = useStyles( - STYLES_NAME.DRAWER, - props.variant, - props.ctv - ); + const styles = useStylesV2({ + styleName: STYLES_NAME.DRAWER, + variantName: props.variant, + customTokens: props.ctv, + }); const device = useMediaDevice(); - const stylesByDevice = styles[device]; + const stylesByDevice = styles?.[device]; const { scrollableRef, shadowRef } = useScrollEffect({ shadowStyles: stylesByDevice.titleContainer?.box_shadow, diff --git a/src/components/drawer/stories/drawerControlled.stories.tsx b/src/components/drawer/stories/drawerControlled.stories.tsx index 7867e4f2..19d1125f 100644 --- a/src/components/drawer/stories/drawerControlled.stories.tsx +++ b/src/components/drawer/stories/drawerControlled.stories.tsx @@ -11,7 +11,11 @@ import { ButtonSizeType, ButtonVariantType } from '@/designSystem/kubit/componen import { themesObject, variantsObject } from '@/designSystem/themesObject'; import { DrawerControlled as Story } from '../index'; -import { DrawerLevelPositionTypes, DrawerTitleComponentType } from '../types'; +import { + DrawerLevelPositionTypes, + DrawerTitleComponentType, + DrawerVariantPositionTypes, +} from '../types'; import { argtypes } from './controlledArgtypes'; const themeSelected = localStorage.getItem('themeSelected') || 'kubit'; @@ -110,6 +114,12 @@ export const DrawerControlledWithCtv: Story = { title: { color: 'red', }, + container: { + [DrawerVariantPositionTypes.DRAWER_RIGHT]: { + min_width: '80vw', + max_width: '80vw', + }, + }, }, TABLET: { title: { diff --git a/src/designSystem/kubit/components/drawer/styles.ts b/src/designSystem/kubit/components/drawer/styles.ts index f3cd8962..d5b7b30d 100644 --- a/src/designSystem/kubit/components/drawer/styles.ts +++ b/src/designSystem/kubit/components/drawer/styles.ts @@ -25,14 +25,23 @@ const containerCommonProps: { [DrawerVariantPositionTypes.DRAWER_RIGHT]: { border_top_left_radius: RADIUS.radius_50, border_bottom_left_radius: RADIUS.radius_50, + max_height: SPACINGS.spacing_100_vh, + background: COLORS.NEUTRAL.color_neutral_bg_250, + min_width: '50vw', + max_width: '50vw', }, [DrawerVariantPositionTypes.DRAWER_LEFT]: { border_top_right_radius: RADIUS.radius_50, border_bottom_right_radius: RADIUS.radius_50, + max_height: SPACINGS.spacing_100_vh, + background: COLORS.NEUTRAL.color_neutral_bg_250, + min_width: '50vw', + max_width: '50vw', }, [DrawerVariantPositionTypes.DRAWER_BOTTOM]: { border_top_right_radius: RADIUS.radius_50, border_top_left_radius: RADIUS.radius_50, + background: COLORS.NEUTRAL.color_neutral_bg_250, }, }; const commonIconContainerProps: CommonStyleType = { @@ -122,6 +131,11 @@ export const DRAWER_STYLES: DrawerStylesType = { [DeviceBreakpointsType.TABLET]: { container: { ...containerCommonProps, + [DrawerVariantPositionTypes.DRAWER_BOTTOM]: { + ...containerCommonProps.DRAWER_BOTTOM, + min_height: 'var(--100svh, 100vh)', + background_color: COLORS.NEUTRAL.color_neutral_bg_250, + }, }, iconContainer: { ...commonIconContainerProps, @@ -165,6 +179,14 @@ export const DRAWER_STYLES: DrawerStylesType = { }, }, [DeviceBreakpointsType.MOBILE]: { + container: { + ...containerCommonProps, + [DrawerVariantPositionTypes.DRAWER_BOTTOM]: { + ...containerCommonProps.DRAWER_BOTTOM, + min_height: 'var(--100svh, 100vh)', + background_color: COLORS.NEUTRAL.color_neutral_bg_250, + }, + }, iconContainer: { ...commonIconContainerProps, padding_top: SPACINGS.spacing_300, From 85cb9f111ab13a33d2434121844bb818f24f8f70 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:16:44 +0200 Subject: [PATCH 03/27] Improve calendar and inputDate Add aria label props and improve the monthSelector functionalitie --- .../monthSelector/utils/monthSelector.test.ts | 69 ++++++++++++++++++- .../utils/monthSelector.utils.ts | 22 ++++++ .../inputDate/components/popoverCalendar.tsx | 1 + .../inputDate/inputDateStandAlone.tsx | 4 +- .../inputDate/stories/inputDate.stories.tsx | 4 +- src/components/inputDate/types/inputDate.ts | 1 + 6 files changed, 95 insertions(+), 6 deletions(-) diff --git a/src/components/calendar/selector/monthSelector/utils/monthSelector.test.ts b/src/components/calendar/selector/monthSelector/utils/monthSelector.test.ts index 0724a632..cc8714c1 100644 --- a/src/components/calendar/selector/monthSelector/utils/monthSelector.test.ts +++ b/src/components/calendar/selector/monthSelector/utils/monthSelector.test.ts @@ -52,6 +52,20 @@ describe('Month Selector utils', () => { const result2 = keyLeftFunction(previous2); expect(result2).toBe(previous2 - 1); }); + it('keyLeftMove - maxDate and minDate in same year - should move to the left when left arrow key is pressed', () => { + const maxDate = new Date(2023, 8, 15); + const currentDate = new Date(2023, 8, 22); + const minDate = new Date(2023, 4, 15); + + const keyLeftFunction = keyLeftMove({ currentDate, maxDate, minDate }); + + const previous1 = minDate.getMonth(); + const previous2 = minDate.getMonth() + 1; + const result1 = keyLeftFunction(previous1); + expect(result1).toBe(maxDate.getMonth()); + const result2 = keyLeftFunction(previous2); + expect(result2).toBe(previous2 - 1); + }); it('keyRightMove - maxDate and currentDate in same year - should move to the right when right arrow key is pressed', () => { const maxDate = new Date(2023, 8, 15); const currentDate = new Date(2023, 8, 22); @@ -94,6 +108,21 @@ describe('Month Selector utils', () => { const result2 = keyRightFunction(previous2); expect(result2).toBe(previous2 + 1); }); + + it('keyRightMove - maxDate and minDate in same year - should move to the right when right arrow key is pressed', () => { + const maxDate = new Date(2023, 8, 15); + const currentDate = new Date(2023, 8, 22); + const minDate = new Date(2023, 4, 15); + + const keyRightFunction = keyRightMove({ currentDate, maxDate, minDate }); + + const previous1 = maxDate.getMonth(); + const previous2 = maxDate.getMonth() - 1; + const result1 = keyRightFunction(previous1); + expect(result1).toBe(minDate.getMonth()); + const result2 = keyRightFunction(previous2); + expect(result2).toBe(previous2 + 1); + }); it('keyUpMove - maxDate and currentDate in same year - should move up when up arrow key is pressed', () => { const maxDate = new Date(2023, 8, 15); const currentDate = new Date(2023, 8, 22); @@ -145,7 +174,24 @@ describe('Month Selector utils', () => { const result3 = keyUpFunction(previous3); expect(result3).toBe(firstMonth); }); - it('keyDownMove - maxDate and currentDate in same year - should move up when up arrow key is pressed', () => { + it('keyUpMove - maxDate and minDate in same year - should move up when up arrow key is pressed', () => { + const maxDate = new Date(2023, 8, 15); + const currentDate = new Date(2023, 8, 22); + const minDate = new Date(2023, 0, 15); + + const keyUpFunction = keyUpMove({ currentDate, maxDate, minDate }); + + const previous1 = minDate.getMonth(); + const previous2 = minDate.getMonth() + 3; + const previous3 = minDate.getMonth() + 2; + const result1 = keyUpFunction(previous1); + expect(result1).toBe(maxDate.getMonth()); + const result2 = keyUpFunction(previous2); + expect(result2).toBe(previous2 - 3); + const result3 = keyUpFunction(previous3); + expect(result3).toBe(minDate.getMonth()); + }); + it('keyDownMove - maxDate and currentDate in same year - should move down when down arrow key is pressed', () => { const maxDate = new Date(2023, 8, 15); const currentDate = new Date(2023, 8, 22); const minDate = new Date(2000, 0, 15); @@ -162,7 +208,7 @@ describe('Month Selector utils', () => { const result3 = keyDownFunction(previous3); expect(result3).toBe(maxDate.getMonth()); }); - it('keyDownMove - minDate and currentDate in same year - should move up when up arrow key is pressed', () => { + it('keyDownMove - minDate and currentDate in same year - should move down when down arrow key is pressed', () => { const maxDate = new Date(2025, 8, 15); const currentDate = new Date(2023, 8, 22); const minDate = new Date(2023, 0, 15); @@ -179,7 +225,7 @@ describe('Month Selector utils', () => { const result3 = keyDownFunction(previous3); expect(result3).toBe(previous3 + 3); }); - it('keyDownMove - currentDate year is different than minDate and maxDate - should move up when up arrow key is pressed', () => { + it('keyDownMove - currentDate year is different than minDate and maxDate - should move down when down arrow key is pressed', () => { const maxDate = new Date(2025, 8, 15); const currentDate = new Date(2023, 8, 22); const minDate = new Date(2000, 0, 15); @@ -196,6 +242,23 @@ describe('Month Selector utils', () => { const result3 = keyDownFunction(previous3); expect(result3).toBe(previous3 + 3); }); + it('keyDownMove - maxDate and minDate in same year - should move down when down arrow key is pressed', () => { + const maxDate = new Date(2023, 8, 15); + const currentDate = new Date(2023, 8, 22); + const minDate = new Date(2023, 0, 15); + + const keyDownFunction = keyDownMove({ currentDate, maxDate, minDate }); + + const previous1 = maxDate.getMonth(); + const previous2 = 1; + const previous3 = maxDate.getMonth() + 3; + const result1 = keyDownFunction(previous1); + expect(result1).toBe(firstMonth); + const result2 = keyDownFunction(previous2); + expect(result2).toBe(previous2 + 3); + const result3 = keyDownFunction(previous3); + expect(result3).toBe(maxDate.getMonth()); + }); it('keyTabMove - should return previous', () => { const previous = 5; const result = keyTabMove(previous); diff --git a/src/components/calendar/selector/monthSelector/utils/monthSelector.utils.ts b/src/components/calendar/selector/monthSelector/utils/monthSelector.utils.ts index 63da8672..810bc4cb 100644 --- a/src/components/calendar/selector/monthSelector/utils/monthSelector.utils.ts +++ b/src/components/calendar/selector/monthSelector/utils/monthSelector.utils.ts @@ -18,6 +18,9 @@ export const keyLeftMove = ({ minDate, }: HandleKeyMoveType): ((previous: number) => number) => { return previous => { + if (isSameYear(maxDate, minDate)) { + return previous === minDate.getMonth() ? maxDate.getMonth() : previous - 1; + } if (isSameYear(maxDate, currentDate)) { return previous === FIRST_MONTH ? maxDate.getMonth() : previous - 1; } @@ -34,6 +37,9 @@ export const keyRightMove = ({ minDate, }: HandleKeyMoveType): ((previous: number) => number) => { return previous => { + if (isSameYear(maxDate, minDate)) { + return previous === maxDate.getMonth() ? minDate.getMonth() : previous + 1; + } if (isSameYear(maxDate, currentDate)) { return previous === maxDate.getMonth() ? FIRST_MONTH : previous + 1; } @@ -50,6 +56,14 @@ export const keyUpMove = ({ minDate, }: HandleKeyMoveType): ((previous: number) => number) => { return previous => { + if (isSameYear(maxDate, minDate)) { + if (previous === minDate.getMonth()) { + return maxDate.getMonth(); + } + return previous <= minDate.getMonth() + NUM_DAYS_IN_ROW + ? minDate.getMonth() + : previous - NUM_DAYS_IN_ROW; + } if (isSameYear(minDate, currentDate)) { if (previous === minDate.getMonth()) { return LAST_MONTH; @@ -77,6 +91,14 @@ export const keyDownMove = ({ minDate, }: HandleKeyMoveType): ((previous: number) => number) => { return previous => { + if (isSameYear(maxDate, minDate)) { + if (previous === maxDate.getMonth()) { + return minDate.getMonth(); + } + return previous >= maxDate.getMonth() - NUM_DAYS_IN_ROW + ? maxDate.getMonth() + : previous + NUM_DAYS_IN_ROW; + } if (isSameYear(minDate, currentDate)) { if (previous === LAST_MONTH) { return minDate.getMonth(); diff --git a/src/components/inputDate/components/popoverCalendar.tsx b/src/components/inputDate/components/popoverCalendar.tsx index 82e49208..3fc66b9c 100644 --- a/src/components/inputDate/components/popoverCalendar.tsx +++ b/src/components/inputDate/components/popoverCalendar.tsx @@ -46,6 +46,7 @@ const PopoverCalendarComponent = ( return ( { const uniqueId = useId('inputDate'); const inputId = props.id ?? uniqueId; - const ariaControls = props.calendarOpen ? `${uniqueId}Calendar` : undefined; + const ariaControls = props.calendarOpen ? `${inputId}Calendar` : undefined; const { onWrapperBlur, ...innerInputProps } = props; return ( @@ -23,6 +23,8 @@ export const InputDateStandAloneComponent = ( , 'children'> & { export interface IConfigAccesibilityPopover extends IConfigAccesibility { openInputIconAriaLabel?: string; closeInputIconAriaLabel?: string; + calendarAriaLabel?: string; } export interface IPopoverCalendar { From d2a8eefa7e8af1db34ebd1a226cb9a4843fa4b32 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:17:10 +0200 Subject: [PATCH 04/27] Add align prop on Text storybook --- src/components/text/stories/argtypes.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/components/text/stories/argtypes.ts b/src/components/text/stories/argtypes.ts index cb648f33..1ebfba6a 100644 --- a/src/components/text/stories/argtypes.ts +++ b/src/components/text/stories/argtypes.ts @@ -26,6 +26,18 @@ export const argtypes = ( category: CATEGORY_CONTROL.MODIFIERS, }, }, + align: { + description: 'Align of text', + type: { name: 'string', required: true }, + control: { type: 'select' }, + options: ['left', 'right', 'center', 'justify'], + table: { + type: { + summary: 'string', + }, + category: CATEGORY_CONTROL.MODIFIERS, + }, + }, component: { description: 'Component type to transform text to', type: { name: 'TextComponentType | GenericLinkType' }, From 7b6c8bd0c00b6eb7d3e1ea7b2bd86235d1a6250d Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:18:32 +0200 Subject: [PATCH 05/27] Update package and prettier --- .prettierrc.cjs | 26 +++++++++++++++++++ .prettierrc.js | 26 ------------------- package.json | 68 ++++++++++++++++++++++++------------------------- 3 files changed, 60 insertions(+), 60 deletions(-) create mode 100644 .prettierrc.cjs delete mode 100644 .prettierrc.js diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 00000000..a86108ac --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,26 @@ +module.exports = { + tabWidth: 2, + useTabs: false, + semi: true, + singleQuote: true, + quoteProps: 'consistent', + jsxSingleQuote: false, + trailingComma: 'es5', + bracketSpacing: true, + bracketSameLine: false, + arrowParens: 'avoid', + printWidth: 100, + endOfLine: 'lf', + plugins: [require.resolve('@trivago/prettier-plugin-sort-imports')], + importOrder: ['react', 'styled-components', 'dayjs', '^(?!\\.|@)', '^@/', '^\\.'], + importOrderSeparation: true, + importOrderSortSpecifiers: true, + overrides: [ + { + files: ['config/**/*.js'], + options: { + printWidth: 160, + }, + }, + ], +}; diff --git a/.prettierrc.js b/.prettierrc.js deleted file mode 100644 index 00677874..00000000 --- a/.prettierrc.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports ={ - "tabWidth": 2, - "useTabs": false, - "semi": true, - "singleQuote": true, - "quoteProps": "consistent", - "jsxSingleQuote": false, - "trailingComma": "es5", - "bracketSpacing": true, - "bracketSameLine": false, - "arrowParens": "avoid", - "printWidth": 100, - "endOfLine": "lf", - "plugins": [require.resolve("@trivago/prettier-plugin-sort-imports")], - "importOrder": ["react", "styled-components", "dayjs", "^(?!\\.|@)", "^@/", "^\\."], - "importOrderSeparation": true, - "importOrderSortSpecifiers": true, - "overrides": [ - { - "files": ["config/**/*.js"], - "options": { - "printWidth": 160 - } - } - ] -} diff --git a/package.json b/package.json index 26bb9007..62309661 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kubit-ui-web/react-components", - "version": "1.12.0", + "version": "1.13.0", "description": "Kubit React Components is a customizable, accessible library of React web components, designed to enhance your application's user experience", "author": { "name": "Kubit", @@ -93,54 +93,54 @@ "@babel/preset-typescript": "^7.24.7", "@eslint/compat": "^1.1.1", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "^9.9.1", - "@storybook/addon-a11y": "^8.2.9", - "@storybook/addon-controls": "^8.2.9", + "@eslint/js": "^9.11.1", + "@storybook/addon-a11y": "^8.3.2", + "@storybook/addon-controls": "^8.3.2", "@storybook/addon-coverage": "^1.0.4", - "@storybook/addon-essentials": "^8.2.9", - "@storybook/addon-interactions": "^8.2.9", - "@storybook/addon-links": "^8.2.9", - "@storybook/addon-themes": "^8.2.9", - "@storybook/blocks": "^8.2.9", - "@storybook/react": "^8.2.9", - "@storybook/react-vite": "^8.2.9", + "@storybook/addon-essentials": "^8.3.2", + "@storybook/addon-interactions": "^8.3.2", + "@storybook/addon-links": "^8.3.2", + "@storybook/addon-themes": "^8.3.2", + "@storybook/blocks": "^8.3.2", + "@storybook/react": "^8.3.2", + "@storybook/react-vite": "^8.3.2", "@testing-library/jest-dom": "^6.5.0", - "@testing-library/react": "^16.0.0", + "@testing-library/react": "^16.0.1", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^14.5.2", "@trivago/prettier-plugin-sort-imports": "^4.3.0", - "@types/jest": "^29.5.12", + "@types/jest": "^29.5.13", "@types/jest-axe": "^3.5.9", - "@types/mocha": "^10.0.7", - "@types/react": "^18.3.4", + "@types/mocha": "^10.0.8", + "@types/react": "^18.3.8", "@types/react-dom": "^18.3.0", "@types/styled-components": "^5.1.34", "@types/ungap__structured-clone": "^1.2.0", - "@typescript-eslint/eslint-plugin": "^8.3.0", - "@typescript-eslint/parser": "^8.3.0", + "@typescript-eslint/eslint-plugin": "^8.7.0", + "@typescript-eslint/parser": "^8.7.0", "@ungap/structured-clone": "^1.2.0", "@vitejs/plugin-react": "^4.3.1", "babel-jest": "^29.7.0", "cpy-cli": "^5.0.0", - "eslint": "^9.9.1", + "eslint": "^9.11.1", "eslint-config-prettier": "^9.1.0", "eslint-config-standard-with-typescript": "^43.0.1", "eslint-import-resolver-typescript": "^3.6.3", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jest": "^28.8.0", - "eslint-plugin-jsx-a11y": "^6.9.0", - "eslint-plugin-n": "^17.10.2", + "eslint-plugin-import": "^2.30.0", + "eslint-plugin-jest": "^28.8.3", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-n": "^17.10.3", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "5.2.1", "eslint-plugin-promise": "^7.1.0", - "eslint-plugin-react": "^7.35.0", + "eslint-plugin-react": "^7.36.1", "eslint-plugin-react-hooks": "^4.6.2", - "eslint-plugin-react-refresh": "^0.4.11", + "eslint-plugin-react-refresh": "^0.4.12", "eslint-plugin-storybook": "^0.8.0", - "eslint-plugin-unused-imports": "^4.1.3", + "eslint-plugin-unused-imports": "^4.1.4", "globals": "^15.9.0", "gts": "^5.3.1", - "html-validate": "^8.21.0", + "html-validate": "^8.23.0", "jest": "^29.7.0", "jest-axe": "^9.0.0", "jest-canvas-mock": "^2.5.2", @@ -152,23 +152,23 @@ "prettier": "^3.3.3", "react-transition-group": "^4.4.5", "sort-imports": "^1.1.0", - "storybook": "^8.2.9", + "storybook": "^8.3.2", "ts-jest": "^29.2.5", "tsc-alias": "1.8.10", - "typedoc": "^0.26.6", - "typedoc-plugin-markdown": "^4.2.6", - "typescript": "^5.5.4", - "vite": "^5.4.2", + "typedoc": "^0.26.7", + "typedoc-plugin-markdown": "^4.2.8", + "typescript": "^5.6.2", + "vite": "^5.4.7", "vite-tsconfig-paths": "^5.0.1", "yarn-deduplicate": "^6.0.2" }, "dependencies": { - "@emotion/is-prop-valid": "^1.3.0", + "@emotion/is-prop-valid": "^1.3.1", "lottie-web": "^5.12.2", "react": "^18.3.1", "react-dom": "^18.3.1", "react-transition-group": "^4.4.5", - "styled-components": "^6.1.12" + "styled-components": "^6.1.13" }, "resolutions": { "strip-ansi": "^6.0.1", @@ -180,4 +180,4 @@ "url": "https://github.com/kubit-ui/kubit-react-components/issues" }, "homepage": "https://www.kubit-ui.com/" -} +} \ No newline at end of file From 76d76095327cd88ef5bb649e0bf979328f68d8da Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:21:53 +0200 Subject: [PATCH 06/27] Improve documentation of isStringTypeOf --- src/utils/isStringTypeOf/isStringTypeOf.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/utils/isStringTypeOf/isStringTypeOf.ts b/src/utils/isStringTypeOf/isStringTypeOf.ts index ba8ac49a..1c8b64ae 100644 --- a/src/utils/isStringTypeOf/isStringTypeOf.ts +++ b/src/utils/isStringTypeOf/isStringTypeOf.ts @@ -1,3 +1,19 @@ -export const isStringTypeOf = (value: unknown): boolean => { +/** + * @description - Typescript validator to assert if a value is `string` type. + * + * @example + * + * let foo: number | string = 0 + * + * if(Math.random() < 0.5){ + * foo = 'bar' + * } + * + * if(isStringTypeOf(foo)) { + * // We asserted the value is actually an string! + * console.log("foo length is: " + foo.length) // foo length is 3 + * } + */ +export const isStringTypeOf = (value: unknown): value is string => { return typeof value === 'string'; }; From 8995641362bd0a017bfed2fe54ccf5d978a88581 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:22:07 +0200 Subject: [PATCH 07/27] New styles to mediaButton component --- .../kubit/components/mediaButton/styles.ts | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/designSystem/kubit/components/mediaButton/styles.ts b/src/designSystem/kubit/components/mediaButton/styles.ts index b6877e0f..d4addd42 100644 --- a/src/designSystem/kubit/components/mediaButton/styles.ts +++ b/src/designSystem/kubit/components/mediaButton/styles.ts @@ -25,24 +25,39 @@ const commonDefaultProps = { icon: { color: COLORS.BRAND.color_brand_bg_50, disabled: { - color: COLORS.ACCENT.color_accent_default_bg_100, + color: COLORS.DISABLED.color_accentDisabled_font_100, }, }, iconToTransition: { color: COLORS.BRAND.color_brand_bg_50, }, loader: { - variant: LoaderVariantType.PRIMARY_WHITE, + variant: LoaderVariantType.PRIMARY_RED, + cursor: 'not-allowed', }, }; export const MEDIA_BUTTON_STYLES: MediaButtonStylesType = { [MediaButtonVariantType.DEFAULT]: { + [MediaButtonSizeType.EXTRA_LARGE]: { + ...commonDefaultProps, + container: { + ...commonContainerProps, + background_color: COLORS.NEUTRAL.color_neutral_bg_250, + }, + buttonContainer: { + ...commonButtonContainerProps, + width: SPACINGS.spacing_650, + height: SPACINGS.spacing_650, + min_width: SPACINGS.spacing_650, + min_height: SPACINGS.spacing_650, + }, + }, [MediaButtonSizeType.LARGE]: { ...commonDefaultProps, container: { ...commonContainerProps, - background_color: COLORS.NEUTRAL.color_neutral_bg_100, + background_color: COLORS.NEUTRAL.color_neutral_bg_250, }, buttonContainer: { ...commonButtonContainerProps, @@ -56,7 +71,7 @@ export const MEDIA_BUTTON_STYLES: MediaButtonStylesType ...commonDefaultProps, container: { ...commonContainerProps, - background_color: COLORS.NEUTRAL.color_neutral_bg_100, + border_radius: RADIUS.radius_00, }, buttonContainer: { ...commonButtonContainerProps, @@ -64,6 +79,7 @@ export const MEDIA_BUTTON_STYLES: MediaButtonStylesType height: SPACINGS.spacing_400, min_width: SPACINGS.spacing_400, min_height: SPACINGS.spacing_400, + margin: SPACINGS.spacing_0, }, }, }, From 6fb247e0fbbf07f648a4f970926b6cb53a6bfa05 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:22:25 +0200 Subject: [PATCH 08/27] New import routes --- .../dropdownSelectedStandAlone.tsx | 2 +- src/components/icon/icon.tsx | 2 +- .../mediaButton/mediaButtonStandAlone.tsx | 2 +- .../mediaButton/stories/mediaButton.stories.tsx | 16 ++++++++++++++-- .../navigationCard/navigationCardStandAlone.tsx | 5 ++--- src/components/pillV2/pillStandAlone.tsx | 4 ++-- src/components/quickButton/quickButton.tsx | 2 +- 7 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/components/dropdownSelected/dropdownSelectedStandAlone.tsx b/src/components/dropdownSelected/dropdownSelectedStandAlone.tsx index 5d069dfb..81ab15c0 100644 --- a/src/components/dropdownSelected/dropdownSelectedStandAlone.tsx +++ b/src/components/dropdownSelected/dropdownSelectedStandAlone.tsx @@ -4,7 +4,7 @@ import { keyDownMove, keyUpMove } from '@/components/listOptions/utils'; import { useId } from '@/hooks/useId/useId'; import { ROLES } from '@/types'; -import { ButtonType } from '../button'; +import { ButtonType } from '../button/types'; import { ElementOrIcon } from '../elementOrIcon'; import { ListOptions, ListOptionsOptionType, ListOptionsType } from '../listOptions'; import { diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx index ffc498b0..a4d6aca0 100644 --- a/src/components/icon/icon.tsx +++ b/src/components/icon/icon.tsx @@ -4,7 +4,7 @@ import { ScreenReaderOnly } from '@/components/screenReaderOnly'; import { useMediaDevice } from '@/hooks/index'; import { pickAriaProps } from '@/utils/aria/aria'; -import { ButtonType } from '../button'; +import { ButtonType } from '../button/types/type'; import { IconButtonStyled } from './icon.styled'; import { IconStandAlone } from './iconStandAlone'; import { IIcon } from './types'; diff --git a/src/components/mediaButton/mediaButtonStandAlone.tsx b/src/components/mediaButton/mediaButtonStandAlone.tsx index 36b751ee..151a0c5f 100644 --- a/src/components/mediaButton/mediaButtonStandAlone.tsx +++ b/src/components/mediaButton/mediaButtonStandAlone.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { ButtonType } from '../button'; +import { ButtonType } from '../button/types'; //components import { ElementOrIcon } from '../elementOrIcon'; import { Loader } from '../loader'; diff --git a/src/components/mediaButton/stories/mediaButton.stories.tsx b/src/components/mediaButton/stories/mediaButton.stories.tsx index 007e67f1..50670482 100644 --- a/src/components/mediaButton/stories/mediaButton.stories.tsx +++ b/src/components/mediaButton/stories/mediaButton.stories.tsx @@ -12,7 +12,7 @@ import { argtypes } from './argtypes'; const themeSelected = localStorage.getItem('themeSelected') || 'kubit'; const meta = { - title: 'Components/Resources/Mediabutton', + title: 'Components/Actions/Mediabutton', component: Story, parameters: { layout: 'centered', @@ -41,7 +41,7 @@ const commonArgs: IMediaButton = { )[0] as MediaButtonSizeType, icon: { icon: ICONS.ICON_PLACEHOLDER }, twistedIcon: { icon: ICONS.ICON_CLOSE }, - hasBackground: false, + hasBackground: true, loading: true, }; @@ -52,6 +52,18 @@ export const Mediabutton: Story = { }, }; +export const MediabuttonWithoutBackground: Story = { + args: { + ...commonArgs, + themeArgs: themesObject[themeSelected][STYLES_NAME.MEDIA_BUTTON], + size: Object.values( + variantsObject[themeSelected].MediaButtonIconSizeVariantType || {} + )[1] as MediaButtonSizeType, + hasBackground: false, + loading: false, + }, +}; + export const MediabuttonWithCtv: Story = { args: { ...commonArgs, diff --git a/src/components/navigationCard/navigationCardStandAlone.tsx b/src/components/navigationCard/navigationCardStandAlone.tsx index d380ecd5..19c59bef 100644 --- a/src/components/navigationCard/navigationCardStandAlone.tsx +++ b/src/components/navigationCard/navigationCardStandAlone.tsx @@ -6,7 +6,7 @@ import { Text } from '@/components/text/text'; import { TextComponentType } from '@/components/text/types/component'; import { useId } from '@/hooks'; -import { ButtonType } from '../button'; +import { ButtonType } from '../button/types'; import { NavigationCardInfo } from './fragments'; import { buildProps } from './helpers'; import { @@ -36,8 +36,7 @@ const NavigationCardStandaloneComponent = ( return ( // Can not be spread -> styled component breaks } aria-disabled={props['aria-disabled']} as={props.url ? props.component : 'button'} className={props.className} diff --git a/src/components/pillV2/pillStandAlone.tsx b/src/components/pillV2/pillStandAlone.tsx index 031144ac..e4f2f0e6 100644 --- a/src/components/pillV2/pillStandAlone.tsx +++ b/src/components/pillV2/pillStandAlone.tsx @@ -7,7 +7,7 @@ import { useId } from '@/hooks'; import { ROLES } from '@/types'; import { InputTypeType } from '@/types/inputType'; -import { ButtonType } from '../button'; +import { ButtonType } from '../button/types'; import { PillAsButton, PillContentContainerStyled, @@ -18,7 +18,7 @@ import { IPillStandAlone, PillType } from './types'; const PillStandAloneComponent = ( { dataTestId = 'pill', type = PillType.BUTTON, ...props }: IPillStandAlone, - ref: React.ForwardedRef | undefined | null + ref: React.ForwardedRef | undefined | null ): JSX.Element => { const id = useId('pill'); const pillContentId = `${id}-content`; diff --git a/src/components/quickButton/quickButton.tsx b/src/components/quickButton/quickButton.tsx index f1b8dddc..acecde45 100644 --- a/src/components/quickButton/quickButton.tsx +++ b/src/components/quickButton/quickButton.tsx @@ -6,7 +6,7 @@ import { States, useManageState } from '@/hooks'; import { useStyles } from '@/hooks/useStyles/useStyles'; import { ErrorBoundary, FallbackComponent } from '@/provider/errorBoundary'; -import { ButtonType } from '../button'; +import { ButtonType } from '../button/types'; //components import { QuickButtonStandAlone } from './quickButtonStandAlone'; import { From 30fa2821f17c7a29ef85e5924122e01e00db7ae9 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:31:09 +0200 Subject: [PATCH 09/27] Fix typescript and eslint errors --- src/components/navigationCard/navigationCardStandAlone.tsx | 2 +- src/components/pillV2/pill.tsx | 2 +- src/components/pillV2/pillStandAlone.tsx | 2 +- src/constants/keyboardKeys.ts | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/navigationCard/navigationCardStandAlone.tsx b/src/components/navigationCard/navigationCardStandAlone.tsx index 19c59bef..610922bd 100644 --- a/src/components/navigationCard/navigationCardStandAlone.tsx +++ b/src/components/navigationCard/navigationCardStandAlone.tsx @@ -36,7 +36,7 @@ const NavigationCardStandaloneComponent = ( return ( // Can not be spread -> styled component breaks } + ref={ref as any} aria-disabled={props['aria-disabled']} as={props.url ? props.component : 'button'} className={props.className} diff --git a/src/components/pillV2/pill.tsx b/src/components/pillV2/pill.tsx index 3e465e01..7e9d3bdf 100644 --- a/src/components/pillV2/pill.tsx +++ b/src/components/pillV2/pill.tsx @@ -9,7 +9,7 @@ import { getPillState } from './utils'; const PillComponent = ( { variant, size, ctv, selected = false, disabled = false, ...props }: IPill, - ref: React.LegacyRef | undefined + ref: React.LegacyRef | undefined ) => { const variantStyles = useStylesV2({ styleName: STYLES_NAME.PILL_V2, diff --git a/src/components/pillV2/pillStandAlone.tsx b/src/components/pillV2/pillStandAlone.tsx index e4f2f0e6..99a84351 100644 --- a/src/components/pillV2/pillStandAlone.tsx +++ b/src/components/pillV2/pillStandAlone.tsx @@ -25,7 +25,7 @@ const PillStandAloneComponent = ( return ( Date: Tue, 24 Sep 2024 09:50:12 +0200 Subject: [PATCH 10/27] Add drag element and improve modal component --- src/components/modal/modal.styled.ts | 4 + src/components/modal/modalControlled.tsx | 14 ++- src/components/modal/modalStandAlone.tsx | 7 ++ src/components/modal/modalUnControlled.tsx | 1 + .../modal/stories/modal.stories.tsx | 7 +- src/components/modal/types/modal.ts | 11 ++- src/components/modal/types/modalTheme.ts | 2 + .../modalV2/fragments/modalHeader.tsx | 93 +++++++++++-------- src/components/modalV2/modal.styled.ts | 8 +- src/components/modalV2/modalControlled.tsx | 14 ++- src/components/modalV2/modalStandAlone.tsx | 10 +- src/components/modalV2/modalUnControlled.tsx | 1 + .../modalV2/stories/modal.stories.tsx | 25 +++++ src/components/modalV2/types/modal.ts | 17 +++- src/components/modalV2/types/modalTheme.ts | 2 + 15 files changed, 162 insertions(+), 54 deletions(-) diff --git a/src/components/modal/modal.styled.ts b/src/components/modal/modal.styled.ts index 3023e1f8..a4824550 100644 --- a/src/components/modal/modal.styled.ts +++ b/src/components/modal/modal.styled.ts @@ -105,3 +105,7 @@ export const ModalContentStyled = styled.div export const ModalFooterStyled = styled.div` ${props => getStyles(props.$styles.footer)} `; + +export const DraggableIcon = styled.div` + ${props => getStyles(props.$styles?.dragIconContainer)} +`; diff --git a/src/components/modal/modalControlled.tsx b/src/components/modal/modalControlled.tsx index 0e5b4b73..5b1cd1f8 100644 --- a/src/components/modal/modalControlled.tsx +++ b/src/components/modal/modalControlled.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { STYLES_NAME } from '@/constants'; -import { useMediaDevice, useScrollEffect, useStyles, useZoomEffect } from '@/hooks'; +import { useMediaDevice, useScrollEffect, useStyles, useSwipeDown, useZoomEffect } from '@/hooks'; import { ErrorBoundary } from '@/provider/errorBoundary/errorBoundary'; import { FallbackComponent } from '@/provider/errorBoundary/fallbackComponent'; import { DeviceBreakpointsType } from '@/types'; @@ -40,17 +40,29 @@ const ModalControlledComponent = React.forwardRef( const zoomRef = useZoomEffect(CONTAINER_STYLES_EDIT, MAX_ZOOM); const zoomRefChild = useZoomEffect(CONTENT_STYLES_EDIT, MAX_ZOOM); + const { setPopoverRef, setDragIconRef } = useSwipeDown(props.popover?.animationOptions, () => + props.onClose?.() + ); + + const handlePopoverCloseInternally = () => { + props.popover?.onCloseInternally?.(); + props.onClose?.(); + }; + const modalStructure = ( ); diff --git a/src/components/modal/modalStandAlone.tsx b/src/components/modal/modalStandAlone.tsx index fc6f02c4..f62e2d53 100644 --- a/src/components/modal/modalStandAlone.tsx +++ b/src/components/modal/modalStandAlone.tsx @@ -12,6 +12,7 @@ import { useId } from '@/hooks'; import { DeviceBreakpointsType } from '@/types'; import { + DraggableIcon, ModalCloseButtonStyled, ModalContentStyled, ModalFooterStyled, @@ -95,6 +96,7 @@ const ModalStandAloneComponent = ( positionVariant={PopoverPositionVariantType.FIXED} trapFocusInsideModal={true} variant={props.styles.popoverVariant} + onCloseInternally={props.onPopoverCloseInternally} {...props.popover} > props.onKeyDown?.(event)} > + {!props.blocked && props.dragIcon && ( + + + + )} {!props.blocked && props.closeIcon?.icon && ( ( open={open} popover={{ ...popover, onCloseInternally: handlePopoverCloseInternally }} variant={variant} + onClose={onClose} onKeyDown={onKeyDown} /> ); diff --git a/src/components/modal/stories/modal.stories.tsx b/src/components/modal/stories/modal.stories.tsx index 9ab0c2c7..4bb1cd6b 100644 --- a/src/components/modal/stories/modal.stories.tsx +++ b/src/components/modal/stories/modal.stories.tsx @@ -44,7 +44,12 @@ const StoryWithHooks = args => { return (
- +
); }; diff --git a/src/components/modal/types/modal.ts b/src/components/modal/types/modal.ts index df9fc145..31331299 100644 --- a/src/components/modal/types/modal.ts +++ b/src/components/modal/types/modal.ts @@ -57,6 +57,9 @@ export interface IModalStandAlone { device: DeviceBreakpointsType; dataTestId?: string; onKeyDown?: React.KeyboardEventHandler; + onPopoverCloseInternally?: () => void; + dragIcon?: IElementOrIcon; + dragIconRef?: (node) => void; /* To useScrollableEffect */ scrollableRef: (node) => void; resizeRef: (node) => void; @@ -73,16 +76,16 @@ type OmitProps = | 'resizeRef' | 'shadowRef' | 'zoomRef' - | 'zoomRefChild'; + | 'zoomRefChild' + | 'dragIconRef'; export interface IModalControlled extends Omit, Omit, 'cts' | 'extraCt'> { variant: V; portalId?: string; + onClose?: () => void; } export interface IModalUnControlled - extends IModalControlled { - onClose?: () => void; -} + extends IModalControlled {} diff --git a/src/components/modal/types/modalTheme.ts b/src/components/modal/types/modalTheme.ts index 0eee3f95..002c0c5f 100644 --- a/src/components/modal/types/modalTheme.ts +++ b/src/components/modal/types/modalTheme.ts @@ -16,6 +16,8 @@ export type ModalBaseStylesType = { closeButton?: { buttonVariant?: string; }; + dragIconContainer?: CommonStyleType; + dragIcon?: IconTypes; }; export type ModalStylesType

= { diff --git a/src/components/modalV2/fragments/modalHeader.tsx b/src/components/modalV2/fragments/modalHeader.tsx index d8d06324..2e8dbf29 100644 --- a/src/components/modalV2/fragments/modalHeader.tsx +++ b/src/components/modalV2/fragments/modalHeader.tsx @@ -3,16 +3,26 @@ import React from 'react'; import { Button } from '@/components/button'; import { ElementOrIcon } from '@/components/elementOrIcon'; import { Text, TextComponentType } from '@/components/text'; +import { DeviceBreakpointsType } from '@/types'; import { + DraggableIcon, ModalCloseButtonStyled, - ModalFilledContainer, ModalHeaderStyled, TitleHiddenContainer, } from '../modal.styled'; import { IModalStandAlone } from '../types'; -type PickedProps = 'styles' | 'dataTestId' | 'blocked' | 'closeIcon' | 'closeButton' | 'title'; +type PickedProps = + | 'styles' + | 'dataTestId' + | 'blocked' + | 'closeIcon' + | 'closeButton' + | 'title' + | 'dragIcon' + | 'dragIconRef' + | 'device'; type ModalHeaderProps = Pick & { titleIdFinal: string; }; @@ -20,42 +30,49 @@ type ModalHeaderProps = Pick & { const ModalHeaderComponent = ( props: ModalHeaderProps, ref: React.ForwardedRef | undefined | null -): JSX.Element => ( - - {/* This element is needed for aligment purpose */} - - {props.title?.visible === undefined || props.title.visible ? ( - <> - - {props.title?.content} - - - ) : ( - {props.title?.content} - )} - {!props.blocked && props.closeIcon?.icon && ( - - - - )} - {!props.blocked && - props.closeButton?.content && - (props.styles.closeButton?.buttonVariant || props.closeButton?.variant) && ( - +): JSX.Element => { + const isMobile = props.device === DeviceBreakpointsType.MOBILE; + + return ( + + {isMobile && !props.blocked && props.dragIcon && ( + + + + )} + {!props.blocked && props.closeIcon?.icon && ( + + + )} - -); + {!props.blocked && + props.closeButton?.content && + (props.styles.closeButton?.buttonVariant || props.closeButton?.variant) && ( + + )} + {props.title?.visible === undefined || props.title.visible ? ( + <> + + {props.title?.content} + + + ) : ( + {props.title?.content} + )} + + ); +}; export const ModalHeader = React.forwardRef(ModalHeaderComponent); diff --git a/src/components/modalV2/modal.styled.ts b/src/components/modalV2/modal.styled.ts index 92825c54..372e229d 100644 --- a/src/components/modalV2/modal.styled.ts +++ b/src/components/modalV2/modal.styled.ts @@ -83,10 +83,6 @@ export const ModalHeaderStyled = styled.div` ${props => getStyles(props.$styles.headerContainer)} `; -export const ModalFilledContainer = styled.div` - ${props => getStyles(props.$styles.closeButtonIcon)}; -`; - export const TitleHiddenContainer = styled.span` display: none; `; @@ -105,3 +101,7 @@ export const ModalContentStyled = styled.div export const ModalFooterStyled = styled.div` ${props => getStyles(props.$styles.footer)} `; + +export const DraggableIcon = styled.div` + ${props => getStyles(props.$styles?.dragIconContainer)} +`; diff --git a/src/components/modalV2/modalControlled.tsx b/src/components/modalV2/modalControlled.tsx index ddec6c51..c1c4d389 100644 --- a/src/components/modalV2/modalControlled.tsx +++ b/src/components/modalV2/modalControlled.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { STYLES_NAME } from '@/constants'; -import { useMediaDevice, useScrollEffect, useStylesV2 } from '@/hooks'; +import { useMediaDevice, useScrollEffect, useStylesV2, useSwipeDown } from '@/hooks'; import { ErrorBoundary } from '@/provider/errorBoundary/errorBoundary'; import { FallbackComponent } from '@/provider/errorBoundary/fallbackComponent'; @@ -26,14 +26,26 @@ const ModalControlledComponent = React.forwardRef( shadowStyles: styles?.headerContainer?.box_shadow, }); + const { setPopoverRef, setDragIconRef } = useSwipeDown(props.popover?.animationOptions, () => + props.onClose?.() + ); + + const handlePopoverCloseInternally = () => { + props.popover?.onCloseInternally?.(); + props.onClose?.(); + }; + const modalStructure = ( ); diff --git a/src/components/modalV2/modalStandAlone.tsx b/src/components/modalV2/modalStandAlone.tsx index b630d22d..ca867381 100644 --- a/src/components/modalV2/modalStandAlone.tsx +++ b/src/components/modalV2/modalStandAlone.tsx @@ -3,7 +3,11 @@ import * as React from 'react'; import { useId } from '@/hooks'; import { Footer } from '../footer'; -import { Popover, PopoverComponentType, PopoverPositionVariantType } from '../popover'; +import { + PopoverControlled as Popover, + PopoverComponentType, + PopoverPositionVariantType, +} from '../popover'; import { ModalHeader } from './fragments'; import { ModalContentStyled, ModalFooterStyled, ModalStyled } from './modal.styled'; import { IModalStandAlone } from './types'; @@ -40,6 +44,7 @@ const ModalStandAloneComponent = ( positionVariant={PopoverPositionVariantType.FIXED} trapFocusInsideModal={true} variant={props.styles.popoverVariant} + onCloseInternally={props.onPopoverCloseInternally} {...props.popover} > ( closeButton={props.closeButton} closeIcon={props.closeIcon} dataTestId={dataTestId} + device={props.device} + dragIcon={props.dragIcon} + dragIconRef={props.dragIconRef} styles={props.styles} title={props.title} titleIdFinal={titleIdFinal} diff --git a/src/components/modalV2/modalUnControlled.tsx b/src/components/modalV2/modalUnControlled.tsx index be79ea38..3bbb05e9 100644 --- a/src/components/modalV2/modalUnControlled.tsx +++ b/src/components/modalV2/modalUnControlled.tsx @@ -51,6 +51,7 @@ const ModalUnControlledComponent = ( open={open} popover={{ ...popover, onCloseInternally: handlePopoverCloseInternally }} variant={variant} + onClose={onClose} onKeyDown={onKeyDown} /> ); diff --git a/src/components/modalV2/stories/modal.stories.tsx b/src/components/modalV2/stories/modal.stories.tsx index 7ee355f8..7f6fbb1d 100644 --- a/src/components/modalV2/stories/modal.stories.tsx +++ b/src/components/modalV2/stories/modal.stories.tsx @@ -24,12 +24,36 @@ const meta = { figmaUrl: 'https://www.figma.com/file/EYQkbENTFO5r8muvXlPoOy/Kubit-v.1.0.0?type=design&node-id=3922-22906&mode=dev', }, + render: ({ ...args }) => , } satisfies Meta; export default meta; type Story = StoryObj & { args: { themeArgs?: object } }; +const StoryWithHooks = args => { + const [open, setOpen] = React.useState(false); + + const handleClose = () => { + setOpen(false); + }; + const handleOpen = () => { + setOpen(true); + }; + + return ( +

+ + +
+ ); +}; + export const Modal: Story = { args: { variant: Object.values(variantsObject[themeSelected].ModalVariantType || {})[0] as string, @@ -65,6 +89,7 @@ export const Modal: Story = { animationYEndPosition: '0px', animationXEndPosition: '0px', }, + focusFirstDescendantAutomatically: false, }, themeArgs: themesObject[themeSelected][STYLES_NAME.MODAL], }, diff --git a/src/components/modalV2/types/modal.ts b/src/components/modalV2/types/modal.ts index 73af8a57..321934a3 100644 --- a/src/components/modalV2/types/modal.ts +++ b/src/components/modalV2/types/modal.ts @@ -54,21 +54,30 @@ export interface IModalStandAlone { device: DeviceBreakpointsType; dataTestId?: string; onKeyDown?: React.KeyboardEventHandler; + onPopoverCloseInternally?: () => void; /* To useScrollableEffect */ scrollableRef: (node) => void; shadowRef: (node) => void; + dragIcon?: IElementOrIcon; + dragIconRef?: (node) => void; } -type OmitProps = 'styles' | 'device' | 'scrollableRef' | 'shadowRef' | 'zoomRef' | 'zoomRefChild'; +type OmitProps = + | 'styles' + | 'device' + | 'scrollableRef' + | 'shadowRef' + | 'zoomRef' + | 'zoomRefChild' + | 'dragIconRef'; export interface IModalControlled extends Omit, Omit, 'cts' | 'extraCt'> { variant: V; portalId?: string; + onClose?: () => void; } export interface IModalUnControlled - extends IModalControlled { - onClose?: () => void; -} + extends IModalControlled {} diff --git a/src/components/modalV2/types/modalTheme.ts b/src/components/modalV2/types/modalTheme.ts index e9742741..c50d4438 100644 --- a/src/components/modalV2/types/modalTheme.ts +++ b/src/components/modalV2/types/modalTheme.ts @@ -14,6 +14,8 @@ export type ModalBaseStylesType = { closeButton?: { buttonVariant?: string; }; + dragIconContainer?: CommonStyleType; + dragIcon?: IconTypes; }; export type ModalStylesType

= { From 4c4df1ab030256e4d36fb9917480858e87197ae0 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:51:58 +0200 Subject: [PATCH 11/27] Hook to enable swipe-down functionality for closing modals on mobile devices. --- .../__test__/useSwipeDown.test.ts | 100 +++++++++++++++ src/hooks/useSwipeDown/useSwipeDown.tsx | 117 ++++++++++++++++++ src/utils/focusHandlers/focusHandlers.ts | 2 +- 3 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 src/hooks/useSwipeDown/__test__/useSwipeDown.test.ts create mode 100644 src/hooks/useSwipeDown/useSwipeDown.tsx diff --git a/src/hooks/useSwipeDown/__test__/useSwipeDown.test.ts b/src/hooks/useSwipeDown/__test__/useSwipeDown.test.ts new file mode 100644 index 00000000..2c721209 --- /dev/null +++ b/src/hooks/useSwipeDown/__test__/useSwipeDown.test.ts @@ -0,0 +1,100 @@ +import { act, fireEvent } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; + +import * as useMediaDevice from '@/hooks/useMediaDevice/useMediaDevice'; +import { windowMatchMedia } from '@/tests/windowMatchMedia'; +import { DeviceBreakpointsType } from '@/types'; + +import { useSwipeDown } from '../useSwipeDown'; + +let containerRefMock; +let dragRefMock; +let onCloseMock; +let animationExitDurationMock; +let containerRoot; + +describe('useSwipeDown hook', () => { + afterEach(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + jest.restoreAllMocks(); + }); + beforeEach(() => { + window.matchMedia = windowMatchMedia('onlyMobile'); + jest + .spyOn(useMediaDevice, 'useMediaDevice') + .mockImplementation(() => DeviceBreakpointsType.MOBILE); + const container = document.createElement('div'); + const content = document.createElement('div'); + + containerRoot?.remove(); + + containerRoot = document.createElement('div'); + + container.appendChild(content); + + containerRoot.appendChild(container); + containerRefMock = { + current: container, + }; + dragRefMock = { + current: content, + }; + onCloseMock = jest.fn(); + animationExitDurationMock = 300; + + document.body.appendChild(containerRoot); + const { result } = renderHook(() => useSwipeDown(onCloseMock)); + + act(() => { + result.current.setPopoverRef?.(containerRefMock.current); + result.current.setDragIconRef?.(dragRefMock.current); + }); + }); + describe('useSwipeDown hook', () => { + it('should set popover and drag icon refs', () => { + const { result } = renderHook(() => useSwipeDown()); + + act(() => { + result.current.setPopoverRef?.(containerRefMock.current); + result.current.setDragIconRef?.(dragRefMock.current); + }); + + expect(result.current.setPopoverRef).toBeDefined(); + expect(result.current.setDragIconRef).toBeDefined(); + }); + it('Should swipeDown', () => { + // Simulate down swipe + fireEvent.mouseDown(dragRefMock.current, { clientY: 100 }); // Start swipe + fireEvent.mouseMove(dragRefMock.current, { clientY: 50 }); // Swipe down + fireEvent.mouseUp(dragRefMock.current, { clientY: 0 }); // End swipe + expect(containerRefMock.current.style.bottom).toBe('0px'); + }); + it('Should not swipeDown on desktop', () => { + window.matchMedia = windowMatchMedia('onlyDesktop'); + jest + .spyOn(useMediaDevice, 'useMediaDevice') + .mockImplementation(() => DeviceBreakpointsType.DESKTOP); + expect(containerRefMock.current.style.bottom).toBe(''); + }); + it('Should not swipeDown if the ref is not ready', () => { + containerRefMock.current = null; + renderHook(() => useSwipeDown(onCloseMock)); + // Simulate down swipe + fireEvent.mouseDown(dragRefMock.current, { clientY: 100 }); // Start swipe + fireEvent.mouseMove(dragRefMock.current, { clientY: 50 }); // Swipe down + fireEvent.mouseUp(dragRefMock.current, { clientY: 0 }); // End swipe + expect(containerRefMock.current).toBe(null); + }); + it('If event type is touchstart, should set the yStart.current with the first touch clientY', () => { + fireEvent.touchStart(dragRefMock.current, { touches: [{ clientY: 100 }] }); + expect(containerRefMock.current.style.bottom).toBe(''); + }); + it('If event type is touchmove, should set the yEnd.current with the last touch clientY and swipe down', () => { + fireEvent.touchStart(dragRefMock.current, { touches: [{ clientY: 100 }] }); + fireEvent.touchMove(dragRefMock.current, { touches: [{ clientY: 50 }] }); + fireEvent.touchEnd(dragRefMock.current, { touches: [{ clientY: 0 }] }); + expect(containerRefMock.current.style.bottom).toBe('0px'); + }); + }); +}); diff --git a/src/hooks/useSwipeDown/useSwipeDown.tsx b/src/hooks/useSwipeDown/useSwipeDown.tsx new file mode 100644 index 00000000..55623452 --- /dev/null +++ b/src/hooks/useSwipeDown/useSwipeDown.tsx @@ -0,0 +1,117 @@ +import { useCallback, useRef } from 'react'; + +import { ICssAnimationOptions } from '@/components/cssAnimation'; +import { convertDurationToNumber } from '@/utils/stringUtility/string.utility'; + +const distanceToTriggerClose = 30; + +type ReturnType = { + setPopoverRef?: (node: HTMLElement) => void; + setDragIconRef?: (node: HTMLElement) => void; +}; + +export const useSwipeDown = ( + animationOptions?: ICssAnimationOptions, + handleClose?: () => void +): ReturnType => { + const containerRef = useRef(null); + const dragRef = useRef(null); + + const setPopoverRef = useCallback(node => { + if (node) { + containerRef.current = node; + } else { + containerRef.current = null; + } + }, []); + + const setDragIconRef = useCallback(node => { + if (node) { + dragRef.current = node; + dragRef?.current?.addEventListener('mousedown', startMove); + dragRef?.current?.addEventListener('mousemove', currentMove); + dragRef?.current?.addEventListener('mouseup', endMove); + + dragRef?.current?.addEventListener('touchstart', startMove); + dragRef?.current?.addEventListener('touchmove', currentMove); + dragRef?.current?.addEventListener('touchend', endMove); + } else { + dragRef?.current?.removeEventListener('mousedown', startMove); + dragRef?.current?.removeEventListener('mousemove', currentMove); + dragRef?.current?.removeEventListener('mouseup', endMove); + + dragRef?.current?.removeEventListener('touchstart', startMove); + dragRef?.current?.removeEventListener('touchmove', currentMove); + dragRef?.current?.removeEventListener('touchend', endMove); + dragRef.current = null; + } + }, []); + + const animationExitDuration = + ((convertDurationToNumber(animationOptions?.exitDuration) || + convertDurationToNumber(animationOptions?.duration) || + 0) + + (convertDurationToNumber(animationOptions?.delay) || 0)) * + 1000; + + const currentBottom = useRef(0); + const yStart = useRef(0); + const yEnd = useRef(0); + const dragMove = useRef(false); + + const startMove = e => { + const swiperContent = containerRef?.current; + if (!swiperContent) { + return; + } + e.preventDefault?.(); + swiperContent.style.removeProperty('transition'); + + if (e.type === 'touchstart') { + yStart.current = e.touches[0].clientY; + } else { + yStart.current = e.clientY; + } + dragMove.current = true; + yEnd.current = null; + }; + + const currentMove = e => { + const swiperContent = containerRef?.current; + if (!dragMove.current || !swiperContent) { + return; + } + if (e.type === 'touchmove' && yEnd !== null) { + yEnd.current = e.touches[0].clientY as number; + } else { + yEnd.current = e.clientY as number; + } + const currentMove = yStart.current - yEnd.current; + if (yEnd.current < yStart.current) { + return; + } + swiperContent.style.bottom = `${currentBottom.current + currentMove}px`; + }; + + const endMove = () => { + const swiperContent = containerRef?.current; + if (!swiperContent || !yEnd.current) { + return; + } + dragMove.current = false; + swiperContent.style.setProperty('transition', `bottom ${animationExitDuration}ms linear`); + + if (yEnd.current < yStart.current + distanceToTriggerClose) { + swiperContent.style.bottom = '0px'; + return; + } + // Move modal + const distance = currentBottom.current + swiperContent.scrollHeight; + swiperContent.style.bottom = `-${distance}px`; + setTimeout(() => { + handleClose?.(); + }, animationExitDuration); + }; + + return { setPopoverRef, setDragIconRef }; +}; diff --git a/src/utils/focusHandlers/focusHandlers.ts b/src/utils/focusHandlers/focusHandlers.ts index 0d482a6f..1fde0093 100644 --- a/src/utils/focusHandlers/focusHandlers.ts +++ b/src/utils/focusHandlers/focusHandlers.ts @@ -1,7 +1,7 @@ import * as React from 'react'; const FOCUSABLE_QUERY_SELECTOR = - 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]):not([tabindex="-1"]), [tabindex]:not([tabindex="-1"])'; + 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]):not([tabindex="-1"]), summary, [tabindex]:not([tabindex="-1"])'; export const getFocusableDescendants = (element: HTMLElement): HTMLElement[] | boolean => { const focusableNodes = Array.from( From 2c8926dddd8fbc391bb0cc4e7ab0ea8c440bd4e1 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:53:30 +0200 Subject: [PATCH 12/27] Add new Video and ProgressBar components --- .../__tests__/progressBar.test.tsx | 69 ++ src/components/progressBar/index.ts | 4 + .../progressBar/progressBar.styled.ts | 29 + src/components/progressBar/progressBar.tsx | 82 +++ .../progressBar/progressBarStandAlone.tsx | 69 ++ .../progressBar/stories/argtypes.ts | 135 ++++ .../stories/progressBar.stories.tsx | 60 ++ src/components/progressBar/types/index.ts | 2 + .../progressBar/types/progressBar.ts | 28 + .../progressBar/types/progressBarTheme.ts | 49 ++ src/components/video/__tests__/video.test.tsx | 438 +++++++++++++ src/components/video/components/index.ts | 7 + .../video/components/linkAndActionButton.tsx | 56 ++ .../video/components/playStopButton.tsx | 47 ++ .../video/components/screenButtons.tsx | 52 ++ .../components/subtitleFullScreenButtons.tsx | 87 +++ src/components/video/components/time.tsx | 21 + .../video/components/videoSkeleton.tsx | 54 ++ src/components/video/components/volume.tsx | 91 +++ src/components/video/index.ts | 5 + src/components/video/stories/argtypes.ts | 596 ++++++++++++++++++ .../video/stories/video.stories.tsx | 102 +++ src/components/video/types/index.ts | 2 + src/components/video/types/video.ts | 131 ++++ src/components/video/types/videoTheme.ts | 59 ++ src/components/video/video.styled.ts | 163 +++++ src/components/video/videoControlled.tsx | 53 ++ src/components/video/videoStandAlone.tsx | 176 ++++++ src/components/video/videoUnControlled.tsx | 252 ++++++++ .../kubit/components/modalV2/styles.ts | 4 +- .../kubit/components/progressBar/index.ts | 2 + .../kubit/components/progressBar/styles.ts | 83 +++ .../kubit/components/progressBar/variants.ts | 9 + .../kubit/components/video/index.ts | 2 + .../kubit/components/video/styles.ts | 181 ++++++ .../kubit/components/video/variants.ts | 3 + 36 files changed, 3202 insertions(+), 1 deletion(-) create mode 100644 src/components/progressBar/__tests__/progressBar.test.tsx create mode 100644 src/components/progressBar/index.ts create mode 100644 src/components/progressBar/progressBar.styled.ts create mode 100644 src/components/progressBar/progressBar.tsx create mode 100644 src/components/progressBar/progressBarStandAlone.tsx create mode 100644 src/components/progressBar/stories/argtypes.ts create mode 100644 src/components/progressBar/stories/progressBar.stories.tsx create mode 100644 src/components/progressBar/types/index.ts create mode 100644 src/components/progressBar/types/progressBar.ts create mode 100644 src/components/progressBar/types/progressBarTheme.ts create mode 100644 src/components/video/__tests__/video.test.tsx create mode 100644 src/components/video/components/index.ts create mode 100644 src/components/video/components/linkAndActionButton.tsx create mode 100644 src/components/video/components/playStopButton.tsx create mode 100644 src/components/video/components/screenButtons.tsx create mode 100644 src/components/video/components/subtitleFullScreenButtons.tsx create mode 100644 src/components/video/components/time.tsx create mode 100644 src/components/video/components/videoSkeleton.tsx create mode 100644 src/components/video/components/volume.tsx create mode 100644 src/components/video/index.ts create mode 100644 src/components/video/stories/argtypes.ts create mode 100644 src/components/video/stories/video.stories.tsx create mode 100644 src/components/video/types/index.ts create mode 100644 src/components/video/types/video.ts create mode 100644 src/components/video/types/videoTheme.ts create mode 100644 src/components/video/video.styled.ts create mode 100644 src/components/video/videoControlled.tsx create mode 100644 src/components/video/videoStandAlone.tsx create mode 100644 src/components/video/videoUnControlled.tsx create mode 100644 src/designSystem/kubit/components/progressBar/index.ts create mode 100644 src/designSystem/kubit/components/progressBar/styles.ts create mode 100644 src/designSystem/kubit/components/progressBar/variants.ts create mode 100644 src/designSystem/kubit/components/video/index.ts create mode 100644 src/designSystem/kubit/components/video/styles.ts create mode 100644 src/designSystem/kubit/components/video/variants.ts diff --git a/src/components/progressBar/__tests__/progressBar.test.tsx b/src/components/progressBar/__tests__/progressBar.test.tsx new file mode 100644 index 00000000..41821366 --- /dev/null +++ b/src/components/progressBar/__tests__/progressBar.test.tsx @@ -0,0 +1,69 @@ +import userEvent from '@testing-library/user-event'; + +import { screen, waitFor } from '@testing-library/react'; +import * as React from 'react'; + +import { axe } from 'jest-axe'; + +import { renderProvider } from '@/tests/renderProvider/renderProvider.utility'; +import { ROLES } from '@/types'; + +import * as SliderUtils from '../../slider/utils/slider.utils'; +import { ProgressBar } from '../progressBar'; + +const MOCK_PROPS = { + variant: 'DEFAULT', + size: 'SMALL', + barProgressDuration: 2000, + dataTestIdBar: 'testIdBar', + dataTestIdProgressBar: 'testIdProgressBar', + dataTestIdBullet: 'testIdBullet', + barAriaLabel: 'aria-label-0', +}; + +describe('ProgressBar', () => { + it('Should render ProgressBar component with no slider variant', async () => { + const { container } = renderProvider(); + + const bar = screen.getByTestId(`${MOCK_PROPS.dataTestIdBar}`); + const progressBar = screen.getByTestId(`${MOCK_PROPS.dataTestIdProgressBar}`); + + expect(bar).toBeDefined(); + expect(progressBar).toBeDefined(); + + const results = await axe(container); + expect(container).toHTMLValidate({ + rules: { + 'prefer-native-element': 'off', + }, + }); + expect(results).toHaveNoViolations(); + }); + it('Should render ProgressBar component with slider variant', async () => { + const { container } = renderProvider( + + ); + + const slider = screen.getByRole(ROLES.SLIDER); + + expect(slider).not.toBeNull(); + + const results = await axe(container); + expect(container).toHTMLValidate({ + rules: { + 'prefer-native-element': 'off', + 'no-inline-style': 'off', + }, + }); + expect(results).toHaveNoViolations(); + }); + + it('When INTERACTIVE, When change slider value, onChange should be called', async () => { + const onChange = jest.fn(); + jest.spyOn(SliderUtils, 'calculateChange').mockImplementation(() => 50); + renderProvider(); + + await userEvent.click(screen.getByTestId('sliderContainer')); + await waitFor(() => expect(onChange).toHaveBeenCalled(), { timeout: 150 }); + }); +}); diff --git a/src/components/progressBar/index.ts b/src/components/progressBar/index.ts new file mode 100644 index 00000000..f882996c --- /dev/null +++ b/src/components/progressBar/index.ts @@ -0,0 +1,4 @@ +export { ProgressBar } from './progressBar'; + +//types +export * from './types'; diff --git a/src/components/progressBar/progressBar.styled.ts b/src/components/progressBar/progressBar.styled.ts new file mode 100644 index 00000000..d33d6faf --- /dev/null +++ b/src/components/progressBar/progressBar.styled.ts @@ -0,0 +1,29 @@ +import styled from 'styled-components'; + +import { getStyles } from '@/utils/getStyles/getStyles'; + +import { ProgressBarSizeStylesType, ProgressBarVariantStylesType } from './types'; + +type ProgressBarStylesType = { + styles: ProgressBarVariantStylesType; + sizeStyles?: ProgressBarSizeStylesType; +}; + +export const ParentContainerStyled = styled.div` + ${props => getStyles(props.styles.container)} +`; + +export const BarContainerStyled = styled.div` + ${props => getStyles(props.styles.barContainer)} +`; + +export const BarStyled = styled.div` + ${props => getStyles(props.styles.bar)} + ${props => getStyles(props.sizeStyles?.bar)} +`; + +export const ProgressBarStyled = styled.div` + ${props => getStyles(props.styles.progressBar)} + ${props => getStyles(props.sizeStyles?.progressBar)} + width: ${({ progressCompleted }) => `${progressCompleted}%`}; +`; diff --git a/src/components/progressBar/progressBar.tsx b/src/components/progressBar/progressBar.tsx new file mode 100644 index 00000000..9046bacc --- /dev/null +++ b/src/components/progressBar/progressBar.tsx @@ -0,0 +1,82 @@ +import * as React from 'react'; + +import { STYLES_NAME } from '@/constants'; +import { useStyles } from '@/hooks/useStyles/useStyles'; +import { ErrorBoundary, FallbackComponent } from '@/provider/errorBoundary'; + +import { ProgressBarStandalone } from './progressBarStandAlone'; +import { + IProgressBar, + IProgressBarStandAlone, + ProgressBarSizeStylesType, + ProgressBarVariantStylesType, +} from './types'; + +const ProgressBarComponent = React.forwardRef( + < + V = undefined extends string | unknown ? string | undefined : string | unknown, + S = undefined extends string | unknown ? string | undefined : string | unknown, + >( + { variant, percentProgressCompleted, ctv, ...props }: IProgressBar, + ref: React.ForwardedRef | undefined | null + ): JSX.Element => { + const styles = useStyles( + STYLES_NAME.PROGRESS_BAR, + variant, + ctv + ); + const sizeStyles = useStyles( + STYLES_NAME.PROGRESS_BAR, + props.size, + props.cts + ); + + // Avoid values not included in 0 - 100 + const progressCompleted = !percentProgressCompleted + ? 0 + : Math.min(Math.max(percentProgressCompleted, 0), 100); + + return ( + + ); + } +); +ProgressBarComponent.displayName = 'ProgressBarComponent'; + +const ProgressBarBoundary = ( + props: IProgressBar, + ref: React.ForwardedRef | undefined | null +): JSX.Element => ( + + + + } + > + + +); + +const ProgressBar = React.forwardRef(ProgressBarBoundary) as ( + props: React.PropsWithChildren> & { + ref?: React.ForwardedRef | undefined | null; + } +) => ReturnType; + +/** + * @description + * ProgressBar component is used to show a progress bar. + * @param {React.PropsWithChildren>} props + * @returns {JSX.Element} + * @constructor + * @example + * + */ +export { ProgressBar }; diff --git a/src/components/progressBar/progressBarStandAlone.tsx b/src/components/progressBar/progressBarStandAlone.tsx new file mode 100644 index 00000000..309d1992 --- /dev/null +++ b/src/components/progressBar/progressBarStandAlone.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'; + +import { Slider } from '../slider/slider'; +import { SliderType } from '../slider/types/type'; +import { + BarContainerStyled, + BarStyled, + ParentContainerStyled, + ProgressBarStyled, +} from './progressBar.styled'; +import { IProgressBarStandAlone } from './types'; + +const SLIDER_MAX_VALUE = 1000; +const SLIDER_MIN_VALUE = 0; +const SLIDER_PERCENTAGE_CONVERSION = 10; + +const ProgressBarStandaloneComponent = ( + props: IProgressBarStandAlone, + ref: React.ForwardedRef | undefined | null +): JSX.Element => { + return ( + + + {props.styles.useAsSlider && props.styles.sliderVariant ? ( + { + if (props.onChange && !Array.isArray(newValue)) { + // in order to retun a value 0 - 100 + props.onChange(newValue / SLIDER_PERCENTAGE_CONVERSION); + } + }} + onDragEnd={props.onDragEnd} + onDragStart={props.onDragStart} + /> + ) : ( + <> + + + + )} + + + ); +}; + +/** + * @description + * ProgressBar component is used to show a progress bar. + * @param {React.PropsWithChildren} props + * @returns {JSX.Element} + */ +export const ProgressBarStandalone = React.forwardRef(ProgressBarStandaloneComponent); diff --git a/src/components/progressBar/stories/argtypes.ts b/src/components/progressBar/stories/argtypes.ts new file mode 100644 index 00000000..fee7d320 --- /dev/null +++ b/src/components/progressBar/stories/argtypes.ts @@ -0,0 +1,135 @@ +import { CATEGORY_CONTROL } from '@/constants/categoryControl'; +import { IThemeObjectVariants } from '@/designSystem/themesObject'; +import { ArgTypesReturn } from '@/types'; + +export const argtypes = (variants: IThemeObjectVariants, themeSelected: string): ArgTypesReturn => { + return { + themeArgs: { + table: { + disable: true, + }, + }, + variant: { + type: { name: 'string', required: true }, + control: { type: 'select' }, + description: 'ProgressBar variant', + options: Object.keys(variants[themeSelected].ProgressBarVariantType || {}), + table: { + type: { + summary: 'string', + }, + category: CATEGORY_CONTROL.MODIFIERS, + }, + }, + size: { + type: { name: 'string', required: true }, + control: { type: 'select' }, + description: 'ProgressBar size', + options: Object.keys(variants[themeSelected].ProgressBarSizeType || {}), + table: { + type: { + summary: 'string', + }, + category: CATEGORY_CONTROL.MODIFIERS, + }, + }, + barAriaLabel: { + control: { type: 'text' }, + type: { name: 'string' }, + description: 'When used as slider, aria label for bar', + table: { + type: { + summary: 'string', + }, + category: CATEGORY_CONTROL.ACCESIBILITY, + }, + }, + percentProgressCompleted: { + description: 'Number that represent the percentage completed of the bar', + control: { type: 'number' }, + type: { name: 'number' }, + table: { + type: { + summary: 'number', + }, + category: CATEGORY_CONTROL.MODIFIERS, + }, + }, + onChange: { + description: 'When used as slider, event called when value changes', + type: { name: 'function' }, + control: false, + table: { + type: { + summary: '(value: number) => void', + }, + category: CATEGORY_CONTROL.FUNCTIONS, + }, + }, + onDragStart: { + description: 'When used as slider, event called when start dragging', + type: { name: 'function' }, + control: false, + table: { + type: { + summary: '() => void', + }, + category: CATEGORY_CONTROL.FUNCTIONS, + }, + }, + onDragEnd: { + description: 'When used as slider, event called when stop dragging', + type: { name: 'function' }, + control: false, + table: { + type: { + summary: '() => void', + }, + category: CATEGORY_CONTROL.FUNCTIONS, + }, + }, + tooltip: { + description: 'When used as slider, thumb tooltip', + type: { name: 'object' }, + table: { + type: { + summary: 'SliderTooltipType', + }, + category: CATEGORY_CONTROL.CONTENT, + }, + }, + dataTestIdBar: { + control: { type: 'text' }, + type: { name: 'string' }, + description: 'String used for testing', + table: { + type: { + summary: 'string', + }, + category: CATEGORY_CONTROL.TESTING, + }, + }, + dataTestIdProgressBar: { + control: { type: 'text' }, + type: { name: 'string' }, + description: 'String used for testing', + table: { + type: { + summary: 'string', + }, + category: CATEGORY_CONTROL.TESTING, + }, + }, + ctv: { + description: 'Object used for update variant styles', + type: { name: 'object' }, + control: { type: 'object' }, + table: { + type: { + summary: 'object', + }, + category: CATEGORY_CONTROL.CUSTOMIZATION, + }, + }, + }; +}; diff --git a/src/components/progressBar/stories/progressBar.stories.tsx b/src/components/progressBar/stories/progressBar.stories.tsx new file mode 100644 index 00000000..79fda8b8 --- /dev/null +++ b/src/components/progressBar/stories/progressBar.stories.tsx @@ -0,0 +1,60 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import * as React from 'react'; + +import { STYLES_NAME } from '@/constants'; +import { themesObject, variantsObject } from '@/designSystem/themesObject'; + +import { ProgressBar as Story } from '../progressBar'; +import { argtypes } from './argtypes'; + +const themeSelected = localStorage.getItem('themeSelected') || 'kubit'; + +const meta = { + title: 'Components/Resources/ProgressBar', + component: Story, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: argtypes(variantsObject, themeSelected), + render: ({ ...args }) => , +} satisfies Meta; + +export default meta; + +type Story = StoryObj & { args: { themeArgs?: object } }; + +const StoryWithHooks = args => { + return ( +

+ +
+ ); +}; + +export const ProgressBar: Story = { + args: { + variant: Object.values(variantsObject[themeSelected].ProgressBarVariantType || {})[0] as string, + size: Object.values(variantsObject[themeSelected].ProgressBarSizeType || {})[0] as string, + percentProgressCompleted: 50, + barAriaLabel: 'aria-label-0', + themeArgs: themesObject[themeSelected][STYLES_NAME.PROGRESS_BAR], + }, +}; + +export const ProgressBarWithCtv: Story = { + args: { + variant: Object.values(variantsObject[themeSelected].ProgressBarVariantType || {})[0] as string, + size: Object.values(variantsObject[themeSelected].ProgressBarSizeType || {})[0] as string, + percentProgressCompleted: 50, + barAriaLabel: 'aria-label-0', + ctv: { + bar: { + background_color: 'green', + }, + progressBar: { + background_color: 'pink', + }, + }, + }, +}; diff --git a/src/components/progressBar/types/index.ts b/src/components/progressBar/types/index.ts new file mode 100644 index 00000000..df4f9e38 --- /dev/null +++ b/src/components/progressBar/types/index.ts @@ -0,0 +1,2 @@ +export * from './progressBarTheme'; +export * from './progressBar'; diff --git a/src/components/progressBar/types/progressBar.ts b/src/components/progressBar/types/progressBar.ts new file mode 100644 index 00000000..4ac634d3 --- /dev/null +++ b/src/components/progressBar/types/progressBar.ts @@ -0,0 +1,28 @@ +import { SliderTooltipType } from '@/components/slider'; +import { CustomTokenTypes } from '@/types'; + +import { ProgressBarSizeStylesType, ProgressBarVariantStylesType } from './progressBarTheme'; + +export interface IProgressBarStandAlone { + styles: ProgressBarVariantStylesType; + sizeStyles: ProgressBarSizeStylesType; + barAriaLabel?: string; + dataTestIdBar?: string; + dataTestIdProgressBar?: string; + progressCompleted: number; + // Slider callbacks + onChange?: (value: number) => void; + onDragStart?: () => void; + onDragEnd?: () => void; + tooltip?: SliderTooltipType; +} + +export interface IProgressBar< + V = undefined extends string | unknown ? string | undefined : string | unknown, + S = undefined extends string | unknown ? string | undefined : string | unknown, +> extends Omit, + Omit, 'extraCt'> { + variant: V; + size: S; + percentProgressCompleted?: number; +} diff --git a/src/components/progressBar/types/progressBarTheme.ts b/src/components/progressBar/types/progressBarTheme.ts new file mode 100644 index 00000000..e22243bc --- /dev/null +++ b/src/components/progressBar/types/progressBarTheme.ts @@ -0,0 +1,49 @@ +import { CommonStyleType } from '@/types'; + +/** + * @description + * interface for the ProgressBarVariantStylesType + * @interface ProgressBarVariantStylesType + */ +export type ProgressBarVariantStylesType = { + container?: CommonStyleType; + barContainer?: CommonStyleType; + bar?: CommonStyleType; + progressBar?: CommonStyleType; + useAsSlider?: boolean; + sliderVariant?: string; +}; + +/** + * @description + * interface for the ProgressBarSizePropsType + * @interface ProgressBarSizePropsType + */ +export type ProgressBarSizeStylesType = { + progressBar?: CommonStyleType; + bar?: CommonStyleType; +}; + +/** + * @interface IProgressBarStyled + */ +export type ProgressBarStylesVariantType

= { + [key in P]?: ProgressBarVariantStylesType; +}; + +/** + * @interface IProgressBarStyled + */ +export type ProgressBarSizeType = { + [key in S]?: ProgressBarSizeStylesType; +}; + +/** + * @description + * interface for the ProgressBarStylesType + * @interface ProgressBarStylesType + */ +export type ProgressBarStylesType< + P extends string | number | symbol, + S extends string | number | symbol, +> = ProgressBarStylesVariantType

& ProgressBarSizeType; diff --git a/src/components/video/__tests__/video.test.tsx b/src/components/video/__tests__/video.test.tsx new file mode 100644 index 00000000..0f1f99ef --- /dev/null +++ b/src/components/video/__tests__/video.test.tsx @@ -0,0 +1,438 @@ +import userEvent from '@testing-library/user-event'; + +import { act, fireEvent, waitFor } from '@testing-library/react'; +import * as React from 'react'; + +import { axe } from 'jest-axe'; + +import * as useMediaDevice from '@/hooks/useMediaDevice/useMediaDevice'; +import { renderProvider } from '@/tests/renderProvider/renderProvider.utility'; +import { windowMatchMedia } from '@/tests/windowMatchMedia'; +import { DeviceBreakpointsType, ROLES } from '@/types'; +import { TrackKindType, VideoType } from '@/types/video'; + +import { Video } from '..'; +import * as SliderUtils from '../../slider/utils/slider.utils'; + +Object.defineProperty(HTMLMediaElement.prototype, 'load', { + value: jest.fn(), +}); + +const mockVideo = (container: HTMLElement) => { + // Configure video and finish loading + const video = container.querySelector('video'); + if (video) { + Object.defineProperty(video, 'play', { + configurable: true, + get() { + return () => { + Object.defineProperty(video, 'paused', { + configurable: true, + get() { + return false; + }, + }); + video.dispatchEvent(new Event('play')); + }; + }, + }); + Object.defineProperty(video, 'pause', { + configurable: true, + get() { + return () => { + Object.defineProperty(video, 'paused', { + configurable: true, + get() { + return true; + }, + }); + video.dispatchEvent(new Event('pause')); + }; + }, + }); + video.dispatchEvent(new Event('canplay')); + } +}; + +const mockDispatchUpdateFullScreen = (fullScreen: boolean) => { + Object.defineProperty(document, 'fullscreenElement', { + configurable: true, + get() { + return fullScreen; + }, + }); + act(() => { + document.dispatchEvent(new Event('fullscreenchange')); + }); +}; + +const mockProps = { + variant: 'REGULAR', + videoSrc: '/src/assets/storybook/videos/mov_bbb.mp4', + videoType: VideoType.MP4, + trackKind: TrackKindType.SUBTITLES, + trackLabel: 'English', + trackSrc: '/src/assets/storybook/videos/exampleSubtitles.vtt', + trackSrcLang: 'en', + autoFullScreen: false, + hasInitialOverlay: false, + screenPlayIcon: { icon: 'ICON_CLOSE', altText: 'screen play alt text' }, + screenLoadingIcon: { icon: 'ICON_GHOST', altText: 'screen loading alt text' }, + barAriaLabel: 'barAriaLabel', + buttonsBarPlayIconTooltip: 'play/stop', + buttonsBarPlayIcon: { icon: 'ICON_CHEVRON_UP', altText: 'play icon alt text' }, + buttonsBarPlayIconToTransition: { icon: 'ICON_CLOSE', altText: 'stop icon alt text' }, + buttonsBarVolumeIcon: { icon: 'ICON_CHEVRON_DOWN', altText: 'volume alt text' }, + buttonsBarVolumeIconTooltip: 'Volume', + buttonsBarVolumeIconToTransition: { icon: 'ICON_CHEVRON_UP', altText: 'volume mute alt text' }, + buttonsBarSubtitlesIcon: { icon: 'ICON_GHOST', altText: 'subtitles icon alt text' }, + buttonsBarSubtitlesIconTooltip: 'Subtitles', + buttonsBarSubtitlesIconToTransition: { + icon: 'ICON_CHEVRON_DOWN', + altText: 'subtitles off icon alt text', + }, + buttonsBarFullScreenIcon: { icon: 'ICON_CHEVRON_LEFT', altText: 'fullscreen icon alt text' }, + buttonsBarFullScreenIconToTransition: { icon: 'ICON_GHOST', altText: 'screen icon alt text' }, + buttonsBarFullScreenIconTooltip: 'Fullscreen', + volumeBarAriaLabel: 'ariaLabelVolumeBar', + actionButton: { + content: 'Transcript', + ['aria-label']: 'ariaLabel actionButton', + onClick: () => null, + }, + linkText: 'Turn on audio description', + linkUrl: 'www.google.com', + dataTestIdParentContainer: 'testId-parentContainer', + dataTestIdVideoSkeleton: 'skeletonVideotest', + dataTestIdVolumeInput: 'testId-rangeInput', +}; + +describe('Video component', () => { + it('Should render video component', async () => { + const { container, getByTestId } = renderProvider( +

+ +
+ ); + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj & { args: { themeArgs?: object } }; + +const commonArgs: IVideoUnControlled = { + variant: 'REGULAR', + videoSrc: video, + videoType: VideoType.MP4, + trackKind: TrackKindType.SUBTITLES, + trackLabel: 'English', + trackSrc: '/src/assets/storybook/videos/exampleSubtitles.vtt', + trackSrcLang: 'en', + autoFullScreen: false, + screenPlayIcon: { icon: ICONS.ICON_CLOSE, altText: 'screen play alt text' }, + screenLoadingIcon: { icon: ICONS.ICON_GHOST, altText: 'screen loading alt text' }, + buttonsBarPlayIconTooltip: 'play/stop', + buttonsBarPlayIcon: { icon: ICONS.ICON_PLACEHOLDER, altText: 'play icon alt text' }, + buttonsBarPlayIconToTransition: { icon: ICONS.ICON_GHOST, altText: 'stop icon alt text' }, + buttonsBarVolumeIcon: { icon: ICONS.ICON_PLACEHOLDER, altText: 'volume alt text' }, + buttonsBarVolumeIconTooltip: 'Volume', + buttonsBarVolumeIconToTransition: { icon: ICONS.ICON_GHOST, altText: 'volume alt text' }, + buttonsBarSubtitlesIcon: { icon: ICONS.ICON_PLACEHOLDER, altText: 'subtitles icon alt text' }, + buttonsBarSubtitlesIconTooltip: 'Subtitles', + buttonsBarSubtitlesIconToTransition: { + icon: ICONS.ICON_GHOST, + altText: 'subtitles icon alt text', + }, + buttonsBarFullScreenIcon: { icon: ICONS.ICON_PLACEHOLDER, altText: 'screen icon alt text' }, + buttonsBarFullScreenIconTooltip: 'Fullscreen', + buttonsBarFullScreenIconToTransition: { icon: ICONS.ICON_GHOST, altText: 'screen icon alt text' }, + volumeBarAriaLabel: 'ariaLabelVolumeBar', + dataTestIdVolumeInput: 'testId-rangeInput', + actionButton: { content: 'Turn on audio description', ['aria-label']: 'ariaLabel actionButton' }, + linkText: 'Transcript', + linkUrl: 'www.google.com', + // timeToHideButtonsBar: 4000, + posterUrl: 'https://img.freepik.com/free-photo/cute-ai-generated-cartoon-bunny_23-2150288886.jpg', +}; + +export const Video: Story = { + args: { + ...commonArgs, + themeArgs: themesObject[themeSelected][STYLES_NAME.VIDEO], + }, +}; + +export const VideoWithoutAutoFullScreen: Story = { + args: { + ...commonArgs, + themeArgs: themesObject[themeSelected][STYLES_NAME.VIDEO], + }, +}; + +export const VideoWithAutoFullScreen: Story = { + args: { + ...commonArgs, + autoFullScreen: true, + themeArgs: themesObject[themeSelected][STYLES_NAME.VIDEO], + }, +}; + +export const VideoWithCtv: Story = { + args: { + ...commonArgs, + ctv: { + container: { + background_color: 'pink', + padding: '10px', + }, + }, + }, +}; diff --git a/src/components/video/types/index.ts b/src/components/video/types/index.ts new file mode 100644 index 00000000..fa2a9058 --- /dev/null +++ b/src/components/video/types/index.ts @@ -0,0 +1,2 @@ +export * from './video'; +export type { VideoStylesType, VideoStyleType, MediaButtonType, SkeletonType } from './videoTheme'; diff --git a/src/components/video/types/video.ts b/src/components/video/types/video.ts new file mode 100644 index 00000000..11d6d896 --- /dev/null +++ b/src/components/video/types/video.ts @@ -0,0 +1,131 @@ +import { IButton } from '@/components/button'; +import { IElementOrIcon } from '@/components/elementOrIcon'; +import { LinkTargetType } from '@/components/link'; +import { GenericLinkType } from '@/provider/genericComponents'; +import { CustomTokenTypes } from '@/types'; +import { TrackKindType, VideoType } from '@/types/video'; + +import { VideoStyleType } from './videoTheme'; + +export enum LinkAndActionButtonAlignment { + LEFT = 'flex-start', + CENTER = 'center', + RIGHT = 'flex-end', +} + +export type VideoButtonType = Omit & { + content: string; + variant?: string; + size?: string; +}; + +export interface IVideoStandAlone { + videoSrc: string; + styles: VideoStyleType; + posterUrl?: string; + videoType: VideoType; + trackKind?: TrackKindType; + trackLabel?: string; + trackSrc?: string; + trackSrcLang?: string; + screenPlayIcon: IElementOrIcon; + screenLoadingIcon: IElementOrIcon; + showButtonsBar?: boolean; + buttonsBarPlayIcon: IElementOrIcon; + buttonsBarPlayIconTooltip: JSX.Element | string; + buttonsBarPlayIconToTransition: IElementOrIcon; + buttonsBarVolumeIcon: IElementOrIcon; + buttonsBarVolumeIconTooltip: JSX.Element | string; + buttonsBarVolumeIconToTransition?: IElementOrIcon; + volumeBarAriaLabel?: string; + buttonsBarSubtitlesIcon: IElementOrIcon; + buttonsBarSubtitlesIconTooltip: JSX.Element | string; + buttonsBarSubtitlesIconToTransition?: IElementOrIcon; + buttonsBarFullScreenIcon: IElementOrIcon; + buttonsBarFullScreenIconTooltip: JSX.Element | string; + buttonsBarFullScreenIconToTransition?: IElementOrIcon; + linkAndActionButtonAlignment?: LinkAndActionButtonAlignment; + videoRef: React.RefObject; + videoContainerRef: React.RefObject; + fullScreen: boolean; + autoFullScreen?: boolean; + playing: boolean; + loading: boolean; + subtitlesActivated: boolean; + volume: string; + currentTime: number; + duration: number; + barAriaLabel?: string; + actionButton?: VideoButtonType; + linkText?: string; + linkUrl?: string; + linkTarget?: LinkTargetType; + componentLink: GenericLinkType; + hasSkeleton?: boolean; + skeletonText?: string; + showControls: boolean; + showScreenButtons?: boolean; + hasOverlay: boolean; + onLinkClick?: React.MouseEventHandler; + onFullScreenClick?: React.MouseEventHandler; + onVolumeChange?: React.ChangeEventHandler; + onVolumeButtonClick?: React.MouseEventHandler; + onSubtitlesClick?: React.MouseEventHandler; + onTogglePlay?: + | React.MouseEventHandler + | React.MouseEventHandler; + onCanPlay?: React.ReactEventHandler; + onLoadedMetadata?: React.ReactEventHandler; + onVideoPause?: React.ReactEventHandler; + onVideoPlay?: React.ReactEventHandler; + onTimeUpdate?: React.ReactEventHandler; + onProgressBarChange?: (value: number) => void; + onProgressBarDragEnd?: () => void; + onProgressBarDragStart?: () => void; + dataTestIdParentContainer?: string; + dataTestIdOverlay?: string; + dataTestIdVideoSkeleton?: string; + dataTestIdVolumeInput?: string; +} + +export interface IVideoControlled + extends Omit, + Omit, 'cts' | 'extraCt'> { + variant: V; +} + +type propsToOmit = + | 'styles' + | 'videoRef' + | 'videoContainerRef' + | 'fullScreen' + | 'playing' + | 'showButtonsBar' + | 'loading' + | 'subtitlesActivated' + | 'volume' + | 'currentTime' + | 'duration' + | 'hasOverlay' + | 'showControls' + | 'showScreenButtons' + | 'onTogglePlay' + | 'onProgressBarChange' + | 'onProgressBarDragStart' + | 'onProgressBarDragEnd' + | 'onVolumeButtonClick' + | 'onSubtitlesClick' + | 'onFullScreenClick'; + +export interface IVideoUnControlled + extends Omit, propsToOmit> { + hasInitialOverlay?: boolean; + timeToHideButtonsBar?: number; + onSubtitlesClick?: (event: React.MouseEvent, activated: boolean) => void; + onFullScreenClick?: (event: React.MouseEvent, activated: boolean) => void; + onVolumeButtonClick?: (event: React.MouseEvent, valume: string) => void; + onTogglePlay?: ( + event: React.MouseEvent, + playing: boolean + ) => void; +} diff --git a/src/components/video/types/videoTheme.ts b/src/components/video/types/videoTheme.ts new file mode 100644 index 00000000..f9ae44a0 --- /dev/null +++ b/src/components/video/types/videoTheme.ts @@ -0,0 +1,59 @@ +import { MediaButtonSizeType } from '@/components/mediaButton/types/sizes'; +import { DeviceBreakpointsType } from '@/types'; +import { CommonStyleType, TypographyTypes } from '@/types/styles'; + +export type MediaButtonType = { + variant?: string; + color?: string; + backgroundColor?: string; + size?: { [key in DeviceBreakpointsType]?: MediaButtonSizeType }; +}; +export type SkeletonType = { + width?: string; + height?: string; + variant?: string; + shapeVariant?: string; +}; + +export type VideoStyleType = { + progressBarVariant?: string; + progressBarSize?: string; + container?: CommonStyleType; + videoContainer?: CommonStyleType; + videoElement?: CommonStyleType; + subtitles?: CommonStyleType & + TypographyTypes & { + padding_bottom?: string; + additionalPaddingForSubtitles?: string; + }; + screenIconContainer?: CommonStyleType; + controlsContainer?: CommonStyleType; + buttonsContainer?: CommonStyleType; + screenPlayLoadingIcon?: MediaButtonType; + buttonsBarPlayIcon?: MediaButtonType; + buttonsBarVolumeIcon?: MediaButtonType; + volumeBar?: CommonStyleType; + videoDuration?: TypographyTypes; + buttonsBarSubtitlesIcon?: MediaButtonType; + buttonsBarFullScreenIcon?: MediaButtonType; + buttonsBarFirstSubcontainer?: CommonStyleType; + buttonsBarSecondSubcontainer?: CommonStyleType; + bottomContainer?: CommonStyleType; + link?: TypographyTypes; + actionButton?: { + size?: string; + variant?: string; + }; + videoSkeletonContainer?: CommonStyleType; + videoSkeleton?: SkeletonType; + buttonsSkeletonContainer?: CommonStyleType; + buttonSkeleton?: SkeletonType; + overlay?: CommonStyleType; + tooltip?: { + variant?: string; + }; +}; + +export type VideoStylesType

= { + [key in P]: VideoStyleType; +}; diff --git a/src/components/video/video.styled.ts b/src/components/video/video.styled.ts new file mode 100644 index 00000000..b365ac93 --- /dev/null +++ b/src/components/video/video.styled.ts @@ -0,0 +1,163 @@ +import styled, { css } from 'styled-components'; + +import { getStyles } from '@/utils/getStyles/getStyles'; + +import { LinkAndActionButtonAlignment, VideoStyleType } from './types'; + +type VideoPropsStyleType = { + styles: VideoStyleType; +}; +type VideoContainerPropsStyleType = VideoPropsStyleType & { paddingBottomSubtitles: string }; + +export const ContainerStyled = styled.div` + ${({ styles }) => getStyles(styles?.container)} +`; + +export const VideoContainerStyled = styled.div` + ${({ styles }) => getStyles(styles?.videoContainer)} +`; + +export const VideoElement = styled.video` + // Allow to set aspect radio to 16:9 + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + object-fit: ${({ isFullScreen }) => (isFullScreen ? 'content' : 'fill')}; + ${({ styles }) => getStyles(styles?.videoElement)}; + //Style text subtitles + ::cue { + ${({ + styles, + theme: { + MEDIA_QUERIES: { onlyDesktop, onlyTablet, onlyMobile }, + }, + }) => css` + ${onlyDesktop} { + color: ${styles?.subtitles?.color}; + font-size: ${styles?.subtitles?.DESKTOP?.font_size}; + line-height: ${styles?.subtitles?.DESKTOP?.line_height}; + font-weight: ${styles?.subtitles?.font_weight}; + background-color: ${styles?.subtitles?.background_color}; + } + ${onlyTablet} { + color: ${styles?.subtitles?.color}; + font-size: ${styles?.subtitles?.TABLET?.font_size}; + line-height: ${styles?.subtitles?.TABLET?.line_height}; + font-weight: ${styles?.subtitles?.font_weight}; + background-color: ${styles?.subtitles?.background_color}; + margin-bottom: ${styles?.subtitles?.margin_bottom}; + } + ${onlyMobile} { + color: ${styles?.subtitles?.color}; + font-size: ${styles?.subtitles?.MOBILE?.font_size}; + line-height: ${styles?.subtitles?.MOBILE?.line_height}; + font-weight: ${styles?.subtitles?.font_weight}; + background-color: ${styles?.subtitles?.background_color}; + margin-bottom: ${styles?.subtitles?.margin_bottom}; + } + `}; + } + ::-webkit-media-text-track-display { + padding-bottom: ${({ paddingBottomSubtitles }) => paddingBottomSubtitles}; + } + // Safari: get margins/paddings for subtitles containers + ::-webkit-media-text-track-container { + padding-bottom: ${({ paddingBottomSubtitles }) => paddingBottomSubtitles}; + } + //Safari. If "::cue" doesnt work, use this + /* ::-webkit-media-text-track-display-backdrop { + ${({ + styles, + theme: { + MEDIA_QUERIES: { onlyDesktop, onlyTablet, onlyMobile }, + }, + }) => css` + ${onlyDesktop} { + color: ${styles?.subtitles?.color}; + font-size: ${styles?.subtitles?.DESKTOP?.font_size}; + line-height: ${styles?.subtitles?.DESKTOP?.line_height}; + font-weight: ${styles?.subtitles?.font_weight}; + background-color: ${styles?.subtitles?.background_color}; + } + ${onlyTablet} { + color: ${styles?.subtitles?.color}; + font-size: ${styles?.subtitles?.TABLET?.font_size}; + line-height: ${styles?.subtitles?.TABLET?.line_height}; + font-weight: ${styles?.subtitles?.font_weight}; + background-color: ${styles?.subtitles?.background_color}; + margin-bottom: ${styles?.subtitles?.margin_bottom}; + } + ${onlyMobile} { + color: ${styles?.subtitles?.color}; + font-size: ${styles?.subtitles?.MOBILE?.font_size}; + line-height: ${styles?.subtitles?.MOBILE?.line_height}; + font-weight: ${styles?.subtitles?.font_weight}; + background-color: ${styles?.subtitles?.background_color}; + margin-bottom: ${styles?.subtitles?.margin_bottom}; + } + `}; + } */ +`; + +export const ScreenIconContainerStyled = styled.div` + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + ${({ styles }) => getStyles(styles?.screenIconContainer)} +`; + +export const ControlsContainerStyled = styled.div` + position: absolute; + bottom: 0; + left: 0; + width: 100%; + opacity: 1; + transition: opacity 0.2s ease-in-out; + ${({ styles }) => getStyles(styles?.controlsContainer)} + opacity: ${({ showControls }) => (!showControls ? '0' : undefined)}; +`; + +export const ButtonsContainerStyled = styled.div` + display: flex; + ${({ styles }) => getStyles(styles?.buttonsContainer)} +`; + +export const VolumeContainerStyled = styled.input` + ${({ styles }) => getStyles(styles?.volumeBar)} +`; + +export const ButtonsBarFirstSubcontainerStyled = styled.div` + display: flex; + ${({ styles }) => getStyles(styles?.buttonsBarFirstSubcontainer)} +`; + +export const ButtonsBarSecondSubontainerStyled = styled.div` + display: flex; + ${({ styles }) => getStyles(styles?.buttonsBarSecondSubcontainer)} +`; + +export const BottomContainerStyled = styled.div< + VideoPropsStyleType & { linkAndActionButtonAlignment?: LinkAndActionButtonAlignment } +>` + display: flex; + justify-content: ${({ linkAndActionButtonAlignment }) => + linkAndActionButtonAlignment ?? 'flex-end'}; + width: 100%; + ${({ styles }) => getStyles(styles?.bottomContainer)} +`; + +export const VideoSkeletonContainerStyled = styled.div` + ${({ styles }) => getStyles(styles?.videoSkeletonContainer)} +`; + +export const ButtonsSkeletonContainerStyled = styled.div` + display: flex; + ${({ styles }) => getStyles(styles?.buttonsSkeletonContainer)} +`; + +export const OverlayStyled = styled.div` + ${({ styles }) => getStyles(styles?.overlay)} +`; diff --git a/src/components/video/videoControlled.tsx b/src/components/video/videoControlled.tsx new file mode 100644 index 00000000..e9a446c6 --- /dev/null +++ b/src/components/video/videoControlled.tsx @@ -0,0 +1,53 @@ +import * as React from 'react'; + +import { STYLES_NAME } from '@/constants'; +import { useStyles } from '@/hooks/useStyles/useStyles'; +import { ErrorBoundary, FallbackComponent } from '@/provider/errorBoundary'; +import { useGenericComponents } from '@/provider/genericComponents'; + +import { VideoStyleType } from './types'; +import { IVideoControlled, IVideoStandAlone } from './types/video'; +import { VideoStandAlone } from './videoStandAlone'; + +const VideoControlledComponent = React.forwardRef( + ( + { variant, ctv, ...props }: IVideoControlled, + ref: React.ForwardedRef | undefined | null + ): JSX.Element => { + const variantStyles = useStyles(STYLES_NAME.VIDEO, variant, ctv); + const { LINK: genericLinkComponent } = useGenericComponents(); + + return ( + + ); + } +); +VideoControlledComponent.displayName = 'VideoControlledComponent'; + +const VideoBoundary = ( + props: IVideoControlled, + ref: React.ForwardedRef | undefined | null +): JSX.Element => ( + + + + } + > + + +); + +const VideoControlled = React.forwardRef(VideoBoundary) as ( + props: React.PropsWithChildren> & { + ref?: React.ForwardedRef | undefined | null; + } +) => ReturnType; + +export { VideoControlled }; diff --git a/src/components/video/videoStandAlone.tsx b/src/components/video/videoStandAlone.tsx new file mode 100644 index 00000000..5ddcc7e9 --- /dev/null +++ b/src/components/video/videoStandAlone.tsx @@ -0,0 +1,176 @@ +import * as React from 'react'; + +import { useId } from '@/hooks'; + +import { ProgressBar } from '../progressBar'; +import { + LinkAndActionButton, + PlayStopButton, + ScreenButtons, + SubtitleFullScreenButtons, + Time, + VideoSkeleton, + Volume, +} from './components'; +import { IVideoStandAlone } from './types'; +import { + ButtonsBarFirstSubcontainerStyled, + ButtonsBarSecondSubontainerStyled, + ButtonsContainerStyled, + ContainerStyled, + ControlsContainerStyled, + OverlayStyled, + VideoContainerStyled, + VideoElement, +} from './video.styled'; + +const VIDEO_BASE_ID = 'video'; + +const VideoStandAloneComponent = ( + props: IVideoStandAlone, + ref: React.ForwardedRef +): React.JSX.Element => { + const BASE_ID = useId(VIDEO_BASE_ID); + const controlsContainerId = `${BASE_ID}-controlsContainer`; + + const getPaddingBottomSubtitles = (): string => { + //This padding must be pressent always + const paddingBottom = parseFloat(props.styles.subtitles?.padding_bottom ?? '0'); + + //This additional padding only should be present when controls container is showed + const additionalPadding = parseFloat( + props.styles.subtitles?.additionalPaddingForSubtitles ?? '0' + ); + return `${paddingBottom + additionalPadding}rem`; + }; + + if (props.hasSkeleton) { + return ( + + ); + } + + return ( + + + {props.hasOverlay && ( + + )} + } + onLoadedMetadata={props.onLoadedMetadata} + onPause={props.onVideoPause} + onPlay={props.onVideoPlay} + onTimeUpdate={props.onTimeUpdate} + > + + {props.subtitlesActivated && ( + + )} + + {props.showScreenButtons && ( + } + loading={props.loading} + playing={props.playing} + screenLoadingIcon={props.screenLoadingIcon} + screenPlayIcon={props.screenPlayIcon} + styles={props.styles} + /> + )} + {!props.loading && !props.hasOverlay && ( + + + + + + } + playing={props.playing} + styles={props.styles} + /> + + + + + + + + )} + + + + ); +}; + +export const VideoStandAlone = React.forwardRef(VideoStandAloneComponent); diff --git a/src/components/video/videoUnControlled.tsx b/src/components/video/videoUnControlled.tsx new file mode 100644 index 00000000..e7e3b460 --- /dev/null +++ b/src/components/video/videoUnControlled.tsx @@ -0,0 +1,252 @@ +import * as React from 'react'; + +import { useMediaDevice } from '@/hooks'; + +import { IVideoUnControlled } from './types'; +import { VideoControlled } from './videoControlled'; + +export const VideoUnControlled = ({ + hasInitialOverlay = true, + timeToHideButtonsBar = 4000, + autoFullScreen = false, + onCanPlay, + onVolumeChange, + onFullScreenClick, + onSubtitlesClick, + onVolumeButtonClick, + onLoadedMetadata, + onVideoPause, + onVideoPlay, + onTimeUpdate, + onTogglePlay, + ...props +}: IVideoUnControlled): React.JSX.Element | null => { + const ref = React.useRef(null); + const videoRef = React.useRef(null); + const device = useMediaDevice(); + const videoContainerRef = React.useRef(null); + const [isLoading, setIsLoading] = React.useState(true); + const [showOverlay, setShowOverlay] = React.useState(hasInitialOverlay); + const [isFullScreen, setIsFullScreen] = React.useState(false); + const [isPlaying, setIsPlaying] = React.useState(false); + const [showScreenButtons, setShowScreenButtons] = React.useState(true); + const [volume, setVolume] = React.useState('50'); + const [volumeSaved, setVolumeSaved] = React.useState('50'); + const [currentTime, setCurrentTime] = React.useState(0); + const [duration, setDuration] = React.useState(0); + const [subtitlesActivated, setSubtitlesActivated] = React.useState(false); + + const [showControls, setShowControls] = React.useState(true); + + // Load video + React.useEffect(() => { + if (videoRef.current) { + videoRef.current.load(); + setIsPlaying(!videoRef.current.paused); + setShowOverlay(hasInitialOverlay); + setShowScreenButtons(true); + } + }, [props.videoSrc]); + + // Listen when fullscren changes + const handleFullScreenHasChanged = () => { + if (document.fullscreenElement) { + setIsFullScreen(true); + } else { + setIsFullScreen(false); + } + }; + + React.useEffect(() => { + document.addEventListener('fullscreenchange', handleFullScreenHasChanged); + return () => { + document.removeEventListener('fullscreenchange', handleFullScreenHasChanged); + }; + }, []); + + // Executed when video is loaded enough to be played + const handleCanPlay: React.ReactEventHandler = e => { + onCanPlay?.(e); + setIsLoading(false); + }; + + const handleLoadedMetadata: React.ReactEventHandler = e => { + if (!videoRef.current) { + return; + } + onLoadedMetadata?.(e); + setDuration(videoRef.current.duration); + }; + + // Press play/pause video + const handleTogglePlay: React.MouseEventHandler = e => { + if (!videoRef.current || isLoading) { + return; + } + onTogglePlay?.(e, videoRef.current.paused); + if (videoRef.current.paused) { + videoRef.current.play(); + } else { + videoRef.current.pause(); + } + // The screens buttons only appear at the beginning, as soon as the user presses play, + // neither the play nor the overlay appears again, even if the user presses pause. + // (It is shown again when videoSrc changes) + if (showScreenButtons) { + setShowScreenButtons(false); + } + }; + + // Executed when video is played + const handlePlayVideo: React.ReactEventHandler = e => { + onVideoPlay?.(e); + // Hide overlay when video is played + setShowOverlay(false); + setIsPlaying(true); + autoFullScreen && + !isFullScreen && + handleClickFullScreen({} as React.MouseEvent); + }; + + // Executed when video is paused + const handlePauseVideo: React.ReactEventHandler = e => { + onVideoPause?.(e); + setIsPlaying(false); + }; + + // Executed when video time is updated + const handleTimeUpdate: React.ReactEventHandler = e => { + if (!videoRef.current) { + return; + } + onTimeUpdate?.(e); + setCurrentTime(videoRef.current.currentTime); + }; + + // Excecuted when moving the progress bar + const handleChangeProgressBar = value => { + if (!videoRef.current) { + return; + } + videoRef.current.currentTime = (videoRef.current.duration * value) / 100; + }; + + const isPausedBfDrag = React.useRef(true); + // Excecuted when dragging progressbar + const handleDragStartProgressBar = () => { + if (!videoRef.current) { + return; + } + isPausedBfDrag.current = videoRef.current.paused; + videoRef.current.pause(); + }; + + // Excecuted when strop dragging progressbar + const handleDragEndProgressBar = () => { + if (!videoRef.current) { + return; + } + if (!isPausedBfDrag.current) { + videoRef.current.play(); + } + }; + + const handleChangeVolume: React.ChangeEventHandler = event => { + onVolumeChange?.(event); + setVolume(event.target.value); + setVolumeSaved(event.target.value); + if (videoRef.current) { + //Change volume on video + videoRef.current.volume = parseFloat(event.target.value) / 100; + } + }; + + const handleClickVolumeButton: React.MouseEventHandler = e => { + if (videoRef.current) { + if (volume !== '0') { + onVolumeButtonClick?.(e, '0'); + setVolume('0'); + //Mute video + videoRef.current.volume = 0; + } else { + onVolumeButtonClick?.(e, volumeSaved); + setVolume(volumeSaved); + //Change volume on video + videoRef.current.volume = parseFloat(volumeSaved) / 100; + } + } + }; + + const handleClickFullScreen: React.MouseEventHandler = e => { + if (!videoContainerRef.current) { + return; + } + onFullScreenClick?.(e, !document.fullscreenElement); + if (document.fullscreenElement) { + // Video exits fullscreen + document.exitFullscreen?.(); + } else { + // Video goes to fullscreen + videoContainerRef.current.requestFullscreen(); + } + }; + + const handleClickSubtitles: React.MouseEventHandler = e => { + const _subtitlesActivated = !subtitlesActivated; + onSubtitlesClick?.(e, _subtitlesActivated); + setSubtitlesActivated(_subtitlesActivated); + }; + + // Hide controls when timeToHideButtonsBar passes + const timeoutShowHideButtonsBar = React.useRef(); + const handleShowHidControls = () => { + setShowControls(true); + if (timeToHideButtonsBar) { + clearTimeout(timeoutShowHideButtonsBar.current); + timeoutShowHideButtonsBar.current = setTimeout(() => { + setShowControls(false); + }, timeToHideButtonsBar); + } + }; + + React.useEffect(() => { + if (timeToHideButtonsBar) { + window.addEventListener('mousemove', handleShowHidControls); + } + return () => { + window.removeEventListener('mousemove', handleShowHidControls); + }; + }, [timeToHideButtonsBar]); + + return ( + + ); +}; diff --git a/src/designSystem/kubit/components/modalV2/styles.ts b/src/designSystem/kubit/components/modalV2/styles.ts index d167199f..ba4a44ce 100644 --- a/src/designSystem/kubit/components/modalV2/styles.ts +++ b/src/designSystem/kubit/components/modalV2/styles.ts @@ -29,8 +29,10 @@ const commonTokens = { headerContainer: { width: SPACINGS.spacing_100_percent, display: 'flex', + flex_direction: 'column', justify_content: 'space-between', align_items: 'center', + gap: SPACINGS.spacing_150, }, title: { font_weight: FONT_WEIGHT.font_weight_600, @@ -47,7 +49,7 @@ const commonTokens = { closeButtonContainer: { display: 'flex', justify_content: 'end', - margin_bottom: SPACINGS.spacing_150, + width: '100%', }, closeButtonIcon: { color: COLORS.NEUTRAL.color_neutral_icon_50, diff --git a/src/designSystem/kubit/components/progressBar/index.ts b/src/designSystem/kubit/components/progressBar/index.ts new file mode 100644 index 00000000..0540595d --- /dev/null +++ b/src/designSystem/kubit/components/progressBar/index.ts @@ -0,0 +1,2 @@ +export * from './styles'; +export * from './variants'; diff --git a/src/designSystem/kubit/components/progressBar/styles.ts b/src/designSystem/kubit/components/progressBar/styles.ts new file mode 100644 index 00000000..61239a35 --- /dev/null +++ b/src/designSystem/kubit/components/progressBar/styles.ts @@ -0,0 +1,83 @@ +import { ProgressBarStylesType } from '@/components/progressBar'; + +import { COLORS, RADIUS, SPACINGS } from '../../foundations'; +import { SliderVariantType } from '../variants'; +import { ProgressBarSizeType, ProgressBarVariantType } from './variants'; + +const containerProps = { + width: 'inherit', + display: 'flex', + gap: SPACINGS.spacing_100, +}; + +const barContainerProps = { + position: 'relative', + width: '100%', +}; + +const progressBarProps = { + position: 'absolute', + top: '50%', + left: '0', + width: '0', + border_radius: RADIUS.radius_100, + background: COLORS.BRAND.color_brand_bg_50, +}; + +const barProps = { + position: 'absolute', + top: '50%', + left: '0', + width: 'inherit', + border_radius: RADIUS.radius_100, + background: COLORS.NEUTRAL.color_neutral_bg_100, +}; + +const commonProps = { + container: { + ...containerProps, + }, + barContainer: { + ...barContainerProps, + }, + bar: { + ...barProps, + }, + progressBar: { + ...progressBarProps, + }, +}; + +export const PROGRESS_BAR_STYLES: ProgressBarStylesType< + ProgressBarVariantType, + ProgressBarSizeType +> = { + [ProgressBarVariantType.DEFAULT]: { + ...commonProps, + }, + [ProgressBarVariantType.INTERACTIVE]: { + ...commonProps, + useAsSlider: true, + sliderVariant: SliderVariantType.PRIMARY, + barContainer: { + ...barContainerProps, + cursor: 'pointer', + }, + }, + [ProgressBarSizeType.SMALL]: { + progressBar: { + height: SPACINGS.spacing_100, + }, + bar: { + height: SPACINGS.spacing_100, + }, + }, + [ProgressBarSizeType.MEDIUM]: { + progressBar: { + height: SPACINGS.spacing_150, + }, + bar: { + height: SPACINGS.spacing_150, + }, + }, +}; diff --git a/src/designSystem/kubit/components/progressBar/variants.ts b/src/designSystem/kubit/components/progressBar/variants.ts new file mode 100644 index 00000000..f01e69ac --- /dev/null +++ b/src/designSystem/kubit/components/progressBar/variants.ts @@ -0,0 +1,9 @@ +export enum ProgressBarVariantType { + DEFAULT = 'DEFAULT', + INTERACTIVE = 'INTERACTIVE', +} + +export enum ProgressBarSizeType { + SMALL = 'SMALL', + MEDIUM = 'MEDIUM', +} diff --git a/src/designSystem/kubit/components/video/index.ts b/src/designSystem/kubit/components/video/index.ts new file mode 100644 index 00000000..0540595d --- /dev/null +++ b/src/designSystem/kubit/components/video/index.ts @@ -0,0 +1,2 @@ +export * from './styles'; +export * from './variants'; diff --git a/src/designSystem/kubit/components/video/styles.ts b/src/designSystem/kubit/components/video/styles.ts new file mode 100644 index 00000000..4dac9940 --- /dev/null +++ b/src/designSystem/kubit/components/video/styles.ts @@ -0,0 +1,181 @@ +import { MediaButtonSizeType } from '@/components/mediaButton/types/sizes'; +import { VideoStylesType } from '@/components/video'; +import { DeviceBreakpointsType } from '@/types'; + +import { COLORS, FONT_WEIGHT, HEADING, SPACINGS, Z_INDEX } from '../../foundations'; +import { ButtonSizeType, ButtonVariantType } from '../button'; +import { MediaButtonVariantType } from '../mediaButton'; +import { SkeletonShapeVariant, SkeletonVariantType } from '../skeleton'; +import { TextVariantType } from '../text'; +import { ProgressBarSizeType, ProgressBarVariantType, TooltipVariantType } from '../variants'; +import { VideoVariantType } from './variants'; + +export const VIDEO_STYLES: VideoStylesType = { + [VideoVariantType.REGULAR]: { + progressBarVariant: ProgressBarVariantType.INTERACTIVE, + progressBarSize: ProgressBarSizeType.MEDIUM, + buttonsContainer: { + padding: SPACINGS.spacing_50, + background_color: COLORS.NEUTRAL.color_neutral_bg_100, + justify_content: 'space-between', + width: '100%', + [DeviceBreakpointsType.MOBILE]: { + flex_direction: 'column', + row_gap: SPACINGS.spacing_100, + }, + }, + subtitles: { + padding_bottom: SPACINGS.spacing_100, + padding: SPACINGS.spacing_100, + color: COLORS.NEUTRAL.color_neutral_bg_100, + background_color: COLORS.NEUTRAL.color_neutral_bg_100, + font_weight: FONT_WEIGHT.font_weight_400, + [DeviceBreakpointsType.DESKTOP]: { + font_size: HEADING.H1[DeviceBreakpointsType.DESKTOP].font_size, + line_height: HEADING.H1[DeviceBreakpointsType.DESKTOP].line_height, + }, + [DeviceBreakpointsType.TABLET]: { + font_size: HEADING.H1[DeviceBreakpointsType.DESKTOP].font_size, + line_height: HEADING.H1[DeviceBreakpointsType.DESKTOP].line_height, + }, + [DeviceBreakpointsType.MOBILE]: { + font_size: HEADING.H1[DeviceBreakpointsType.MOBILE].font_size, + line_height: HEADING.H1[DeviceBreakpointsType.MOBILE].line_height, + }, + }, + screenIconContainer: { + z_index: Z_INDEX.INTERN_3, + }, + screenPlayLoadingIcon: { + variant: MediaButtonVariantType.DEFAULT, + color: COLORS.BRAND.color_brand_bg_50, + backgroundColor: COLORS.NEUTRAL.color_neutral_bg_100, + size: { + [DeviceBreakpointsType.DESKTOP]: MediaButtonSizeType.LARGE, + [DeviceBreakpointsType.TABLET]: MediaButtonSizeType.LARGE, + [DeviceBreakpointsType.MOBILE]: MediaButtonSizeType.LARGE, + [DeviceBreakpointsType.LARGE_DESKTOP]: MediaButtonSizeType.LARGE, + }, + }, + buttonsBarPlayIcon: { + variant: MediaButtonVariantType.DEFAULT, + color: COLORS.NEUTRAL.color_neutral_bg_100, + backgroundColor: COLORS.NEUTRAL.color_neutral_bg_100, + size: { + [DeviceBreakpointsType.DESKTOP]: MediaButtonSizeType.LARGE, + [DeviceBreakpointsType.TABLET]: MediaButtonSizeType.LARGE, + [DeviceBreakpointsType.MOBILE]: MediaButtonSizeType.LARGE, + [DeviceBreakpointsType.LARGE_DESKTOP]: MediaButtonSizeType.LARGE, + }, + }, + buttonsBarVolumeIcon: { + variant: MediaButtonVariantType.DEFAULT, + color: COLORS.NEUTRAL.color_neutral_bg_100, + backgroundColor: COLORS.NEUTRAL.color_neutral_bg_100, + size: { + [DeviceBreakpointsType.DESKTOP]: MediaButtonSizeType.LARGE, + [DeviceBreakpointsType.TABLET]: MediaButtonSizeType.LARGE, + [DeviceBreakpointsType.MOBILE]: MediaButtonSizeType.LARGE, + [DeviceBreakpointsType.LARGE_DESKTOP]: MediaButtonSizeType.LARGE, + }, + }, + volumeBar: { + accent_color: COLORS.NEUTRAL.color_neutral_bg_100, + width: SPACINGS.spacing_100, + }, + videoDuration: { + font_variant: TextVariantType.DEFAULT, + color: COLORS.NEUTRAL.color_neutral_bg_100, + font_weight: FONT_WEIGHT.font_weight_400, + }, + buttonsBarSubtitlesIcon: { + variant: MediaButtonVariantType.DEFAULT, + color: COLORS.NEUTRAL.color_neutral_bg_100, + backgroundColor: COLORS.NEUTRAL.color_neutral_bg_100, + size: { + [DeviceBreakpointsType.DESKTOP]: MediaButtonSizeType.LARGE, + [DeviceBreakpointsType.TABLET]: MediaButtonSizeType.LARGE, + [DeviceBreakpointsType.MOBILE]: MediaButtonSizeType.LARGE, + [DeviceBreakpointsType.LARGE_DESKTOP]: MediaButtonSizeType.LARGE, + }, + }, + buttonsBarFullScreenIcon: { + variant: MediaButtonVariantType.DEFAULT, + color: COLORS.NEUTRAL.color_neutral_bg_100, + backgroundColor: COLORS.NEUTRAL.color_neutral_bg_100, + size: { + [DeviceBreakpointsType.DESKTOP]: MediaButtonSizeType.LARGE, + [DeviceBreakpointsType.TABLET]: MediaButtonSizeType.LARGE, + [DeviceBreakpointsType.MOBILE]: MediaButtonSizeType.LARGE, + [DeviceBreakpointsType.LARGE_DESKTOP]: MediaButtonSizeType.LARGE, + }, + }, + buttonsBarFirstSubcontainer: { + column_gap: SPACINGS.spacing_50, + }, + buttonsBarSecondSubcontainer: { + column_gap: SPACINGS.spacing_50, + }, + bottomContainer: { + padding: SPACINGS.spacing_50, + column_gap: SPACINGS.spacing_50, + background_color: COLORS.NEUTRAL.color_neutral_bg_100, + }, + actionButton: { + variant: ButtonVariantType.PRIMARY, + size: ButtonSizeType.LARGE, + }, + link: { + font_variant: TextVariantType.DEFAULT, + font_weight: FONT_WEIGHT.font_weight_400, + color: COLORS.ACCENT.color_accent_default_bg_100, + }, + videoSkeletonContainer: { + [DeviceBreakpointsType.DESKTOP]: { + width: '1200px', + height: '675px', + }, + [DeviceBreakpointsType.TABLET]: { + width: '720px', + height: '405px', + }, + [DeviceBreakpointsType.MOBILE]: { + width: '328px', + height: '185px', + }, + }, + buttonsSkeletonContainer: { + padding: `${SPACINGS.spacing_50} ${SPACINGS.spacing_50} ${SPACINGS.spacing_50} ${SPACINGS.spacing_0}`, + column_gap: SPACINGS.spacing_50, + justify_content: 'flex-end', + [DeviceBreakpointsType.MOBILE]: { + padding: SPACINGS.spacing_50, + justify_content: 'flex-start', + }, + }, + videoSkeleton: { + variant: SkeletonVariantType.DEFAULT, + shapeVariant: SkeletonShapeVariant.SQUARE, + width: '100%', + height: '100%', + }, + buttonSkeleton: { + variant: SkeletonVariantType.DEFAULT, + shapeVariant: SkeletonShapeVariant.SQUARE, + width: SPACINGS.spacing_100, + height: SPACINGS.spacing_100, + }, + overlay: { + position: 'absolute', + top: '0', + z_index: Z_INDEX.OVERLAY, + width: '100%', + height: '100%', + opacity: '0.7', + background_color: COLORS.ACCENT.color_accent_default_bg_100, + }, + tooltip: { + variant: TooltipVariantType.DEFAULT, + }, + }, +}; diff --git a/src/designSystem/kubit/components/video/variants.ts b/src/designSystem/kubit/components/video/variants.ts new file mode 100644 index 00000000..33afd32a --- /dev/null +++ b/src/designSystem/kubit/components/video/variants.ts @@ -0,0 +1,3 @@ +export enum VideoVariantType { + REGULAR = 'REGULAR', +} From 2a93517151aed07ee0da1b4410527556012ba001 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:53:40 +0200 Subject: [PATCH 13/27] Add icons to storybook --- src/assets/storybook/icons/icon_ds_handle.svg | 3 +++ src/assets/storybook/index.ts | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 src/assets/storybook/icons/icon_ds_handle.svg diff --git a/src/assets/storybook/icons/icon_ds_handle.svg b/src/assets/storybook/icons/icon_ds_handle.svg new file mode 100644 index 00000000..f49f25f3 --- /dev/null +++ b/src/assets/storybook/icons/icon_ds_handle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/storybook/index.ts b/src/assets/storybook/index.ts index ccb93934..8fc774e7 100644 --- a/src/assets/storybook/index.ts +++ b/src/assets/storybook/index.ts @@ -3,6 +3,7 @@ import ICON_CHEVRON_DOWN from './icons/icon_chevron_down.svg'; import ICON_CHEVRON_LEFT from './icons/icon_chevron_left.svg'; import ICON_CHEVRON_RIGHT from './icons/icon_chevron_right.svg'; import ICON_CHEVRON_UP from './icons/icon_chevron_up.svg'; +import ICON_DRAG from './icons/icon_ds_handle.svg'; import ICON_GHOST from './icons/icon_ghost.svg'; import ICON_PLACEHOLDER from './icons/icon_placeholder.svg'; import ICON_CLOSE from './icons/icon_x_close.svg'; @@ -26,6 +27,7 @@ export const ICONS = { ICON_PLAY_BUTTON, ICON_CHECKMARK_THICK, ICON_REPLACE, + ICON_DRAG, }; export const ILLUSTRATIONS = { From 4a75ec3a453c4e177b27d72ff60cb094058e990a Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:53:47 +0200 Subject: [PATCH 14/27] Fix width button error --- src/components/button/button.styled.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/button/button.styled.ts b/src/components/button/button.styled.ts index 8be139f2..60222441 100644 --- a/src/components/button/button.styled.ts +++ b/src/components/button/button.styled.ts @@ -49,7 +49,6 @@ export const ButtonStyled = styled.button` align-items: center; justify-content: center; text-align: ${({ alignText }) => alignText ?? 'left'}; - min-width: ${props => props.minWidth}; cursor: pointer; flex-direction: ${props => props.$iconPosition === IconPositionType.LEFT ? 'row' : 'row-reverse'}; @@ -86,7 +85,7 @@ export const ButtonStyled = styled.button` ${({ $styles, $sizeStyles }) => setTokens(ButtonStateType.DEFAULT, $styles, $sizeStyles)} } } - + min-width: ${props => props.minWidth}; width: ${props => (props.$fullWidth ? '100%' : 'auto')}; &::after { From f4294f981a5eee345a0b7528d9ed365345a8c4dd Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:54:52 +0200 Subject: [PATCH 15/27] Include swipe on drawer and tooltip components --- src/components/drawer/drawerControlled.tsx | 8 +- src/components/drawer/drawerStandAlone.tsx | 9 +- src/components/drawer/drawerUnControlled.tsx | 4 +- src/components/drawer/stories/argtypes.ts | 162 ------------------ .../drawer/stories/controlledArgtypes.ts | 10 ++ .../drawer/stories/drawer.stories.tsx | 111 ------------ .../stories/drawerControlled.stories.tsx | 9 +- .../drawer/stories/uncontrolledArgTypes.ts | 10 ++ src/components/drawer/types/drawer.ts | 4 +- src/components/tooltip/tooltipStandAlone.tsx | 20 +-- .../tooltip/tooltipUnControlled.tsx | 12 +- src/components/tooltip/types/tooltip.ts | 4 +- .../kubit/components/modal/styles.ts | 7 +- src/hooks/index.ts | 1 + src/hooks/useScrollEffect/useScrollEffect.tsx | 20 ++- 15 files changed, 87 insertions(+), 304 deletions(-) delete mode 100644 src/components/drawer/stories/argtypes.ts delete mode 100644 src/components/drawer/stories/drawer.stories.tsx diff --git a/src/components/drawer/drawerControlled.tsx b/src/components/drawer/drawerControlled.tsx index f7e88978..37b0e1b5 100644 --- a/src/components/drawer/drawerControlled.tsx +++ b/src/components/drawer/drawerControlled.tsx @@ -30,6 +30,11 @@ const DrawerControlledComponent = React.forwardRef( ref: React.ForwardedRef | undefined | null ): JSX.Element => { useDeviceHeight(); + + const handleScroll = (e: Event) => { + props.onContentScroll?.(e); + }; + const styles = useStylesV2({ styleName: STYLES_NAME.DRAWER, variantName: props.variant, @@ -41,6 +46,7 @@ const DrawerControlledComponent = React.forwardRef( const { scrollableRef, shadowRef } = useScrollEffect({ shadowStyles: stylesByDevice.titleContainer?.box_shadow, shadowVisible: SCROLL_DISTANCE, + scrollCallback: handleScroll, }); const footerRef = useZoomEffect(FOOTER_EDIT_STYLES, MAX_ZOOM); @@ -49,9 +55,9 @@ const DrawerControlledComponent = React.forwardRef( diff --git a/src/components/drawer/drawerStandAlone.tsx b/src/components/drawer/drawerStandAlone.tsx index a9895e1f..ea0be76e 100644 --- a/src/components/drawer/drawerStandAlone.tsx +++ b/src/components/drawer/drawerStandAlone.tsx @@ -20,7 +20,6 @@ import { IDrawerStandAlone } from './types'; const DrawerStandAloneComponent = ( { blocked = false, - scrollableRef, shadowRef, footerRef, contentRef, @@ -61,11 +60,7 @@ const DrawerStandAloneComponent = ( /> )} - + {props.title?.content} - + {props.children} {props.footer?.content && props.styles.footer?.variant && ( diff --git a/src/components/drawer/drawerUnControlled.tsx b/src/components/drawer/drawerUnControlled.tsx index c5b560ee..b1666c7f 100644 --- a/src/components/drawer/drawerUnControlled.tsx +++ b/src/components/drawer/drawerUnControlled.tsx @@ -24,7 +24,7 @@ const DrawerUnControlledComponent = ( closeIcon?.onClick?.(e); }; - const handlePopoverCloseInterllay = () => { + const handlePopoverCloseInternally = () => { handleCloseDrawer(); popover?.onCloseInternally?.(); }; @@ -35,7 +35,7 @@ const DrawerUnControlledComponent = ( ref={ref} closeIcon={{ ...closeIcon, onClick: handleCloseIconClick }} open={open} - popover={{ ...popover, onCloseInternally: handlePopoverCloseInterllay }} + popover={{ ...popover, onCloseInternally: handlePopoverCloseInternally }} /> ); }; diff --git a/src/components/drawer/stories/argtypes.ts b/src/components/drawer/stories/argtypes.ts deleted file mode 100644 index 405e58de..00000000 --- a/src/components/drawer/stories/argtypes.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { CATEGORY_CONTROL } from '@/constants/categoryControl'; -import { IThemeObjectVariants } from '@/designSystem/themesObject'; -import { ArgTypesReturn } from '@/types'; - -import { DrawerLevelPositionTypes } from '../types'; - -export const argtypes = (variants: IThemeObjectVariants, themeSelected: string): ArgTypesReturn => { - return { - theme: { - table: { - disable: true, - }, - }, - variant: { - control: { type: 'select' }, - type: { name: 'string', required: true }, - description: 'Drawer variant', - options: Object.keys(variants[themeSelected].DrawerVariantType || {}), - table: { - type: { - summary: 'string', - }, - category: CATEGORY_CONTROL.MODIFIERS, - }, - }, - defaultOpen: { - description: 'Defines the default open option', - type: { name: 'boolean' }, - control: { type: 'boolean' }, - table: { - type: { - summary: 'boolean', - }, - defaultValue: { summary: true }, - category: CATEGORY_CONTROL.MODIFIERS, - }, - }, - portalId: { - description: 'Identifier of portal to render drawer', - type: { name: 'string' }, - control: { type: 'text' }, - table: { - type: { - summary: 'string', - }, - category: CATEGORY_CONTROL.CUSTOMIZATION, - }, - }, - onHandleOpen: { - description: 'Informs when the drawer is closed or opened', - control: false, - table: { - type: { - summary: '(open: boolean) => void', - }, - category: CATEGORY_CONTROL.FUNCTIONS, - }, - }, - children: { - description: 'Content', - type: { name: 'string', required: true }, - control: { type: 'text' }, - table: { - type: { - summary: 'ReactNode', - }, - category: CATEGORY_CONTROL.CONTENT, - }, - }, - footer: { - description: 'Footer of the Drawer', - type: { name: 'object' }, - control: { type: 'object' }, - table: { - type: { - summary: 'DrawerFooterType', - }, - category: CATEGORY_CONTROL.CONTENT, - }, - }, - closeIcon: { - description: 'Close icon', - control: { type: 'object' }, - type: { name: 'object' }, - table: { - type: { - summary: 'IElementOrIcon', - }, - category: CATEGORY_CONTROL.CONTENT, - }, - }, - title: { - description: 'Tile of the drawer', - type: { name: 'object' }, - control: { type: 'object' }, - table: { - type: { - summary: 'DrawerTextType', - }, - category: CATEGORY_CONTROL.CONTENT, - }, - }, - level: { - description: 'Select the right or left icon depends of the value', - control: { type: 'select' }, - type: { name: 'string', required: true }, - options: Object.keys(DrawerLevelPositionTypes), - table: { - type: { - summary: 'DrawerLevelPositionTypes', - detail: Object.keys(DrawerLevelPositionTypes).join(', '), - }, - category: CATEGORY_CONTROL.MODIFIERS, - }, - }, - isBlocking: { - description: 'Block drawer', - type: { name: 'boolean' }, - control: { type: 'boolean' }, - table: { - type: { - summary: 'boolean', - }, - defaultValue: { summary: false }, - category: CATEGORY_CONTROL.MODIFIERS, - }, - }, - dataTestId: { - description: 'String used for testing', - control: { type: 'text' }, - type: { name: 'string' }, - table: { - type: { - summary: 'string', - }, - category: CATEGORY_CONTROL.TESTING, - }, - }, - ctv: { - description: 'Object used for update variant styles', - type: { name: 'object' }, - control: { type: 'object' }, - table: { - type: { - summary: 'object', - }, - category: CATEGORY_CONTROL.CUSTOMIZATION, - }, - }, - extraCt: { - description: 'Object used for update grid styles', - type: { name: 'object' }, - control: { type: 'object' }, - table: { - type: { - summary: 'object', - }, - category: CATEGORY_CONTROL.CUSTOMIZATION, - }, - }, - }; -}; diff --git a/src/components/drawer/stories/controlledArgtypes.ts b/src/components/drawer/stories/controlledArgtypes.ts index d4ee924d..ffd13291 100644 --- a/src/components/drawer/stories/controlledArgtypes.ts +++ b/src/components/drawer/stories/controlledArgtypes.ts @@ -126,6 +126,16 @@ export const argtypes = (variants: IThemeObjectVariants, themeSelected: string): category: CATEGORY_CONTROL.MODIFIERS, }, }, + onContentScroll: { + description: 'Function to handle scroll event', + type: { name: 'function' }, + table: { + type: { + summary: 'function', + }, + category: CATEGORY_CONTROL.FUNCTIONS, + }, + }, dataTestId: { description: 'String used for testing', control: { type: 'text' }, diff --git a/src/components/drawer/stories/drawer.stories.tsx b/src/components/drawer/stories/drawer.stories.tsx deleted file mode 100644 index 661a3c4f..00000000 --- a/src/components/drawer/stories/drawer.stories.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import React from 'react'; - -import { ICONS } from '@/assets'; -import { FooterPositionType } from '@/components/footer'; -import { ReplaceContent } from '@/components/storybook/replaceContent/replaceContent'; -import { TextComponentType } from '@/components/text'; -import { STYLES_NAME } from '@/constants'; -import { themesObject, variantsObject } from '@/designSystem/themesObject'; - -import { Drawer as Story } from '../index'; -import { DrawerLevelPositionTypes, DrawerTitleComponentType } from '../types'; -import { argtypes } from './argtypes'; - -const themeSelected = localStorage.getItem('themeSelected') || 'kubit'; - -const meta = { - title: 'Components/Containment/Drawer', - component: Story, - tags: ['autodocs'], - argTypes: argtypes(variantsObject, themeSelected), - render: ({ ...args }) => , - parameters: { - layout: 'centered', - githubUrl: 'https://github.com/kubit-ui/kubit-react-components/tree/main/src/components/drawer', - figmaUrl: - 'https://www.figma.com/file/EYQkbENTFO5r8muvXlPoOy/Kubit-v.1.0.0?type=design&node-id=3922-22903&mode=dev', - }, -} satisfies Meta; - -export default meta; - -type Story = StoryObj & { args: { themeArgs?: object } }; - -const StoryWithHooks = args => { - const [level, setLevel] = React.useState(args.level); - - const onClickBackFirstLevel = () => { - setLevel(DrawerLevelPositionTypes.FIRST_LEVEL); - }; - - return ; -}; - -export const Drawer: Story = { - args: { - variant: Object.values(variantsObject[themeSelected].DrawerVariantType || {})[0] as string, - title: { - content: 'Drawer title', - component: DrawerTitleComponentType.H3 as unknown as TextComponentType, - ['aria-label']: 'aria label title', - }, - closeIcon: { icon: ICONS.ICON_CLOSE, altText: 'alt text icon', ['aria-label']: 'close icon' }, - level: DrawerLevelPositionTypes.FIRST_LEVEL, - defaultOpen: true, - children: , - blocked: false, - popover: { - blockBack: true, - }, - footer: { - content: [ - , - , - ], - }, - themeArgs: themesObject[themeSelected][STYLES_NAME.DRAWER], - }, -}; - -export const DrawerWithCtv: Story = { - args: { - variant: Object.values(variantsObject[themeSelected].DrawerVariantType || {})[0] as string, - title: { - content: 'Drawer title', - component: DrawerTitleComponentType.H3 as unknown as TextComponentType, - ['aria-label']: 'aria label title', - }, - closeIcon: { icon: ICONS.ICON_CLOSE, altText: 'alt text icon', ['aria-label']: 'close icon' }, - level: DrawerLevelPositionTypes.FIRST_LEVEL, - defaultOpen: true, - children: , - blocked: false, - popover: { - blockBack: true, - }, - footer: { - content: [ - , - , - ], - }, - ctv: { - DESKTOP: { - title: { - color: 'red', - }, - }, - TABLET: { - title: { - color: 'red', - }, - }, - MOBILE: { - title: { - color: 'red', - }, - }, - }, - }, -}; diff --git a/src/components/drawer/stories/drawerControlled.stories.tsx b/src/components/drawer/stories/drawerControlled.stories.tsx index 19d1125f..0c20b4ab 100644 --- a/src/components/drawer/stories/drawerControlled.stories.tsx +++ b/src/components/drawer/stories/drawerControlled.stories.tsx @@ -72,7 +72,14 @@ export const DrawerControlled: Story = { closeIcon: { icon: ICONS.ICON_CLOSE, altText: 'alt text icon', ['aria-label']: 'close icon' }, level: DrawerLevelPositionTypes.FIRST_LEVEL, open: false, - children: , + children: ( + <> + + + + + + ), blocked: false, popover: { blockBack: true, diff --git a/src/components/drawer/stories/uncontrolledArgTypes.ts b/src/components/drawer/stories/uncontrolledArgTypes.ts index 68fb772d..c4eb9c92 100644 --- a/src/components/drawer/stories/uncontrolledArgTypes.ts +++ b/src/components/drawer/stories/uncontrolledArgTypes.ts @@ -135,6 +135,16 @@ export const argtypes = (variants: IThemeObjectVariants, themeSelected: string): category: CATEGORY_CONTROL.MODIFIERS, }, }, + onContentScroll: { + description: 'Function to handle scroll event', + type: { name: 'function' }, + table: { + type: { + summary: 'function', + }, + category: CATEGORY_CONTROL.FUNCTIONS, + }, + }, dataTestId: { description: 'String used for testing', control: { type: 'text' }, diff --git a/src/components/drawer/types/drawer.ts b/src/components/drawer/types/drawer.ts index c287e191..c037d1b6 100644 --- a/src/components/drawer/types/drawer.ts +++ b/src/components/drawer/types/drawer.ts @@ -38,14 +38,14 @@ export interface IDrawerStandAlone { blocked?: boolean; popover?: DrawerPopoverType; /* To useScrollableEffect */ - scrollableRef: (node) => void; shadowRef: (node) => void; /* To useZoomEffect */ footerRef?: (node) => void; contentRef?: (node) => void; + onContentScroll?: (e: Event) => void; } -type OmitProps = 'styles' | 'device' | 'scrollableRef' | 'shadowRef' | 'footerRef' | 'contentRef'; +type OmitProps = 'styles' | 'device' | 'shadowRef' | 'footerRef' | 'contentRef'; export interface IDrawerControlled extends Omit, diff --git a/src/components/tooltip/tooltipStandAlone.tsx b/src/components/tooltip/tooltipStandAlone.tsx index 59da6597..5c8a6207 100644 --- a/src/components/tooltip/tooltipStandAlone.tsx +++ b/src/components/tooltip/tooltipStandAlone.tsx @@ -39,6 +39,10 @@ const TooltipStandAlone = ({ const contentId = `${uniqueId}Content`; const isTextContent = typeof props.content?.content === 'string'; + const isDesktop = props.mediaDevice === DeviceBreakpointsType.DESKTOP; + const isMobile = props.mediaDevice === DeviceBreakpointsType.MOBILE; + const isTablet = props.mediaDevice === DeviceBreakpointsType.TABLET; + if (props.disabled) { return ( @@ -67,18 +71,15 @@ const TooltipStandAlone = ({ })} data-testid={`${props.dataTestId}TooltipContent`} id={uniqueId} - role={ - props.mediaDevice === DeviceBreakpointsType.DESKTOP && !props.tooltipAsModal - ? ROLES.TOOLTIP - : undefined - } + role={isDesktop && !props.tooltipAsModal ? ROLES.TOOLTIP : undefined} styles={props.styles} onFocus={props.onTooltipFocus} onKeyDown={props.onTooltipKeyDown} > - {props.dragIcon && ( + {(isMobile || isTablet) && props.dragIcon && ( @@ -167,16 +168,13 @@ const TooltipStandAlone = ({ // https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/tooltip_role // or https://dequeuniversity.com/library/aria/tooltip ariaDescribedBy={ - !props.tooltipAsModal && - (props.mediaDevice === DeviceBreakpointsType.DESKTOP || props.popoverOpen) - ? ariaDescriptorsBy - : undefined + !props.tooltipAsModal && (isDesktop || props.popoverOpen) ? ariaDescriptorsBy : undefined } childrenAsButton={childrenAsButton} > {props.children} - {props.mediaDevice === DeviceBreakpointsType.DESKTOP ? ( + {isDesktop ? ( Tooltip ) : ( ; popover?: TooltipPopoverType; dragIcon?: IElementOrIcon; + dragIconRef?: (node) => void; tooltipAriaLabel?: string; } @@ -85,7 +86,8 @@ type propsToOmitUnControlled = | 'labelRef' | 'onTooltipFocus' | 'onKeyDown' - | 'onTooltipKeyDown'; + | 'onTooltipKeyDown' + | 'dragIconRef'; /** * @name ITooltipUnControlled diff --git a/src/designSystem/kubit/components/modal/styles.ts b/src/designSystem/kubit/components/modal/styles.ts index d3212e18..67e59e55 100644 --- a/src/designSystem/kubit/components/modal/styles.ts +++ b/src/designSystem/kubit/components/modal/styles.ts @@ -28,6 +28,11 @@ const commonProps = { }, headerContainer: { width: SPACINGS.spacing_100_percent, + display: 'flex', + flex_direction: 'column', + justify_content: 'space-between', + align_items: 'center', + gap: SPACINGS.spacing_150, }, title: { font_weight: FONT_WEIGHT.font_weight_600, @@ -44,7 +49,7 @@ const commonProps = { closeButtonContainer: { display: 'flex', justify_content: 'end', - margin_bottom: SPACINGS.spacing_150, + width: '100%', }, closeButtonIcon: { color: COLORS.NEUTRAL.color_neutral_icon_50, diff --git a/src/hooks/index.ts b/src/hooks/index.ts index affc742f..b6f06068 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -18,3 +18,4 @@ export * from './useScrollPosition/useScrollPosition'; export { useZoomEffect } from './useZoomEffect/useZoomEffect'; export { useDeviceHeight } from './useDeviceHeight/useDeviceHeight'; export { useElementBoundingClientRect } from './useElementBoundingClientRect/useElementBoundingClientRect'; +export { useSwipeDown } from './useSwipeDown/useSwipeDown'; diff --git a/src/hooks/useScrollEffect/useScrollEffect.tsx b/src/hooks/useScrollEffect/useScrollEffect.tsx index 60695771..13e11561 100644 --- a/src/hooks/useScrollEffect/useScrollEffect.tsx +++ b/src/hooks/useScrollEffect/useScrollEffect.tsx @@ -12,6 +12,7 @@ interface CustomHookProps { shadowStyles?: string; conditional?: boolean; shadowVisible?: number; + scrollCallback?: (e: Event) => void; } const MAX_PERCENTAGE = 100; @@ -22,6 +23,7 @@ export const useScrollEffect = ({ conditional = true, shadowStyles = defaultShadow, shadowVisible = 1, + scrollCallback, }: CustomHookProps): CustomHookReturnValue => { // the scrollable element ref const innerScrollableRef = useRef(null); @@ -84,13 +86,19 @@ export const useScrollEffect = ({ if (node) { innerScrollableRef.current = node; applyEffect(); - innerScrollableRef.current?.addEventListener('scroll', applyEffect); + innerScrollableRef.current?.addEventListener('scroll', e => { + applyEffect(); + scrollCallback?.(e); + }); } else { - innerScrollableRef.current?.removeEventListener('scroll', applyEffect); + innerScrollableRef.current?.removeEventListener('scroll', e => { + applyEffect(); + scrollCallback?.(e); + }); innerScrollableRef.current = null; } }, - [applyEffect] + [applyEffect, scrollCallback] ); const resizeRef = useCallback(node => { @@ -111,5 +119,9 @@ export const useScrollEffect = ({ } }, []); - return { scrollableRef, resizeRef, shadowRef }; + return { + scrollableRef, + resizeRef, + shadowRef, + }; }; From d288b9f85cd0718895ebf5a2bc7aaeb11e6878df Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:55:21 +0200 Subject: [PATCH 16/27] Delete unncessary files --- .../hooks/useDataTableStickyColumns.test.ts | 147 ----- .../hooks/useDataTableStickyColumns.ts | 78 --- src/components/inputDate/utils/normalize.ts | 6 - .../utils/mediaProgressBarIndex.ts | 33 - src/components/table/stories/mockTable.tsx | 617 ------------------ .../hooks/useTableStickyColumns.test.ts | 123 ---- .../tableV2/hooks/useTableStickyColumns.ts | 63 -- .../commons/components/loader/index.ts | 2 - .../commons/components/loader/styles.ts | 31 - .../commons/components/loader/variants.ts | 5 - .../components/colors/colors.styled.ts | 53 -- .../provider/utilsProvider/utilsProvider.tsx | 76 --- src/utils/date/__tests__/format.test.ts | 78 --- src/utils/date/format.ts | 58 -- 14 files changed, 1370 deletions(-) delete mode 100644 src/components/dataTable/__tests__/hooks/useDataTableStickyColumns.test.ts delete mode 100644 src/components/dataTable/hooks/useDataTableStickyColumns.ts delete mode 100644 src/components/inputDate/utils/normalize.ts delete mode 100644 src/components/mediaProgressBar/utils/mediaProgressBarIndex.ts delete mode 100644 src/components/table/stories/mockTable.tsx delete mode 100644 src/components/tableV2/__tests__/hooks/useTableStickyColumns.test.ts delete mode 100644 src/components/tableV2/hooks/useTableStickyColumns.ts delete mode 100644 src/designSystem/kubitWireframe/commons/components/loader/index.ts delete mode 100644 src/designSystem/kubitWireframe/commons/components/loader/styles.ts delete mode 100644 src/designSystem/kubitWireframe/commons/components/loader/variants.ts delete mode 100644 src/storybook/components/colors/colors.styled.ts delete mode 100644 src/storybook/provider/utilsProvider/utilsProvider.tsx delete mode 100644 src/utils/date/__tests__/format.test.ts delete mode 100644 src/utils/date/format.ts diff --git a/src/components/dataTable/__tests__/hooks/useDataTableStickyColumns.test.ts b/src/components/dataTable/__tests__/hooks/useDataTableStickyColumns.test.ts deleted file mode 100644 index 4256a4fa..00000000 --- a/src/components/dataTable/__tests__/hooks/useDataTableStickyColumns.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { renderHook } from '@testing-library/react-hooks'; - -import { useDataTableStickyColumns } from '../../hooks/useDataTableStickyColumns'; - -describe('useDataTableStickyColumns', () => { - let wrapper: HTMLDivElement; - let scrollableContainer: HTMLDivElement; - let divider: HTMLDivElement; - let table: HTMLTableElement; - let tableHead: HTMLTableSectionElement; - let tableRow: HTMLTableRowElement; - let tableCell: HTMLTableCellElement; - let rightBoxShadowContainer: HTMLDivElement; - let ref: React.RefObject; - - beforeAll(() => { - global.ResizeObserver = class ResizeObserver { - callback; - constructor(callback) { - this.callback = callback; - } - observe() { - // Call the callback - this.callback(); - } - unobserve() { - // do nothing - } - disconnect() { - // do nothing - } - }; - }); - - beforeEach(() => { - wrapper = document.createElement('div'); - scrollableContainer = document.createElement('div'); - scrollableContainer.setAttribute('data-datatable-scrollable-container', ''); - scrollableContainer.scroll = jest.fn().mockImplementation((x, y) => { - scrollableContainer.scrollTop = y; - scrollableContainer.scrollLeft = x; - scrollableContainer.dispatchEvent(new Event('scroll')); - }); - divider = document.createElement('div'); - divider.setAttribute('data-table-divider', ''); - table = document.createElement('table'); - tableHead = document.createElement('thead'); - tableHead.setAttribute('data-table-head', ''); - tableRow = document.createElement('tr'); - tableRow.setAttribute('data-table-row', ''); - tableCell = document.createElement('td'); - tableCell.setAttribute('data-sticky', ''); - rightBoxShadowContainer = document.createElement('div'); - rightBoxShadowContainer.setAttribute('data-datatable-right-shadow', ''); - - tableRow.appendChild(tableCell); - tableHead.appendChild(tableRow); - table.appendChild(tableHead); - scrollableContainer.appendChild(table); - scrollableContainer.appendChild(divider); - wrapper.appendChild(scrollableContainer); - wrapper.appendChild(rightBoxShadowContainer); - document.body.appendChild(wrapper); - ref = { current: wrapper }; - }); - - afterEach(() => { - document.body.removeChild(wrapper); - }); - - it('should do nothing when there is not scrollable container', () => { - const wrapperWithoutScrollableContainer = { current: document.createElement('div') }; - renderHook(() => useDataTableStickyColumns({ ref: wrapperWithoutScrollableContainer })); - expect(tableCell.style.right).toBe(''); - }); - - it('When there is horizontal scroll, right style for sticky columns should be set', () => { - // Simulate horizontal scroll - Object.defineProperty(scrollableContainer, 'scrollWidth', { - value: 200, - }); - Object.defineProperty(scrollableContainer, 'clientWidth', { - value: 100, - }); - renderHook(() => useDataTableStickyColumns({ ref })); - expect(tableCell.style.right).toBe('0px'); - }); - - it('When there is not horizontal scroll, right style for sticky columns should not be set', () => { - // Simulate not horizontal scroll - Object.defineProperty(scrollableContainer, 'scrollWidth', { - value: 100, - }); - Object.defineProperty(scrollableContainer, 'clientWidth', { - value: 200, - }); - renderHook(() => useDataTableStickyColumns({ ref })); - expect(tableCell.style.right).toBe(''); - }); - - it('When the right position of the sticky columns have been set, the rightBoxShadowContainer right position is set, bearing in mind th scrollbar size too', () => { - // Simulate horizontal scroll - Object.defineProperty(scrollableContainer, 'scrollWidth', { - value: 200, - }); - Object.defineProperty(scrollableContainer, 'clientWidth', { - value: 100, - }); - // Simulate vertical scroll - Object.defineProperty(scrollableContainer, 'scrollHeight', { - value: 200, - }); - Object.defineProperty(scrollableContainer, 'clientHeight', { - value: 100, - }); - Object.defineProperty(scrollableContainer, 'offsetWidth', { - value: 105, - }); - renderHook(() => useDataTableStickyColumns({ ref })); - expect(rightBoxShadowContainer.style.right).toBe('5px'); - }); - - it('When the right position of the sticky columns have been set and there is horizontal scroll the dividers width is set to the max width of the sticky columns', () => { - // Simulate horizontal scroll - Object.defineProperty(scrollableContainer, 'scrollWidth', { - value: 200, - }); - Object.defineProperty(scrollableContainer, 'clientWidth', { - value: 100, - }); - // Simulate offset width for the table row - Object.defineProperty(tableRow, 'offsetWidth', { - value: 200, - }); - renderHook(() => useDataTableStickyColumns({ ref })); - expect(divider.style.width).toBe('200px'); - }); - - it('When the right position of the sticky columns have been set and there is no horizontal scroll the dividers width is not set', () => { - // Simulate offset width for the table row - Object.defineProperty(tableRow, 'offsetWidth', { - value: 200, - }); - renderHook(() => useDataTableStickyColumns({ ref })); - expect(divider.style.width).toBe(''); - }); -}); diff --git a/src/components/dataTable/hooks/useDataTableStickyColumns.ts b/src/components/dataTable/hooks/useDataTableStickyColumns.ts deleted file mode 100644 index 50bf4f62..00000000 --- a/src/components/dataTable/hooks/useDataTableStickyColumns.ts +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from 'react'; - -import { hasHorizontalScroll, hasVerticalScroll } from '@/utils'; - -type UseDataTableStickyColumnsParamsType = { - ref: React.RefObject; -}; - -type UseDataTableStickyColumnsReturnType = object; - -export const useDataTableStickyColumns = ({ - ref, -}: UseDataTableStickyColumnsParamsType): UseDataTableStickyColumnsReturnType => { - React.useEffect(() => { - const scrollableContainer = ref.current?.querySelector('[data-datatable-scrollable-container]'); - const rightBoxShadowContainer = ref.current?.querySelector('[data-datatable-right-shadow]'); - let resizeObserver: ResizeObserver; - if (scrollableContainer instanceof HTMLElement) { - const handleElementResize = (element: HTMLElement) => { - const _hasHorizontalScroll = hasHorizontalScroll(element); - // For each row, set the right position of its sticky cells - const rows = element.querySelectorAll('[data-table-row]'); - // Divider width when horizontal scroll should be calc from the inner tables - let dividerWidth = 0; - // used to calc rightBoxShadowContainer position - let maxStickyWidth = 0; - rows.forEach(row => { - if (row instanceof HTMLElement) { - // Retrieve all the cell with sticky attribute - const stickyCells = Array.from(row.querySelectorAll('[data-sticky]')).reverse(); - let right = 0; - stickyCells.forEach(stickyCell => { - if (stickyCell instanceof HTMLElement) { - stickyCell.style.right = _hasHorizontalScroll ? `${right}px` : 'auto'; - right += stickyCell.offsetWidth; - } - }); - dividerWidth = Math.max(dividerWidth, row.offsetWidth); - maxStickyWidth = Math.max(maxStickyWidth, right); - } - }); - // Once the sticky position have been added, set the position of the rightBoxShadowContainer - // This position should be the same as the max width of the sticky cells - if (rightBoxShadowContainer instanceof HTMLElement) { - // If vertical scroll, add scroll bar size to maxStickyWidth - const _hasVerticalScroll = hasVerticalScroll(element); - if (_hasVerticalScroll) { - const scrollBarSize = element.offsetWidth - element.clientWidth; - maxStickyWidth += scrollBarSize; - } - rightBoxShadowContainer.style.right = `${maxStickyWidth}px`; - } - // All dividers should be updated with the offset width of the table in order to have a width of 100% - const dividers = element.querySelectorAll('[data-table-divider]'); - dividers.forEach(divider => { - if (divider instanceof HTMLElement) { - if (_hasHorizontalScroll) { - divider.style.width = `${dividerWidth}px`; - } else { - // Remove width if no horizontal scroll - divider.style.width = ''; - } - } - }); - }; - handleElementResize(scrollableContainer); - resizeObserver = new ResizeObserver(() => { - handleElementResize(scrollableContainer); - }); - resizeObserver.observe(scrollableContainer); - } - return () => { - resizeObserver?.disconnect(); - }; - }, []); - - return {}; -}; diff --git a/src/components/inputDate/utils/normalize.ts b/src/components/inputDate/utils/normalize.ts deleted file mode 100644 index 04a3b7ad..00000000 --- a/src/components/inputDate/utils/normalize.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const normalizeDate = (date: Date | number): Date => { - if (typeof date === 'number') { - return new Date(date); - } - return date; -}; diff --git a/src/components/mediaProgressBar/utils/mediaProgressBarIndex.ts b/src/components/mediaProgressBar/utils/mediaProgressBarIndex.ts deleted file mode 100644 index ed540712..00000000 --- a/src/components/mediaProgressBar/utils/mediaProgressBarIndex.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { DeviceBreakpointsType } from '@/types'; - -interface IUseMediaProgressBarIndexProps { - index: number; - barsNum: number; - currentBar: number; - device: DeviceBreakpointsType; - maxBars: ({ default: number } & { [device in DeviceBreakpointsType]?: number }) | undefined; -} -export const mediaProgressBarIndex = ({ - index, - barsNum, - currentBar, - device, - maxBars, -}: IUseMediaProgressBarIndexProps): number | undefined => { - const MAX_NUMBER_OF_BARS = maxBars?.[device] || maxBars?.default; - - const lastBarIndex = barsNum - 1; - - if (!MAX_NUMBER_OF_BARS) return index; - - // avoid render more bars than MAX_NUMBER_OF_BARS - if (index >= MAX_NUMBER_OF_BARS) return; - // currentBar goes to the last bar when number of bars is greater than MAX_NUMBER_OF_BARS - if (currentBar === lastBarIndex && barsNum > MAX_NUMBER_OF_BARS) { - return index + (currentBar + 1 - MAX_NUMBER_OF_BARS); - // currentBar stays in the next to last bar when there is more bars than MAX_NUMBER_OF_BARS - } else if (currentBar + 2 >= MAX_NUMBER_OF_BARS && currentBar !== lastBarIndex) { - return index + (currentBar + 2 - MAX_NUMBER_OF_BARS); - } - return index; -}; diff --git a/src/components/table/stories/mockTable.tsx b/src/components/table/stories/mockTable.tsx deleted file mode 100644 index 4339202a..00000000 --- a/src/components/table/stories/mockTable.tsx +++ /dev/null @@ -1,617 +0,0 @@ -import React from 'react'; - -import { ICONS } from '@/assets'; -import { Button } from '@/components/button/button'; -import { FooterPositionType } from '@/components/footer'; -import { Icon } from '@/components/icon'; -import { DeviceBreakpointsType } from '@/types'; - -import { FormatListHeaderPriorityType } from '../types'; - -export const mockTableWithLineSeparatorAndCenterFooter = { - expandedContentHelpMessage: 'Need to expand to show the content', - headers: [ - { - id: 'date', - label: 'Date', - config: { hasDivider: true }, - }, - { - id: 'name', - label: ( - - Recipient name* - - ), - config: { - alignHeader: 'left', - alignValue: 'left', - }, - value: (value): string | JSX.Element => - value.surname ? ( -

-
{value.name}
-
{value.surname}
-
- ) : ( - value.name - ), - }, - { - id: 'routingNumber', - label: 'Routing number', - config: { alignHeader: 'left', alignValue: 'left' }, - }, - { - id: 'accountNumber', - label: 'Account number', - config: { - alignHeader: { - [DeviceBreakpointsType.LARGE_DESKTOP]: 'left', - [DeviceBreakpointsType.DESKTOP]: 'left', - [DeviceBreakpointsType.TABLET]: 'right', - [DeviceBreakpointsType.MOBILE]: 'right', - }, - alignValue: { - [DeviceBreakpointsType.LARGE_DESKTOP]: 'left', - [DeviceBreakpointsType.DESKTOP]: 'left', - [DeviceBreakpointsType.TABLET]: 'right', - [DeviceBreakpointsType.MOBILE]: 'right', - }, - }, - }, - { - id: 'transferMoney', - label: 'Transfer money', - config: { alignHeader: 'center', alignValue: 'center' }, - value: (): string | JSX.Element => ( - - ), - }, - { - id: 'edit', - label: 'Edit', - config: { alignHeader: 'center', alignValue: 'center' }, - value: (): string | JSX.Element => ( - null} - /> - ), - }, - { - id: 'delete', - label: 'Delete', - config: { alignHeader: 'center' }, - value: (): string | JSX.Element => ( - null} - /> - ), - }, - ], - values: [ - { - date: '18 DIC', - name: 'Michael', - surname: 'Scott', - routingNumber: '123456789', - accountNumber: '****999999', - transactionNumber: '0000', - }, - { - date: '18 DIC', - name: 'Pam', - surname: 'Beasley', - routingNumber: '123456789', - accountNumber: '****999999', - transactionNumber: '0000', - accordionIconExpandedAriaLabel: 'Collapse content', - accordionIconCollapsedAriaLabel: 'Expand content', - expandedContent: { - [DeviceBreakpointsType.DESKTOP]: ( -
-
- Routing number: 123456789-DESKTOP-EXPANDED-CONTENT -
-
- Account number: 98765 -
-
- Transaction number: 00000 -
-
- ), - [DeviceBreakpointsType.TABLET]: ( -
-
- Routing number: 123456789-TABLET-EXPANDED-CONTENT -
-
- Account number: 98765 -
-
- Transaction number: 00000 -
-
- ), - - [DeviceBreakpointsType.MOBILE]: ( -
-
- Routing number: 123456789-MOBILE-EXPANDED-CONTENT -
-
- Account number: 98765 -
-
- Transaction number: 00000 -
-
- ), - }, - rowVariant: 'DEFAULT', - }, - { - date: '19 DIC', - name: 'Dwight', - routingNumber: '987654321', - accountNumber: '****333333', - rowVariant: 'DEFAULT', - }, - { - date: '21 DIC', - name: 'Jim', - routingNumber: '987654321', - accountNumber: '****444444', - rowVariant: 'DEFAULT', - }, - ], - accordionIcon: { icon: ICONS.ICON_PLACEHOLDER }, - footer: { - content: ( -
- -
- ), - }, -}; - -export const mockTableWithDivider = { - headers: [ - { - id: 'date', - label: 'Date', - config: { hasDivider: true }, - }, - { - id: 'name', - label: 'Recipient name', - config: { - alignHeader: 'left', - alignValue: 'left', - hidden: { - [DeviceBreakpointsType.LARGE_DESKTOP]: false, - [DeviceBreakpointsType.DESKTOP]: false, - [DeviceBreakpointsType.TABLET]: true, - [DeviceBreakpointsType.MOBILE]: true, - }, - }, - value: (value): JSX.Element => - value.surname ? ( -
-
{value.name}
-
{value.surname}
-
- ) : ( - value.name - ), - }, - { - id: 'routingNumber', - label: 'Routing number', - config: { alignHeader: 'left', alignValue: 'left' }, - }, - { - id: 'accountNumber', - label: 'Account number', - config: { alignHeader: 'left', alignValue: 'left' }, - }, - { - id: 'transferMoney', - label: 'Transfer money', - config: { alignHeader: 'center', alignValue: 'center' }, - value: (): JSX.Element => ( - - ), - }, - { - id: 'edit', - label: 'Edit', - config: { alignHeader: 'center', alignValue: 'center' }, - value: (): JSX.Element => ( - null} - /> - ), - }, - { - id: 'delete', - label: 'Delete', - config: { alignHeader: 'center', alignValue: 'center' }, - value: (): JSX.Element => ( - null} - /> - ), - }, - ], - values: [ - { - date: '18 DIC', - name: 'Michael', - surname: 'Scott', - routingNumber: '123456789', - accountNumber: '****999999', - transactionNumber: '0000', - dividerContent: { - leftLabel: { content: '18 DIC Lorem ipsum' }, - icon: { icon: 'UNICORN', altText: 'Icon alt text' }, - tooltip: { - title: { content: 'Tooltip title' }, - content: { content: 'Tooltip content' }, - }, - }, - }, - { - date: '18 DIC', - name: 'Pam', - surname: 'Beasley', - routingNumber: '123456789', - accountNumber: '****999999', - transactionNumber: '0000', - dividerContent: { - leftLabel: { content: '18 DIC Lorem ipsum' }, - icon: { icon: 'UNICORN', altText: 'Icon alt text' }, - tooltip: { - title: { content: 'Tooltip title' }, - content: { content: 'Tooltip content' }, - }, - }, - }, - { - date: '19 DIC', - name: 'Dwight', - routingNumber: '987654321', - accountNumber: '****333333', - dividerContent: { - leftLabel: { content: '19 DIC Lorem ipsum' }, - icon: { icon: 'UNICORN', altText: 'Icon alt text' }, - tooltip: { - title: { content: 'Tooltip title' }, - content: { content: 'Tooltip content' }, - }, - }, - }, - ], -}; - -export const mockBasicTable = { - headers: [ - { - id: 'date', - label: 'Date', - config: { alignHeader: 'center', alignValue: 'center' }, - }, - { - id: 'name', - label: 'Recipient name', - config: { - alignHeader: 'left', - alignValue: 'left', - hidden: { - [DeviceBreakpointsType.LARGE_DESKTOP]: false, - [DeviceBreakpointsType.DESKTOP]: false, - [DeviceBreakpointsType.TABLET]: false, - [DeviceBreakpointsType.MOBILE]: false, - }, - }, - value: (value): string | JSX.Element => - value.surname ? ( -
-
{value.name}
-
{value.surname}
-
- ) : ( - value.name - ), - }, - { - id: 'routingNumber', - label: 'Routing number', - config: { alignHeader: 'center', alignValue: 'center' }, - }, - { - id: 'accountNumber', - label: 'Account number', - config: { alignHeader: 'center', alignValue: 'center' }, - }, - { - id: 'transferMoney', - label: 'Transfer money', - config: { alignHeader: 'center', alignValue: 'center' }, - value: (): JSX.Element => ( - - ), - }, - { - id: 'edit', - label: 'Edit', - config: { alignHeader: 'center', alignValue: 'center' }, - value: (): JSX.Element => ( - null} - /> - ), - }, - { - id: 'delete', - label: 'Delete', - config: { alignHeader: 'center', alignValue: 'center' }, - value: (): JSX.Element => ( - null} - /> - ), - }, - ], - values: [ - { - date: '18 DIC', - name: 'Pam', - surname: 'Beasley', - routingNumber: '123456789', - accountNumber: '****999999', - transactionNumber: '0000', - }, - { - date: '18 DIC', - name: 'Dwight', - surname: 'Schrute', - routingNumber: '123456789', - accountNumber: '****999999', - transactionNumber: '0000', - }, - { - date: '19 DIC', - name: 'Jim', - surname: 'Halpert', - routingNumber: '987654321', - accountNumber: '****333333', - }, - ], -}; - -export const mockCustomizableTable = { - headerVariant: 'CUSTOMIZABLE_HEADER', - hiddenHeaderOn: { - [DeviceBreakpointsType.LARGE_DESKTOP]: false, - [DeviceBreakpointsType.DESKTOP]: false, - [DeviceBreakpointsType.TABLET]: true, - [DeviceBreakpointsType.MOBILE]: true, - }, - formatList: { - [DeviceBreakpointsType.TABLET]: true, - [DeviceBreakpointsType.MOBILE]: true, - }, - formatListHeaderPriority: FormatListHeaderPriorityType.ROW_HEADER, - headers: [ - { - id: 'date', - label: 'Date', - config: { alignHeader: 'center', alignValue: 'center' }, - }, - { - id: 'name', - label: 'Recipient name', - config: { - alignHeader: 'left', - alignValue: 'left', - // width: '100px', - }, - value: (value): string | JSX.Element => - value.surname ? ( -
-
{value.name.value ?? value.name}
-
{value.surname}
-
- ) : ( - (value.name.value ?? value.name) - ), - }, - { - id: 'routingNumber', - label: 'Routing number', - config: { alignHeader: 'center', alignValue: 'center' }, - variant: 'PRIMARY', - }, - { - id: 'accountNumber', - label: 'Account number', - config: { alignHeader: 'center', alignValue: 'center', backgroundColor: 'red' }, - }, - { - id: 'transferMoney', - label: 'Transfer money', - config: { alignHeader: 'center', alignValue: 'center' }, - value: (): JSX.Element => ( - - ), - }, - { - id: 'edit', - label: 'Edit', - config: { alignHeader: 'center', alignValue: 'center' }, - value: (): JSX.Element => ( - null} - /> - ), - }, - { - id: 'delete', - label: 'Delete', - config: { alignHeader: 'center', alignValue: 'center' }, - value: (): JSX.Element => ( - null} - /> - ), - }, - ], - values: [ - { - date: '18 DIC', - name: { value: 'Kevin' }, - surname: 'Malone', - routingNumber: '123456789', - accountNumber: '****999999', - transactionNumber: '0000', - rowVariant: 'CUSTOMIZABLE_ROW', - rowHeader: { - label: 'Row header 1', - variant: 'CUSTOMIZABLE_HEADER', - // config: { - // alignHeader: 'center', - // alignValue: 'center', - // width: '100px', - // backgroundColor: 'green', - // }, - }, - }, - { - date: '18 DIC', - name: { value: 'Dwight', backgroundColor: 'green', align: 'right' }, - surname: 'Malone', - routingNumber: '123456789', - accountNumber: '****999999', - transactionNumber: '0000', - rowVariant: 'CUSTOMIZABLE_ROW', - backgroundColor: 'aqua', - rowHeader: { - label: 'Row header 1', - variant: 'CUSTOMIZABLE_HEADER', - }, - }, - { - date: '19 DIC', - name: 'Marta', - routingNumber: '987654321', - accountNumber: '****333333', - rowVariant: 'CUSTOMIZABLE_ROW', - rowHeader: { - label: 'Row header 1', - variant: 'CUSTOMIZABLE_HEADER', - }, - }, - { - date: '19 DIC', - name: 'John', - routingNumber: '9876234234', - accountNumber: '****5234234', - rowVariant: 'CUSTOMIZABLE_ROW', - rowHeader: { - label: 'Row header 1', - variant: 'CUSTOMIZABLE_HEADER', - }, - }, - { - date: '19 DIC', - name: 'Jes', - routingNumber: '9876132224', - accountNumber: '****5986756', - rowVariant: 'CUSTOMIZABLE_ROW', - delete: { backgroundColor: 'pink' }, - rowHeader: { - label: 'Row header 1', - variant: 'CUSTOMIZABLE_HEADER', - }, - }, - ], -}; diff --git a/src/components/tableV2/__tests__/hooks/useTableStickyColumns.test.ts b/src/components/tableV2/__tests__/hooks/useTableStickyColumns.test.ts deleted file mode 100644 index 17f82d9b..00000000 --- a/src/components/tableV2/__tests__/hooks/useTableStickyColumns.test.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { renderHook } from '@testing-library/react-hooks'; - -import { useTableStickyColumns } from '../../hooks/useTableStickyColumns'; - -describe('useTableStickyColumns', () => { - let wrapper: HTMLDivElement; - let scrollableContainer: HTMLDivElement; - let table: HTMLTableElement; - let tableHead: HTMLTableSectionElement; - let tableRow: HTMLTableRowElement; - let tableCell: HTMLTableCellElement; - let rightBoxShadowContainer: HTMLDivElement; - let ref: React.RefObject; - - beforeAll(() => { - global.ResizeObserver = class ResizeObserver { - callback; - constructor(callback) { - this.callback = callback; - } - observe() { - // Call the callback - this.callback(); - } - unobserve() { - // do nothing - } - disconnect() { - // do nothing - } - }; - }); - - beforeEach(() => { - wrapper = document.createElement('div'); - scrollableContainer = document.createElement('div'); - scrollableContainer.setAttribute('data-table-scrollable-container', ''); - scrollableContainer.scroll = jest.fn().mockImplementation((x, y) => { - scrollableContainer.scrollTop = y; - scrollableContainer.scrollLeft = x; - scrollableContainer.dispatchEvent(new Event('scroll')); - }); - table = document.createElement('table'); - tableHead = document.createElement('thead'); - tableHead.setAttribute('data-table-head', ''); - tableRow = document.createElement('tr'); - tableRow.setAttribute('data-table-row', ''); - tableCell = document.createElement('td'); - tableCell.setAttribute('data-sticky', ''); - rightBoxShadowContainer = document.createElement('div'); - rightBoxShadowContainer.setAttribute('data-table-right-shadow', ''); - - tableRow.appendChild(tableCell); - tableHead.appendChild(tableRow); - table.appendChild(tableHead); - scrollableContainer.appendChild(table); - wrapper.appendChild(scrollableContainer); - wrapper.appendChild(rightBoxShadowContainer); - document.body.appendChild(wrapper); - ref = { current: wrapper }; - }); - - afterEach(() => { - document.body.removeChild(wrapper); - }); - - it('should do nothing when disabled is true', () => { - renderHook(() => useTableStickyColumns({ ref, disabled: true })); - expect(tableCell.style.right).toBe(''); - }); - - it('should do nothing when there is not scrollable container', () => { - const wrapperWithoutScrollableContainer = { current: document.createElement('div') }; - renderHook(() => useTableStickyColumns({ ref: wrapperWithoutScrollableContainer })); - expect(tableCell.style.right).toBe(''); - }); - - it('When there is horizontal scroll, right style for sticky columns should be set', () => { - // Simulate horizontal scroll - Object.defineProperty(scrollableContainer, 'scrollWidth', { - value: 200, - }); - Object.defineProperty(scrollableContainer, 'clientWidth', { - value: 100, - }); - renderHook(() => useTableStickyColumns({ ref })); - expect(tableCell.style.right).toBe('0px'); - }); - - it('When there is not horizontal scroll, right style for sticky columns should not be set', () => { - // Simulate not horizontal scroll - Object.defineProperty(scrollableContainer, 'scrollWidth', { - value: 100, - }); - Object.defineProperty(scrollableContainer, 'clientWidth', { - value: 200, - }); - renderHook(() => useTableStickyColumns({ ref })); - expect(tableCell.style.right).toBe(''); - }); - - it('When the right position of the sticky columns have been set, the rightBoxShadowContainer right position is set, bearing in mind th scrollbar size too', () => { - // Simulate horizontal scroll - Object.defineProperty(scrollableContainer, 'scrollWidth', { - value: 200, - }); - Object.defineProperty(scrollableContainer, 'clientWidth', { - value: 100, - }); - // Simulate vertical scroll - Object.defineProperty(scrollableContainer, 'scrollHeight', { - value: 200, - }); - Object.defineProperty(scrollableContainer, 'clientHeight', { - value: 100, - }); - Object.defineProperty(scrollableContainer, 'offsetWidth', { - value: 105, - }); - renderHook(() => useTableStickyColumns({ ref })); - expect(rightBoxShadowContainer.style.right).toBe('5px'); - }); -}); diff --git a/src/components/tableV2/hooks/useTableStickyColumns.ts b/src/components/tableV2/hooks/useTableStickyColumns.ts deleted file mode 100644 index 7da8bab3..00000000 --- a/src/components/tableV2/hooks/useTableStickyColumns.ts +++ /dev/null @@ -1,63 +0,0 @@ -import * as React from 'react'; - -import { hasHorizontalScroll, hasVerticalScroll } from '@/utils'; - -type UseTableStickyColumnsParamsType = { - ref: React.RefObject; - disabled?: boolean; -}; - -type UseTableStickyColumnsReturnType = object; - -export const useTableStickyColumns = ({ - ref, - disabled = false, -}: UseTableStickyColumnsParamsType): UseTableStickyColumnsReturnType => { - React.useEffect(() => { - const scrollableContainer = ref.current?.querySelector('[data-table-scrollable-container]'); - const rightBoxShadowContainer = ref.current?.querySelector('[data-table-right-shadow]'); - let resizeObserver: ResizeObserver; - if (scrollableContainer instanceof HTMLElement && !disabled) { - const handleElementResize = (element: HTMLElement) => { - const _hasHorizontalScroll = hasHorizontalScroll(element); - // For each row, set the right position of its sticky cells - const rows = element.querySelectorAll('[data-table-row]'); - // used to calc rightBoxShadowContainer position - let maxStickyWidth = 0; - rows.forEach(row => { - // Retrieve all the cell with sticky attribute - const stickyCells = Array.from(row.querySelectorAll('[data-sticky]')).reverse(); - let right = 0; - stickyCells.forEach(stickyCell => { - if (stickyCell instanceof HTMLElement) { - stickyCell.style.right = _hasHorizontalScroll ? `${right}px` : 'auto'; - right += stickyCell.offsetWidth; - } - }); - maxStickyWidth = Math.max(maxStickyWidth, right); - }); - // Once the sticky position have been added, set the position of the rightBoxShadowContainer - // This position should be the same as the max width of the sticky cells - if (rightBoxShadowContainer instanceof HTMLElement) { - // If vertical scroll, add scroll bar size to maxStickyWidth - const _hasVerticalScroll = hasVerticalScroll(element); - if (_hasVerticalScroll) { - const scrollBarSize = element.offsetWidth - element.clientWidth; - maxStickyWidth += scrollBarSize; - } - rightBoxShadowContainer.style.right = `${maxStickyWidth}px`; - } - }; - handleElementResize(scrollableContainer); - resizeObserver = new ResizeObserver(() => { - handleElementResize(scrollableContainer); - }); - resizeObserver.observe(scrollableContainer); - } - return () => { - resizeObserver?.disconnect(); - }; - }, [disabled]); - - return {}; -}; diff --git a/src/designSystem/kubitWireframe/commons/components/loader/index.ts b/src/designSystem/kubitWireframe/commons/components/loader/index.ts deleted file mode 100644 index 0540595d..00000000 --- a/src/designSystem/kubitWireframe/commons/components/loader/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './styles'; -export * from './variants'; diff --git a/src/designSystem/kubitWireframe/commons/components/loader/styles.ts b/src/designSystem/kubitWireframe/commons/components/loader/styles.ts deleted file mode 100644 index bfe75d48..00000000 --- a/src/designSystem/kubitWireframe/commons/components/loader/styles.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { LoaderStylesType } from '@/components/loader/types'; - -import { buildPrimaryLoader } from '../../assets/animations'; -import { SIZES } from '../../foundations'; -import { LoaderVariantType } from './variants'; - -const container = { - display: 'inline-block', - width: SIZES.size_200, - height: SIZES.size_200, -}; -const transparent = 'transparent'; - -export const getLoaderStyles = (COLORS: { - [key: string]: { [key: string]: string }; -}): LoaderStylesType => { - return { - [LoaderVariantType.PRIMARY_WHITE]: { - container, - animation: buildPrimaryLoader(COLORS.NEUTRAL.color_neutral_icon_250, transparent), - }, - [LoaderVariantType.PRIMARY_RED]: { - container, - animation: buildPrimaryLoader(COLORS.SECONDARY.color_secondary_icon_100, transparent), - }, - [LoaderVariantType.PRIMARY_BLACK]: { - container, - animation: buildPrimaryLoader(COLORS.NEUTRAL.color_neutral_icon_50, transparent), - }, - }; -}; diff --git a/src/designSystem/kubitWireframe/commons/components/loader/variants.ts b/src/designSystem/kubitWireframe/commons/components/loader/variants.ts deleted file mode 100644 index cccdef15..00000000 --- a/src/designSystem/kubitWireframe/commons/components/loader/variants.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum LoaderVariantType { - PRIMARY_WHITE = 'PRIMARY_WHITE', - PRIMARY_RED = 'PRIMARY_RED', - PRIMARY_BLACK = 'PRIMARY_BLACK', -} diff --git a/src/storybook/components/colors/colors.styled.ts b/src/storybook/components/colors/colors.styled.ts deleted file mode 100644 index 4b29e081..00000000 --- a/src/storybook/components/colors/colors.styled.ts +++ /dev/null @@ -1,53 +0,0 @@ -import styled from 'styled-components'; - -export const ColorsStyled = styled.div` - padding: 20px 50px; -`; - -export const ColorsTitleStyled = styled.h2` - font-size: 32px; - font-style: normal; - font-weight: 500; - line-height: 40px; -`; - -export const ColorsContainerStyled = styled.div` - margin-top: 30px; - display: flex; - flex-direction: column; - gap: 2rem; -`; - -export const ColorsColorDataStyled = styled.div` - display: flex; - justify-content: space-between; - border-bottom: 1px solid #d7d7d7; - padding-bottom: 20px; - padding-left: 20px; - padding-right: 20px; - align-items: center; -`; - -export const ColorsCircleNameStyled = styled.div` - display: flex; - gap: 50px; - align-items: center; -`; - -export const ColorsCicleStyled = styled.div` - width: 40px; - height: 40px; - border-radius: 100%; - box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.1); -`; - -export const ColorsNameStyled = styled.div` - display: flex; - flex-direction: column; - gap: 5px; -`; - -export const ColorsHexNameStyled = styled.div` - display: flex; - flex-direction: column; -`; diff --git a/src/storybook/provider/utilsProvider/utilsProvider.tsx b/src/storybook/provider/utilsProvider/utilsProvider.tsx deleted file mode 100644 index 6d646947..00000000 --- a/src/storybook/provider/utilsProvider/utilsProvider.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; - -import { UtilsProvider } from '@/provider'; -import { DateFormatOptions } from '@/provider/utils/types'; -import { FormatWeekdayOptionType } from '@/types'; -import { - formatDate, - getAddDays, - getAddMonths, - getAddYears, - getAllMonthNames, - getAllWeekdayNames, - getSubDays, - getSubMonths, - getSubYears, - isAfter, - isBefore, - isDatesEqual, - transformDate, -} from '@/utils/date'; - -export const UtilsProviderDocStorybook = ({ - children, - theme, -}: { - children: JSX.Element; - theme: string; -}): JSX.Element => { - return ( - { - return isAfter(date1, date2); - }, - isBefore: (date1: Date, date2: Date) => { - return isBefore(date1, date2); - }, - isDatesEqual: (firsDate: string | number | Date, secondDate: string | number | Date) => { - return isDatesEqual(firsDate, secondDate); - }, - getAddDays: (date: Date, days: number) => { - return getAddDays(date, days); - }, - getAddMonths: (date: Date, months: number) => { - return getAddMonths(date, months); - }, - getAddYears: (date: Date, years: number) => { - return getAddYears(date, years); - }, - getSubDays: (date: Date, days: number) => { - return getSubDays(date, days); - }, - getSubMonths: (date: Date, months: number) => { - return getSubMonths(date, months); - }, - getSubYears: (date: Date, years: number) => { - return getSubYears(date, years); - }, - getAllMonthName: () => { - return getAllMonthNames(); - }, - getAllWeekdayName: (weekdayFormat: FormatWeekdayOptionType, isSundayFirst: boolean) => { - return getAllWeekdayNames(weekdayFormat, isSundayFirst); - }, - }} - formatDate={(date: Date, format: string | DateFormatOptions) => { - return formatDate(date, format); - }} - transformDate={(date: string | number, format: string | undefined) => { - return transformDate(date, format); - }} - > - {children} - - ); -}; diff --git a/src/utils/date/__tests__/format.test.ts b/src/utils/date/__tests__/format.test.ts deleted file mode 100644 index fe5e82f5..00000000 --- a/src/utils/date/__tests__/format.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { formatDate } from '../format'; -import { locale } from '../locale'; -import { FORMAT_DATE } from '../types'; - -it('Format D - Spanish', () => { - locale.setLocale('es'); - const today = new Date('2022-12-09'); - const date = formatDate(today, FORMAT_DATE.d); - expect(date).toBe('09/12/2022'); -}); - -it('Format D - Korean', () => { - locale.setLocale('ko-KR'); - const today = new Date('2022-12-09'); - const date = formatDate(today, FORMAT_DATE.d); - expect(date).toBe('2022. 12. 09.'); -}); - -it('Format DD - French', () => { - locale.setLocale('fr'); - const today = new Date('2022-12-09'); - const date = formatDate(today, FORMAT_DATE.dd); - expect(date).toContain('déc.'); -}); - -it('Format DD - English', () => { - locale.setLocale('en'); - const today = new Date('2022-12-09'); - const date = formatDate(today, FORMAT_DATE.dd); - expect(date).toContain('Dec'); -}); - -it('Format Custom string - English', () => { - locale.setLocale('en'); - const today = new Date('2022-12-09'); - const date = formatDate(today, 'WW, MMMM DD, YYYY'); - - expect(date).toBe('Friday, December 09, 2022'); -}); - -it('Format Custom string - Spanish', () => { - locale.setLocale('es'); - const today = new Date('2022-12-09'); - const date = formatDate(today, 'WW, DD [de] MMMM [de] YYYY'); - const date2 = formatDate(today, 'W, DD [de] MMM [de] YYYY'); - - expect(date).toBe('viernes, 09 de diciembre de 2022'); - expect(date2).toBe('vie, 09 de dic de 2022'); -}); - -it('Format Custom string - short', () => { - const today = new Date('2022-12-09'); - const date = formatDate(today, 'D-M-YY'); - const date2 = formatDate(today, 'DD-MM-YYYY'); - expect(date).toBe('9-12-22'); - expect(date2).toBe('09-12-2022'); -}); - -it('Format Custom string - with hours', () => { - const today = new Date('2022-12-09 09:30:01'); - const date = formatDate(today, 'DD-MM-YYYY H:m:s'); - const date2 = formatDate(today, 'DD-MM-YYYY HH:mm:ss'); - expect(date).toBe('09-12-2022 9:30:1'); - expect(date2).toBe('09-12-2022 09:30:01'); -}); - -it('Format Custom options - English', () => { - locale.setLocale('en'); - const today = new Date('2022-12-09'); - const date = formatDate(today, { - year: 'numeric', - month: 'long', - day: 'numeric', - weekday: 'long', - }); - - expect(date).toBe('Friday, December 9, 2022'); -}); diff --git a/src/utils/date/format.ts b/src/utils/date/format.ts deleted file mode 100644 index c34003c5..00000000 --- a/src/utils/date/format.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { FORMATTING_TOKENS, MACRO_TOKEN_TO_FORMAT_OPTS } from './constants'; -import { locale } from './locale'; -import { DateFormatOptions, FORMAT_DATE } from './types/format.types'; - -const addZero = (value: number) => `${value < 10 ? '0' : ''}${value}`; - -export const formatDate = ( - date: Date, - format: DateFormatOptions | FORMAT_DATE | string -): string => { - const formatAux = typeof format === 'string' ? MACRO_TOKEN_TO_FORMAT_OPTS[format] : format; - - if (typeof format === 'string' && !formatAux) { - const formatParts = format.match(FORMATTING_TOKENS) || []; - const matches: { [key: string]: [string, (value: Date) => string] } = { - YY: ['year', (value: Date) => `${new Date(value).getFullYear()}`.slice(-2)], - YYYY: ['year', (value: Date) => `${new Date(value).getFullYear()}`], - M: ['month', (value: Date) => `${new Date(value).getMonth() + 1}`], - MM: ['month', (value: Date) => `${addZero(new Date(value).getMonth() + 1)}`], - MMM: [ - 'month', - (value: Date) => new Date(value).toLocaleString(locale.getLocale(), { month: 'short' }), - ], - MMMM: [ - 'month', - (value: Date) => new Date(value).toLocaleString(locale.getLocale(), { month: 'long' }), - ], - D: ['day', (value: Date) => `${new Date(value).getDate()}`], - DD: ['day', (value: Date) => `${addZero(new Date(value).getDate())}`], - W: [ - 'weekday', - (value: Date) => new Date(value).toLocaleString(locale.getLocale(), { weekday: 'short' }), - ], - WW: [ - 'weekday', - (value: Date) => new Date(value).toLocaleString(locale.getLocale(), { weekday: 'long' }), - ], - H: ['hour', (value: Date) => `${new Date(value).getHours()}`], - HH: ['hour', (value: Date) => `${addZero(new Date(value).getHours())}`], - m: ['minute', (value: Date) => `${new Date(value).getMinutes()}`], - mm: ['minute', (value: Date) => `${addZero(new Date(value).getMinutes())}`], - s: ['second', (value: Date) => `${new Date(value).getSeconds()}`], - ss: ['second', (value: Date) => `${addZero(new Date(value).getSeconds())}`], - }; - const values = formatParts.map((token: string) => { - const parseTo = matches[token]; - const key = parseTo && parseTo[0]; - const value = parseTo && parseTo[1](date); - return key ? value : token; - }); - - return values.join('').replace(/[\\[\]]/g, ''); - } - - return date.toLocaleString(locale.getLocale(), { - ...formatAux, - }); -}; From 4bc081ac1d533c04357cd35253358a564b90d15c Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:55:40 +0200 Subject: [PATCH 17/27] Change interface on IconHighleted --- src/components/iconHighlighted/types/iconHighlighted.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/iconHighlighted/types/iconHighlighted.ts b/src/components/iconHighlighted/types/iconHighlighted.ts index c5b9b183..7bb96103 100644 --- a/src/components/iconHighlighted/types/iconHighlighted.ts +++ b/src/components/iconHighlighted/types/iconHighlighted.ts @@ -12,7 +12,6 @@ export interface IIconHighlightedStyled { export interface IIconHighlightedStandAlone extends IElementOrIcon, IIconHighlightedStyled { size: IconHighlightedSizeType; - icon?: string; type?: IconHighlightedType; } From 9bb190a90083195b8ec713c46f44575b57863364 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:56:45 +0200 Subject: [PATCH 18/27] Add new toles and arias to Text component --- src/components/text/types/component.ts | 1 + src/components/text/types/text.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/text/types/component.ts b/src/components/text/types/component.ts index d70d4478..042f468f 100644 --- a/src/components/text/types/component.ts +++ b/src/components/text/types/component.ts @@ -27,4 +27,5 @@ export enum TextComponentType { DL = 'dl', DT = 'dt', DD = 'dd', + DIV = 'div', } diff --git a/src/components/text/types/text.ts b/src/components/text/types/text.ts index 5b823b60..435390d7 100644 --- a/src/components/text/types/text.ts +++ b/src/components/text/types/text.ts @@ -20,7 +20,7 @@ export interface ITextStyled { type TextAriaAttributes = Pick< React.AriaAttributes, - 'aria-label' | 'aria-labelledby' | 'aria-describedby' | 'aria-hidden' + 'aria-label' | 'aria-labelledby' | 'aria-describedby' | 'aria-hidden' | 'aria-level' >; export interface ITextStandAlone extends ITextStyled, TextAriaAttributes { From 4c6000aa3df99a17365069ba21ec78f6670cd159 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:57:00 +0200 Subject: [PATCH 19/27] Improve quickbutton roles props --- .../__tests__/quickButton.test.tsx | 20 +++++++++++++++++++ .../quickButton/quickButtonStandAlone.tsx | 1 + .../quickButton/types/quickButton.ts | 1 + 3 files changed, 22 insertions(+) diff --git a/src/components/quickButton/__tests__/quickButton.test.tsx b/src/components/quickButton/__tests__/quickButton.test.tsx index df856de2..7ac369d6 100644 --- a/src/components/quickButton/__tests__/quickButton.test.tsx +++ b/src/components/quickButton/__tests__/quickButton.test.tsx @@ -34,6 +34,26 @@ describe('QuickButton component', () => { expect(container).toHTMLValidate(); expect(results).toHaveNoViolations(); }); + it('Should have a correct html structure, when a role us provided', async () => { + const { container } = renderProvider( + + ); + + const quickButton = screen.getByRole('link'); + const label = screen.getByText('label'); + + expect(quickButton).toBeDefined(); + expect(label).toBeDefined(); + + const results = await axe(container); + // This is because the role is conditional + expect(container).toHTMLValidate({ + rules: { + 'prefer-native-element': 'off', + }, + }); + expect(results).toHaveNoViolations(); + }); it('Variant is optional', async () => { const { container } = renderProvider(); diff --git a/src/components/quickButton/quickButtonStandAlone.tsx b/src/components/quickButton/quickButtonStandAlone.tsx index bae1e233..bdc64f64 100644 --- a/src/components/quickButton/quickButtonStandAlone.tsx +++ b/src/components/quickButton/quickButtonStandAlone.tsx @@ -17,6 +17,7 @@ const QuickButtonStandAloneComponent = ( data-testid={props.dataTestId} disabled={props.state === QuickButtonState.DISABLED} id={props.buttonId} + role={props.role} styles={props.styles} type={props.type} onClick={props.onClick} diff --git a/src/components/quickButton/types/quickButton.ts b/src/components/quickButton/types/quickButton.ts index 649e82b4..5b7dd3eb 100644 --- a/src/components/quickButton/types/quickButton.ts +++ b/src/components/quickButton/types/quickButton.ts @@ -26,6 +26,7 @@ export interface IQuickButtonStandAlone { dataTestId?: string; type?: ButtonType; ['aria-label']?: string; + role?: string; } /** From 20a45b5aad35ae5fba6ac32eade1aaea5242ef35 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:57:30 +0200 Subject: [PATCH 20/27] Improve title prop to message component --- .../message/__tests__/message.test.tsx | 2 + src/components/message/messageStandAlone.tsx | 38 +++++++++++-------- src/components/message/types/message.ts | 2 +- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/components/message/__tests__/message.test.tsx b/src/components/message/__tests__/message.test.tsx index 62b43530..c50567b5 100644 --- a/src/components/message/__tests__/message.test.tsx +++ b/src/components/message/__tests__/message.test.tsx @@ -75,6 +75,7 @@ describe('Message component', () => { }); it('Should render tag and extra action', async () => { + const title = { content:
Title React Node
}; const { container } = renderProvider( { status: 'NORMAL', content: 'Tag', }} + title={title} /> ); diff --git a/src/components/message/messageStandAlone.tsx b/src/components/message/messageStandAlone.tsx index e349027d..5e23da81 100644 --- a/src/components/message/messageStandAlone.tsx +++ b/src/components/message/messageStandAlone.tsx @@ -88,6 +88,27 @@ const MessageStandAloneComponent = ( ); }; + const buildTitle = () => { + return typeof props.title?.content === 'string' ? ( + + + {props.title.content} + + + ) : ( + props.title?.content + ); + }; + return ( {props.open && ( @@ -118,22 +139,7 @@ const MessageStandAloneComponent = ( withIcon={!!props.closeIcon} onClick={props.titleAndContentContainerProps?.onClick} > - {props.title && ( - - - {props.title.content} - - - )} + {props.title && buildTitle()} {props.tag?.content && buildTag()} {buildContent()} diff --git a/src/components/message/types/message.ts b/src/components/message/types/message.ts index 641ebadd..2f97b7b6 100644 --- a/src/components/message/types/message.ts +++ b/src/components/message/types/message.ts @@ -25,7 +25,7 @@ export type MessageContentType = Omit, 'children'> & { }; export type MessageTitleType = Omit, 'children'> & { - content: string; + content: React.ReactNode; }; export type MessageTagType = Omit & { From d6b6d9cd39f420ae3efd74270ec73b4547cb1765 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:57:54 +0200 Subject: [PATCH 21/27] Include optionChecked icon and error props to InputSearch --- src/components/inputSearch/components/optionsList.tsx | 1 + .../inputSearch/components/popoverSearchList.tsx | 2 ++ src/components/inputSearch/inputSearch.tsx | 1 + src/components/inputSearch/inputSearchStandAlone.tsx | 6 +++++- src/components/inputSearch/types/inputSearch.ts | 8 +++++++- 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/inputSearch/components/optionsList.tsx b/src/components/inputSearch/components/optionsList.tsx index f6e8004f..8d07fea8 100644 --- a/src/components/inputSearch/components/optionsList.tsx +++ b/src/components/inputSearch/components/optionsList.tsx @@ -48,6 +48,7 @@ export const OptionsListComponent = ( ref={sendRef} caseSensitive={props.caseSensitive} charsHighlighted={props.searchText?.toString()} + checkedIcon={props.optionCheckedIcon} dataTestId={props.dataTestId} hightlightedOptionVariant={props.stylesListOption?.hightlightedOptionVariant} id={props['aria-controls']} diff --git a/src/components/inputSearch/components/popoverSearchList.tsx b/src/components/inputSearch/components/popoverSearchList.tsx index 05140bf0..2037136b 100644 --- a/src/components/inputSearch/components/popoverSearchList.tsx +++ b/src/components/inputSearch/components/popoverSearchList.tsx @@ -76,6 +76,7 @@ export const PopoverSearchListComponent = ( loader={props.loader} loading={props.loading} loadingText={props.loadingText} + optionCheckedIcon={props.optionCheckedIcon} optionVariant={section?.optionVariant} options={section?.options || []} searchText={labelInResultTextWrittenByUser} @@ -165,6 +166,7 @@ export const PopoverSearchListComponent = ( variant={props.styles?.[props.state]?.popoverVariant?.[props.device]} onCloseInternally={() => { props.onOpenOptions(false); + refInput?.current?.focus(); }} > {useActionBottomSheet ? ( diff --git a/src/components/inputSearch/inputSearch.tsx b/src/components/inputSearch/inputSearch.tsx index b557771a..f542d83d 100644 --- a/src/components/inputSearch/inputSearch.tsx +++ b/src/components/inputSearch/inputSearch.tsx @@ -117,6 +117,7 @@ const InputSearchComponent = React.forwardRef( blockBackPopover={blockBackPopover} caseSensitive={caseSensitive} device={device} + error={error} hasHighlightedOption={showHighlightedOption} hasResultTextWrittenByUser={hasResultTextWrittenByUser} highlightedOption={highlightedOption} diff --git a/src/components/inputSearch/inputSearchStandAlone.tsx b/src/components/inputSearch/inputSearchStandAlone.tsx index 4948135b..353dde97 100644 --- a/src/components/inputSearch/inputSearchStandAlone.tsx +++ b/src/components/inputSearch/inputSearchStandAlone.tsx @@ -15,7 +15,7 @@ import { InputSearchStyled } from './inputSearch.styled'; import { IInputSearchStandAlone } from './types'; export const InputSearchStandAloneComponent = ( - props: IInputSearchStandAlone, + { error, ...props }: IInputSearchStandAlone, ref: ForwardedRef ): JSX.Element => { const inputWrapperRef = useRef(null); @@ -80,6 +80,9 @@ export const InputSearchStandAloneComponent = ( overrideStyles: props.styles, placeholder: props.placeholder, value: props.inputPopoverValue, + error: error, + errorIcon: props.errorIcon, + errorMessage: props.errorMessage, // The variant is the same for all the states variant: props.inputPopoverVariant ?? (props.styles?.[props.state]?.inputVariant as string), @@ -93,6 +96,7 @@ export const InputSearchStandAloneComponent = ( loadingText={props.loadingText} noResultsText={props.noResultsText} open={props.open} + optionCheckedIcon={props.optionCheckedIcon} optionList={props.optionList} optionsListDefaultArias={props.optionsListDefaultArias} preventCloseOnClickElements={[ diff --git a/src/components/inputSearch/types/inputSearch.ts b/src/components/inputSearch/types/inputSearch.ts index a4e982dc..074cba34 100644 --- a/src/components/inputSearch/types/inputSearch.ts +++ b/src/components/inputSearch/types/inputSearch.ts @@ -58,6 +58,9 @@ export type PopoverSearchInputType = { placeholder?: string; value?: string; variant?: string; + error?: boolean; + errorIcon?: IElementOrIcon; + errorMessage?: InputSearchLoadingTextType; onChange?: React.ChangeEventHandler; onKeyDown?: React.KeyboardEventHandler; onIconClick?: React.MouseEventHandler; @@ -91,6 +94,7 @@ export interface IPopoverSearchList { regex?: string | RegExp; value?: string; caseSensitive?: boolean; + optionCheckedIcon?: IElementOrIcon; // Functions onOpenOptions: (value: boolean) => void; onValueSelected: (value: string) => void; @@ -116,6 +120,7 @@ export interface IOptionsListSearchList extends OptionGroupAriasTypes { loadingText?: InputSearchLoadingTextType; value?: string; searchText?: string; + optionCheckedIcon?: IElementOrIcon; } export interface ILoadingIcon { @@ -154,6 +159,7 @@ export interface IInputSearchStandAlone extends Omit variant: V; open?: boolean; disabled?: boolean; - error?: boolean; searchFilterConfig?: SearchFilterConfig; disableErrorInvalidOption?: boolean; regex?: string | RegExp; From e287ce42084159b99fc6402d8454f399adf1dda8 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:58:14 +0200 Subject: [PATCH 22/27] Improve veridyDate on inputDate --- .../inputDate/hooks/useInputDate.ts | 36 ++++++++++--------- .../inputDate/stories/inputDate.stories.tsx | 2 +- src/components/inputDate/utils/verifyDate.ts | 2 +- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/components/inputDate/hooks/useInputDate.ts b/src/components/inputDate/hooks/useInputDate.ts index 7edba872..3cfb5e95 100644 --- a/src/components/inputDate/hooks/useInputDate.ts +++ b/src/components/inputDate/hooks/useInputDate.ts @@ -270,14 +270,16 @@ export const useInputDate = ({ ? dateValue.split(props.dateSeparator as string) : dateValue.split(' '); - const firstDate = formatDateToNative(dates[0], props.format); - const secondDate = secondDateExists ? formatDateToNative(dates[1], props.format) : ''; + const firstDate = formatDateToNative(dates[0].trim(), props.format, true); + const secondDate = secondDateExists + ? formatDateToNative(dates[1].trim(), props.format, true) + : ''; - const firstValueAsNumber = firstDate.length && new Date(firstDate).getTime(); - const secondValueAsNumber = secondDate.length && new Date(secondDate).getTime(); + const firstValueAsNumber = firstDate.length && formatDateToUTC(firstDate).getTime(); + const secondValueAsNumber = secondDate.length && formatDateToUTC(secondDate).getTime(); - const firstValueAsDate = firstDate.length && new Date(firstDate); - const secondValueAsDate = secondDate.length && new Date(secondDate); + const firstValueAsDate = firstDate.length && formatDateToUTC(firstDate); + const secondValueAsDate = secondDate.length && formatDateToUTC(secondDate); adaptEvent = { target: { @@ -346,14 +348,16 @@ export const useInputDate = ({ ? dateValue.split(props.dateSeparator as string) : dateValue.split(' '); - const firstDate = formatDateToNative(dates[0], props.format); - const secondDate = secondDateExists ? formatDateToNative(dates[1], props.format) : ''; + const firstDate = formatDateToNative(dates[0].trim(), props.format, true); + const secondDate = secondDateExists + ? formatDateToNative(dates[1].trim(), props.format, true) + : ''; - const firstValueAsNumber = firstDate.length && new Date(firstDate).getTime(); - const secondValueAsNumber = secondDate.length && new Date(secondDate).getTime(); + const firstValueAsNumber = firstDate.length && formatDateToUTC(firstDate).getTime(); + const secondValueAsNumber = secondDate.length && formatDateToUTC(secondDate).getTime(); - const firstValueAsDate = firstDate.length && new Date(firstDate); - const secondValueAsDate = secondDate.length && new Date(secondDate); + const firstValueAsDate = firstDate.length && formatDateToUTC(firstDate); + const secondValueAsDate = secondDate.length && formatDateToUTC(secondDate); adaptEvent = { target: { @@ -395,10 +399,10 @@ export const useInputDate = ({ let adaptEvent; if (hasSecondaDate) { - const firstNativeDate = formatDateToNative(getDate(orderedDates[0]), props.format); - const secondNativeDate = formatDateToNative(getDate(orderedDates[1]), props.format); - const firstValueAsNumber = new Date(orderedDates[0]).getTime(); - const secondValueAsNumber = new Date(orderedDates[1]).getTime(); + const firstNativeDate = formatDateToNative(getDate(orderedDates[0]), props.format, true); + const secondNativeDate = formatDateToNative(getDate(orderedDates[1]), props.format, true); + const firstValueAsNumber = formatDateToUTC(orderedDates[0]).getTime(); + const secondValueAsNumber = formatDateToUTC(orderedDates[1]).getTime(); adaptEvent = { target: { diff --git a/src/components/inputDate/stories/inputDate.stories.tsx b/src/components/inputDate/stories/inputDate.stories.tsx index ac777636..cab3afb5 100644 --- a/src/components/inputDate/stories/inputDate.stories.tsx +++ b/src/components/inputDate/stories/inputDate.stories.tsx @@ -88,7 +88,7 @@ const commonArgs: IInputDate = { backToMonthAriaLabel: 'Back to month view', }, disabledDates: [new Date('2020-01-01'), new Date('2020-01-05')], - errorExecution: ERROR_EXECUTION.ON_BLUR, + errorExecution: ERROR_EXECUTION.ON_CHANGE, }; export const InputDate: Story = { diff --git a/src/components/inputDate/utils/verifyDate.ts b/src/components/inputDate/utils/verifyDate.ts index 1b5a1f5a..6921a4d1 100644 --- a/src/components/inputDate/utils/verifyDate.ts +++ b/src/components/inputDate/utils/verifyDate.ts @@ -8,7 +8,7 @@ const checkLeapYear = (year: number): void => { } }; -const splitDate = ( +export const splitDate = ( date: string, format: string, today: string, From f1e0c089757bea2e97d4b9de300d689cd060fd03 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:58:43 +0200 Subject: [PATCH 23/27] Improve syntheticDate elements and utilities --- .../__tests__/syntheticDate.test.ts | 14 +++++++++++--- .../syntheticDate/helper/formatDateToNative.ts | 11 +++++++++-- .../syntheticDate/syntheticDate.ts | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/utils/syntheticComponents/__tests__/syntheticDate.test.ts b/src/utils/syntheticComponents/__tests__/syntheticDate.test.ts index a54ee521..2e70ad46 100644 --- a/src/utils/syntheticComponents/__tests__/syntheticDate.test.ts +++ b/src/utils/syntheticComponents/__tests__/syntheticDate.test.ts @@ -15,15 +15,23 @@ describe('syntheticDate test', () => { const date = '11-02-2020'; const format = 'DD-MM-YYYY'; - const rightFormatDate = formatDateToNative(date, format); + const rightFormatDate = formatDateToNative(date, format, false); expect(rightFormatDate).toBe('2020-02-11'); }); - it('fill in the year with zeros when it is less than four digits', () => { + it('empty date when it doesnt comply with valid format', () => { + const date = '2024-09-1'; + const format = 'YYYY-MM-DD'; + + const rightFormatDate = formatDateToNative(date, format, false); + + expect(rightFormatDate).toBe(''); + }); + it('fill in the year with zeros when year is in the last position and value is less than four digits', () => { const date = '11-02-2'; const format = 'DD-MM-YYYY'; - const rightFormatDate = formatDateToNative(date, format); + const rightFormatDate = formatDateToNative(date, format, false); expect(rightFormatDate).toBe('0002-02-11'); }); diff --git a/src/utils/syntheticComponents/syntheticDate/helper/formatDateToNative.ts b/src/utils/syntheticComponents/syntheticDate/helper/formatDateToNative.ts index 2ea49fad..ce8c7950 100644 --- a/src/utils/syntheticComponents/syntheticDate/helper/formatDateToNative.ts +++ b/src/utils/syntheticComponents/syntheticDate/helper/formatDateToNative.ts @@ -1,9 +1,16 @@ +import { splitDate } from '@/components/inputDate/utils/verifyDate'; + const DD = 'DD'; const MM = 'MM'; const YYYY = 'YYYY'; -export const formatDateToNative = (value: string, format: string): string => { - if (value.length > 5) { +export const formatDateToNative = (value: string, format: string, hasRange: boolean): string => { + const { dateToCheck } = splitDate(value, format, '', hasRange); + + // Check if year is the last position in the format to trigger zeros autocomplete + const isYearLastPosition = format.slice(-4) === 'YYYY' && value.length > 5; + + if (isYearLastPosition || dateToCheck) { const regex = /[a-zA-Z0-9\s]/g; const symbol = format.replace(regex, '').split('')[0]; diff --git a/src/utils/syntheticComponents/syntheticDate/syntheticDate.ts b/src/utils/syntheticComponents/syntheticDate/syntheticDate.ts index 5e0c3647..089b84f8 100644 --- a/src/utils/syntheticComponents/syntheticDate/syntheticDate.ts +++ b/src/utils/syntheticComponents/syntheticDate/syntheticDate.ts @@ -20,7 +20,7 @@ export const syntheticDate = (name?: string): ReturnValue => { }); const setDate = (value: string, format: string) => { - const validDate = formatDateToNative(value, format); + const validDate = formatDateToNative(value, format, false); dateElement.value = validDate; dateElement.dispatchEvent(changeEvent); From 06cceda847ec26b4cbe1034692b3e955662d5741 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:59:05 +0200 Subject: [PATCH 24/27] Improve hook for inputs --- src/hooks/useInput/__tests__/useInput.test.ts | 5 +- src/hooks/useInput/useInput.ts | 66 +++++++++++-------- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/hooks/useInput/__tests__/useInput.test.ts b/src/hooks/useInput/__tests__/useInput.test.ts index 59993894..6d3a1d84 100644 --- a/src/hooks/useInput/__tests__/useInput.test.ts +++ b/src/hooks/useInput/__tests__/useInput.test.ts @@ -1,7 +1,6 @@ import { act, renderHook } from '@testing-library/react-hooks'; import React, { ChangeEvent } from 'react'; -import { FormatNumber } from '@/components'; import * as validationsProvider from '@/provider/validations/validationsProvider'; import { useInput } from '../useInput'; @@ -9,7 +8,7 @@ import { useInput } from '../useInput'; describe('useInput Hook', () => { it('useInput - on internal change should call parent onChange', () => { const onChange = jest.fn(); - const formatNumber = { style: 'decimal' } as FormatNumber; + const formatNumber = { style: 'decimal' }; const ref = React.createRef(); const currentValue = '123234'; const regex = new RegExp('^[0-9]*$'); @@ -44,7 +43,7 @@ describe('useInput Hook', () => { }); it('useInput - on internal blur should call parent onBlur', () => { const onBlur = jest.fn(); - const formatNumber = { style: 'decimal' } as FormatNumber; + const formatNumber = { style: 'decimal' }; const { result } = renderHook(() => useInput({ onBlur, formatNumber })); act(() => { diff --git a/src/hooks/useInput/useInput.ts b/src/hooks/useInput/useInput.ts index 31a00c5a..e5e37f0d 100644 --- a/src/hooks/useInput/useInput.ts +++ b/src/hooks/useInput/useInput.ts @@ -118,6 +118,17 @@ export const useInput = (props: ParamsTypeInputHook): ReturnTypeInputHook => { useEffect(() => { if (eventKeyPressRef.current && props.mask && inputRef?.current && value) { + const { start, end } = eventKeyPressRef.current.cursor; + // if multiple digits are selected, recovery the selected area + const area = Math.abs(start - end); + // this the mask representation of the selected area + const selected = props.mask.slice(start, end); + // match the mask representation with the mask character + const maskChart = selected.match(/#/g); + // calculate the difference between the selected area and the mask representation + const diffStart = maskChart ? Math.abs(maskChart.length - area) : 0; + const diffEnd = maskChart ? maskChart.length : 0; + const position = getPosition( eventKeyPressRef.current.key, value, @@ -125,10 +136,7 @@ export const useInput = (props: ParamsTypeInputHook): ReturnTypeInputHook => { positionRef.current ); inputRef.current.focus(); - inputRef.current.setSelectionRange( - eventKeyPressRef.current.cursor.start + position, - eventKeyPressRef.current.cursor.end + position - ); + inputRef.current.setSelectionRange(start + diffStart + position, end - diffEnd + position); } }, [value]); @@ -290,47 +298,53 @@ export const useInput = (props: ParamsTypeInputHook): ReturnTypeInputHook => { key, currentTarget: { selectionStart, selectionEnd }, } = event; - let start = selectionStart; - let end = selectionEnd; + let start = selectionStart ?? 0; + let end = selectionEnd ?? 0; + + const isSelected = Math.abs(start - end) > 0; // Check if the input has a mask if (props.mask && inputRef.current && start !== null && end !== null) { // Check if the key pressed is "Backspace" or "Delete" // Check if the character at the cursor position is a mask character if (key === BACKSPACE.key && props.mask[start - 1] && props.mask[start - 1] !== '#') { - let count = 1; - // Check if the character at the cursor position is a mask character - for (let index = start - 1; index >= 0; index--) { - if (props.mask[index] === '#') { - count = start - 1 - index; - break; + if (!isSelected) { + let count = 1; + // Check if the character at the cursor position is a mask character + for (let index = start - 1; index >= 0; index--) { + if (props.mask[index] === '#') { + count = start - 1 - index; + break; + } } + // Calculate the new cursor position + start = start - count; + end = end - count; } - // Calculate the new cursor position - start = start - count; - end = end - count; // Set the cursor to the new position inputRef.current.setSelectionRange(start, end); } else if (key === DELETE.key && props.mask[end] && props.mask[end] !== '#') { - let count = 1; - // Check if the character at the cursor position is a mask character - for (let index = end; index < props.mask.length; index++) { - if (props.mask[index] === '#') { - count = index - end; - break; + if (!isSelected) { + let count = 1; + // Check if the character at the cursor position is a mask character + for (let index = end; index < props.mask.length; index++) { + if (props.mask[index] === '#') { + count = index - end; + break; + } } + // Calculate the new cursor position + start = start + count; + end = end + count; } - // Calculate the new cursor position - start = start + count; - end = end + count; // Set the cursor to the new position inputRef.current.setSelectionRange(start, end); } } const cursor = { - start: start ?? 0, - end: end ?? 0, + start: start, + end: end, }; eventKeyPressRef.current = { key, cursor }; props.onKeyDown?.(event); From 065dda7afc75024fddd6a351fdf693c1e4b72cc7 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 09:59:20 +0200 Subject: [PATCH 25/27] Improve useManageState hook --- src/hooks/useManageState/useManageState.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/hooks/useManageState/useManageState.tsx b/src/hooks/useManageState/useManageState.tsx index 0b64cebf..2f753cdc 100644 --- a/src/hooks/useManageState/useManageState.tsx +++ b/src/hooks/useManageState/useManageState.tsx @@ -54,13 +54,12 @@ export const useManageState = ({ const setRef = useCallback(node => { if (node) { innerRef.current = node; - if (props.states.includes(STATES.HOVER)) { - innerRef?.current?.addEventListener('mouseover', onMouseOver); - innerRef?.current?.addEventListener('mouseout', onMouseOut); - } if (props.states.includes(STATES.PRESSED)) { innerRef?.current?.addEventListener('mousedown', onMouseDown); innerRef?.current?.addEventListener('mouseup', onMouseUp); + } else if (props.states.includes(STATES.HOVER)) { + innerRef?.current?.addEventListener('mouseover', onMouseOver); + innerRef?.current?.addEventListener('mouseout', onMouseOut); } } else { // delete over listeners From 21ff8f29be47d1a48aef47e268678a97308767ae Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 10:03:54 +0200 Subject: [PATCH 26/27] New exports and solve eslint errors --- src/components/index.ts | 2 ++ src/components/option/optionStandAlone.tsx | 1 + src/constants/keyboardKeys.ts | 4 ++-- src/designSystem/kubit/components/styles.ts | 2 ++ src/designSystem/kubit/components/variants.ts | 2 ++ 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/index.ts b/src/components/index.ts index f1872625..3549ea29 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -82,6 +82,8 @@ export * from './tableCaption'; export * from './tableDivider'; export * from './tableV2'; export * from './dataTable'; +export * from './video'; +export * from './progressBar'; export * from './virtualKeyboard'; /** diff --git a/src/components/option/optionStandAlone.tsx b/src/components/option/optionStandAlone.tsx index 29188844..e8d27886 100644 --- a/src/components/option/optionStandAlone.tsx +++ b/src/components/option/optionStandAlone.tsx @@ -43,6 +43,7 @@ const OptionStandAlone = React.forwardRef( const hasCheckedIcon = [ OptionStateType.MULTIPLE_SELECTED, OptionStateType.MULTIPLE_SELECTED_HOVER, + OptionStateType.SELECTED, ].includes(state); const { firstHighlightedIndex, lastHighlightedIndex } = getHighlightedIndexes( diff --git a/src/constants/keyboardKeys.ts b/src/constants/keyboardKeys.ts index 0267d9c1..6ebf7f11 100644 --- a/src/constants/keyboardKeys.ts +++ b/src/constants/keyboardKeys.ts @@ -67,8 +67,8 @@ const ARROW_DOWN = { const DELETE = { key: 'Delete', - which: 46, - keyCode: 46, + which: 8, + keyCode: 8, code: 'ArrowDecimal', }; diff --git a/src/designSystem/kubit/components/styles.ts b/src/designSystem/kubit/components/styles.ts index d2f8f29b..958c2887 100644 --- a/src/designSystem/kubit/components/styles.ts +++ b/src/designSystem/kubit/components/styles.ts @@ -89,4 +89,6 @@ export * from './tableCaption/styles'; export * from './tableDivider/styles'; export * from './tableV2/styles'; export * from './dataTable/styles'; +export * from './video/styles'; export * from './virtualKeyboard/styles'; +export * from './progressBar/styles'; diff --git a/src/designSystem/kubit/components/variants.ts b/src/designSystem/kubit/components/variants.ts index 96d17294..f882483c 100644 --- a/src/designSystem/kubit/components/variants.ts +++ b/src/designSystem/kubit/components/variants.ts @@ -89,4 +89,6 @@ export * from './tableCaption/variants'; export * from './tableDivider/variants'; export * from './tableV2/variants'; export * from './dataTable/variants'; +export * from './video/variants'; export * from './virtualKeyboard/variants'; +export * from './progressBar/variants'; From 0ea04cb8af5afd5a1376f6e29355bff595810060 Mon Sep 17 00:00:00 2001 From: Kubit Date: Tue, 24 Sep 2024 10:06:28 +0200 Subject: [PATCH 27/27] Add utilsProvider to storybook --- src/hooks/useInput/__tests__/useInput.test.ts | 5 +- src/storybook/index.ts | 1 + .../provider/utilsProvider/utilsProvider.tsx | 91 +++++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 src/storybook/provider/utilsProvider/utilsProvider.tsx diff --git a/src/hooks/useInput/__tests__/useInput.test.ts b/src/hooks/useInput/__tests__/useInput.test.ts index 6d3a1d84..59993894 100644 --- a/src/hooks/useInput/__tests__/useInput.test.ts +++ b/src/hooks/useInput/__tests__/useInput.test.ts @@ -1,6 +1,7 @@ import { act, renderHook } from '@testing-library/react-hooks'; import React, { ChangeEvent } from 'react'; +import { FormatNumber } from '@/components'; import * as validationsProvider from '@/provider/validations/validationsProvider'; import { useInput } from '../useInput'; @@ -8,7 +9,7 @@ import { useInput } from '../useInput'; describe('useInput Hook', () => { it('useInput - on internal change should call parent onChange', () => { const onChange = jest.fn(); - const formatNumber = { style: 'decimal' }; + const formatNumber = { style: 'decimal' } as FormatNumber; const ref = React.createRef(); const currentValue = '123234'; const regex = new RegExp('^[0-9]*$'); @@ -43,7 +44,7 @@ describe('useInput Hook', () => { }); it('useInput - on internal blur should call parent onBlur', () => { const onBlur = jest.fn(); - const formatNumber = { style: 'decimal' }; + const formatNumber = { style: 'decimal' } as FormatNumber; const { result } = renderHook(() => useInput({ onBlur, formatNumber })); act(() => { diff --git a/src/storybook/index.ts b/src/storybook/index.ts index 07635cbb..88452087 100644 --- a/src/storybook/index.ts +++ b/src/storybook/index.ts @@ -1 +1,2 @@ export * from './components'; +export * from './provider/utilsProvider/utilsProvider'; diff --git a/src/storybook/provider/utilsProvider/utilsProvider.tsx b/src/storybook/provider/utilsProvider/utilsProvider.tsx new file mode 100644 index 00000000..b4e6c9d1 --- /dev/null +++ b/src/storybook/provider/utilsProvider/utilsProvider.tsx @@ -0,0 +1,91 @@ +import React from 'react'; + +import { UtilsProvider } from '@/provider'; +import { DateFormatOptions } from '@/provider/utils/types'; +import { FormatWeekdayOptionType } from '@/types'; +import { + formatDate, + getAddDays, + getAddMonths, + getAddYears, + getAllMonthNames, + getAllWeekdayNames, + getSubDays, + getSubMonths, + getSubYears, + isAfter, + isBefore, + isDatesEqual, + transformDate, +} from '@/utils/date'; + +//delete on kubit +const folder = { + santander: 'santander', + modelbank: 'mb', + santander_black: 'santander', + modelbank_cc: 'mb', + santander_cc: 'santander', +}; +//delete on kubit + +export const UtilsProviderDocStorybook = ({ + children, + theme, +}: { + children: JSX.Element; + theme: string; +}): JSX.Element => { + return ( + { + return isAfter(date1, date2); + }, + isBefore: (date1: Date, date2: Date) => { + return isBefore(date1, date2); + }, + isDatesEqual: (firsDate: string | number | Date, secondDate: string | number | Date) => { + return isDatesEqual(firsDate, secondDate); + }, + getAddDays: (date: Date, days: number) => { + return getAddDays(date, days); + }, + getAddMonths: (date: Date, months: number) => { + return getAddMonths(date, months); + }, + getAddYears: (date: Date, years: number) => { + return getAddYears(date, years); + }, + getSubDays: (date: Date, days: number) => { + return getSubDays(date, days); + }, + getSubMonths: (date: Date, months: number) => { + return getSubMonths(date, months); + }, + getSubYears: (date: Date, years: number) => { + return getSubYears(date, years); + }, + getAllMonthName: () => { + return getAllMonthNames(); + }, + getAllWeekdayName: (weekdayFormat: FormatWeekdayOptionType, isSundayFirst: boolean) => { + return getAllWeekdayNames(weekdayFormat, isSundayFirst); + }, + }} + formatDate={(date: Date, format: string | DateFormatOptions) => { + return formatDate(date, format); + }} + transformDate={(date: string | number, format: string | undefined) => { + return transformDate(date, format); + }} + > + {children} + + ); +};