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 (
+
+
+
+ }
+ />
+ );
+};
+
+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":
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+ ,
+ "container":
- ,
- "container": ,
"debug": [Function],
"findAllByAltText": [Function],
@@ -118,6 +126,39 @@ Object {
"asFragment": [Function],
"baseElement":
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+ ,
+ "container":
-
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JD
+
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
+
+
+
+
+ events
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+