Commit 286f1972 authored by edy's avatar edy

fix(ui): refine task panel layout

parent 6acc6e51
import { useEffect, useMemo, useRef, useState } from "react" import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { createPortal } from "react-dom"
import type { TaskPanelArtifact, TaskPanelItem, TaskPanelStatus } from "@qjclaw/shared-types" import type { TaskPanelArtifact, TaskPanelItem, TaskPanelStatus } from "@qjclaw/shared-types"
import { renderExpertIcon } from "../../components/icons/AppIcons" import { renderExpertIcon } from "../../components/icons/AppIcons"
import { Panel } from "../../components/ui/Panel" import { Panel } from "../../components/ui/Panel"
...@@ -114,7 +115,10 @@ function TaskArtifactList({ ...@@ -114,7 +115,10 @@ function TaskArtifactList({
function TaskArtifacts({ item }: { item: TaskPanelItem }) { function TaskArtifacts({ item }: { item: TaskPanelItem }) {
const [copiedArtifactId, setCopiedArtifactId] = useState("") const [copiedArtifactId, setCopiedArtifactId] = useState("")
const [isMenuOpen, setIsMenuOpen] = useState(false) const [isMenuOpen, setIsMenuOpen] = useState(false)
const [popoverPosition, setPopoverPosition] = useState<{ top: number; left: number; width: number; maxHeight: number } | null>(null)
const menuRef = useRef<HTMLDivElement | null>(null) const menuRef = useRef<HTMLDivElement | null>(null)
const triggerRef = useRef<HTMLButtonElement | null>(null)
const popoverRef = useRef<HTMLDivElement | null>(null)
const copiedTimerRef = useRef<number | null>(null) const copiedTimerRef = useRef<number | null>(null)
useEffect(() => { useEffect(() => {
...@@ -125,6 +129,27 @@ function TaskArtifacts({ item }: { item: TaskPanelItem }) { ...@@ -125,6 +129,27 @@ function TaskArtifacts({ item }: { item: TaskPanelItem }) {
} }
}, []) }, [])
const updatePopoverPosition = useCallback(() => {
const triggerElement = triggerRef.current
if (!triggerElement) {
return
}
const margin = 16
const gap = 8
const triggerRect = triggerElement.getBoundingClientRect()
const width = Math.min(520, Math.max(280, window.innerWidth - margin * 2))
const left = Math.min(Math.max(triggerRect.left, margin), window.innerWidth - width - margin)
const belowTop = triggerRect.bottom + gap
const spaceBelow = window.innerHeight - belowTop - margin
const spaceAbove = triggerRect.top - margin - gap
const preferBelow = spaceBelow >= 160 || spaceBelow >= spaceAbove
const maxHeight = Math.min(220, Math.max(140, preferBelow ? spaceBelow : spaceAbove))
const top = preferBelow ? belowTop : Math.max(margin, triggerRect.top - gap - maxHeight)
setPopoverPosition({ top, left, width, maxHeight })
}, [])
useEffect(() => { useEffect(() => {
setIsMenuOpen(false) setIsMenuOpen(false)
setCopiedArtifactId("") setCopiedArtifactId("")
...@@ -137,7 +162,12 @@ function TaskArtifacts({ item }: { item: TaskPanelItem }) { ...@@ -137,7 +162,12 @@ function TaskArtifacts({ item }: { item: TaskPanelItem }) {
const handlePointerDown = (event: PointerEvent) => { const handlePointerDown = (event: PointerEvent) => {
const menuElement = menuRef.current const menuElement = menuRef.current
if (menuElement && event.target instanceof Node && !menuElement.contains(event.target)) { const popoverElement = popoverRef.current
if (
event.target instanceof Node
&& !menuElement?.contains(event.target)
&& !popoverElement?.contains(event.target)
) {
setIsMenuOpen(false) setIsMenuOpen(false)
} }
} }
...@@ -157,6 +187,21 @@ function TaskArtifacts({ item }: { item: TaskPanelItem }) { ...@@ -157,6 +187,21 @@ function TaskArtifacts({ item }: { item: TaskPanelItem }) {
} }
}, [isMenuOpen]) }, [isMenuOpen])
useEffect(() => {
if (!isMenuOpen) {
return
}
updatePopoverPosition()
window.addEventListener("resize", updatePopoverPosition)
window.addEventListener("scroll", updatePopoverPosition, true)
return () => {
window.removeEventListener("resize", updatePopoverPosition)
window.removeEventListener("scroll", updatePopoverPosition, true)
}
}, [isMenuOpen, updatePopoverPosition])
const copyArtifactUrl = async (artifactId: string, artifactUrl: string) => { const copyArtifactUrl = async (artifactId: string, artifactUrl: string) => {
try { try {
await navigator.clipboard.writeText(artifactUrl) await navigator.clipboard.writeText(artifactUrl)
...@@ -196,24 +241,57 @@ function TaskArtifacts({ item }: { item: TaskPanelItem }) { ...@@ -196,24 +241,57 @@ function TaskArtifacts({ item }: { item: TaskPanelItem }) {
return ( return (
<div className="task-panel-artifact-menu" ref={menuRef}> <div className="task-panel-artifact-menu" ref={menuRef}>
<button <button
ref={triggerRef}
type="button" type="button"
className="task-panel-artifact-trigger" className="task-panel-artifact-trigger"
aria-haspopup="menu" aria-haspopup="menu"
aria-expanded={isMenuOpen} aria-expanded={isMenuOpen}
aria-controls={item.id + "-artifact-menu"} aria-controls={item.id + "-artifact-menu"}
onClick={() => setIsMenuOpen((current) => !current)} onClick={() => {
if (!isMenuOpen) {
updatePopoverPosition()
}
setIsMenuOpen((current) => !current)
}}
> >
{item.artifacts.length} 个产物 {item.artifacts.length} 个产物
</button> </button>
{isMenuOpen ? ( {isMenuOpen && popoverPosition ? createPortal((
<div className="task-panel-artifact-popover" id={item.id + "-artifact-menu"} role="menu"> <div
ref={popoverRef}
className="task-panel-artifact-popover"
id={item.id + "-artifact-menu"}
role="menu"
style={{
top: popoverPosition.top,
left: popoverPosition.left,
width: popoverPosition.width,
maxHeight: popoverPosition.maxHeight
}}
>
<TaskArtifactList <TaskArtifactList
artifacts={item.artifacts} artifacts={item.artifacts}
copiedArtifactId={copiedArtifactId} copiedArtifactId={copiedArtifactId}
onCopy={(artifactId, artifactUrl) => void copyArtifactUrl(artifactId, artifactUrl)} onCopy={(artifactId, artifactUrl) => void copyArtifactUrl(artifactId, artifactUrl)}
/> />
</div> </div>
) : null} ), document.body) : null}
</div>
)
}
function TaskPanelLoadingState() {
return (
<div className="task-panel-loading-state" role="status" aria-label="任务列表加载中">
{Array.from({ length: 4 }, (_, index) => (
<div className="task-panel-loading-row" key={index} aria-hidden="true">
<span className="task-panel-loading-avatar" />
<span className="task-panel-loading-line task-panel-loading-line-name" />
<span className="task-panel-loading-line task-panel-loading-line-task" />
<span className="task-panel-loading-pill" />
<span className="task-panel-loading-line task-panel-loading-line-artifact" />
</div>
))}
</div> </div>
) )
} }
...@@ -224,8 +302,27 @@ export function TaskPanelView() { ...@@ -224,8 +302,27 @@ export function TaskPanelView() {
const [selectedTaskIds, setSelectedTaskIds] = useState<Record<string, string>>({}) const [selectedTaskIds, setSelectedTaskIds] = useState<Record<string, string>>({})
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [errorText, setErrorText] = useState("") const [errorText, setErrorText] = useState("")
const [greetingText, setGreetingText] = useState("")
const dateInputRef = useRef<HTMLInputElement | null>(null) const dateInputRef = useRef<HTMLInputElement | null>(null)
useEffect(() => {
const greeting = "Hi,今日任务请查收~"
let index = 0
setGreetingText("")
const timer = window.setInterval(() => {
index += 1
setGreetingText(greeting.slice(0, index))
if (index >= greeting.length) {
window.clearInterval(timer)
}
}, 42)
return () => {
window.clearInterval(timer)
}
}, [])
useEffect(() => { useEffect(() => {
let active = true let active = true
setLoading(true) setLoading(true)
...@@ -278,7 +375,8 @@ export function TaskPanelView() { ...@@ -278,7 +375,8 @@ export function TaskPanelView() {
<Panel className="task-panel-page" bodyClassName="task-panel-body"> <Panel className="task-panel-page" bodyClassName="task-panel-body">
<div className="task-panel-header"> <div className="task-panel-header">
<div className="task-panel-title-group"> <div className="task-panel-title-group">
<h1><span>任务面板</span></h1> <h1 className="task-panel-heading">任务面板</h1>
<p className="task-panel-greeting" aria-label="任务面板问候语">{greetingText}</p>
</div> </div>
<div className="task-panel-date-field"> <div className="task-panel-date-field">
<button <button
...@@ -315,7 +413,7 @@ export function TaskPanelView() { ...@@ -315,7 +413,7 @@ export function TaskPanelView() {
</div> </div>
<ScrollArea className="scroll-panel task-panel-scroll" aria-busy={loading}> <ScrollArea className="scroll-panel task-panel-scroll" aria-busy={loading}>
{loading ? <div className="empty-state task-panel-state">任务列表加载中...</div> : null} {loading ? <TaskPanelLoadingState /> : null}
{!loading && errorText ? <div className="notice error task-panel-state" role="alert">{errorText}</div> : null} {!loading && errorText ? <div className="notice error task-panel-state" role="alert">{errorText}</div> : null}
{!loading && !errorText && !items.length ? <div className="empty-state task-panel-state">当天暂无任务</div> : null} {!loading && !errorText && !items.length ? <div className="empty-state task-panel-state">当天暂无任务</div> : null}
{!loading && !errorText && items.length ? ( {!loading && !errorText && items.length ? (
...@@ -348,7 +446,7 @@ export function TaskPanelView() { ...@@ -348,7 +446,7 @@ export function TaskPanelView() {
}} }}
> >
{row.tasks.map((task) => ( {row.tasks.map((task) => (
<option key={task.id} value={task.id}>{task.taskTitle}</option> <option key={task.id} title={task.taskTitle} value={task.id}>{task.taskTitle}</option>
))} ))}
</select> </select>
</div> </div>
......
...@@ -7,18 +7,18 @@ ...@@ -7,18 +7,18 @@
.task-panel-page { .task-panel-page {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
padding: 22px; padding: 18px;
overflow: hidden; overflow: hidden;
border-radius: 28px; border-radius: 22px;
border: 1px solid var(--ui-color-border); border: 1px solid var(--ui-color-border);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(248, 252, 251, 0.96)); background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(248, 250, 252, 0.96));
box-shadow: var(--ui-shadow-panel); box-shadow: 0 16px 36px rgba(15, 23, 42, 0.07);
} }
.task-panel-body { .task-panel-body {
display: grid; display: grid;
grid-template-rows: auto minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr);
gap: 16px; gap: 12px;
min-height: 0; min-height: 0;
height: 100%; height: 100%;
padding: 0; padding: 0;
...@@ -26,10 +26,11 @@ ...@@ -26,10 +26,11 @@
.task-panel-header { .task-panel-header {
display: flex; display: flex;
align-items: center; align-items: flex-end;
justify-content: space-between; justify-content: space-between;
gap: 16px; gap: 14px;
min-width: 0; min-width: 0;
padding: 0 2px 2px;
} }
.task-panel-title-group { .task-panel-title-group {
...@@ -37,32 +38,36 @@ ...@@ -37,32 +38,36 @@
min-width: 0; min-width: 0;
} }
.task-panel-title-group h1 { .task-panel-heading {
display: inline-flex; position: absolute;
width: fit-content; width: 1px;
margin: 0; height: 1px;
padding: 4px; overflow: hidden;
border-radius: 999px; clip: rect(0 0 0 0);
border: 1px solid rgba(191, 219, 254, 0.92); white-space: nowrap;
background: linear-gradient(180deg, #ffffff 0%, #eef7ff 100%);
box-shadow: 0 10px 22px rgba(37, 99, 235, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.96);
color: #1d344f;
font-size: 18px;
line-height: 1.2;
} }
.task-panel-title-group h1 span { .task-panel-greeting {
display: inline-flex; margin: 0;
min-height: 36px; color: #334155;
align-items: center; font-size: 15px;
padding: 0 18px; font-weight: 650;
border-radius: 999px; line-height: 1.35;
background: #ffffff; letter-spacing: 0;
box-shadow: inset 0 0 0 1px rgba(226, 232, 240, 0.9);
font-weight: 800;
white-space: nowrap; white-space: nowrap;
} }
.task-panel-greeting::after {
content: "";
display: inline-block;
width: 8px;
height: 1.08em;
margin-left: 2px;
vertical-align: -0.12em;
background: linear-gradient(180deg, rgba(37, 99, 235, 0.72), rgba(59, 130, 246, 0.18));
animation: task-panel-greeting-caret 0.9s steps(1, end) infinite;
}
.task-panel-date-field { .task-panel-date-field {
position: relative; position: relative;
display: inline-grid; display: inline-grid;
...@@ -82,8 +87,8 @@ ...@@ -82,8 +87,8 @@
.task-panel-header .task-panel-date-pill { .task-panel-header .task-panel-date-pill {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 8px; gap: 7px;
min-height: 42px; min-height: 36px;
padding: 0; padding: 0;
border: 0; border: 0;
border-radius: 0; border-radius: 0;
...@@ -110,11 +115,11 @@ ...@@ -110,11 +115,11 @@
.task-panel-date-calendar { .task-panel-date-calendar {
display: grid; display: grid;
grid-template-rows: 10px 1fr; grid-template-rows: 10px 1fr;
width: 32px; width: 30px;
height: 32px; height: 30px;
overflow: hidden; overflow: hidden;
place-items: center; place-items: center;
border-radius: 9px; border-radius: 8px;
background: linear-gradient(180deg, #ffffff 0%, #eef4ff 100%); background: linear-gradient(180deg, #ffffff 0%, #eef4ff 100%);
border: 1px solid #d7e1ef; border: 1px solid #d7e1ef;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.94), 0 5px 10px rgba(15, 23, 42, 0.08); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.94), 0 5px 10px rgba(15, 23, 42, 0.08);
...@@ -134,22 +139,22 @@ ...@@ -134,22 +139,22 @@
.task-panel-date-calendar-day { .task-panel-date-calendar-day {
color: #1f2f49; color: #1f2f49;
font-size: 12px; font-size: 11px;
font-weight: 800; font-weight: 800;
line-height: 1; line-height: 1;
} }
.task-panel-date-value { .task-panel-date-value {
display: inline-flex; display: inline-flex;
min-height: 42px; min-height: 36px;
align-items: center; align-items: center;
padding: 0 16px; padding: 0 13px;
border: 1px solid rgba(203, 213, 225, 0.92); border: 1px solid rgba(203, 213, 225, 0.92);
border-radius: 999px; border-radius: 999px;
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%); background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
box-shadow: none; box-shadow: none;
font-size: 14px; font-size: 13px;
font-weight: 800; font-weight: 700;
line-height: 1; line-height: 1;
white-space: nowrap; white-space: nowrap;
} }
...@@ -162,46 +167,62 @@ ...@@ -162,46 +167,62 @@
.task-panel-scroll { .task-panel-scroll {
min-height: 0; min-height: 0;
padding-right: 4px; padding-right: 2px;
} }
.task-panel-state { .task-panel-state {
padding: 20px; display: grid;
min-height: 220px;
place-items: center;
padding: 28px;
border-radius: 14px;
color: #64748b;
font-size: 13px;
font-weight: 600;
background: linear-gradient(180deg, rgba(248, 251, 255, 0.96), rgba(255, 255, 255, 0.96));
border-color: rgba(203, 213, 225, 0.82);
} }
.task-panel-table { .task-panel-table {
display: grid; display: grid;
gap: 8px; gap: 6px;
min-width: 920px; min-width: 860px;
} }
.task-panel-row { .task-panel-row {
display: grid; display: grid;
grid-template-columns: 165px 225px 170px minmax(250px, 1fr); grid-template-columns: 150px 150px 138px minmax(280px, 1fr);
gap: 14px; gap: 12px;
align-items: center; align-items: center;
min-height: 104px; min-height: 82px;
max-height: 104px; padding: 12px 14px;
padding: 14px 16px;
overflow: visible; overflow: visible;
border-radius: 16px; border-radius: 12px;
border: 1px solid rgba(215, 216, 229, 0.96); border: 1px solid rgba(226, 232, 240, 0.96);
background: rgba(255, 255, 255, 0.92); background: rgba(255, 255, 255, 0.88);
box-shadow: 0 12px 28px rgba(17, 24, 39, 0.04); box-shadow: 0 8px 18px rgba(15, 23, 42, 0.035);
transition: border-color 160ms ease, background 160ms ease, box-shadow 160ms ease, transform 160ms ease;
} }
.task-panel-row-head { .task-panel-row-head {
min-height: 44px; min-height: 36px;
max-height: 44px;
align-items: center; align-items: center;
text-align: center; padding: 8px 14px;
padding: 10px 16px;
overflow: hidden; overflow: hidden;
border-radius: 14px; border-radius: 10px;
background: rgba(239, 246, 255, 0.78); background: rgba(248, 250, 252, 0.92);
color: #53637f; color: #64748b;
font-size: 12px; font-size: 12px;
font-weight: 800; font-weight: 700;
box-shadow: none;
}
.task-panel-row:not(.task-panel-row-head):hover,
.task-panel-row:not(.task-panel-row-head):focus-within {
border-color: rgba(147, 197, 253, 0.86);
background: #ffffff;
box-shadow: 0 12px 26px rgba(15, 23, 42, 0.07);
transform: translateY(-1px);
} }
.task-panel-row > div { .task-panel-row > div {
...@@ -209,20 +230,20 @@ ...@@ -209,20 +230,20 @@
} }
.task-panel-row:not(.task-panel-row-head) > div:nth-child(3) { .task-panel-row:not(.task-panel-row-head) > div:nth-child(3) {
padding-left: 54px; padding-left: 14px;
} }
.task-panel-row:not(.task-panel-row-head) > div:nth-child(4) { .task-panel-row:not(.task-panel-row-head) > div:nth-child(4) {
display: flex; display: flex;
position: relative; position: relative;
align-items: center; align-items: center;
justify-content: center; justify-content: flex-start;
padding-left: 0; padding-left: 0;
} }
.task-panel-expert-cell { .task-panel-expert-cell {
display: grid; display: grid;
grid-template-columns: 36px minmax(0, 1fr); grid-template-columns: 32px minmax(0, 1fr);
align-items: center; align-items: center;
gap: 10px; gap: 10px;
min-width: 0; min-width: 0;
...@@ -230,18 +251,18 @@ ...@@ -230,18 +251,18 @@
.task-panel-expert-icon { .task-panel-expert-icon {
display: grid; display: grid;
width: 36px; width: 32px;
height: 36px; height: 32px;
place-items: center; place-items: center;
border-radius: 12px; border-radius: 10px;
color: #2563eb; color: #2563eb;
background: #eff6ff; background: #eff6ff;
border: 1px solid #dbeafe; border: 1px solid #dbeafe;
} }
.task-panel-expert-icon svg { .task-panel-expert-icon svg {
width: 21px; width: 19px;
height: 21px; height: 19px;
} }
.task-panel-expert-icon-planner { .task-panel-expert-icon-planner {
...@@ -272,7 +293,7 @@ ...@@ -272,7 +293,7 @@
min-width: 0; min-width: 0;
color: #1c324d; color: #1c324d;
font-size: 13px; font-size: 13px;
font-weight: 500; font-weight: 650;
line-height: 1.4; line-height: 1.4;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
...@@ -280,23 +301,38 @@ ...@@ -280,23 +301,38 @@
} }
.task-panel-task-cell { .task-panel-task-cell {
display: flex;
min-width: 0; min-width: 0;
} }
.task-panel-task-cell select { .task-panel-task-cell select {
width: 100%; width: 10em;
max-width: 100%;
min-width: 0; min-width: 0;
min-height: 38px; min-height: 34px;
padding: 0 34px 0 12px; padding: 0 34px 0 12px;
border-radius: 12px; border-radius: 10px;
border: 1px solid #d8e1ef; border: 1px solid #d8e1ef;
background: #ffffff; background: #f8fafc;
color: #1f2f49; color: #1f2f49;
font: inherit; font: inherit;
font-size: 13px; font-size: 13px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
cursor: pointer;
transition: border-color 160ms ease, background 160ms ease, box-shadow 160ms ease;
}
.task-panel-task-cell select:hover,
.task-panel-task-cell select:focus-visible {
border-color: #93c5fd;
background: #ffffff;
}
.task-panel-task-cell select:focus-visible {
outline: 2px solid rgba(37, 99, 235, 0.26);
outline-offset: 2px;
} }
.task-panel-status { .task-panel-status {
...@@ -309,8 +345,8 @@ ...@@ -309,8 +345,8 @@
.task-panel-status-icon { .task-panel-status-icon {
display: grid; display: grid;
flex: 0 0 auto; flex: 0 0 auto;
width: 34px; width: 28px;
height: 28px; height: 24px;
place-items: center; place-items: center;
border-radius: 999px; border-radius: 999px;
font-size: 13px; font-size: 13px;
...@@ -321,8 +357,8 @@ ...@@ -321,8 +357,8 @@
.task-panel-status-text { .task-panel-status-text {
min-width: 0; min-width: 0;
color: #1f2f49; color: #1f2f49;
font-size: 13px; font-size: 12.5px;
font-weight: 600; font-weight: 650;
line-height: 1.5; line-height: 1.5;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
...@@ -386,8 +422,8 @@ ...@@ -386,8 +422,8 @@
.task-panel-artifact-list { .task-panel-artifact-list {
display: grid; display: grid;
gap: 6px; gap: 5px;
width: min(100%, 420px); width: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
...@@ -400,7 +436,7 @@ ...@@ -400,7 +436,7 @@
width: 100%; width: 100%;
min-width: 0; min-width: 0;
align-items: center; align-items: center;
justify-content: center; justify-content: flex-start;
} }
.task-panel-artifact-menu { .task-panel-artifact-menu {
...@@ -409,24 +445,24 @@ ...@@ -409,24 +445,24 @@
width: 100%; width: 100%;
min-width: 0; min-width: 0;
align-items: center; align-items: center;
justify-content: center; justify-content: flex-start;
z-index: 4; z-index: 4;
} }
.task-panel-artifact-trigger { .task-panel-artifact-trigger {
display: inline-flex; display: inline-flex;
min-height: 34px; min-height: 32px;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0 14px; padding: 0 12px;
border-radius: 999px; border-radius: 10px;
border: 1px solid #cfe0f5; border: 1px solid #cfe0f5;
background: #ffffff; background: #ffffff;
color: #1f2f49; color: #1f2f49;
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.06); box-shadow: none;
font: inherit; font: inherit;
font-size: 13px; font-size: 13px;
font-weight: 800; font-weight: 700;
line-height: 1; line-height: 1;
white-space: nowrap; white-space: nowrap;
cursor: pointer; cursor: pointer;
...@@ -445,19 +481,15 @@ ...@@ -445,19 +481,15 @@
} }
.task-panel-artifact-popover { .task-panel-artifact-popover {
position: absolute; position: fixed;
top: calc(100% + 8px);
left: 50%;
width: min(520px, calc(100vw - 64px));
max-height: 220px;
overflow-y: auto; overflow-y: auto;
padding: 8px; padding: 6px;
border-radius: 14px; border-radius: 12px;
border: 1px solid rgba(203, 213, 225, 0.96); border: 1px solid rgba(203, 213, 225, 0.96);
background: #ffffff; background: #ffffff;
box-shadow: 0 18px 42px rgba(15, 23, 42, 0.14); box-shadow: 0 18px 42px rgba(15, 23, 42, 0.14);
transform: translateX(-50%); transform: none;
z-index: 20; z-index: 1200;
} }
.task-panel-artifact-popover .task-panel-artifact-list { .task-panel-artifact-popover .task-panel-artifact-list {
...@@ -467,25 +499,32 @@ ...@@ -467,25 +499,32 @@
.task-panel-artifact-list li { .task-panel-artifact-list li {
display: grid; display: grid;
position: relative; position: relative;
grid-template-columns: minmax(110px, 0.86fr) minmax(0, 1.34fr); grid-template-columns: minmax(96px, 0.72fr) minmax(0, 1.28fr) auto;
align-items: center; align-items: center;
gap: 4px; gap: 8px;
min-width: 0; min-width: 0;
min-height: 34px; min-height: 32px;
padding: 6px 10px; padding: 5px 8px;
border-radius: 12px; border-radius: 9px;
background: #f8fbff; background: rgba(248, 250, 252, 0.88);
border: 1px solid #e3ebf5; border: 1px solid transparent;
transition: border-color 160ms ease, background 160ms ease;
}
.task-panel-artifact-list li:hover,
.task-panel-artifact-list li:focus-within {
border-color: #dbeafe;
background: #ffffff;
} }
.task-panel-artifact-list li.task-panel-artifact-item-copied { .task-panel-artifact-list li.task-panel-artifact-item-copied {
padding-right: 58px; padding-right: 8px;
} }
.task-panel-artifact-name, .task-panel-artifact-name,
.task-panel-artifact-url { .task-panel-artifact-url {
min-width: 0; min-width: 0;
font-size: 13px; font-size: 12.5px;
line-height: 1.5; line-height: 1.5;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
...@@ -494,19 +533,17 @@ ...@@ -494,19 +533,17 @@
.task-panel-artifact-name { .task-panel-artifact-name {
color: #1f2f49; color: #1f2f49;
font-weight: 700; font-weight: 750;
} }
.task-panel-artifact-url { .task-panel-artifact-url {
color: #60728c; color: #60728c;
justify-self: end;
width: 100%;
min-height: 24px; min-height: 24px;
padding: 0; padding: 0;
border: 0; border: 0;
background: transparent; background: transparent;
font: inherit; font: inherit;
text-align: right; text-align: left;
cursor: pointer; cursor: pointer;
} }
...@@ -524,18 +561,90 @@ ...@@ -524,18 +561,90 @@
} }
.task-panel-artifact-copied { .task-panel-artifact-copied {
position: absolute; position: static;
right: 10px;
top: 50%;
color: #047857; color: #047857;
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 650;
line-height: 1.5; line-height: 1.5;
text-align: right; text-align: right;
transform: translateY(-50%);
white-space: nowrap; white-space: nowrap;
} }
.task-panel-loading-state {
display: grid;
gap: 6px;
min-width: 860px;
}
.task-panel-loading-row {
display: grid;
grid-template-columns: 32px 106px 150px 138px minmax(280px, 1fr);
gap: 12px;
align-items: center;
min-height: 82px;
padding: 12px 14px;
border-radius: 12px;
border: 1px solid rgba(226, 232, 240, 0.78);
background: rgba(255, 255, 255, 0.76);
}
.task-panel-loading-avatar,
.task-panel-loading-line,
.task-panel-loading-pill {
display: block;
overflow: hidden;
background: linear-gradient(90deg, #eef2f7 0%, #f8fafc 48%, #eef2f7 100%);
background-size: 220% 100%;
animation: task-panel-loading-shimmer 1.3s ease-in-out infinite;
}
.task-panel-loading-avatar {
width: 32px;
height: 32px;
border-radius: 10px;
}
.task-panel-loading-line {
height: 12px;
border-radius: 999px;
}
.task-panel-loading-line-name {
width: 88px;
}
.task-panel-loading-line-task {
width: min(100%, 10em);
}
.task-panel-loading-pill {
width: 78px;
height: 24px;
border-radius: 999px;
}
.task-panel-loading-line-artifact {
width: min(100%, 360px);
}
@keyframes task-panel-greeting-caret {
0%, 49% {
opacity: 1;
}
50%, 100% {
opacity: 0.18;
}
}
@keyframes task-panel-loading-shimmer {
0% {
background-position: 120% 0;
}
100% {
background-position: -120% 0;
}
}
@keyframes task-status-dot-bounce { @keyframes task-status-dot-bounce {
0%, 80%, 100% { 0%, 80%, 100% {
opacity: 0.42; opacity: 0.42;
...@@ -554,6 +663,11 @@ ...@@ -554,6 +663,11 @@
} }
@media (max-width: 720px) { @media (max-width: 720px) {
.task-panel-page {
padding: 14px;
border-radius: 18px;
}
.task-panel-header { .task-panel-header {
align-items: stretch; align-items: stretch;
flex-direction: column; flex-direction: column;
...@@ -562,4 +676,52 @@ ...@@ -562,4 +676,52 @@
.task-panel-date-field { .task-panel-date-field {
width: fit-content; width: fit-content;
} }
.task-panel-table,
.task-panel-loading-state {
min-width: 0;
}
.task-panel-row {
grid-template-columns: minmax(0, 1fr);
gap: 10px;
min-height: 0;
}
.task-panel-row-head {
display: none;
}
.task-panel-row:not(.task-panel-row-head) > div:nth-child(3) {
padding-left: 0;
}
.task-panel-loading-row {
grid-template-columns: 32px minmax(0, 1fr);
}
.task-panel-loading-line-task,
.task-panel-loading-pill,
.task-panel-loading-line-artifact {
grid-column: 1 / -1;
}
}
@media (prefers-reduced-motion: reduce) {
.task-panel-greeting::after,
.task-panel-running-dots span,
.task-panel-loading-avatar,
.task-panel-loading-line,
.task-panel-loading-pill {
animation: none;
}
.task-panel-row {
transition: none;
}
.task-panel-row:not(.task-panel-row-head):hover,
.task-panel-row:not(.task-panel-row-head):focus-within {
transform: none;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment