diff --git a/src/EntityCard/EntityCard.stories.tsx b/src/EntityCard/EntityCard.stories.tsx index b592bcc..0cea076 100644 --- a/src/EntityCard/EntityCard.stories.tsx +++ b/src/EntityCard/EntityCard.stories.tsx @@ -113,13 +113,13 @@ const StatusTemplate = (props: TemplateProps) => { handleChange(value, 'users') } - const handleStatusChange = (value: string) => { - setSelectedStatus(value) + const handleStatusChange = (value: string[]) => { + setSelectedStatus(value[0]) handleChange(value, 'status') } - const handlePriorityChange = (value: string) => { - setSelectedPriority(value) + const handlePriorityChange = (value: string[]) => { + setSelectedPriority(value[0]) handleChange(value, 'priority') } @@ -203,6 +203,7 @@ export const ProgressView: Story = { assigneeOptions: allUsers, statusOptions: statuses, statusMiddle: true, + statusNameOnly: true, }, render: (args) => , } diff --git a/src/EntityCard/EntityCard.styled.ts b/src/EntityCard/EntityCard.styled.ts index f129693..bcba8e7 100644 --- a/src/EntityCard/EntityCard.styled.ts +++ b/src/EntityCard/EntityCard.styled.ts @@ -309,11 +309,6 @@ export const Card = styled.div` display: none; } } - - /* hide status */ - .status { - display: none; - } } ` @@ -520,8 +515,15 @@ export const Tag = styled.span` } ` +type StatusProps = { + $breakpoints?: { + short?: number + icon?: number + } +} + // container is always full width -export const StatusContainer = styled.div` +export const StatusContainer = styled.div` z-index: 0; position: absolute; left: 0; @@ -572,11 +574,69 @@ export const StatusContainer = styled.div` padding: 0; display: grid; grid-template-columns: 0fr; - transition: all 130ms; + /* transition: all 130ms; */ + transition: grid-template-columns 130ms, padding 130ms; span { overflow: hidden; } } + + .status-icon { + font-variation-settings: 'FILL' 1, 'wght' 200, 'GRAD' 200, 'opsz' 20; + } + + /* short is only shown when things are too small */ + .status-short { + visibility: hidden; + position: absolute; + } + } + + &.name-only { + .status { + .status-icon { + visibility: hidden; + position: absolute; + } + } + } + + & { + /* when container gets too small we show different status sizes */ + container-name: footer; + container-type: inline-size; + } + /* use container query for when the card gets smaller */ + + /* SHOW SHORT */ + @container card (inline-size < ${(props) => props.$breakpoints?.short}px) { + .status-wrapper .status.tag { + /* hide full label */ + .status-label { + visibility: hidden; + position: absolute; + } + /* show short */ + .status-short { + visibility: visible; + position: relative; + } + } + } + /* SHOW ICON */ + @container card (inline-size < ${(props) => props.$breakpoints?.icon}px) { + .status-wrapper .status.tag { + /* hide short */ + .status-short { + visibility: hidden; + position: absolute; + } + /* show icon */ + .status-icon { + visibility: visible; + position: relative; + } + } } ` diff --git a/src/EntityCard/EntityCard.tsx b/src/EntityCard/EntityCard.tsx index 76a1aa0..d5b1881 100644 --- a/src/EntityCard/EntityCard.tsx +++ b/src/EntityCard/EntityCard.tsx @@ -1,4 +1,4 @@ -import { forwardRef, KeyboardEvent, MouseEvent, useRef } from 'react' +import { forwardRef, KeyboardEvent, MouseEvent, useLayoutEffect, useRef, useState } from 'react' import { Icon, IconType } from '../Icon' import * as Styled from './EntityCard.styled' import { User, UserImagesStacked } from '../User/UserImagesStacked' @@ -52,6 +52,7 @@ export interface EntityCardProps extends React.HTMLAttributes { users?: User[] | null // bottom left status?: Status // bottom right statusMiddle?: boolean // puts status in the center and priority in the bottom right + statusNameOnly?: boolean // only show the status name unless it's too small to show, then use icon priority?: PriorityType // bottom left after users hidePriority?: boolean imageUrl?: string @@ -74,8 +75,8 @@ export interface EntityCardProps extends React.HTMLAttributes { priorityOptions?: PriorityType[] // editing callbacks onAssigneeChange?: (users: string[]) => void - onStatusChange?: (status: string) => void - onPriorityChange?: (priority: string) => void + onStatusChange?: (status: string[]) => void + onPriorityChange?: (priority: string[]) => void // other functions onThumbnailKeyDown?: (e: React.KeyboardEvent) => void onActivate?: () => void @@ -94,6 +95,7 @@ export const EntityCard = forwardRef( users, status, statusMiddle, + statusNameOnly, priority, hidePriority, imageUrl, @@ -169,6 +171,39 @@ export const EntityCard = forwardRef( } } + // used to calculate the width of the status and when to hide it + const bottomRowRef = useRef(null) + const [statusBreakpoints, setStatusBreakpoints] = useState<{ + short?: number + icon?: number + }>({}) + + useLayoutEffect(() => { + if (!bottomRowRef.current || !status) return + + const container = bottomRowRef.current + // calculate how much space things other than status take up + const containerPadding = 2 + const usersWidth = + (container.querySelector('.tag.users') as HTMLElement)?.offsetWidth + 12 || 0 + const priorityWidth = + (container.querySelector('.tag.priority') as HTMLElement)?.offsetWidth || 0 + const takenWidth = usersWidth + priorityWidth + containerPadding * 2 + + // calculate the width of the status states + const statusTextWidth = + (container.querySelector('.tag.status .status-label span') as HTMLElement)?.offsetWidth || 0 + const statusShortWidth = + (container.querySelector('.tag.status .status-short') as HTMLElement)?.offsetWidth || 0 + const statusIconWidth = + (container.querySelector('.tag.status .status-icon') as HTMLElement)?.offsetWidth || 0 + + setStatusBreakpoints({ + short: takenWidth + statusTextWidth, + icon: takenWidth + statusShortWidth, + }) + }, [bottomRowRef.current, status]) + // check thumbnail image const [isThumbnailLoading, isThumbnailError] = useImageLoader(imageUrl) // check first and second user images @@ -296,6 +331,7 @@ export const EntityCard = forwardRef( full: statusMiddle, ['hide-priority']: hidePriority, })} + ref={bottomRowRef} > {atLeastOneEditable && ( <> @@ -318,7 +354,7 @@ export const EntityCard = forwardRef( value={[status.name]} options={statusOptions} ref={statusDropdownRef} - onChange={(value) => onStatusChange(value)} + onChange={(value) => onStatusChange([value])} /> )} @@ -329,7 +365,7 @@ export const EntityCard = forwardRef( options={priorityOptions} dataKey="name" ref={priorityDropdownRef} - onChange={(value) => onPriorityChange(value[0]?.toString())} + onChange={(value) => onPriorityChange(value as string[])} /> )} @@ -369,7 +405,15 @@ export const EntityCard = forwardRef( {/* bottom center - status */} {shouldShowTag(status, 'status') && (
( onMouseEnter={(e) => handleEditableHover(e, 'status')} onClick={(e) => handleEditableHover(e, 'status')} > - {status?.icon && } + {status?.icon && ( + + )} {status?.name && ( {status.name} )} + {status?.shortName && {status.shortName}}
@@ -394,7 +445,7 @@ export const EntityCard = forwardRef( {/* bottom right - priority */} {shouldShowTag(priority && !hidePriority, 'priority') && ( handleEditableHover(e, 'priority')} onClick={(e) => handleEditableHover(e, 'priority')} >