Skip to content

Commit f715208

Browse files
feat: enhance DataTable to handle empty values with hyphen display (#508)
1 parent b99b825 commit f715208

File tree

2 files changed

+168
-94
lines changed

2 files changed

+168
-94
lines changed

apps/site/src/demos/dataTableDemo.tsx

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ const SimpleDataTableExample = () => {
175175
},
176176
{
177177
id: 3,
178-
name: 'iPad Air',
178+
name: '', // Empty name to show hyphen
179179
category: {
180180
options: [
181181
{
@@ -203,12 +203,12 @@ const SimpleDataTableExample = () => {
203203
icon: <Smartphone size={16} />,
204204
},
205205
],
206-
selectedValue: 'tablet',
206+
selectedValue: '', // Empty selection to show hyphen
207207
placeholder: 'Select category...',
208208
},
209-
price: 799.99,
209+
price: 0, // Zero price to show hyphen (will be handled as empty)
210210
launchDate: {
211-
date: '2023-08-15',
211+
date: '', // Empty date to show hyphen
212212
format: 'MMM dd, yyyy',
213213
},
214214
status: {
@@ -223,7 +223,7 @@ const SimpleDataTableExample = () => {
223223
sublabel: 'SVP Hardware Engineering',
224224
imageUrl: 'https://randomuser.me/api/portraits/men/3.jpg',
225225
},
226-
rating: 4.3,
226+
rating: 0, // Zero rating to show hyphen (will be handled as empty)
227227
tags: {
228228
values: ['tablet', 'creative'],
229229
labels: ['Tablet', 'Creative'],
@@ -349,7 +349,7 @@ const SimpleDataTableExample = () => {
349349
},
350350
{
351351
id: 6,
352-
name: 'MacBook Air M3 with Revolutionary Performance and All-Day Battery Life',
352+
name: '', // Empty name to show hyphen
353353
category: {
354354
options: [
355355
{
@@ -377,12 +377,12 @@ const SimpleDataTableExample = () => {
377377
icon: <Smartphone size={16} />,
378378
},
379379
],
380-
selectedValue: 'laptop',
380+
selectedValue: '', // Empty selection to show hyphen
381381
placeholder: 'Select category...',
382382
},
383383
price: 1299.99,
384384
launchDate: {
385-
date: '2024-03-15',
385+
date: 'invalid-date', // Invalid date to show hyphen
386386
format: 'MMM dd, yyyy',
387387
},
388388
status: {
@@ -732,17 +732,20 @@ const SimpleDataTableExample = () => {
732732
📦 Product Inventory - Simple DataTable Example
733733
</h3>
734734
<p style={{ margin: 0, fontSize: '14px', color: '#166534' }}>
735-
🎯 <strong>NEW COLUMN TYPES DEMO:</strong> This table
736-
showcases <strong>DROPDOWN</strong> and{' '}
737-
<strong>DATE</strong> column types! The Category column is a
738-
dropdown menu using the SingleSelect component with{' '}
739-
<strong>icons</strong> - click on any category to see the
740-
dropdown options with category icons. The Launch Date column
741-
displays formatted dates. Both columns support sorting and
742-
can be used for filtering. Try editing the rows to see how
743-
dropdown selections work in edit mode. This is a simpler
744-
example focused on the new column types without the
745-
complexity of the main user management table above.
735+
🎯 <strong>NEW FEATURES DEMO:</strong> This table showcases{' '}
736+
<strong>DROPDOWN</strong> and <strong>DATE</strong> column
737+
types, plus <strong>EMPTY VALUE HANDLING</strong>! The
738+
Category column is a dropdown menu using the SingleSelect
739+
component with <strong>icons</strong> - click on any
740+
category to see the dropdown options with category icons.
741+
The Launch Date column displays formatted dates.{' '}
742+
<strong>🎯 HYPHEN DISPLAY:</strong> Notice that{' '}
743+
<strong>Row #3 (iPad Air)</strong> and{' '}
744+
<strong>Row #6</strong> have empty/null values in various
745+
columns that now display <strong>"-" (hyphens)</strong>{' '}
746+
instead of blank cells. This includes empty text fields,
747+
null prices, missing dates, invalid dates, and empty
748+
dropdown selections. This improves table readability!
746749
</p>
747750
</div>
748751

packages/blend/lib/components/DataTable/TableCell/index.tsx

Lines changed: 146 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,42 @@ const StyledTableCell = styled.td<{
2626
${({ $isFirstRow }) => $isFirstRow && 'border-top: none'}
2727
`
2828

29+
const isEmptyValue = (value: unknown, columnType?: ColumnType): boolean => {
30+
if (value == null) return true
31+
32+
if (typeof value === 'string' && value.trim() === '') return true
33+
34+
if (typeof value === 'number' && value === 0) return true
35+
36+
if (columnType === ColumnType.DROPDOWN) {
37+
const dropdownData = value as DropdownColumnProps
38+
if (
39+
!dropdownData ||
40+
!dropdownData.options ||
41+
!dropdownData.selectedValue ||
42+
(typeof dropdownData.selectedValue === 'string' &&
43+
dropdownData.selectedValue.trim() === '')
44+
) {
45+
return true
46+
}
47+
}
48+
49+
if (columnType === ColumnType.DATE) {
50+
const dateData = value as DateColumnProps
51+
if (
52+
!dateData ||
53+
!dateData.date ||
54+
(typeof dateData.date === 'string' &&
55+
dateData.date.trim() === '') ||
56+
isNaN(new Date(dateData.date).getTime())
57+
) {
58+
return true
59+
}
60+
}
61+
62+
return false
63+
}
64+
2965
const TruncatedTextWithTooltip = ({
3066
text,
3167
style = {},
@@ -193,78 +229,68 @@ const TableCell = forwardRef<
193229

194230
if (column.type === ColumnType.DROPDOWN && !isEditing) {
195231
const dropdownData = displayValue as DropdownColumnProps
196-
if (dropdownData && dropdownData.options) {
197-
const selectedOption = dropdownData.options.find(
198-
(opt) => opt.value === dropdownData.selectedValue
199-
)
200-
201-
const selectItems: SelectMenuGroupType[] = [
202-
{
203-
items: dropdownData.options.map((option) => ({
204-
label: option.label,
205-
value: String(option.value),
206-
slot1: option.icon || undefined,
207-
})),
208-
showSeparator: false,
209-
},
210-
]
211232

233+
if (isEmptyValue(dropdownData, ColumnType.DROPDOWN)) {
212234
return (
213-
<div style={{ width: '100%', minWidth: '150px' }}>
214-
<SingleSelect
215-
label=""
216-
placeholder={
217-
dropdownData.placeholder || 'Select...'
218-
}
219-
variant={SelectMenuVariant.NO_CONTAINER}
220-
items={selectItems}
221-
selected={String(
222-
dropdownData.selectedValue || ''
223-
)}
224-
slot={selectedOption?.icon}
225-
onSelect={(value) => {
226-
const updatedDropdownData: DropdownColumnProps =
227-
{
228-
...dropdownData,
229-
selectedValue: value,
230-
}
231-
if (onFieldChange) {
232-
onFieldChange(updatedDropdownData)
233-
}
234-
}}
235-
minMenuWidth={150}
236-
maxMenuWidth={250}
237-
/>
238-
</div>
235+
<Block
236+
style={{
237+
width: '100%',
238+
lineHeight: '1.5',
239+
cursor: 'default',
240+
}}
241+
>
242+
<TruncatedTextWithTooltip text="-" />
243+
</Block>
239244
)
240245
}
241-
}
242246

243-
if (column.type === ColumnType.DATE && !isEditing) {
244-
const dateData = displayValue as DateColumnProps
245-
if (dateData && dateData.date) {
246-
const date = new Date(dateData.date)
247-
const showTime = dateData.showTime || false
248-
249-
const formatDate = (date: Date): string => {
250-
if (isNaN(date.getTime())) return 'Invalid Date'
247+
const selectedOption = dropdownData.options.find(
248+
(opt) => opt.value === dropdownData.selectedValue
249+
)
251250

252-
const options: Intl.DateTimeFormatOptions = {
253-
year: 'numeric',
254-
month: 'short',
255-
day: '2-digit',
256-
}
251+
const selectItems: SelectMenuGroupType[] = [
252+
{
253+
items: dropdownData.options.map((option) => ({
254+
label: option.label,
255+
value: String(option.value),
256+
slot1: option.icon || undefined,
257+
})),
258+
showSeparator: false,
259+
},
260+
]
257261

258-
if (showTime) {
259-
options.hour = '2-digit'
260-
options.minute = '2-digit'
261-
}
262+
return (
263+
<div style={{ width: '100%', minWidth: '150px' }}>
264+
<SingleSelect
265+
label=""
266+
placeholder={
267+
dropdownData.placeholder || 'Select...'
268+
}
269+
variant={SelectMenuVariant.NO_CONTAINER}
270+
items={selectItems}
271+
selected={String(dropdownData.selectedValue || '')}
272+
slot={selectedOption?.icon}
273+
onSelect={(value) => {
274+
const updatedDropdownData: DropdownColumnProps =
275+
{
276+
...dropdownData,
277+
selectedValue: value,
278+
}
279+
if (onFieldChange) {
280+
onFieldChange(updatedDropdownData)
281+
}
282+
}}
283+
minMenuWidth={150}
284+
maxMenuWidth={250}
285+
/>
286+
</div>
287+
)
288+
}
262289

263-
return new Intl.DateTimeFormat('en-US', options).format(
264-
date
265-
)
266-
}
290+
if (column.type === ColumnType.DATE && !isEditing) {
291+
const dateData = displayValue as DateColumnProps
267292

293+
if (isEmptyValue(dateData, ColumnType.DATE)) {
268294
return (
269295
<Block
270296
style={{
@@ -273,18 +299,51 @@ const TableCell = forwardRef<
273299
cursor: 'default',
274300
}}
275301
>
276-
<TruncatedTextWithTooltip
277-
text={formatDate(date)}
278-
style={{
279-
fontSize:
280-
FOUNDATION_THEME.font.size.body.sm
281-
.fontSize,
282-
color: FOUNDATION_THEME.colors.gray[700],
283-
}}
284-
/>
302+
<TruncatedTextWithTooltip text="-" />
285303
</Block>
286304
)
287305
}
306+
307+
const date = new Date(dateData.date)
308+
const showTime = dateData.showTime || false
309+
310+
const formatDate = (date: Date): string => {
311+
if (isNaN(date.getTime())) return '-'
312+
313+
const options: Intl.DateTimeFormatOptions = {
314+
year: 'numeric',
315+
month: 'short',
316+
day: '2-digit',
317+
}
318+
319+
if (showTime) {
320+
options.hour = '2-digit'
321+
options.minute = '2-digit'
322+
}
323+
324+
return new Intl.DateTimeFormat('en-US', options).format(
325+
date
326+
)
327+
}
328+
329+
return (
330+
<Block
331+
style={{
332+
width: '100%',
333+
lineHeight: '1.5',
334+
cursor: 'default',
335+
}}
336+
>
337+
<TruncatedTextWithTooltip
338+
text={formatDate(date)}
339+
style={{
340+
fontSize:
341+
FOUNDATION_THEME.font.size.body.sm.fontSize,
342+
color: FOUNDATION_THEME.colors.gray[700],
343+
}}
344+
/>
345+
</Block>
346+
)
288347
}
289348

290349
if (column.renderCell) {
@@ -301,6 +360,20 @@ const TableCell = forwardRef<
301360
)
302361
}
303362

363+
if (isEmptyValue(displayValue, column.type)) {
364+
return (
365+
<Block
366+
style={{
367+
width: '100%',
368+
lineHeight: '1.5',
369+
cursor: 'default',
370+
}}
371+
>
372+
<TruncatedTextWithTooltip text="-" />
373+
</Block>
374+
)
375+
}
376+
304377
return (
305378
<Block
306379
style={{
@@ -309,9 +382,7 @@ const TableCell = forwardRef<
309382
cursor: 'default',
310383
}}
311384
>
312-
<TruncatedTextWithTooltip
313-
text={displayValue != null ? String(displayValue) : ''}
314-
/>
385+
<TruncatedTextWithTooltip text={String(displayValue)} />
315386
</Block>
316387
)
317388
}

0 commit comments

Comments
 (0)