diff --git a/. npmignore b/. npmignore index 2fe0e01b1d..979b3b4e68 100644 --- a/. npmignore +++ b/. npmignore @@ -1,3 +1,4 @@ node_modules .npmrc -docs/ \ No newline at end of file +docs/ +examples diff --git a/.eslintignore b/.eslintignore index 9981ed82ec..25c3721f81 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,5 @@ node_modules/ public/ coverage/ docs/ -rollup.* \ No newline at end of file +rollup.* +examples diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 437bcfdbfa..96cc1b56dc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,7 +5,7 @@ on: - master jobs: release: - # uncomment next line as soon as visual tesing starts working + # uncomment next line as soon as visual testing starts working # needs: visual_test name: Release tag runs-on: ubuntu-latest diff --git a/.github/workflows/next.yml b/.github/workflows/next.yml new file mode 100644 index 0000000000..fe3db7cc7f --- /dev/null +++ b/.github/workflows/next.yml @@ -0,0 +1,59 @@ +name: "Release next" +on: + push: + branches: + - v4.x +jobs: + release: + name: Release tag + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-node@v1 + with: + node-version: '20.x' + + - name: npm install + run: npm ci + + - name: setup git + run: git checkout -b preparing-module-for-npm-publish && git config --global user.email "release-bot@innovaccer.com" && git config --global user.name "release-bot" + + - name: bump module version + run: npm version --no-commit-hooks prerelease -m "Released %s [skip ci]" + + - name: generate .npmrc + run: cp .npmrc.stub .npmrc + + - name: Publish to npm js + run: npm publish --access public --tag=next + env: + NPM_TOKEN: ${{secrets.NPM_TOKEN}} + + - name: remove .npmrc + run: rm -rf .npmrc + + - name: push tag to github + run: git push https://${{ secrets.GH_TOKEN }}@github.com/$GITHUB_REPOSITORY.git $(node ./getVersion.js) + + - name: remove css/dist dist/ from commit + run: | + git reset HEAD~1 + git add package.json + git commit -m "Released $(node ./getVersion.js) [skip ci]" --no-verify + + - name: push version to github + run: git push https://${{ secrets.GH_TOKEN }}@github.com/$GITHUB_REPOSITORY.git HEAD:next + + - name: deploy storybook + run: npm run deploy-storybook -- --bucket-path=webui-mds-sb-next --aws-profile=NONE --ci + env: + AWS_SECRET_ACCESS_KEY: ${{ secrets.DOCS_KEY }} + AWS_ACCESS_KEY_ID: ${{ secrets.DOCS_KEY_ID }} + AWS_REGION: ${{ secrets.DOCS_REGION }} + + - name: notify release + uses: /innovaccer/design-system/actions/notify-release@next + env: + GCHAT_PATH: ${{ secrets.GCHAT_PATH }} diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 8d398ecbac..e34ab864a3 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -16,9 +16,6 @@ jobs: - name: npm install run: npm ci - - name: lint - run: npm run lint:check - - name: test run: npm run test diff --git a/.storybook/main.js b/.storybook/main.js index 5c8e775db1..0d84bcbb76 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,5 +1,10 @@ const path = require('path'); +const cssTokenFiles = [ + path.resolve(__dirname, '../css/src/variables/index.css'), + path.resolve(__dirname, '../css/src/tokens/index.css') +]; + module.exports = { stories: ['../core/components/**/*.story.@(js|jsx|ts|tsx)', '../core/ai-components/**/*.story.@(js|jsx|ts|tsx)'], addons: [ @@ -16,6 +21,33 @@ module.exports = { '@storybook/addon-essentials', '@storybook/addon-a11y', '@storybook/addon-knobs', + { + name: '@storybook/addon-postcss', + options: { + postcssLoaderOptions: { + implementation: require('postcss'), + postcssOptions: { + plugins: [ + require('autoprefixer'), + require('postcss-color-mod-function')({ + importFrom: cssTokenFiles, + }), + ], + }, + }, + }, + }, + { + name: 'storybook-css-modules', + options: { + cssModulesLoaderOptions: { + importLoaders: 1, + modules: { + localIdentName: '[local]', // Use local class names directly + }, + }, + }, + }, ], typescript: { check: false, @@ -33,6 +65,7 @@ module.exports = { }, webpackFinal: async (config, { configType }) => { config.resolve.alias['@'] = path.resolve(__dirname, '../core'); + config.resolve.alias['@css'] = path.resolve(__dirname, '../css/src'); config.resolve.alias['@innovaccer/mds-images/ui-states'] = path.resolve(__dirname, '../mds-images/ui-states'); // Return the altered config return config; diff --git a/CHANGELOG.md b/CHANGELOG.md index f6a7666ca7..d1de2f2b2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,49 @@ +## 3.5.0 (2024-12-12) + +### Highlights + +- feat(avatar): add presence indicator support in avatar component (ce945f26) +- feat(avatar): add status indicator support in avatar (58857846) +- fix(input): update cursor position on input type change (8f45613f) +- fix(table): update table stories structure on storybook (9e3b4d53) +- feat(statusHint): update color for default appearance (a64c5128) + +### Breaking changes + +NA + +### Migration guide + +NA + +### Deprecations + +NA + +### Features + +- feat(avatar): add presence indicator support in avatar component (ce945f26) +- feat(avatar): add status indicator support in avatar (58857846) +- feat(statusHint): update color for default appearance (a64c5128) + +### Fixes + +- fix(input): update cursor position on input type change (8f45613f) + +### Improvements + +- chore: fix lint error in dropzone component & toast figma connect (9c8a41ec) +- fix(progressIndicators): update story of spinner and progress ring (506ca9b1) +- fix(table): update table stories structure on storybook (9e3b4d53) +- refactor(table): update table props description on storybook (f041f10f) + +### Documentation + +- docs(docs): add status and presence indicator documentation in avatar (dc14464c) +- docs(docs): update table preview link in documentation (1df056c2) + +--- + ## 3.4.0 (2024-11-18) ### Highlights diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 998bac027c..6949e80c38 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -279,28 +279,28 @@ const AvatarAppearance = appearance || colors[(initials.charCodeAt(0) + (initials.charCodeAt(1) || 0)) % 8] || DefaultAppearance; ``` -- [ClassNames](https://www.npmjs.com/package/classnames) is a utility for conditionally joining CSS classNames together. -- CSS is added according to [BEM Convention](http://getbem.com/naming/). +- CSS Module has been used to define the class names. + +1. Create a CSS file inside `css` directory with the file extension `module.css` (e.g `avatar.module.css`) +2. Import the CSS file inside your component's `.tsx` file using: + +```jsx +import styles from './avatar.module.css'; +``` + +3. Use [ClassNames](https://www.npmjs.com/package/classnames), a utility for conditionally joining CSS classNames together. +4. Follow the [BEM Convention](http://getbem.com/naming/) and use CSS Modules for naming CSS classes. ```jsx const classes = classNames( { - Avatar: true, - [`Avatar--${size}`]: size, - [`Avatar--${AvatarAppearance}`]: AvatarAppearance, - ['Avatar--disabled']: !initials || !withTooltip, + [styles.Avatar]: true, + [styles[`Avatar--${size}`]]: size, + [styles[`Avatar--${AvatarAppearance}`]]: AvatarAppearance, + [styles['Avatar--disabled']]: !initials || !withTooltip, }, className ); - -const ContentClass = classNames({ - [`Avatar-content--${size}`]: size, - [`Avatar-content--${AvatarAppearance}`]: AvatarAppearance, -}); - -const IconClass = classNames({ - [`Avatar-content--${AvatarAppearance}`]: AvatarAppearance, -}); ``` - Add rendering logic and `data-test` attribute. diff --git a/__mocks__/styleMock.js b/__mocks__/styleMock.js new file mode 100644 index 0000000000..f053ebf797 --- /dev/null +++ b/__mocks__/styleMock.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/core/ai-components/AIButton/index.tsx b/core/ai-components/AIButton/index.tsx index 8c667af4c6..773c4f048b 100644 --- a/core/ai-components/AIButton/index.tsx +++ b/core/ai-components/AIButton/index.tsx @@ -4,6 +4,7 @@ import { TButtonAppearance, TButtonType, TBaseHtmlProps } from '../common.type'; import BasicIcon from './icons/Basic.svg'; import BasicDisabledIcon from './icons/BasicDisabled.svg'; import PrimaryIcon from './icons/Primary.svg'; +import styles from '@css/ai-components/button.module.css'; export interface AIButtonProps extends TBaseHtmlProps { /** @@ -53,15 +54,15 @@ export const AIButton = (props: AIButtonProps) => { const buttonClassNames = classNames( { - AIButton: true, - 'AIButton--primary': appearance === 'primary', - 'AIButton--basic': appearance === 'basic', + [styles.AIButton]: true, + [styles['AIButton--primary']]: appearance === 'primary', + [styles['AIButton--basic']]: appearance === 'basic', }, className ); const IconClassNames = classNames({ - 'AIButton-Icon': true, + [styles['AIButton-Icon']]: true, }); const buttonIcon = appearance === 'primary' ? PrimaryIcon : disabled ? BasicDisabledIcon : BasicIcon; diff --git a/core/ai-components/AIChip/index.tsx b/core/ai-components/AIChip/index.tsx index c4bdb469e3..bd7ac563c0 100644 --- a/core/ai-components/AIChip/index.tsx +++ b/core/ai-components/AIChip/index.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import classNames from 'classnames'; +import styles from '@css/ai-components/chip.module.css'; export interface AIChipProps extends React.ComponentProps<'button'> { /** @@ -29,22 +30,22 @@ export const AIChip = (props: AIChipProps) => { const ChipClassNames = classNames( { - AIChip: true, - 'AIChip--disabled': disabled, + [styles.AIChip]: true, + [styles['AIChip--disabled']]: disabled, }, className ); const IconClassNames = classNames({ - 'AIChip-icon': true, - 'AIChip-icon--disabled': disabled, + [styles['AIChip-icon']]: true, + [styles['AIChip-icon--disabled']]: disabled, ['material-symbols']: true, ['material-symbols-rounded']: true, }); const TextClassNames = classNames({ - 'AIChip-text': true, - 'AIChip-text--disabled': disabled, + [styles['AIChip-text']]: true, + [styles['AIChip-text--disabled']]: disabled, }); return ( diff --git a/core/ai-components/AIIconButton/SaraIcon.tsx b/core/ai-components/AIIconButton/SaraIcon.tsx index 6a7ecf73a7..3dd019e20a 100644 --- a/core/ai-components/AIIconButton/SaraIcon.tsx +++ b/core/ai-components/AIIconButton/SaraIcon.tsx @@ -5,6 +5,7 @@ import SaraIconTop from './icons/SaraIconTop'; import SaraIconBottom from './icons/SaraIconBottom'; import SaraDisabledTop from './icons/SaraDisabledTop'; import SaraDisabledBottom from './icons/SaraDisabledBottom'; +import styles from '@css/ai-components/iconButton.module.css'; interface SaraIconProp { size: TSize2Hierarchy; @@ -16,11 +17,11 @@ export const SaraIcon = (props: SaraIconProp) => { const { size, position, disabled } = props; const AIIconClassNames = classNames({ - 'AIIconButton-AIIcon': true, - 'AIIconButton-AIIcon--largeTop': position === 'top' && size === 'large', - 'AIIconButton-AIIcon--regularTop': position === 'top' && size === 'regular', - 'AIIconButton-AIIcon--regularBottom': position === 'bottom' && size === 'regular', - 'AIIconButton-AIIcon--largeBottom': position === 'bottom' && size === 'large', + [styles['AIIconButton-AIIcon']]: true, + [styles['AIIconButton-AIIcon--largeTop']]: position === 'top' && size === 'large', + [styles['AIIconButton-AIIcon--regularTop']]: position === 'top' && size === 'regular', + [styles['AIIconButton-AIIcon--regularBottom']]: position === 'bottom' && size === 'regular', + [styles['AIIconButton-AIIcon--largeBottom']]: position === 'bottom' && size === 'large', }); if (disabled && position === 'bottom') { diff --git a/core/ai-components/AIIconButton/index.tsx b/core/ai-components/AIIconButton/index.tsx index 80c04cf018..ada79811d8 100644 --- a/core/ai-components/AIIconButton/index.tsx +++ b/core/ai-components/AIIconButton/index.tsx @@ -3,6 +3,7 @@ import classNames from 'classnames'; import { TIconPosition, TButtonType, TSize2Hierarchy, TBaseHtmlProps } from '../common.type'; import { Tooltip } from '@/index'; import SaraIcon from './SaraIcon'; +import styles from '@css/ai-components/iconButton.module.css'; export interface AIIconButtonProps extends Omit, 'size'> { /** @@ -64,13 +65,13 @@ export const AIIconButton = (props: AIIconButtonProps) => { const buttonClassNames = classNames( { - AIIconButton: true, + [styles.AIIconButton]: true, }, className ); const IconClassNames = classNames({ - 'AIIconButton-icon': true, + [styles['AIIconButton-icon']]: true, ['material-symbols']: true, ['material-symbols-rounded']: true, }); diff --git a/core/ai-components/AIResponse/ChatActionBar.tsx b/core/ai-components/AIResponse/ChatActionBar.tsx index c9bad56f6f..4372b8fec8 100644 --- a/core/ai-components/AIResponse/ChatActionBar.tsx +++ b/core/ai-components/AIResponse/ChatActionBar.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import classNames from 'classnames'; import { TBaseHtmlProps } from '../common.type'; +import styles from '@css/ai-components/chat.module.css'; export interface ChatActionBarProps extends TBaseHtmlProps { /** @@ -22,7 +23,7 @@ export const ChatActionBar = (props: ChatActionBarProps) => { const actionBarClassNames = classNames( { - 'AIResponse-actionBar': true, + [styles['AIResponse-actionBar']]: true, }, className ); diff --git a/core/ai-components/AIResponse/ChatBox.tsx b/core/ai-components/AIResponse/ChatBox.tsx index 3996aa1e14..d743c7a0a5 100644 --- a/core/ai-components/AIResponse/ChatBox.tsx +++ b/core/ai-components/AIResponse/ChatBox.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import classNames from 'classnames'; import { TBaseHtmlProps } from '../common.type'; +import styles from '@css/ai-components/chat.module.css'; export interface ChatBoxProps extends TBaseHtmlProps { /** @@ -22,7 +23,7 @@ export const ChatBox = (props: ChatBoxProps) => { const chatBoxClassNames = classNames( { - 'AIResponse-box': true, + [styles['AIResponse-box']]: true, }, className ); diff --git a/core/ai-components/AIResponse/ChatButton.tsx b/core/ai-components/AIResponse/ChatButton.tsx index 7a64c5e1c5..28c84e2fd5 100644 --- a/core/ai-components/AIResponse/ChatButton.tsx +++ b/core/ai-components/AIResponse/ChatButton.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import classNames from 'classnames'; import { ButtonProps } from '@/index.type'; import { Button } from '@/index'; +import styles from '@css/ai-components/chat.module.css'; type ChatButtonType = Omit; @@ -10,8 +11,8 @@ export const ChatButton = (props: ChatButtonType) => { const chatButtonClassNames = classNames( { - 'AIResponse-button': true, - 'AIResponse-button--selected': selected, + [styles['AIResponse-button']]: true, + [styles['AIResponse-button--selected']]: selected, }, className ); diff --git a/core/components/atoms/_chip/__tests__/__snapshots__/_chip.test.tsx.snap b/core/components/atoms/_chip/__tests__/__snapshots__/_chip.test.tsx.snap index 7c900106cc..f1f8fca233 100644 --- a/core/components/atoms/_chip/__tests__/__snapshots__/_chip.test.tsx.snap +++ b/core/components/atoms/_chip/__tests__/__snapshots__/_chip.test.tsx.snap @@ -6,7 +6,7 @@ exports[`Chip component
{ const iconClass = (align: string) => classNames({ - ['Chip-icon']: true, - [`Chip-icon--${align}`]: align, - [`Chip-icon-disabled--right`]: align === 'right' && disabled, + [styles['Chip-icon']]: true, + [styles[`Chip-icon--${align}`]]: align, + [styles[`Chip-icon-disabled--right`]]: align === 'right' && disabled, ['cursor-pointer']: align === 'right' && !disabled, - ['Chip-icon--selected']: align === 'right' && selected, + [styles['Chip-icon--selected']]: align === 'right' && selected, }); const onCloseHandler = (e: React.MouseEvent | React.KeyboardEvent) => { @@ -74,26 +75,38 @@ export const GenericChip = (props: GenericChipProps) => { ['inverse']: !selected && align === 'left', }) as IconProps['appearance']; + const chipTextClass = classNames({ + [styles['Chip-text']]: true, + ['mr-3']: true, + }); + const textColor = classNames({ ['primary-dark']: selected, ['inverse']: !disabled && !selected, }) as TextProps['color']; + const chipWrapperClass = classNames( + { + [styles['Chip-wrapper']]: true, + }, + className + ); + const renderLabel = () => { if (typeof label === 'string') { return ( -
+
{labelPrefix && ( {labelPrefix} )} - + {label}
@@ -125,7 +138,7 @@ export const GenericChip = (props: GenericChipProps) => { role="button" onKeyDown={onChipKeyDownHandler} {...baseProps} - className={`Chip-wrapper ${className}`} + className={chipWrapperClass} onClick={onClickHandler} > {icon && ( diff --git a/core/components/atoms/actionCard/ActionCard.tsx b/core/components/atoms/actionCard/ActionCard.tsx index 14e525fa6d..f2cf765c9c 100644 --- a/core/components/atoms/actionCard/ActionCard.tsx +++ b/core/components/atoms/actionCard/ActionCard.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { BaseHtmlProps, BaseProps } from '@/utils/types'; import classNames from 'classnames'; +import styles from '@css/components/actionCard.module.css'; export interface ActionCardProps extends BaseProps, BaseHtmlProps { /** @@ -26,9 +27,9 @@ export const ActionCard = (props: ActionCardProps) => { const classes = classNames( { - ['ActionCard']: true, - ['ActionCard--default']: !disabled, - ['ActionCard--disabled']: disabled, + [styles['ActionCard']]: true, + [styles['ActionCard--default']]: !disabled, + [styles['ActionCard--disabled']]: disabled, }, className ); @@ -56,7 +57,7 @@ export const ActionCard = (props: ActionCardProps) => { {...rest} > {disabled && ( -
+
)} {children}
diff --git a/core/components/atoms/avatar/Avatar.tsx b/core/components/atoms/avatar/Avatar.tsx index 5e4e54fd61..bc96c940e6 100644 --- a/core/components/atoms/avatar/Avatar.tsx +++ b/core/components/atoms/avatar/Avatar.tsx @@ -7,6 +7,9 @@ import { AccentAppearance, AvatarSize, AvatarShape } from '@/common.type'; import AvatarIcon from './avatarIcon'; import AvatarImage from './avatarImage'; import AvatarProvider from './AvatarProvider'; +import styles from '@css/components/avatar.module.css'; + +type TPresence = 'active' | 'away'; export interface AvatarProps extends BaseProps { /** @@ -57,6 +60,18 @@ export interface AvatarProps extends BaseProps { * Defines tabIndex of the `Avatar` */ tabIndex?: number; + /** + * Show presence indicator for the `Avatar` + */ + presence?: TPresence; + /** + * Show status indicator for the `Avatar` + */ + status?: React.ReactNode; + /** + * Stroke color of `Presence indicator` & `Status indicator` in `Avatar` + */ + strokeColor?: string; } const initialsLength = 2; @@ -77,6 +92,9 @@ export const Avatar = (props: AvatarProps) => { disabled, tooltipSuffix, tabIndex, + presence, + status, + strokeColor, role = 'presentation', } = props; @@ -99,34 +117,46 @@ export const Avatar = (props: AvatarProps) => { appearance || colors[(initials.charCodeAt(0) + (initials.charCodeAt(1) || 0)) % 8] || DefaultAppearance; const darkAppearance = ['secondary', 'success', 'warning', 'accent1', 'accent4']; + const showPresence = presence && !disabled && shape === 'round'; + const showStatus = status && size === 'regular' && shape === 'round'; const AvatarClassNames = classNames( { - Avatar: true, - ['Avatar--square']: shape === 'square', - [`Avatar--${size}`]: shape !== 'square', - [`Avatar--${AvatarAppearance}`]: AvatarAppearance, - ['Avatar--noInitials']: !initials || !withTooltip, - ['Avatar--disabled']: disabled, - ['Avatar--default']: !disabled, + [styles.Avatar]: true, + [styles['Avatar--square']]: shape === 'square', + [styles[`Avatar--${size}`]]: shape !== 'square', + [styles[`Avatar--${AvatarAppearance}`]]: AvatarAppearance, + [styles['Avatar--noInitials']]: !initials || !withTooltip, + [styles['Avatar--disabled']]: disabled, + [styles['Avatar--default']]: !disabled, }, className ); const AvatarWrapperClassNames = classNames({ - ['Avatar-wrapper--square']: shape === 'square', - [`Avatar--${size}`]: shape === 'square', + [styles['Avatar-wrapper--square']]: shape === 'square', + [styles[`Avatar--${size}`]]: shape === 'square', }); const TextClassNames = classNames({ - [`Avatar-content--${size}`]: size, - ['Avatar-content']: darkAppearance.includes(AvatarAppearance), + [styles[`Avatar-content--${size}`]]: size, + [styles['Avatar-content']]: darkAppearance.includes(AvatarAppearance), }); const IconClassNames = classNames({ - ['Avatar-content']: darkAppearance.includes(AvatarAppearance), + [styles['Avatar-content']]: darkAppearance.includes(AvatarAppearance), + }); + + const presenceClassNames = classNames({ + [styles['Avatar-presence']]: presence, + [styles['Avatar-presence--active']]: presence === 'active', + [styles['Avatar-presence--away']]: presence === 'away', }); + const borderStyle = { + boxShadow: `0 0 0 var(--spacing-s) ${strokeColor}`, + }; + const sharedProp = { size, firstName, @@ -180,17 +210,25 @@ export const Avatar = (props: AvatarProps) => { ); }; - const renderTooltip = () => { - if (withTooltip && initials) { - return ( - + const renderTooltip = () => ( + + {withTooltip && initials ? ( + {renderAvatar()} - ); - } - - return renderAvatar(); - }; + ) : ( + renderAvatar() + )} + {showPresence && ( + + )} + {showStatus && ( + + {status} + + )} + + ); return renderTooltip(); }; @@ -205,6 +243,7 @@ Avatar.defaultProps = { withTooltip: true, size: 'regular', shape: 'round', + strokeColor: 'var(--white)', }; export default Avatar; diff --git a/core/components/atoms/avatar/__stories__/Presence.story.jsx b/core/components/atoms/avatar/__stories__/Presence.story.jsx new file mode 100644 index 0000000000..bafe97f20f --- /dev/null +++ b/core/components/atoms/avatar/__stories__/Presence.story.jsx @@ -0,0 +1,36 @@ +import * as React from 'react'; +import { Avatar, Row, Column, Text } from '@/index'; + +// CSF format story +export const presence = () => { + const weight = 'strong'; + + return ( + + + Active +
+
+ +
+ + Away +
+
+ +
+
+ ); +}; + +export default { + title: 'Components/Avatar/Avatar/Presence', + component: Avatar, + parameters: { + docs: { + docPage: { + title: 'Avatar', + }, + }, + }, +}; diff --git a/core/components/atoms/avatar/__stories__/assets/status-image.png b/core/components/atoms/avatar/__stories__/assets/status-image.png new file mode 100644 index 0000000000..a303c6ebe5 Binary files /dev/null and b/core/components/atoms/avatar/__stories__/assets/status-image.png differ diff --git a/core/components/atoms/avatar/__stories__/statusWithIcon.story.jsx b/core/components/atoms/avatar/__stories__/statusWithIcon.story.jsx new file mode 100644 index 0000000000..178872189a --- /dev/null +++ b/core/components/atoms/avatar/__stories__/statusWithIcon.story.jsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import { Avatar, Icon, Tooltip } from '@/index'; + +// CSF format story +export const statusWithIcon = () => { + return ( + + +
+ } + /> + ); +}; + +export default { + title: 'Components/Avatar/Avatar/Status/Status With Icon', + component: Avatar, + parameters: { + docs: { + docPage: { + title: 'Avatar', + }, + }, + }, +}; diff --git a/core/components/atoms/avatar/__stories__/statusWithImage.story.jsx b/core/components/atoms/avatar/__stories__/statusWithImage.story.jsx new file mode 100644 index 0000000000..abded76a1f --- /dev/null +++ b/core/components/atoms/avatar/__stories__/statusWithImage.story.jsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import { Avatar, Tooltip } from '@/index'; +import StatusImage from './assets/status-image.png'; + +// CSF format story +export const statusWithImage = () => { + return ( + + DND + + } + /> + ); +}; + +export default { + title: 'Components/Avatar/Avatar/Status/Status With Image', + component: Avatar, + parameters: { + docs: { + docPage: { + title: 'Avatar', + }, + }, + }, +}; diff --git a/core/components/atoms/avatar/__tests__/Avatar.test.tsx b/core/components/atoms/avatar/__tests__/Avatar.test.tsx index cf7279134d..3c160c82f6 100644 --- a/core/components/atoms/avatar/__tests__/Avatar.test.tsx +++ b/core/components/atoms/avatar/__tests__/Avatar.test.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { fireEvent, render } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import Avatar, { AvatarProps as Props } from '../Avatar'; import { AccentAppearance, AvatarShape, AvatarSize } from '@/common.type'; import { testHelper, filterUndefined, valueHelper, testMessageHelper } from '@/utils/testHelper'; @@ -20,6 +20,8 @@ const sizes: AvatarSize[] = ['regular', 'tiny']; const shapes: AvatarShape[] = ['round', 'square']; const booleanValues = [true, false]; +const statusComponent =
status
; + describe('Avatar component', () => { const mapper = { appearance: valueHelper(appearances, { required: true, iterate: true }), @@ -231,3 +233,86 @@ describe('Avatar component with tooltip', () => { expect(tooltip).toHaveTextContent('John Doe (Deactivated)'); }); }); + +describe('Avatar component with prop:presence', () => { + it('presence should be available for only round avatar', () => { + const { getByTestId } = render(); + const presenceEle = getByTestId('DesignSystem-Avatar--Presence'); + expect(presenceEle).toBeInTheDocument(); + expect(presenceEle).toHaveClass('Avatar-presence'); + }); + + it('presence should not be available for square avatar', () => { + render(); + const presenceEle = screen.queryByText('DesignSystem-Avatar--Presence'); + expect(presenceEle).not.toBeInTheDocument(); + }); + + it('presence should not be available for disabled avatar', () => { + render(); + const presenceEle = screen.queryByText('DesignSystem-Avatar--Presence'); + expect(presenceEle).not.toBeInTheDocument(); + }); + + it('presence should have active class for prop presence:active', () => { + const { getByTestId } = render(); + const presenceEle = getByTestId('DesignSystem-Avatar--Presence'); + expect(presenceEle).toHaveClass('Avatar-presence--active'); + }); + + it('presence should have active class for prop presence:away', () => { + const { getByTestId } = render(); + const presenceEle = getByTestId('DesignSystem-Avatar--Presence'); + expect(presenceEle).toHaveClass('Avatar-presence--away'); + }); + + it('presence should have custom stroke color', () => { + const { getByTestId } = render(); + const presenceEle = getByTestId('DesignSystem-Avatar--Presence'); + expect(presenceEle).toHaveStyle('box-shadow: 0 0 0 var(--spacing-s) red'); + }); +}); + +describe('Avatar component with prop:status', () => { + it('should have the Avatar-status class when size is regular', () => { + const { getByTestId } = render(Design); + const statusElement = getByTestId('DesignSystem-Avatar--Status'); + expect(statusElement).toBeInTheDocument(); + expect(statusElement).toHaveClass('Avatar-status'); + }); + + it('should not have the Avatar-status class when size is tiny', () => { + render( + + Design + + ); + const statusElement = screen.queryByText('DesignSystem-Avatar--Status'); + expect(statusElement).not.toBeInTheDocument(); + }); + + it('should have the Avatar-status class when shape is round', () => { + const { getByTestId } = render(Design); + const statusElement = getByTestId('DesignSystem-Avatar--Status'); + expect(statusElement).toBeInTheDocument(); + expect(statusElement).toHaveClass('Avatar-status'); + }); + + it('should not have the Avatar-status class when shape is square', () => { + render( + + Design + + ); + const statusElement = screen.queryByText('DesignSystem-Avatar--Status'); + expect(statusElement).not.toBeInTheDocument(); + }); + + it('status should have custom stroke color', () => { + const { getByTestId } = render( + + ); + const statusElement = getByTestId('DesignSystem-Avatar--Status'); + expect(statusElement).toHaveStyle('box-shadow: 0 0 0 var(--spacing-s) red'); + }); +}); diff --git a/core/components/atoms/avatar/__tests__/__snapshots__/Avatar.test.tsx.snap b/core/components/atoms/avatar/__tests__/__snapshots__/Avatar.test.tsx.snap index c61690820e..eae3cd0c62 100644 --- a/core/components/atoms/avatar/__tests__/__snapshots__/Avatar.test.tsx.snap +++ b/core/components/atoms/avatar/__tests__/__snapshots__/Avatar.test.tsx.snap @@ -7,6 +7,39 @@ Object { "asFragment": [Function], "baseElement":
+ +