1+ import styled from "styled-components" ;
2+ import type { WorkPackage } from "../../openProjectTypes" ;
3+ import { linkToWorkPackage } from "../../services/openProjectApi" ;
4+ import {
5+ defaultWpVariables ,
6+ WorkPackageId ,
7+ WorkPackageType ,
8+ WorkPackageStatus ,
9+ WorkPackageTitle ,
10+ WorkPackageTitleLink ,
11+ } from "../WorkPackage/atoms" ;
12+ import {
13+ typeColor ,
14+ statusColor ,
15+ statusBorderColor ,
16+ statusTextColor ,
17+ statusBackgroundColor ,
18+ } from "../../services/colors" ;
19+
20+ const DESCRIPTION_MAX_CHARS = 300 ;
21+
22+ export interface BlockCardSharedProps {
23+ workPackage : WorkPackage ;
24+ inDropdown ?: boolean ;
25+ linkTitle ?: boolean ;
26+ onClick ?: ( e : React . MouseEvent < HTMLDivElement > ) => void ;
27+ }
28+
29+ function buildTitle ( workPackage : WorkPackage , linkTitle : boolean ) {
30+ const href = linkToWorkPackage ( workPackage . id ) ;
31+ if ( ! linkTitle ) return workPackage . subject ;
32+ return (
33+ < WorkPackageTitleLink
34+ href = { href }
35+ onClick = { ( e ) => {
36+ e . preventDefault ( ) ;
37+ e . stopPropagation ( ) ;
38+ window . open ( href , "_blank" , "noopener,noreferrer" ) ;
39+ } }
40+ >
41+ { workPackage . subject }
42+ </ WorkPackageTitleLink >
43+ ) ;
44+ }
45+
46+ const CardBase = styled . div < { $inDropdown : boolean } > `
47+ ${ defaultWpVariables }
48+ padding: var(--spacer-m) var(--spacer-l);
49+ background-color: var(--highlight-wp-background);
50+ border-radius: var(--bn-border-radius-small);
51+
52+ ${ ( { $inDropdown } ) =>
53+ $inDropdown &&
54+ `
55+ padding: var(--spacer-s) 0;
56+ background-color: transparent;
57+ ` }
58+ ` ;
59+
60+ const CardDetails = styled . div . attrs ( {
61+ className : "op-bn-work-package--details" ,
62+ } ) `
63+ display: flex;
64+ flex-wrap: wrap;
65+ gap: 0 10px;
66+ width: 100%;
67+ font-size: 0.86em;
68+ ` ;
69+
70+ const CardDetailsSpaced = styled ( CardDetails ) `
71+ margin-bottom: var(--spacer-s);
72+ ` ;
73+
74+ const MetaItem = styled . span `
75+ color: var(--bn-colors-highlights-gray-text);
76+ font-size: 0.9em;
77+ ` ;
78+
79+ // Description snippet used in XL — clamped to 3 lines via CSS.
80+ const DescriptionSnippet = styled . p `
81+ margin: var(--spacer-s) 0 0;
82+ padding: 0;
83+ font-size: 0.85em;
84+ color: var(--bn-colors-highlights-gray-text);
85+ line-height: 1.5;
86+ overflow: hidden;
87+ display: -webkit-box;
88+ -webkit-line-clamp: 3;
89+ -webkit-box-orient: vertical;
90+ ` ;
91+
92+ // M — Compact card: Type, ID, Status + Subject
93+ export const BlockCardM = ( {
94+ workPackage,
95+ inDropdown = false ,
96+ linkTitle = false ,
97+ onClick,
98+ cardRef,
99+ } : BlockCardSharedProps & { cardRef ?: React . Ref < HTMLDivElement > } ) => (
100+ < CardBase
101+ ref = { cardRef }
102+ className = "op-bn-work-package op-bn-work-package--m"
103+ $inDropdown = { inDropdown }
104+ onClick = { onClick }
105+ data-testid = "block-card"
106+ style = { onClick ? { cursor : "pointer" } : undefined }
107+ >
108+ < CardDetails >
109+ < WorkPackageType $color = { typeColor ( workPackage ) } >
110+ { workPackage . _links ?. type ?. title }
111+ </ WorkPackageType >
112+ < WorkPackageId > #{ workPackage . id } </ WorkPackageId >
113+ < WorkPackageStatus
114+ $baseColor = { statusColor ( workPackage ) }
115+ $borderColor = { statusBorderColor ( ) }
116+ $textColor = { statusTextColor ( ) }
117+ $bgColor = { statusBackgroundColor ( ) }
118+ >
119+ { workPackage . _links ?. status ?. title }
120+ </ WorkPackageStatus >
121+ </ CardDetails >
122+ < WorkPackageTitle > { buildTitle ( workPackage , linkTitle ) } </ WorkPackageTitle >
123+ </ CardBase >
124+ ) ;
125+
126+
127+ // L — Regular card: Type, ID, Status, Parent, Project + Subject
128+ export const BlockCardL = ( {
129+ workPackage,
130+ inDropdown = false ,
131+ linkTitle = false ,
132+ onClick,
133+ cardRef,
134+ } : BlockCardSharedProps & { cardRef ?: React . Ref < HTMLDivElement > } ) => (
135+ < CardBase
136+ ref = { cardRef }
137+ className = "op-bn-work-package op-bn-work-package--l"
138+ $inDropdown = { inDropdown }
139+ onClick = { onClick }
140+ data-testid = "block-card"
141+ style = { onClick ? { cursor : "pointer" } : undefined }
142+ >
143+ < CardDetailsSpaced >
144+ < WorkPackageType $color = { typeColor ( workPackage ) } >
145+ { workPackage . _links ?. type ?. title }
146+ </ WorkPackageType >
147+ < WorkPackageId > #{ workPackage . id } </ WorkPackageId >
148+ < WorkPackageStatus
149+ $baseColor = { statusColor ( workPackage ) }
150+ $borderColor = { statusBorderColor ( ) }
151+ $textColor = { statusTextColor ( ) }
152+ $bgColor = { statusBackgroundColor ( ) }
153+ >
154+ { workPackage . _links ?. status ?. title }
155+ </ WorkPackageStatus >
156+ { workPackage . _links ?. parent ?. title && (
157+ < MetaItem > ↑ { workPackage . _links . parent . title } </ MetaItem >
158+ ) }
159+ { workPackage . _links ?. project ?. title && (
160+ < MetaItem > ◈ { workPackage . _links . project . title } </ MetaItem >
161+ ) }
162+ </ CardDetailsSpaced >
163+ < WorkPackageTitle > { buildTitle ( workPackage , linkTitle ) } </ WorkPackageTitle >
164+ </ CardBase >
165+ ) ;
166+
167+ // XL — Full card: Type, ID, Status, Parent, Project + Subject + Description
168+ export const BlockCardXL = ( {
169+ workPackage,
170+ inDropdown = false ,
171+ linkTitle = false ,
172+ onClick,
173+ cardRef,
174+ } : BlockCardSharedProps & { cardRef ?: React . Ref < HTMLDivElement > } ) => {
175+ const rawDescription = workPackage . description ?. raw ;
176+ const snippetText = rawDescription
177+ ? rawDescription . slice ( 0 , DESCRIPTION_MAX_CHARS )
178+ : undefined ;
179+ const isTruncated = rawDescription
180+ ? rawDescription . length > DESCRIPTION_MAX_CHARS
181+ : false ;
182+
183+ return (
184+ < CardBase
185+ ref = { cardRef }
186+ className = "op-bn-work-package op-bn-work-package--xl"
187+ $inDropdown = { inDropdown }
188+ onClick = { onClick }
189+ data-testid = "block-card"
190+ style = { onClick ? { cursor : "pointer" } : undefined }
191+ >
192+ < CardDetailsSpaced >
193+ < WorkPackageType $color = { typeColor ( workPackage ) } >
194+ { workPackage . _links ?. type ?. title }
195+ </ WorkPackageType >
196+ < WorkPackageId > #{ workPackage . id } </ WorkPackageId >
197+ < WorkPackageStatus
198+ $baseColor = { statusColor ( workPackage ) }
199+ $borderColor = { statusBorderColor ( ) }
200+ $textColor = { statusTextColor ( ) }
201+ $bgColor = { statusBackgroundColor ( ) }
202+ >
203+ { workPackage . _links ?. status ?. title }
204+ </ WorkPackageStatus >
205+ { workPackage . _links ?. parent ?. title && (
206+ < MetaItem > ↑ { workPackage . _links . parent . title } </ MetaItem >
207+ ) }
208+ { workPackage . _links ?. project ?. title && (
209+ < MetaItem > ◈ { workPackage . _links . project . title } </ MetaItem >
210+ ) }
211+ </ CardDetailsSpaced >
212+ < WorkPackageTitle > { buildTitle ( workPackage , linkTitle ) } </ WorkPackageTitle >
213+ { snippetText && (
214+ < DescriptionSnippet >
215+ { snippetText }
216+ { isTruncated && "…" }
217+ </ DescriptionSnippet >
218+ ) }
219+ </ CardBase >
220+ ) ;
221+ } ;
0 commit comments