diff --git a/assets/scss/dark-theme/tables.scss b/assets/scss/dark-theme/tables.scss index 3d338d7..bf12d9e 100644 --- a/assets/scss/dark-theme/tables.scss +++ b/assets/scss/dark-theme/tables.scss @@ -18,6 +18,11 @@ background-color: $color-gunpowder-gray; border-bottom-color: $color-marlin-black; } + + &.restables-clone tbody td:first-child { + color: $color-white; + background-color: $color-gunpowder-gray; + } } } diff --git a/assets/scss/tables.scss b/assets/scss/tables.scss index 58e667f..22253a9 100644 --- a/assets/scss/tables.scss +++ b/assets/scss/tables.scss @@ -38,8 +38,44 @@ writing-mode: vertical-lr; } } - + } + + &.restables-clone { + display: none; + width: 100%; + + tbody { + display: table; + width: 100%; + border-collapse: collapse; + + + tbody { + margin-top: 10px; + } + + td { + box-shadow: none; + } + + td:first-child { + width: 35%; + color: $color-black; + font-weight: 700; + background-color: $color-mist-gray; + } + } + } + } +} + +.table-responsive--stacked { + table.restables-origin { + display: none; + } + + table.restables-clone { + display: block; } } diff --git a/docs/_index.md b/docs/_index.md index 448c54c..b617c3b 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -92,9 +92,10 @@ theme = ["GDCDocsTheme", "docsy"] ## Reference | Page | Description | -|------|-------------| +| ---- | ----------- | | [Markdown Reference](markdown-reference) | All Markdown formatting options with live rendered output. | | [Shortcodes](shortcodes) | Every Hugo shortcode bundled with the theme. | +| [Table Examples](tables) | Live examples of all table variants, including responsive stacking for wide tables. | ## License diff --git a/docs/tables.md b/docs/tables.md new file mode 100644 index 0000000..3c61ab9 --- /dev/null +++ b/docs/tables.md @@ -0,0 +1,96 @@ +--- +title: "Table Examples" +linkTitle: "Table Examples" +weight: 30 +type: docs +description: "Live examples of every table variant supported by the theme — basic Markdown tables, alignment, and responsive wide tables." +--- + +This page demonstrates how tables render in the GoodData Docs Theme. Every table on a docs page is automatically wrapped in a responsive container by `static/js/tables.js`. A table that does not fit its container is restacked into a two-column label/value layout by the [resTables](https://github.com/codefog/restables) plugin, with horizontal scrolling and a fade hint as the fallback for tables that cannot be restacked. All variants are styled for both the light and dark theme. + +--- + +## Basic table + +A standard Markdown pipe table. The header row gets a shaded background, and cells are separated by borders on all sides. + +```markdown +| Name | Type | Required | Description | +|-------------|---------|----------|------------------------------------| +| `workspace` | string | Yes | Workspace identifier. | +| `title` | string | No | Human-readable dashboard title. | +| `tags` | array | No | Tags used for filtering and search.| +``` + +| Name | Type | Required | Description | +|-------------|---------|----------|------------------------------------| +| `workspace` | string | Yes | Workspace identifier. | +| `title` | string | No | Human-readable dashboard title. | +| `tags` | array | No | Tags used for filtering and search.| + +--- + +## Column alignment + +Control alignment with the position of `:` in the separator row. + +```markdown +| Left-aligned | Centered | Right-aligned | +|:-------------|:--------:|--------------:| +| Metric | Active | 1,234.56 | +| Insight | Draft | 42.00 | +``` + +| Left-aligned | Centered | Right-aligned | +|:-------------|:--------:|--------------:| +| Metric | Active | 1,234.56 | +| Insight | Draft | 42.00 | + +--- + +## Formatting inside cells + +Cells support inline Markdown — code, emphasis, links, and even line breaks with `
`. + +```markdown +| Element | Example | +|-----------------|--------------------------------------------------| +| Inline code | `SELECT * FROM metrics` | +| Bold / italic | **required**, *optional* | +| Link | [GoodData website](https://www.gooddata.com) | +| Multiple lines | First line
Second line | +``` + +| Element | Example | +|-----------------|--------------------------------------------------| +| Inline code | `SELECT * FROM metrics` | +| Bold / italic | **required**, *optional* | +| Link | [GoodData website](https://www.gooddata.com) | +| Multiple lines | First line
Second line | + +--- + +## Responsive behavior + +The theme makes every table responsive automatically — no shortcode or extra markup is needed. Instead of a fixed breakpoint, the theme checks whether each table actually fits its container (re-checked on every window resize): + +- **Table fits:** it renders as a normal table, regardless of screen size. +- **Table is wider than its container:** the [resTables](https://github.com/codefog/restables) plugin swaps in a stacked two-column clone of the table, where each original row becomes a group of label/value pairs — no horizontal scrolling needed. Narrow the browser window until a table on this page stops fitting to see it restack. + +Tables with merged cells (`rowspan`/`colspan`) or without a header row cannot be mapped to label/value pairs, so they keep their original layout. When such a table overflows, it falls back to scrolling horizontally with a fade overlay on the right edge as a visual hint that more content is available. + +The wide table below is the easiest place to see the swap — as the window narrows, it is the first to stop fitting and restack: + +| Endpoint | Method | Authentication | Rate limit | Request body | Response | Since version | Notes | +| -------- | ------ | -------------- | ---------- | ------------ | -------- | ------------- | ----- | +| `/api/v1/entities/workspaces` | GET | Bearer token | 100 req/min | — | JSON:API collection of workspaces | 1.0 | Supports `include`, `filter`, and pagination query parameters. | +| `/api/v1/entities/workspaces/{id}` | PUT | Bearer token | 60 req/min | JSON:API workspace document | Updated workspace entity | 1.0 | Requires the `MANAGE` permission on the workspace. | +| `/api/v1/actions/workspaces/{id}/execution` | POST | Bearer token | 30 req/min | Execution definition (AFM) | Execution result handle | 1.2 | Long-running executions return `202 Accepted` with a polling URL. | +| `/api/v1/layout/workspaces/{id}/logicalModel` | GET | Bearer token | 100 req/min | — | Declarative logical data model | 1.1 | Use `?includeParents=true` to include inherited datasets. | + +--- + +## See also + +- [Markdown Reference](markdown-reference) — all Markdown formatting options, including table syntax basics. +- [Shortcodes reference](shortcodes) — GoodData-specific components that extend Markdown. diff --git a/layouts/partials/hooks/body-end.html b/layouts/partials/hooks/body-end.html index a1be57b..8ba5d78 100644 --- a/layouts/partials/hooks/body-end.html +++ b/layouts/partials/hooks/body-end.html @@ -1,6 +1,7 @@ {{/* (C) 2026 GoodData Corporation */}} + diff --git a/static/js/restables.min.js b/static/js/restables.min.js new file mode 100644 index 0000000..04496a0 --- /dev/null +++ b/static/js/restables.min.js @@ -0,0 +1,8 @@ +/** + * resTables - jQuery plugin for responsive tables + * Copyright (C) 2011-2016 Codefog + * @author Codefog + * @author Kamil Kuzminski + * @license MIT + */ +!function(t){"use strict";"function"==typeof define&&define.amd?define(["jquery"],t):"undefined"!=typeof module&&module.exports?module.exports=t(require("jquery")):t(jQuery)}(function(t){"use strict";function e(e,i){this.element=t(e),this.settings=t.extend({},s,i),this._defaults=s,this._name=n,this.init()}var n="resTables",s={merge:{},move:{},skip:[],span:[],cssClassOrigin:"restables-origin",cssClassClone:"restables-clone",uniqueAttributes:["id","for"],attributeSuffix:"-restables-clone",cloneCallback:null};t.extend(e.prototype,{init:function(){this.buildStructure(),this.mergeColumns(),this.settings.span.length>0&&this.spanColumns(),this.moveColumns(),this.settings.skip.length>0&&this.skipColumns(),this.generateTable()},buildStructure:function(){var e=this;e.headers=[],e.element.find("thead").find("th").each(function(){e.headers.push(t(this).html())}),e.structure=[],e.element.find("tbody").find("tr").each(function(){var n=[];t(this).find("td").each(function(s){n.push([e.headers[s],t(this).html().trim()])}),e.structure.push(n)})},mergeColumns:function(){var t=this;for(var e in t.settings.merge)t.structure.forEach(function(n,s){t.settings.merge[e].forEach(function(n){t.structure[s][e][1]+=" "+t.structure[s][n][1],t.structure[s].splice(n,1)})})},moveColumns:function(){var t=this;for(var e in t.settings.move)t.structure.forEach(function(n,s){t.structure[s].splice(t.settings.move[e],0,t.structure[s].splice(e,1)[0])})},skipColumns:function(){var e=this;e.structure.forEach(function(n,s){e.structure[s]=e.structure[s].filter(function(n,s){return t.inArray(s,e.settings.skip)===-1})})},spanColumns:function(){var t=this;t.structure.forEach(function(e,n){t.settings.span.forEach(function(e){t.structure[n][e]=[t.structure[n][e][1]]})})},generateTable:function(){var e=this,n=t(e.element[0].cloneNode(!1));e.structure.forEach(function(e){var s=t("");e.forEach(function(e){var n=t(""),i=t("").html(e[0]).appendTo(n);e.hasOwnProperty(1)?n.append(t("").html(e[1])):i.attr("colspan",2),n.appendTo(s)}),s.appendTo(n)}),e.settings.uniqueAttributes.length>0&&e.makeAttributesUnique(n),"function"==typeof e.settings.cloneCallback&&e.settings.cloneCallback(n),n.addClass(e.settings.cssClassClone).insertAfter(e.element.addClass(e.settings.cssClassOrigin))},makeAttributesUnique:function(e){var n=this,s=[];n.settings.uniqueAttributes.forEach(function(t){s.push("["+t+"]")}),e.find(s.join(",")).each(function(){var e=t(this);n.settings.uniqueAttributes.forEach(function(t){e.attr(t)&&e.attr(t,e.attr(t)+n.settings.attributeSuffix)})})}}),t.fn[n]=function(s){return this.each(function(){t.data(this,"plugin_"+n)||t.data(this,"plugin_"+n,new e(this,s))})}}); \ No newline at end of file diff --git a/static/js/tables.js b/static/js/tables.js index 9dddbc9..7e45bf2 100644 --- a/static/js/tables.js +++ b/static/js/tables.js @@ -1,17 +1,87 @@ // (C) 2026 GoodData Corporation $(document).ready(function () { + const responsiveTable = function(wrapper) { + if (wrapper.width() === 0) { + return; + } + + const hasClone = wrapper.find("table.restables-clone").length > 0; + + wrapper.removeClass("table-responsive--stacked"); + const table = wrapper.find("table").filter(":visible").first(); + const overflows = table.length > 0 && + (table[0].scrollWidth > table[0].clientWidth + 1 || table.width() > wrapper.width()); + + if (hasClone) { + wrapper.toggleClass("table-responsive--stacked", overflows); + } + + const shown = wrapper.find("table").filter(":visible").first(); + const shownOverflows = shown.length > 0 && + (shown[0].scrollWidth > shown[0].clientWidth + 1 || shown.width() > wrapper.width()); + wrapper.toggleClass("table-responsive__wide", shownOverflows); + }; + const responsiveTables = function() { $(".table-responsive").each(function() { - const wrapperWidth = $(this).width(); - const tableWidth = $(this).find("table").width(); - $(this).toggleClass("table-responsive__wide", tableWidth > wrapperWidth); + responsiveTable($(this)); }); - } + }; $(".td-content table").wrap($("
").addClass("table-responsive")).wrap($("
").addClass("table-responsive__inner")); + + if ($.fn.resTables) { + $(".td-content table").filter(function() { + const table = $(this); + + if (table.hasClass("td-initial") || + table.find("table").length > 0 || table.parents("table").length > 0 || + table.find("[rowspan], [colspan]").length > 0) { + return false; + } + + const headerRows = table.find("thead tr"); + if (headerRows.length !== 1 || headerRows.find("td").length > 0) { + return false; + } + const headerCount = headerRows.find("th").length; + if (headerCount === 0) { + return false; + } + + const bodyRows = table.find("tbody tr"); + let regular = bodyRows.length > 0; + bodyRows.each(function() { + const row = $(this); + if (row.find("th").length > 0 || row.find("td").length !== headerCount) { + regular = false; + return false; + } + }); + return regular; + }).resTables(); + } + responsiveTables(); - $(window).on("resize", responsiveTables); + + if (window.ResizeObserver) { + const observer = new ResizeObserver(function(entries) { + entries.forEach(function(entry) { + const wrapper = $(entry.target); + const width = entry.contentRect.width; + if (wrapper.data("responsiveWidth") !== width) { + wrapper.data("responsiveWidth", width); + responsiveTable(wrapper); + } + }); + }); + $(".table-responsive").each(function() { + observer.observe(this); + }); + } else { + $(window).on("resize", responsiveTables); + } // Vertical column in tables - wrap around span const verticalColumn = document.querySelector("tbody tr:first-child td[rowspan]:first-child");