Skip to content

Commit

Permalink
Merge pull request #959 from Bedrock-OSS/jsontomd
Browse files Browse the repository at this point in the history
Table Component
  • Loading branch information
QuazChick authored Feb 9, 2025
2 parents 9d2fd4c + bfd0dbb commit 0166396
Show file tree
Hide file tree
Showing 23 changed files with 738 additions and 12 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
},
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"vue/multi-word-component-names": "off"
"vue/multi-word-component-names": "off",
"vue/no-v-html": "off"
},
"ignorePatterns": ["!docs/.vitepress", "docs/.vitepress/cache", "/docs/.vitepress/dist"]
}
8 changes: 7 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,11 @@
"eslint.enable": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
}
},
"json.schemas": [
{
"fileMatch": ["docs/public/assets/tables/**/*.json"],
"url": "./schemas/table.json"
}
]
}
97 changes: 97 additions & 0 deletions docs/.vitepress/theme/components/content/Table.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<script setup lang="ts">
import { computed, ref, watch } from "vue";
import TableHeader from "./TableHeader.vue";
import TableCell from "./TableCell.vue";
import sortTableRows, { TableSorting } from "../../data/tables/sortTableRows";
import { data as tables } from "../../data/tables/tables.data";
import displayError from "../../utils/displayError";
import useData from "../../composables/data";
const { page } = useData();
const props = defineProps<{
data: string;
}>();
const table = computed(() => {
let path = "public";
if (props.data[0] !== "/") {
path += "/assets/tables/" + page.value.relativePath.replace(/\.md$/, "/");
}
path += props.data;
const table = tables[path];
if (!table) {
displayError(new TypeError(`No table with the path "${path}" exists.`));
}
return table;
});
const sorting = ref<TableSorting | null>(null);
const sortedRows = ref(table.value.rows);
function toggleSorting(column: string) {
if (sorting.value?.column === column) {
if (sorting.value.order === "ascending") {
sorting.value = { column, order: "descending" };
return;
}
sorting.value = null;
return;
}
sorting.value = { column, order: "ascending" };
}
function sortRows() {
if (sorting.value === null) {
sortedRows.value = table.value.rows;
return;
}
const rows = table.value.rows.map((row, index) => ({
...row,
__initial_index__: index, // Persist index to prevent unnecessary Vue rerenders
}));
sortTableRows(sorting.value, rows);
sortedRows.value = rows;
}
watch(sorting, sortRows);
</script>

<template>
<table>
<thead>
<tr>
<TableHeader
v-for="(column, columnId) in table.columns"
:key="columnId"
:column-id="columnId as string"
:column
:sorting
@toggle-sorting="toggleSorting(columnId as string)"
/>
</tr>
</thead>

<tbody>
<tr v-for="(row, index) in sortedRows" :key="(row.__initial_index__ as number) ?? index">
<TableCell
v-for="(column, columnId) in table.columns"
:key="columnId"
:column
:value="row[columnId] ?? column.default"
/>
</tr>
</tbody>
</table>
</template>
117 changes: 117 additions & 0 deletions docs/.vitepress/theme/components/content/TableCell.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import { TableValue, TableColumn } from "../../types";
const props = defineProps<{
column: TableColumn;
value: TableValue;
}>();
const shortListLength = 10;
const showMore = ref(false);
const content = ref<HTMLElement | null>(null);
const isOverflowing = ref(false);
const canShowMore = computed(() => {
if (Array.isArray(props.value)) return props.value.length > shortListLength;
if (typeof props.value === "string") return isOverflowing.value;
return false;
});
const checkOverflow = () => {
if (content.value === null) return;
isOverflowing.value = content.value.scrollHeight > content.value.clientHeight;
};
onMounted(() => {
checkOverflow();
window.addEventListener("resize", checkOverflow);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", checkOverflow);
});
</script>

<template>
<td :style="{ textAlign: column.textAlign }">
<ul v-if="Array.isArray(value)" class="content">
<li
v-for="(item, itemIndex) in showMore ? value : value.slice(0, shortListLength)"
:key="itemIndex"
v-html="item"
/>
</ul>

