From e2b73845f677ffa7206dc3a422f5758beb49d037 Mon Sep 17 00:00:00 2001 From: Jake Laderman Date: Tue, 17 Dec 2024 15:03:34 -0500 Subject: [PATCH 1/7] add header option to card --- src/components/Card.tsx | 86 ++++++++++++++++++++++-------------- src/stories/Card.stories.tsx | 33 ++++++++++++-- 2 files changed, 82 insertions(+), 37 deletions(-) diff --git a/src/components/Card.tsx b/src/components/Card.tsx index ff4be8d5..7f151c9f 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -1,21 +1,18 @@ import chroma from 'chroma-js' import { Div, type DivProps } from 'honorable' -import { forwardRef } from 'react' -import styled, { type DefaultTheme } from 'styled-components' import { memoize } from 'lodash-es' +import { type ComponentProps, type ReactNode, forwardRef } from 'react' +import styled, { type DefaultTheme } from 'styled-components' import { type Severity, type SeverityExt, sanitizeSeverity } from '../types' import { type FillLevel, FillLevelProvider, - isFillLevel, toFillLevel, useFillLevel, } from './contexts/FillLevelContext' -const HUES = ['default', 'lighter', 'lightest'] as const - const CARD_SEVERITIES = [ 'info', 'success', @@ -27,12 +24,9 @@ const CARD_SEVERITIES = [ type CornerSize = 'medium' | 'large' type CardFillLevel = Exclude -type CardHue = (typeof HUES)[number] type CardSeverity = Extract type BaseCardProps = { - /** @deprecated Colors set by `FillLevelContext`. If you need to override context, use `fillLevel` */ - hue?: CardHue /** Used to override a fill level set by `FillLevelContext` */ fillLevel?: FillLevel cornerSize?: CornerSize @@ -40,6 +34,11 @@ type BaseCardProps = { disabled?: boolean selected?: boolean severity?: SeverityExt + header?: { + size?: 'medium' | 'large' + content?: ReactNode + headerProps?: ComponentProps<'div'> + } } type CardProps = DivProps & BaseCardProps @@ -65,12 +64,6 @@ const fillToNeutralHoverBgC = { 3: 'fill-three-hover', } as const satisfies Record -const hueToFill = { - default: 1, - lighter: 2, - lightest: 3, -} as const satisfies Record - const fillToNeutralSelectedBgC = { 0: 'fill-one-selected', 1: 'fill-one-selected', @@ -78,22 +71,14 @@ const fillToNeutralSelectedBgC = { 3: 'fill-three-selected', } as const satisfies Record -export function useDecideFillLevel({ - hue, - fillLevel, -}: { - hue?: CardHue - fillLevel?: number -}) { +export function useDecideFillLevel({ fillLevel }: { fillLevel?: number }) { const parentFillLevel = useFillLevel() - if (isFillLevel(fillLevel)) { - return toFillLevel(Math.max(1, fillLevel)) as CardFillLevel - } - - return isFillLevel(hueToFill[hue]) - ? hueToFill[hue] - : (toFillLevel(parentFillLevel + 1) as CardFillLevel) + return ( + typeof fillLevel === 'number' + ? toFillLevel(Math.max(1, fillLevel)) + : toFillLevel(parentFillLevel + 1) + ) as CardFillLevel } export const getFillToLightBgC = memoize( @@ -151,6 +136,22 @@ const getBgColor = ({ return fillToLightBgC[severity][fillLevel] } +const HeaderSC = styled.div<{ + fillLevel: CardFillLevel + selected: boolean + size: 'medium' | 'large' +}>(({ theme, fillLevel, selected, size }) => ({ + ...theme.partials.text.overline, + color: theme.colors['text-xlight'], + borderBottom: `1px solid ${theme.colors[fillToNeutralBorderC[fillLevel]]}`, + backgroundColor: selected + ? theme.colors[fillToNeutralSelectedBgC[fillLevel]] + : getBgColor({ theme, fillLevel }), + height: size === 'large' ? 48 : 40, + padding: `0 ${theme.spacing.medium}px`, + overflow: 'hidden', +})) + const CardSC = styled(Div)<{ $fillLevel: CardFillLevel $cornerSize: CornerSize @@ -199,29 +200,34 @@ const CardSC = styled(Div)<{ const Card = forwardRef( ( { + header, cornerSize = 'large', - hue, // Deprecated, prefer fillLevel severity = 'neutral', fillLevel, selected = false, clickable = false, disabled = false, + children, ...props }: CardProps, ref ) => { - fillLevel = useDecideFillLevel({ hue, fillLevel }) + const { size, content: headerContent, headerProps } = header ?? {} + + const mainFillLevel = useDecideFillLevel({ fillLevel }) + const headerFillLevel = useDecideFillLevel({ fillLevel: mainFillLevel + 1 }) + const cardSeverity = sanitizeSeverity(severity, { allowList: CARD_SEVERITIES, default: 'neutral', }) return ( - + + > + {header && ( + + {headerContent} + + )} + {children} + ) } ) export default Card -export type { BaseCardProps, CardProps, CornerSize, CardHue, CardFillLevel } +export type { BaseCardProps, CardFillLevel, CardProps, CornerSize } diff --git a/src/stories/Card.stories.tsx b/src/stories/Card.stories.tsx index b2a96b33..91e83a38 100644 --- a/src/stories/Card.stories.tsx +++ b/src/stories/Card.stories.tsx @@ -1,12 +1,12 @@ import { Flex } from 'honorable' -import { type ComponentProps } from 'react' +import { type ComponentProps, type ReactNode } from 'react' import { useTheme } from 'styled-components' import { type FillLevel } from '../components/contexts/FillLevelContext' -import { Card } from '../index' import type { CardProps } from '../components/Card' +import { Card, InfoOutlineIcon, Tooltip } from '../index' export default { title: 'Card', @@ -16,6 +16,13 @@ export default { options: ['neutral', 'info', 'success', 'warning', 'danger', 'critical'], control: { type: 'select' }, }, + headerSize: { + options: ['medium', 'large'], + control: { type: 'select' }, + }, + headerContent: { + control: { type: 'text' }, + }, }, } @@ -32,7 +39,14 @@ function Template({ width, height, severity, -}: { width: number; height: number } & CardProps) { + headerSize, + headerContent, +}: { + width: number + height: number + headerSize: ComponentProps['header']['size'] + headerContent: ReactNode +} & CardProps) { return ( +

Header

+ + + +
+ ), } export const Clickable = Template.bind({}) From 2ddad012e82309d168527973669821825f143a61 Mon Sep 17 00:00:00 2001 From: Jake Laderman Date: Tue, 17 Dec 2024 15:32:14 -0500 Subject: [PATCH 2/7] add inactive option to chips --- src/components/Chip.tsx | 27 ++++++++++++++++++++------- src/stories/Chip.stories.tsx | 13 ++++++------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/components/Chip.tsx b/src/components/Chip.tsx index 0ffe712f..b346f515 100644 --- a/src/components/Chip.tsx +++ b/src/components/Chip.tsx @@ -22,14 +22,15 @@ import Tooltip from './Tooltip' export const CHIP_CLOSE_ATTR_KEY = 'data-close-button' as const const SIZES = ['small', 'medium', 'large'] as const -type ChipSize = (typeof SIZES)[number] -type ChipSeverity = (typeof SEVERITIES)[number] +export type ChipSize = (typeof SIZES)[number] +export type ChipSeverity = (typeof SEVERITIES)[number] export type ChipProps = Omit & BaseCardProps & { size?: ChipSize condensed?: boolean severity?: ChipSeverity + inactive?: boolean icon?: ReactElement loading?: boolean closeButton?: boolean @@ -73,15 +74,26 @@ const sizeToCloseHeight = { const ChipCardSC = styled(Card)<{ $size: ChipSize $severity: ChipSeverity + $inactive: boolean $truncateWidth?: number $truncateEdge?: 'start' | 'end' $condensed?: boolean -}>(({ $size, $severity, $truncateWidth, $truncateEdge, $condensed, theme }) => { - const textColor = - theme.colors[severityToColor[$severity]] || theme.colors.text +}>(({ + $size, + $severity, + $inactive, + $truncateWidth, + $truncateEdge, + $condensed, + theme, +}) => { + const textColor = $inactive + ? theme.colors['text-xlight'] + : theme.colors[severityToColor[$severity]] ?? theme.colors.text return { '&&': { + backgroundColor: $inactive ? 'transparent' : undefined, padding: `${$size === 'large' ? 6 : theme.spacing.xxxsmall}px ${ $size === 'large' && $condensed ? 6 @@ -164,9 +176,9 @@ function ChipRef( size = 'medium', condensed = false, severity = 'neutral', + inactive = false, truncateWidth, truncateEdge, - hue, fillLevel, loading = false, icon, @@ -180,7 +192,7 @@ function ChipRef( }: ChipProps, ref: Ref ) { - fillLevel = useDecideFillLevel({ hue, fillLevel }) + fillLevel = useDecideFillLevel({ fillLevel }) const theme = useTheme() const iconCol = severityToIconColor[severity] || 'icon-default' @@ -193,6 +205,7 @@ function ChipRef( fillLevel={fillLevel} clickable={clickable} disabled={clickable && disabled} + $inactive={inactive} $size={size} $condensed={condensed} $severity={severity} diff --git a/src/stories/Chip.stories.tsx b/src/stories/Chip.stories.tsx index 7bd0353c..7573fc19 100644 --- a/src/stories/Chip.stories.tsx +++ b/src/stories/Chip.stories.tsx @@ -13,12 +13,6 @@ export default { title: 'Chip', component: Chip, argTypes: { - hue: { - options: [undefined, 'default', 'lighter', 'lightest'], - control: { - type: 'select', - }, - }, onFillLevel: { options: [0, 1, 2, 3], control: { @@ -42,7 +36,12 @@ const sizes: ComponentProps['size'][] = [ const severities = SEVERITIES -const versionsArgs = [{}, { loading: true }, { icon: }] +const versionsArgs = [ + {}, + { loading: true }, + { icon: }, + { inactive: true }, +] function Template({ onFillLevel, asLink, ...args }: any) { if (asLink) { From 26c564a5bf01e14d0e9dbca1e5b4b743ec44aefe Mon Sep 17 00:00:00 2001 From: Jake Laderman Date: Tue, 17 Dec 2024 15:57:11 -0500 Subject: [PATCH 3/7] new icon and small fixes --- src/components/IconFrame.tsx | 2 +- src/components/icons/CostManagementIcon.tsx | 28 +++++++++++++++++++++ src/icons.ts | 3 ++- src/index.ts | 7 +++++- src/types/react-table.d.ts | 3 ++- 5 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 src/components/icons/CostManagementIcon.tsx diff --git a/src/components/IconFrame.tsx b/src/components/IconFrame.tsx index ee668491..25fd93cb 100644 --- a/src/components/IconFrame.tsx +++ b/src/components/IconFrame.tsx @@ -74,7 +74,7 @@ const sizeToIconSize: Record = { xsmall: 8, small: 16, medium: 16, - large: 24, + large: 16, xlarge: 24, } diff --git a/src/components/icons/CostManagementIcon.tsx b/src/components/icons/CostManagementIcon.tsx new file mode 100644 index 00000000..abbaf68e --- /dev/null +++ b/src/components/icons/CostManagementIcon.tsx @@ -0,0 +1,28 @@ +import createIcon from './createIcon' + +export default createIcon(({ size, color }) => ( + + + + + +)) diff --git a/src/icons.ts b/src/icons.ts index f626a48d..647a3611 100644 --- a/src/icons.ts +++ b/src/icons.ts @@ -56,6 +56,7 @@ export { default as ConfettiIcon } from './components/icons/ConfettiIcon' export { default as ConsoleIcon } from './components/icons/ConsoleIcon' export { default as CookieIcon } from './components/icons/CookieIcon' export { default as CopyIcon } from './components/icons/CopyIcon' +export { default as CostManagementIcon } from './components/icons/CostManagementIcon' export { default as CpuIcon } from './components/icons/CpuIcon' export { default as CraneIcon } from './components/icons/CraneIcon' export { default as CreditCardIcon } from './components/icons/CreditCardIcon' @@ -144,7 +145,6 @@ export { default as PadlockIcon } from './components/icons/PadlockIcon' export { default as PadlockLockedIcon } from './components/icons/PadlockLockedIcon' export { default as PaperclipIcon } from './components/icons/PaperclipIcon' export { default as PauseIcon } from './components/icons/PauseIcon' -export { default as RamIcon } from './components/icons/RamIcon' export { default as PencilIcon } from './components/icons/PencilIcon' export { default as PeopleIcon } from './components/icons/PeopleIcon' export { default as PeoplePlusIcon } from './components/icons/PeoplePlusIcon' @@ -164,6 +164,7 @@ export { default as ProtectedManagementClusterIcon } from './components/icons/Pr export { default as PrQueueIcon } from './components/icons/PrQueueIcon' export { default as PushPinFilledIcon } from './components/icons/PushPinFilledIcon' export { default as PushPinOutlineIcon } from './components/icons/PushPinOutlineIcon' +export { default as RamIcon } from './components/icons/RamIcon' export { default as ReloadIcon } from './components/icons/ReloadIcon' export { default as RestoreIcon } from './components/icons/RestoreIcon' export { default as ReturnIcon } from './components/icons/ReturnIcon' diff --git a/src/index.ts b/src/index.ts index fd61fb75..809c2dda 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,7 +19,12 @@ export type { CalloutProps } from './components/Callout' export { default as Callout } from './components/Callout' export { default as CatalogCard } from './components/CatalogCard' export { default as Checkbox } from './components/Checkbox' -export { default as Chip, type ChipProps } from './components/Chip' +export { + default as Chip, + type ChipProps, + type ChipSize, + type ChipSeverity, +} from './components/Chip' export { default as ChipList } from './components/ChipList' export { default as Code } from './components/Code' export { default as CodeEditor } from './components/CodeEditor' diff --git a/src/types/react-table.d.ts b/src/types/react-table.d.ts index 713f24fa..28c5d461 100644 --- a/src/types/react-table.d.ts +++ b/src/types/react-table.d.ts @@ -1,4 +1,5 @@ import '@tanstack/react-table' +import { type ReactNode } from 'react' declare module '@tanstack/table-core' { // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -6,7 +7,7 @@ declare module '@tanstack/table-core' { truncate?: boolean gridTemplate?: string center?: boolean - tooltip?: string + tooltip?: ReactNode highlight?: boolean } } From e1ec387010ef348445e8d95abb64146703e50d5a Mon Sep 17 00:00:00 2001 From: Jake Laderman Date: Tue, 17 Dec 2024 17:17:59 -0500 Subject: [PATCH 4/7] add prop we always add to tables by default --- src/components/table/Table.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/table/Table.tsx b/src/components/table/Table.tsx index c15b8d91..d36223aa 100644 --- a/src/components/table/Table.tsx +++ b/src/components/table/Table.tsx @@ -324,6 +324,7 @@ function TableRef( setScrollTop(target?.scrollTop) } width="100%" + height="100%" {...props} > From 113f24d9350371bdf9d38af1074bcf518ec680e2 Mon Sep 17 00:00:00 2001 From: Jake Laderman Date: Tue, 17 Dec 2024 17:57:24 -0500 Subject: [PATCH 5/7] center header content --- src/components/Card.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/Card.tsx b/src/components/Card.tsx index 7f151c9f..449454da 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -142,6 +142,8 @@ const HeaderSC = styled.div<{ size: 'medium' | 'large' }>(({ theme, fillLevel, selected, size }) => ({ ...theme.partials.text.overline, + display: 'flex', + alignItems: 'center', color: theme.colors['text-xlight'], borderBottom: `1px solid ${theme.colors[fillToNeutralBorderC[fillLevel]]}`, backgroundColor: selected From ca359f99d27d65d74951c455e94ea033901e1c97 Mon Sep 17 00:00:00 2001 From: Jake Laderman Date: Tue, 17 Dec 2024 18:33:36 -0500 Subject: [PATCH 6/7] move header outside main card content --- src/components/Card.tsx | 121 ++++++++++++++++++++++++----------- src/stories/Card.stories.tsx | 22 +++++-- 2 files changed, 100 insertions(+), 43 deletions(-) diff --git a/src/components/Card.tsx b/src/components/Card.tsx index 449454da..26546ac6 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -12,6 +12,7 @@ import { toFillLevel, useFillLevel, } from './contexts/FillLevelContext' +import WrapWithIf from './WrapWithIf' const CARD_SEVERITIES = [ 'info', @@ -38,6 +39,7 @@ type BaseCardProps = { size?: 'medium' | 'large' content?: ReactNode headerProps?: ComponentProps<'div'> + outerProps?: ComponentProps<'div'> } } @@ -137,24 +139,37 @@ const getBgColor = ({ } const HeaderSC = styled.div<{ - fillLevel: CardFillLevel - selected: boolean - size: 'medium' | 'large' -}>(({ theme, fillLevel, selected, size }) => ({ - ...theme.partials.text.overline, - display: 'flex', - alignItems: 'center', - color: theme.colors['text-xlight'], - borderBottom: `1px solid ${theme.colors[fillToNeutralBorderC[fillLevel]]}`, - backgroundColor: selected - ? theme.colors[fillToNeutralSelectedBgC[fillLevel]] - : getBgColor({ theme, fillLevel }), - height: size === 'large' ? 48 : 40, - padding: `0 ${theme.spacing.medium}px`, - overflow: 'hidden', -})) + $fillLevel: CardFillLevel + $selected: boolean + $size: 'medium' | 'large' + $cornerSize: CornerSize +}>( + ({ + theme, + $fillLevel: fillLevel, + $selected: selected, + $size: size, + $cornerSize: cornerSize, + }) => ({ + ...theme.partials.text.overline, + flexShrink: 0, + display: 'flex', + alignItems: 'center', + color: theme.colors['text-xlight'], + border: `1px solid ${theme.colors[fillToNeutralBorderC[fillLevel]]}`, + borderBottom: 'none', + borderRadius: `${theme.borderRadiuses[cornerSize]}px ${theme.borderRadiuses[cornerSize]}px 0 0`, + backgroundColor: selected + ? theme.colors[fillToNeutralSelectedBgC[fillLevel]] + : getBgColor({ theme, fillLevel }), + height: size === 'large' ? 48 : 40, + padding: `0 ${theme.spacing.medium}px`, + overflow: 'hidden', + }) +) const CardSC = styled(Div)<{ + $hasHeader: boolean $fillLevel: CardFillLevel $cornerSize: CornerSize $severity: Severity @@ -164,6 +179,7 @@ const CardSC = styled(Div)<{ }>( ({ theme, + $hasHeader, $fillLevel: fillLevel, $cornerSize: cornerSize, $severity: severity, @@ -172,8 +188,16 @@ const CardSC = styled(Div)<{ $disabled: disabled, }) => ({ ...theme.partials.reset.button, - border: `1px solid ${theme.colors[fillToNeutralBorderC[fillLevel]]}`, - borderRadius: theme.borderRadiuses[cornerSize], + border: `1px solid ${ + theme.colors[ + fillToNeutralBorderC[ + $hasHeader ? toFillLevel(fillLevel + 1) : fillLevel + ] + ] + }`, + borderRadius: $hasHeader + ? `0 0 ${theme.borderRadiuses[cornerSize]}px ${theme.borderRadiuses[cornerSize]}px` + : theme.borderRadiuses[cornerSize], backgroundColor: selected ? theme.colors[fillToNeutralSelectedBgC[fillLevel]] : getBgColor({ theme, fillLevel }), @@ -199,6 +223,14 @@ const CardSC = styled(Div)<{ }) ) +const OuterWrapSC = styled.div({ + display: 'flex', + flexDirection: 'column', + overflow: 'hidden', + width: '100%', + height: '100%', +}) + const Card = forwardRef( ( { @@ -214,7 +246,13 @@ const Card = forwardRef( }: CardProps, ref ) => { - const { size, content: headerContent, headerProps } = header ?? {} + const hasHeader = !!header + const { + size, + content: headerContent, + headerProps, + outerProps, + } = header ?? {} const mainFillLevel = useDecideFillLevel({ fillLevel }) const headerFillLevel = useDecideFillLevel({ fillLevel: mainFillLevel + 1 }) @@ -226,33 +264,40 @@ const Card = forwardRef( return ( - } > {header && ( {headerContent} )} - {children} - + + {children} + + ) } diff --git a/src/stories/Card.stories.tsx b/src/stories/Card.stories.tsx index 91e83a38..32aacdf4 100644 --- a/src/stories/Card.stories.tsx +++ b/src/stories/Card.stories.tsx @@ -1,4 +1,3 @@ -import { Flex } from 'honorable' import { type ComponentProps, type ReactNode } from 'react' import { useTheme } from 'styled-components' @@ -6,7 +5,7 @@ import { useTheme } from 'styled-components' import { type FillLevel } from '../components/contexts/FillLevelContext' import type { CardProps } from '../components/Card' -import { Card, InfoOutlineIcon, Tooltip } from '../index' +import { Card, Flex, InfoOutlineIcon, Tooltip } from '../index' export default { title: 'Card', @@ -72,7 +71,6 @@ function Template({ }} > - {fillLevels.map((fillLevel) => ( + {fillLevels.map((fillLevel, index) => ( fillLevel= {fillLevel === undefined ? 'undefined' : `"${fillLevel}"`} @@ -178,4 +181,13 @@ WithFillLevelContext.args = { clickable: false, disabled: false, width: 400, + headerSize: 'medium', + headerContent: ( + +

Header

+ + + +
+ ), } From d1f31a72ad26efe6a88c8d3c927c8e4f8dac9171 Mon Sep 17 00:00:00 2001 From: Jake Laderman Date: Thu, 19 Dec 2024 16:29:31 -0500 Subject: [PATCH 7/7] add expand and collapse list icons --- src/components/icons/CollapseListIcon.tsx | 55 +++++++++++++++++++++++ src/components/icons/ExpandListIcon.tsx | 55 +++++++++++++++++++++++ src/icons.ts | 2 + 3 files changed, 112 insertions(+) create mode 100644 src/components/icons/CollapseListIcon.tsx create mode 100644 src/components/icons/ExpandListIcon.tsx diff --git a/src/components/icons/CollapseListIcon.tsx b/src/components/icons/CollapseListIcon.tsx new file mode 100644 index 00000000..a94d8540 --- /dev/null +++ b/src/components/icons/CollapseListIcon.tsx @@ -0,0 +1,55 @@ +import createIcon from './createIcon' + +export default createIcon(({ size, color }) => ( + + + + + + + + + +)) diff --git a/src/components/icons/ExpandListIcon.tsx b/src/components/icons/ExpandListIcon.tsx new file mode 100644 index 00000000..54dcebd5 --- /dev/null +++ b/src/components/icons/ExpandListIcon.tsx @@ -0,0 +1,55 @@ +import createIcon from './createIcon' + +export default createIcon(({ size, color }) => ( + + + + + + + + + +)) diff --git a/src/icons.ts b/src/icons.ts index 647a3611..23a3c443 100644 --- a/src/icons.ts +++ b/src/icons.ts @@ -46,6 +46,7 @@ export { default as CloseRoundedIcon } from './components/icons/CloseRoundedIcon export { default as CloudIcon } from './components/icons/CloudIcon' export { default as ClusterIcon } from './components/icons/ClusterIcon' export { default as CollapseIcon } from './components/icons/CollapseIcon' +export { default as CollapseListIcon } from './components/icons/CollapseListIcon' export { default as CommandIcon } from './components/icons/CommandIcon' export { default as CompassIcon } from './components/icons/CompassIcon' export { default as CompatibilityIcon } from './components/icons/CompatibilityIcon' @@ -79,6 +80,7 @@ export { default as EmojiHoverIcon } from './components/icons/EmojiHoverIcon' export { default as EmojiIcon } from './components/icons/EmojiIcon' export { default as ErrorIcon } from './components/icons/ErrorIcon' export { default as ExpandIcon } from './components/icons/ExpandIcon' +export { default as ExpandListIcon } from './components/icons/ExpandListIcon' export { default as EyeClosedIcon } from './components/icons/EyeClosedIcon' export { default as EyeIcon } from './components/icons/EyeIcon' export { default as FastForwardIcon } from './components/icons/FastForwardIcon'