From 4950c1db8e2c6f731abf219c844794f26c6fc91f Mon Sep 17 00:00:00 2001 From: MinJae Date: Sun, 2 Nov 2025 18:19:34 +0900 Subject: [PATCH 1/3] fix: improve color contrast in AlertBox info variant (#8297) * fix: improve color contrast in AlertBox info variant - Change info background from bg-info-600 to bg-info-400 - Improves text contrast ratio to meet WCAG 2.1 AA standards - Ensures better readability for users with visual impairments The darker background (info-400) provides better contrast with white text, meeting the minimum 4.5:1 contrast ratio requirement. Fixes #8290 * fix: resolve CSS conflict between AlertBox and markdown styles - Keep background color at bg-info-600 as per design system - Add !important to paragraph color to override markdown styles - Fix text color conflict where p tags inherit markdown paragraph styles The issue was a CSS specificity conflict. Paragraph elements inside AlertBox were inheriting black text color from markdown styles instead of maintaining the white text color defined in AlertBox styles. Fixes #8290 * improve color contrast in AlertBox info variant * fix: add span and div to text color override * fix: use wildcard selector for text color override --- packages/ui-components/src/Common/AlertBox/index.module.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ui-components/src/Common/AlertBox/index.module.css b/packages/ui-components/src/Common/AlertBox/index.module.css index c0dae1fbba9ee..25538a4b223e4 100644 --- a/packages/ui-components/src/Common/AlertBox/index.module.css +++ b/packages/ui-components/src/Common/AlertBox/index.module.css @@ -15,6 +15,10 @@ text-sm text-white; + * { + @apply text-white; + } + a { @apply font-bold text-white From 1bd3851fcf242a426441cf96deb0fe8c5d5117cb Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Sun, 2 Nov 2025 16:54:40 +0300 Subject: [PATCH 2/3] fix: Text selection in code blocks (#8301) * fix: codebox selectable code * fix: selectable text in popover --- packages/rehype-shiki/src/transformers/twoslash/index.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/rehype-shiki/src/transformers/twoslash/index.css b/packages/rehype-shiki/src/transformers/twoslash/index.css index 92b9502372b46..111ced3696eee 100644 --- a/packages/rehype-shiki/src/transformers/twoslash/index.css +++ b/packages/rehype-shiki/src/transformers/twoslash/index.css @@ -2,12 +2,14 @@ .twoslash-hover { cursor: text; + user-select: text; } .twoslash-popup-code { max-width: 600px; display: block; width: fit-content; + user-select: none; } .twoslash-popup-code:not(:has(div)) { From f58851b7cebee7b18299b45adfd44c3ec50f84ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Sun, 2 Nov 2025 10:54:54 -0300 Subject: [PATCH 3/3] test: create remark table plugin tests (#8295) --- apps/site/util/__tests__/table.test.mjs | 434 ++++++++++++++++++++++++ apps/site/util/table.ts | 5 +- 2 files changed, 436 insertions(+), 3 deletions(-) create mode 100644 apps/site/util/__tests__/table.test.mjs diff --git a/apps/site/util/__tests__/table.test.mjs b/apps/site/util/__tests__/table.test.mjs new file mode 100644 index 0000000000000..7ae4a004075a6 --- /dev/null +++ b/apps/site/util/__tests__/table.test.mjs @@ -0,0 +1,434 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import remarkTableTitles from '#site/util/table'; + +describe('remarkTableTitles', () => { + it('should add data-label attributes to table cells based on headers', () => { + const tree = { + type: 'root', + children: [ + { + type: 'table', + children: [ + // Header row + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{ type: 'text', value: 'Name' }], + }, + { + type: 'tableCell', + children: [{ type: 'text', value: 'Age' }], + }, + { + type: 'tableCell', + children: [{ type: 'text', value: 'City' }], + }, + ], + }, + // Data row + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{ type: 'text', value: 'John' }], + }, + { + type: 'tableCell', + children: [{ type: 'text', value: '25' }], + }, + { + type: 'tableCell', + children: [{ type: 'text', value: 'NYC' }], + }, + ], + }, + ], + }, + ], + }; + + const plugin = remarkTableTitles(); + plugin(tree); + + const table = tree.children[0]; + const dataRow = table.children[1]; + + assert.equal(dataRow.children[0].data.hProperties['data-label'], 'Name'); + assert.equal(dataRow.children[1].data.hProperties['data-label'], 'Age'); + assert.equal(dataRow.children[2].data.hProperties['data-label'], 'City'); + }); + + it('should handle multiple data rows', () => { + const tree = { + type: 'root', + children: [ + { + type: 'table', + children: [ + // Header row + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{ type: 'text', value: 'Product' }], + }, + { + type: 'tableCell', + children: [{ type: 'text', value: 'Price' }], + }, + ], + }, + // First data row + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{ type: 'text', value: 'Apple' }], + }, + { + type: 'tableCell', + children: [{ type: 'text', value: '$1.00' }], + }, + ], + }, + // Second data row + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{ type: 'text', value: 'Banana' }], + }, + { + type: 'tableCell', + children: [{ type: 'text', value: '$0.50' }], + }, + ], + }, + ], + }, + ], + }; + + const plugin = remarkTableTitles(); + plugin(tree); + + const table = tree.children[0]; + const firstDataRow = table.children[1]; + const secondDataRow = table.children[2]; + + assert.equal( + firstDataRow.children[0].data.hProperties['data-label'], + 'Product' + ); + assert.equal( + firstDataRow.children[1].data.hProperties['data-label'], + 'Price' + ); + assert.equal( + secondDataRow.children[0].data.hProperties['data-label'], + 'Product' + ); + assert.equal( + secondDataRow.children[1].data.hProperties['data-label'], + 'Price' + ); + }); + + it('should add data-cards="false" for single column tables', () => { + const tree = { + type: 'root', + children: [ + { + type: 'table', + children: [ + // Header row with single column + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{ type: 'text', value: 'Items' }], + }, + ], + }, + // Data row + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{ type: 'text', value: 'Item 1' }], + }, + ], + }, + ], + }, + ], + }; + + const plugin = remarkTableTitles(); + plugin(tree); + + const table = tree.children[0]; + + assert.equal(table.data.hProperties['data-cards'], 'false'); + }); + + it('should handle empty tables (less than 2 rows)', () => { + const tree = { + type: 'root', + children: [ + { + type: 'table', + children: [ + // Only header row, no data rows + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{ type: 'text', value: 'Header' }], + }, + ], + }, + ], + }, + ], + }; + + const plugin = remarkTableTitles(); + plugin(tree); + + const table = tree.children[0]; + + // Should not crash and should not modify the table + assert.equal(table.children.length, 1); + }); + + it('should handle cells with more columns than headers', () => { + const tree = { + type: 'root', + children: [ + { + type: 'table', + children: [ + // Header row with 2 columns + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{ type: 'text', value: 'Name' }], + }, + { + type: 'tableCell', + children: [{ type: 'text', value: 'Age' }], + }, + ], + }, + // Data row with 3 columns (more than headers) + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{ type: 'text', value: 'John' }], + }, + { + type: 'tableCell', + children: [{ type: 'text', value: '25' }], + }, + { + type: 'tableCell', + children: [{ type: 'text', value: 'Extra' }], + }, + ], + }, + ], + }, + ], + }; + + const plugin = remarkTableTitles(); + plugin(tree); + + const table = tree.children[0]; + const dataRow = table.children[1]; + + assert.equal(dataRow.children[0].data.hProperties['data-label'], 'Name'); + assert.equal(dataRow.children[1].data.hProperties['data-label'], 'Age'); + // Third cell should not have data-label since there's no corresponding header + assert.deepEqual(dataRow.children[2].data, undefined); + }); + + it('should handle complex header content with nested elements', () => { + const tree = { + type: 'root', + children: [ + { + type: 'table', + children: [ + // Header row with complex content + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [ + { + type: 'emphasis', + children: [{ type: 'text', value: 'Product' }], + }, + { type: 'text', value: ' Name' }, + ], + }, + { + type: 'tableCell', + children: [ + { + type: 'strong', + children: [{ type: 'text', value: 'Price' }], + }, + ], + }, + ], + }, + // Data row + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{ type: 'text', value: 'Apple' }], + }, + { + type: 'tableCell', + children: [{ type: 'text', value: '$1.00' }], + }, + ], + }, + ], + }, + ], + }; + + const plugin = remarkTableTitles(); + plugin(tree); + + const table = tree.children[0]; + const dataRow = table.children[1]; + + assert.equal( + dataRow.children[0].data.hProperties['data-label'], + 'Product Name' + ); + assert.equal(dataRow.children[1].data.hProperties['data-label'], 'Price'); + }); + + it('should preserve existing cell data and merge with new hProperties', () => { + const tree = { + type: 'root', + children: [ + { + type: 'table', + children: [ + // Header row + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{ type: 'text', value: 'Name' }], + }, + ], + }, + // Data row with existing data + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{ type: 'text', value: 'John' }], + data: { + existingProperty: 'value', + hProperties: { + className: 'existing-class', + }, + }, + }, + ], + }, + ], + }, + ], + }; + + const plugin = remarkTableTitles(); + plugin(tree); + + const table = tree.children[0]; + const dataRow = table.children[1]; + const cell = dataRow.children[0]; + + assert.equal(cell.data.existingProperty, 'value'); + assert.equal(cell.data.hProperties['data-label'], 'Name'); + }); + + it('should handle empty header cells', () => { + const tree = { + type: 'root', + children: [ + { + type: 'table', + children: [ + // Header row with empty cell + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [], + }, + { + type: 'tableCell', + children: [{ type: 'text', value: 'Age' }], + }, + ], + }, + // Data row + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{ type: 'text', value: 'John' }], + }, + { + type: 'tableCell', + children: [{ type: 'text', value: '25' }], + }, + ], + }, + ], + }, + ], + }; + + const plugin = remarkTableTitles(); + plugin(tree); + + const table = tree.children[0]; + const dataRow = table.children[1]; + + assert.equal(dataRow.children[0].data.hProperties['data-label'], ''); + assert.equal(dataRow.children[1].data.hProperties['data-label'], 'Age'); + }); +}); diff --git a/apps/site/util/table.ts b/apps/site/util/table.ts index fa1bc93f7fc91..d44646c4fb8a5 100644 --- a/apps/site/util/table.ts +++ b/apps/site/util/table.ts @@ -32,11 +32,10 @@ export default function remarkTableTitles() { // Assign data-label to each cell in data rows dataRows.forEach(row => { row.children.forEach((cell, idx) => { - cell.data ??= {}; - - if (idx > headerLabels.length) { + if (idx > headerLabels.length - 1) { return; } + cell.data ??= {}; cell.data.hProperties = { 'data-label': headerLabels[idx],