@duro-app/ui 0.43.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duro-app/ui",
3
- "version": "0.43.0",
3
+ "version": "0.44.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@tanstack/react-virtual": "^3.14.6",
58
- "@duro-app/tokens": "^0.43.0"
58
+ "@duro-app/tokens": "^0.44.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@babel/preset-typescript": "^7.28.0",
@@ -1134,3 +1134,60 @@ export const SlotPropsOnRoot: Story = {
1134
1134
  await expect(canvas.getAllByText('Name').length).toBeGreaterThan(0)
1135
1135
  },
1136
1136
  }
1137
+
1138
+ // --- Regression: many columns + long content must wrap, not overlap ---
1139
+ //
1140
+ // In a wide-but-tight container, columns whose combined content exceeds the
1141
+ // width used to keep their intrinsic size (minWidth: auto) and spill over their
1142
+ // grid track into the next column. Cells now shrink to their track and wrap.
1143
+ const overlapWrapStyles = css.create({
1144
+ wide: {width: 760},
1145
+ })
1146
+
1147
+ const LONG_EMAIL = 'a-really-long-service-account.address@subdomain.example-company.com'
1148
+
1149
+ export const ManyColumnsWrapNoOverlap: Story = {
1150
+ render: () => (
1151
+ <html.div style={overlapWrapStyles.wide}>
1152
+ <Table.Root>
1153
+ <Table.Header>
1154
+ <Table.Row>
1155
+ <Table.HeaderCell>Display Name</Table.HeaderCell>
1156
+ <Table.HeaderCell>Type</Table.HeaderCell>
1157
+ <Table.HeaderCell>Email</Table.HeaderCell>
1158
+ <Table.HeaderCell>Status</Table.HeaderCell>
1159
+ <Table.HeaderCell width="max-content">Actions</Table.HeaderCell>
1160
+ </Table.Row>
1161
+ </Table.Header>
1162
+ <Table.Body>
1163
+ <Table.Row>
1164
+ <Table.Cell>SMTP submission service account</Table.Cell>
1165
+ <Table.Cell>
1166
+ <Badge>User</Badge>
1167
+ </Table.Cell>
1168
+ <Table.Cell>{LONG_EMAIL}</Table.Cell>
1169
+ <Table.Cell>
1170
+ <Badge variant="warning">No certs</Badge>
1171
+ </Table.Cell>
1172
+ <Table.Cell isActions>
1173
+ <Button size="small">Send Cert</Button>
1174
+ <Button size="small" variant="secondary">
1175
+ Certificates
1176
+ </Button>
1177
+ <Button size="small" variant="danger">
1178
+ Revoke All
1179
+ </Button>
1180
+ </Table.Cell>
1181
+ </Table.Row>
1182
+ </Table.Body>
1183
+ </Table.Root>
1184
+ </html.div>
1185
+ ),
1186
+ play: async ({canvas}) => {
1187
+ const emailCell = canvas.getByText(LONG_EMAIL).closest('[role="cell"]') as HTMLElement
1188
+ await expect(emailCell).toBeTruthy()
1189
+ // The long email wraps inside its column instead of overflowing into the
1190
+ // next one: the cell's content is no wider than the cell box.
1191
+ await expect(emailCell.scrollWidth).toBeLessThanOrEqual(emailCell.clientWidth + 2)
1192
+ },
1193
+ }
@@ -460,7 +460,7 @@ export function Cell({children, isActions}: {children: ReactNode; isActions?: bo
460
460
  {responsive && !isActions && label !== '' ? (
461
461
  <html.span style={styles.cellLabel}>{label}</html.span>
462
462
  ) : null}
463
- {children}
463
+ {isActions ? children : <html.div style={styles.cellValue}>{children}</html.div>}
464
464
  </html.div>
465
465
  )
466
466
  }
@@ -154,6 +154,10 @@ export const styles = css.create({
154
154
  fontWeight: typography.fontWeightSemibold,
155
155
  color: colors.textMuted,
156
156
  textAlign: 'start',
157
+ // Match the body cell: shrink inside the track + wrap, so header labels
158
+ // don't overlap when many columns are cramped.
159
+ minWidth: 0,
160
+ overflowWrap: 'break-word',
157
161
  whiteSpace: {
158
162
  default: 'normal',
159
163
  [`@container (max-width: ${COMPACT_BP})`]: 'nowrap',
@@ -176,14 +180,13 @@ export const styles = css.create({
176
180
  [`@container (max-width: ${STACK_BP})`]: 'grid',
177
181
  },
178
182
  alignItems: 'center',
179
- minWidth: {
180
- // Allow the cell to shrink inside its grid track. In default (wide)
181
- // mode there's enough room so `auto` is fine; in compact and stack
182
- // we explicitly drop to 0 so wrappable content can size to the
183
- // track instead of forcing the track to grow.
184
- default: 'auto',
185
- [`@container (max-width: ${COMPACT_BP})`]: 0,
186
- },
183
+ // Always let the cell shrink inside its `minmax(0, 1fr)` track. Previously
184
+ // this was `auto` in the default (wide) band on the assumption there's
185
+ // "enough room" — but a table with many columns (or wide content) whose
186
+ // combined intrinsic width exceeds the container then can't shrink, so
187
+ // content spills its track and overlaps the next column. `0` + the
188
+ // wrapping below keeps every cell inside its column at any width.
189
+ minWidth: 0,
187
190
  overflow: {
188
191
  // Safety against pathological unbreakable content (long URLs,
189
192
  // dataless ids) at narrow widths.
@@ -191,6 +194,9 @@ export const styles = css.create({
191
194
  [`@container (max-width: ${COMPACT_BP})`]: 'hidden',
192
195
  },
193
196
  whiteSpace: 'normal',
197
+ // Break long unbreakable tokens (emails, UUIDs) so they wrap within the
198
+ // column instead of forcing it wider / overflowing.
199
+ overflowWrap: 'break-word',
194
200
  gridTemplateColumns: {
195
201
  default: null,
196
202
  [`@container (max-width: ${STACK_BP})`]: '1fr 2fr',
@@ -201,6 +207,16 @@ export const styles = css.create({
201
207
  },
202
208
  },
203
209
 
210
+ // Wrapper around a (non-actions) cell's value. A raw text node is an
211
+ // anonymous flex item with `min-width: auto`, so it refuses to shrink and
212
+ // overflows its column even when the cell itself can shrink. Giving the value
213
+ // its own `min-width: 0` flex item lets the text wrap inside the column.
214
+ cellValue: {
215
+ minWidth: 0,
216
+ flexGrow: 1,
217
+ overflowWrap: 'break-word',
218
+ },
219
+
204
220
  // Label rendered as a real <span> inside each cell. Display:none in
205
221
  // default/compact, shown only in stack — matches StyleX guidance to
206
222
  // prefer real elements over ::before/::after for non-shadow-DOM cases.