Skip to content

Commit

Permalink
Merge pull request #172 from ynput/develop
Browse files Browse the repository at this point in the history
Release: EntityCard responsive status sizes
  • Loading branch information
Innders authored Aug 14, 2024
2 parents 960f911 + f38e6f2 commit fab69e3
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 19 deletions.
9 changes: 5 additions & 4 deletions src/EntityCard/EntityCard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}

Expand Down Expand Up @@ -203,6 +203,7 @@ export const ProgressView: Story = {
assigneeOptions: allUsers,
statusOptions: statuses,
statusMiddle: true,
statusNameOnly: true,
},
render: (args) => <StatusTemplate {...args} />,
}
Expand Down
74 changes: 67 additions & 7 deletions src/EntityCard/EntityCard.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,11 +309,6 @@ export const Card = styled.div<CardProps>`
display: none;
}
}
/* hide status */
.status {
display: none;
}
}
`

Expand Down Expand Up @@ -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<StatusProps>`
z-index: 0;
position: absolute;
left: 0;
Expand Down Expand Up @@ -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;
}
}
}
`

Expand Down
67 changes: 59 additions & 8 deletions src/EntityCard/EntityCard.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -52,6 +52,7 @@ export interface EntityCardProps extends React.HTMLAttributes<HTMLDivElement> {
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
Expand All @@ -74,8 +75,8 @@ export interface EntityCardProps extends React.HTMLAttributes<HTMLDivElement> {
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<HTMLDivElement>) => void
onActivate?: () => void
Expand All @@ -94,6 +95,7 @@ export const EntityCard = forwardRef<HTMLDivElement, EntityCardProps>(
users,
status,
statusMiddle,
statusNameOnly,
priority,
hidePriority,
imageUrl,
Expand Down Expand Up @@ -169,6 +171,39 @@ export const EntityCard = forwardRef<HTMLDivElement, EntityCardProps>(
}
}

// used to calculate the width of the status and when to hide it
const bottomRowRef = useRef<HTMLDivElement>(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
Expand Down Expand Up @@ -296,6 +331,7 @@ export const EntityCard = forwardRef<HTMLDivElement, EntityCardProps>(
full: statusMiddle,
['hide-priority']: hidePriority,
})}
ref={bottomRowRef}
>
{atLeastOneEditable && (
<>
Expand All @@ -318,7 +354,7 @@ export const EntityCard = forwardRef<HTMLDivElement, EntityCardProps>(
value={[status.name]}
options={statusOptions}
ref={statusDropdownRef}
onChange={(value) => onStatusChange(value)}
onChange={(value) => onStatusChange([value])}
/>
)}

Expand All @@ -329,7 +365,7 @@ export const EntityCard = forwardRef<HTMLDivElement, EntityCardProps>(
options={priorityOptions}
dataKey="name"
ref={priorityDropdownRef}
onChange={(value) => onPriorityChange(value[0]?.toString())}
onChange={(value) => onPriorityChange(value as string[])}
/>
)}
</Styled.Editor>
Expand Down Expand Up @@ -369,7 +405,15 @@ export const EntityCard = forwardRef<HTMLDivElement, EntityCardProps>(
{/* bottom center - status */}
{shouldShowTag(status, 'status') && (
<Styled.StatusContainer
className={clsx('status-container', { middle: statusMiddle })}
className={clsx(
'status-container',
{
middle: statusMiddle,
'name-only': statusNameOnly,
},
`variant-${variant}`,
)}
$breakpoints={statusBreakpoints}
>
<div className="status-wrapper">
<Styled.Tag
Expand All @@ -380,12 +424,19 @@ export const EntityCard = forwardRef<HTMLDivElement, EntityCardProps>(
onMouseEnter={(e) => handleEditableHover(e, 'status')}
onClick={(e) => handleEditableHover(e, 'status')}
>
{status?.icon && <Icon icon={status.icon} style={{ color: status.color }} />}
{status?.icon && (
<Icon
icon={status.icon}
className="status-icon"
style={{ color: status.color }}
/>
)}
{status?.name && (
<span className="expander status-label">
<span>{status.name}</span>
</span>
)}
{status?.shortName && <span className="status-short">{status.shortName}</span>}
</Styled.Tag>
</div>
</Styled.StatusContainer>
Expand All @@ -394,7 +445,7 @@ export const EntityCard = forwardRef<HTMLDivElement, EntityCardProps>(
{/* bottom right - priority */}
{shouldShowTag(priority && !hidePriority, 'priority') && (
<Styled.Tag
className={clsx('tag', { editable: priorityEditable, isLoading })}
className={clsx('tag priority', { editable: priorityEditable, isLoading })}
onMouseEnter={(e) => handleEditableHover(e, 'priority')}
onClick={(e) => handleEditableHover(e, 'priority')}
>
Expand Down

0 comments on commit fab69e3

Please sign in to comment.