<div
v-else-if="typeof value === 'string'"
ref="content"
class="content"
:data-show-more="showMore"
v-html="value"
/>

<div v-else-if="typeof value === 'boolean'" class="content">{{ value ? "✔️" : "❌" }}</div>

<div v-else class="content">{{ value }}</div>

<template v-if="canShowMore">
<button
v-if="showMore"
type="button"
class="show-less-button"
@click="() => (showMore = false)"
>
Show Less
</button>
<button v-else type="button" class="show-more-button" @click="() => (showMore = true)">
…Show More
</button>
</template>
</td>
</template>

<style lang="scss" scoped>
.content[data-show-more="false"] {
max-height: 5lh;
overflow: hidden;
}
td {
position: relative;
}
.show-more-button,
.show-less-button {
color: var(--accent-color);
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
.show-more-button {
position: absolute;
right: 1em;
bottom: 0.5em;
background-color: var(--cell-bg-color);
&::before {
content: "";
position: absolute;
left: -1.5em;
width: 1.5em;
height: 1lh;
background: linear-gradient(90deg, transparent, var(--cell-bg-color) 80%);
}
}
</style>
69 changes: 69 additions & 0 deletions docs/.vitepress/theme/components/content/TableHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<script setup lang="ts">
import { computed } from "vue";
import { TableColumn } from "../../types";
import { TableSorting } from "../../data/tables/sortTableRows";
import SortIcon from "../icons/SortIcon.vue";
const props = defineProps<{
columnId: string;
column: TableColumn;
sorting: TableSorting | null;
}>();
const emit = defineEmits(["toggleSorting"]);
const sortOrder = computed(() => {
if (props.sorting?.column === props.columnId) return props.sorting.order;
return undefined;
});
const sortButtonTitle = computed(() => {
if (sortOrder.value === "ascending") return "Sort Descending";
if (sortOrder.value === "descending") return "Sort Initial";
return "Sort Ascending";
});
</script>

<template>
<th :key="columnId" :style="{ textAlign: column.textAlign }">
<div>
<span v-html="column.name" />
<button
v-if="column.sortable"
class="sort-button"
type="button"
:title="sortButtonTitle"
@click="emit('toggleSorting')"
>
<SortIcon :order="sortOrder" />
</button>
</div>
</th>
</template>

<style lang="scss" scoped>
th > div {
display: grid;
grid-template-columns: 1fr max-content;
align-items: center;
}
.sort-button {
cursor: pointer;
border-radius: 6px;
border: var(--border);
background-color: var(--light-bg-color);
transition: background-color 0.1s;
padding: 6px;
margin-left: 1em;
margin-right: -0.5em;
&:hover {
background-color: var(--bg-color);
}
}
</style>
37 changes: 37 additions & 0 deletions docs/.vitepress/theme/components/icons/SortIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script setup lang="ts">
defineProps<{
order?: "ascending" | "descending";
}>();
</script>

<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
<g :data-active="order === 'ascending'">
<path d="m 2.3,7.5 5,-4.5 5,4.5" />
<path d="M 7.3,3 V 19" />
</g>
<g :data-active="order === 'descending'">
<path d="M 16.7,5 V 21" />
<path d="m 11.7,16.5 5,4.5 5,-4.5" />
</g>
</svg>
</template>

<style lang="scss" scoped>
svg {
display: block;
}
path {
fill: none;
stroke: currentColor;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 3;
}
[data-active="false"] path {
filter: brightness(70%);
stroke-width: 2.5;
}
</style>
5 changes: 5 additions & 0 deletions docs/.vitepress/theme/data/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { SiteConfig } from "vitepress";

const config: SiteConfig = globalThis.VITEPRESS_CONFIG;

export default config;
9 changes: 9 additions & 0 deletions docs/.vitepress/theme/data/markdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createMarkdownRenderer } from "vitepress";
import config from "./config";

export default await createMarkdownRenderer(
config.srcDir,
config.markdown,
config.site.base,
config.logger
);
Loading

0 comments on commit 0166396

Please sign in to comment.