diff --git a/packages/web-ui/src/components/ui/SchemaDiagram.tsx b/packages/web-ui/src/components/ui/SchemaDiagram.tsx index 397658b6b..af45e3148 100644 --- a/packages/web-ui/src/components/ui/SchemaDiagram.tsx +++ b/packages/web-ui/src/components/ui/SchemaDiagram.tsx @@ -17,6 +17,7 @@ import '@xyflow/react/dist/style.css'; import dagre from 'dagre'; import {useEditor} from '@/hooks/useEditor'; import {SchemaTableNodeComponent} from './SchemaTableNode'; +import {SchemaDiagramLegend} from './SchemaDiagramLegend'; import { Loader2, ArrowLeftRight, @@ -437,7 +438,9 @@ const SchemaDiagramContent = ({ )} {/* Toolbar per i controlli */} -
+
+ + + + {isOpen && ( +
+ {/* Primary Key Item */} +
+
+ +
+
+ + {t('erDiagram.primaryKey', 'Primary Key')} + + + {t( + 'erDiagram.primaryKeyDesc', + 'Unique column identifying rows in the table', + )} + +
+
+ + {/* Foreign Key Item */} +
+
+ +
+
+ + {t('erDiagram.foreignKey', 'Foreign Key')} + + + {t( + 'erDiagram.foreignKeyDesc', + 'References a primary or unique key in another table', + )} + +
+
+ + {/* Relationship Connector */} +
+
+
+
+
+ + {t('erDiagram.relationship', 'Relationship')} + + + {t( + 'erDiagram.relationshipDesc', + 'Foreign key reference connecting child and parent tables', + )} + +
+
+
+ )} +
+ ); +}); diff --git a/packages/web-ui/src/i18n/locales/de.json b/packages/web-ui/src/i18n/locales/de.json index 72637680c..ef8f15180 100644 --- a/packages/web-ui/src/i18n/locales/de.json +++ b/packages/web-ui/src/i18n/locales/de.json @@ -2426,7 +2426,14 @@ "exportSuccess": "Diagramm exportiert", "exportDbml": "Als DBML exportieren", "exportMarkdown": "Als Markdown exportieren", - "export": "Exportieren" + "export": "Exportieren", + "legend": "Legende", + "primaryKey": "Primärschlüssel", + "primaryKeyDesc": "Eindeutige Spalte zur Zeilenidentifikation in der Tabelle", + "foreignKey": "Fremdschlüssel", + "foreignKeyDesc": "Verweist auf einen Primär- oder eindeutigen Schlüssel einer anderen Tabelle", + "relationship": "Beziehung", + "relationshipDesc": "Fremdschlüssel-Referenz zwischen untergeordneter und übergeordneter Tabelle" }, "views": { "createView": "Ansicht erstellen", diff --git a/packages/web-ui/src/i18n/locales/en.json b/packages/web-ui/src/i18n/locales/en.json index f10a31562..e09f0aece 100644 --- a/packages/web-ui/src/i18n/locales/en.json +++ b/packages/web-ui/src/i18n/locales/en.json @@ -2497,7 +2497,14 @@ "exportSuccess": "Diagram exported", "exportDbml": "Export as DBML", "exportMarkdown": "Export as Markdown", - "export": "Export" + "export": "Export", + "legend": "Legend", + "primaryKey": "Primary Key", + "primaryKeyDesc": "Unique column identifying rows in the table", + "foreignKey": "Foreign Key", + "foreignKeyDesc": "References a primary or unique key in another table", + "relationship": "Relationship", + "relationshipDesc": "Foreign key reference connecting child and parent tables" }, "views": { "createView": "Create View", diff --git a/packages/web-ui/src/i18n/locales/es.json b/packages/web-ui/src/i18n/locales/es.json index 7debbfc09..88e991520 100644 --- a/packages/web-ui/src/i18n/locales/es.json +++ b/packages/web-ui/src/i18n/locales/es.json @@ -2397,7 +2397,14 @@ "exportSuccess": "Diagrama exportado", "exportDbml": "Exportar como DBML", "exportMarkdown": "Exportar como Markdown", - "export": "Exportar" + "export": "Exportar", + "legend": "Leyenda", + "primaryKey": "Clave primaria", + "primaryKeyDesc": "Columna única que identifica filas en la tabla", + "foreignKey": "Clave foránea", + "foreignKeyDesc": "Hace referencia a una clave primaria o única en otra tabla", + "relationship": "Relación", + "relationshipDesc": "Referencia de clave foránea entre tablas secundarias y principales" }, "views": { "createView": "Crear Vista", diff --git a/packages/web-ui/src/i18n/locales/fr.json b/packages/web-ui/src/i18n/locales/fr.json index 0bf55c160..e7d98fc15 100644 --- a/packages/web-ui/src/i18n/locales/fr.json +++ b/packages/web-ui/src/i18n/locales/fr.json @@ -2466,7 +2466,14 @@ "exportSuccess": "Diagramme exporté", "exportDbml": "Exporter en DBML", "exportMarkdown": "Exporter en Markdown", - "export": "Exporter" + "export": "Exporter", + "legend": "Légende", + "primaryKey": "Clé primaire", + "primaryKeyDesc": "Colonne unique identifiant les lignes de la table", + "foreignKey": "Clé étrangère", + "foreignKeyDesc": "Fait référence à une clé primaire ou unique dans une autre table", + "relationship": "Relation", + "relationshipDesc": "Référence de clé étrangère reliant les tables enfants et parentes" }, "views": { "createView": "Créer une vue", diff --git a/packages/web-ui/src/i18n/locales/it.json b/packages/web-ui/src/i18n/locales/it.json index 8753055b6..82783f80f 100644 --- a/packages/web-ui/src/i18n/locales/it.json +++ b/packages/web-ui/src/i18n/locales/it.json @@ -2486,7 +2486,14 @@ "exportSuccess": "Diagramma esportato", "exportDbml": "Esporta come DBML", "exportMarkdown": "Esporta come Markdown", - "export": "Esporta" + "export": "Esporta", + "legend": "Legenda", + "primaryKey": "Chiave primaria", + "primaryKeyDesc": "Colonna univoca che identifica le righe della tabella", + "foreignKey": "Chiave esterna", + "foreignKeyDesc": "Fa riferimento a una chiave primaria o univoca in un'altra tabella", + "relationship": "Relazione", + "relationshipDesc": "Riferimento di chiave esterna tra tabella figlia e genitore" }, "views": { "createView": "Crea Vista", diff --git a/packages/web-ui/src/i18n/locales/ja.json b/packages/web-ui/src/i18n/locales/ja.json index e2c7e5309..9d1c34a70 100644 --- a/packages/web-ui/src/i18n/locales/ja.json +++ b/packages/web-ui/src/i18n/locales/ja.json @@ -2437,7 +2437,14 @@ "exportSuccess": "図をエクスポートしました", "exportDbml": "DBML としてエクスポート", "exportMarkdown": "Markdown としてエクスポート", - "export": "エクスポート" + "export": "エクスポート", + "legend": "凡例", + "primaryKey": "主キー", + "primaryKeyDesc": "テーブル内の行を一意に識別する列", + "foreignKey": "外部キー", + "foreignKeyDesc": "別のテーブルの主キーまたは一意キーを参照します", + "relationship": "リレーション", + "relationshipDesc": "子テーブルと親テーブルを結ぶ外部キー参照" }, "views": { "createView": "ビューを作成", diff --git a/packages/web-ui/src/i18n/locales/ko.json b/packages/web-ui/src/i18n/locales/ko.json index de2e6ca05..d9c7f0dff 100644 --- a/packages/web-ui/src/i18n/locales/ko.json +++ b/packages/web-ui/src/i18n/locales/ko.json @@ -2400,7 +2400,14 @@ "exportSuccess": "다이어그램 내보내기 완료", "exportDbml": "DBML로 내보내기", "exportMarkdown": "Markdown으로 내보내기", - "export": "내보내기" + "export": "내보내기", + "legend": "범례", + "primaryKey": "기본 키", + "primaryKeyDesc": "테이블의 행을 고유하게 식별하는 열", + "foreignKey": "외래 키", + "foreignKeyDesc": "다른 테이블의 기본 키 또는 고유 키를 참조합니다", + "relationship": "관계", + "relationshipDesc": "하위 테이블과 상위 테이블을 연결하는 외래 키 참조" }, "views": { "createView": "뷰 생성", diff --git a/packages/web-ui/src/i18n/locales/pt-BR.json b/packages/web-ui/src/i18n/locales/pt-BR.json index b11d8dddc..72adf2eb7 100644 --- a/packages/web-ui/src/i18n/locales/pt-BR.json +++ b/packages/web-ui/src/i18n/locales/pt-BR.json @@ -2444,7 +2444,14 @@ "exportSuccess": "Diagrama exportado", "exportDbml": "Exportar como DBML", "exportMarkdown": "Exportar como Markdown", - "export": "Exportar" + "export": "Exportar", + "legend": "Legenda", + "primaryKey": "Chave primária", + "primaryKeyDesc": "Coluna exclusiva que identifica linhas na tabela", + "foreignKey": "Chave estrangeira", + "foreignKeyDesc": "Faz referência a uma chave primária ou exclusiva em outra tabela", + "relationship": "Relacionamento", + "relationshipDesc": "Referência de chave estrangeira entre tabelas dependentes e principais" }, "views": { "createView": "Criar View", diff --git a/packages/web-ui/src/i18n/locales/ru.json b/packages/web-ui/src/i18n/locales/ru.json index 18bb1ef02..ca263391b 100644 --- a/packages/web-ui/src/i18n/locales/ru.json +++ b/packages/web-ui/src/i18n/locales/ru.json @@ -2449,7 +2449,14 @@ "exportSuccess": "Диаграмма экспортирована", "exportDbml": "Экспортировать как DBML", "exportMarkdown": "Экспортировать как Markdown", - "export": "Экспорт" + "export": "Экспорт", + "legend": "Легенда", + "primaryKey": "Первичный ключ", + "primaryKeyDesc": "Уникальный столбец, идентифицирующий строки в таблице", + "foreignKey": "Внешний ключ", + "foreignKeyDesc": "Ссылается на первичный или уникальный ключ в другой таблице", + "relationship": "Связь", + "relationshipDesc": "Связь по внешнему ключу между дочерней и родительской таблицами" }, "views": { "createView": "Создать представление", diff --git a/packages/web-ui/src/i18n/locales/tl.json b/packages/web-ui/src/i18n/locales/tl.json index 76d85ef84..60faa8a10 100644 --- a/packages/web-ui/src/i18n/locales/tl.json +++ b/packages/web-ui/src/i18n/locales/tl.json @@ -2456,7 +2456,14 @@ "exportSuccess": "Na-export ang diagram", "exportDbml": "I-export bilang DBML", "exportMarkdown": "I-export bilang Markdown", - "export": "I-export" + "export": "I-export", + "legend": "Gabay", + "primaryKey": "Primary Key", + "primaryKeyDesc": "Natatanging column na tumutukoy sa mga hilera sa talahanayan", + "foreignKey": "Foreign Key", + "foreignKeyDesc": "Sumasangguni sa primary o natatanging key sa ibang talahanayan", + "relationship": "Ugnayan", + "relationshipDesc": "Sanggunian ng foreign key na nag-uugnay sa mga kaugnay na talahanayan" }, "views": { "createView": "Gumawa ng View", diff --git a/packages/web-ui/src/i18n/locales/vi.json b/packages/web-ui/src/i18n/locales/vi.json index f65e89290..e232120d4 100644 --- a/packages/web-ui/src/i18n/locales/vi.json +++ b/packages/web-ui/src/i18n/locales/vi.json @@ -2463,7 +2463,14 @@ "exportSuccess": "Đã xuất sơ đồ", "exportDbml": "Xuất ra DBML", "exportMarkdown": "Xuất ra Markdown", - "export": "Xuất" + "export": "Xuất", + "legend": "Chú giải", + "primaryKey": "Khóa chính", + "primaryKeyDesc": "Cột duy nhất xác định các hàng trong bảng", + "foreignKey": "Khóa ngoại", + "foreignKeyDesc": "Tham chiếu khóa chính hoặc khóa duy nhất trong bảng khác", + "relationship": "Quan hệ", + "relationshipDesc": "Tham chiếu khóa ngoại kết nối bảng con và bảng cha" }, "views": { "createView": "Tạo view", diff --git a/packages/web-ui/src/i18n/locales/zh-TW.json b/packages/web-ui/src/i18n/locales/zh-TW.json index 426fff367..7e7f03316 100644 --- a/packages/web-ui/src/i18n/locales/zh-TW.json +++ b/packages/web-ui/src/i18n/locales/zh-TW.json @@ -2462,7 +2462,14 @@ "exportSuccess": "圖表已匯出", "exportDbml": "匯出為 DBML", "exportMarkdown": "匯出為 Markdown", - "export": "匯出" + "export": "匯出", + "legend": "圖例", + "primaryKey": "主鍵", + "primaryKeyDesc": "唯一識別表中列的欄位", + "foreignKey": "外鍵", + "foreignKeyDesc": "引用另一個表中的主鍵或唯一鍵", + "relationship": "關聯", + "relationshipDesc": "連接子表與父表的外鍵引用" }, "views": { "createView": "建立檢視", diff --git a/packages/web-ui/src/i18n/locales/zh.json b/packages/web-ui/src/i18n/locales/zh.json index b0b06d97f..aa0367a5e 100644 --- a/packages/web-ui/src/i18n/locales/zh.json +++ b/packages/web-ui/src/i18n/locales/zh.json @@ -2379,7 +2379,14 @@ "exportSuccess": "图表已导出", "exportDbml": "导出为 DBML", "exportMarkdown": "导出为 Markdown", - "export": "导出" + "export": "导出", + "legend": "图例", + "primaryKey": "主键", + "primaryKeyDesc": "唯一标识表中行的列", + "foreignKey": "外键", + "foreignKeyDesc": "引用另一个表中的主键或唯一键", + "relationship": "关系", + "relationshipDesc": "连接子表与父表的外键引用" }, "views": { "createView": "创建视图", diff --git a/packages/web-ui/tests/components/ui/SchemaDiagramLegend.test.tsx b/packages/web-ui/tests/components/ui/SchemaDiagramLegend.test.tsx new file mode 100644 index 000000000..91edf2c48 --- /dev/null +++ b/packages/web-ui/tests/components/ui/SchemaDiagramLegend.test.tsx @@ -0,0 +1,76 @@ +import {render, screen, fireEvent} from '@testing-library/react'; +import {describe, expect, it, vi} from 'vitest'; +import {SchemaDiagramLegend} from '@/components/ui/SchemaDiagramLegend'; + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string, fallback?: string) => fallback ?? key, + }), +})); + +describe('SchemaDiagramLegend', () => { + it('associates each legend trigger with its own content', () => { + render( + <> + + + , + ); + const triggers = screen.getAllByRole('button', {name: /legend/i}); + const ids = triggers.map((trigger) => trigger.getAttribute('aria-controls')); + expect(new Set(ids).size).toBe(2); + for (const trigger of triggers) { + fireEvent.click(trigger); + expect( + document.getElementById(trigger.getAttribute('aria-controls')!), + ).toHaveTextContent('Primary Key'); + } + }); + + it('renders collapsed by default with accessible attributes', () => { + render(); + + const trigger = screen.getByRole('button', {name: /legend/i}); + expect(trigger).toBeInTheDocument(); + expect(trigger).toHaveAttribute('aria-expanded', 'false'); + expect(screen.queryByText('Primary Key')).not.toBeInTheDocument(); + expect(screen.queryByText('Foreign Key')).not.toBeInTheDocument(); + expect(screen.queryByText('Relationship')).not.toBeInTheDocument(); + }); + + it('expands when clicked and displays accurate PK, FK and relationship items', () => { + render(); + + const trigger = screen.getByRole('button', {name: /legend/i}); + fireEvent.click(trigger); + + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + expect(screen.getByText('Primary Key')).toBeInTheDocument(); + expect(screen.getByText('Unique column identifying rows in the table')).toBeInTheDocument(); + expect(screen.getByText('Foreign Key')).toBeInTheDocument(); + expect( + screen.getByText('References a primary or unique key in another table'), + ).toBeInTheDocument(); + expect(screen.getByText('Relationship')).toBeInTheDocument(); + expect( + screen.getByText('Foreign key reference connecting child and parent tables'), + ).toBeInTheDocument(); + + // Clicking again collapses the legend + fireEvent.click(trigger); + expect(trigger).toHaveAttribute('aria-expanded', 'false'); + expect(screen.queryByText('Primary Key')).not.toBeInTheDocument(); + }); + + it('collapses on Escape key when open', () => { + render(); + + const trigger = screen.getByRole('button', {name: /legend/i}); + fireEvent.click(trigger); + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + + fireEvent.keyDown(window, {key: 'Escape'}); + expect(trigger).toHaveAttribute('aria-expanded', 'false'); + expect(screen.queryByText('Primary Key')).not.toBeInTheDocument(); + }); +});