merge: components package preparation, replace components on learn-example (#835)
* [#812,package][xl]: package preparation, replace components on learn-example * [#812,package][xs]: upgrade portaljs/components version on learn-example * [package][xs]: add deboundebinput back to lean-example
This commit is contained in:
@@ -7,14 +7,12 @@ import { Mermaid } from '@flowershow/core';
|
|||||||
// to handle import statements. Instead, you must include components in scope
|
// to handle import statements. Instead, you must include components in scope
|
||||||
// here.
|
// here.
|
||||||
const components = {
|
const components = {
|
||||||
Table: dynamic(() => import('./Table')),
|
Table: dynamic(() => import('@portaljs/components').then(mod => mod.Table)),
|
||||||
Catalog: dynamic(() => import('./Catalog')),
|
Catalog: dynamic(() => import('./Catalog')),
|
||||||
mermaid: Mermaid,
|
mermaid: Mermaid,
|
||||||
// Excel: dynamic(() => import('../components/Excel')),
|
Vega: dynamic(() => import('@portaljs/components').then(mod => mod.Vega)),
|
||||||
// TODO: try and make these dynamic ...
|
VegaLite: dynamic(() => import('@portaljs/components').then(mod => mod.VegaLite)),
|
||||||
Vega: dynamic(() => import('./Vega')),
|
LineChart: dynamic(() => import('@portaljs/components').then(mod => mod.LineChart)),
|
||||||
VegaLite: dynamic(() => import('./VegaLite')),
|
|
||||||
LineChart: dynamic(() => import('./LineChart')),
|
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
export default function DRD({ source }: { source: any }) {
|
export default function DRD({ source }: { source: any }) {
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import VegaLite from "./VegaLite";
|
|
||||||
|
|
||||||
export default function LineChart({
|
|
||||||
data = [],
|
|
||||||
fullWidth = false,
|
|
||||||
title = "",
|
|
||||||
xAxis = "x",
|
|
||||||
yAxis = "y",
|
|
||||||
}) {
|
|
||||||
var tmp = data;
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
tmp = data.map((r, i) => {
|
|
||||||
return { x: r[0], y: r[1] };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const vegaData = { table: tmp };
|
|
||||||
const spec = {
|
|
||||||
$schema: "https://vega.github.io/schema/vega-lite/v5.json",
|
|
||||||
title,
|
|
||||||
width: "container",
|
|
||||||
height: 300,
|
|
||||||
mark: {
|
|
||||||
type: "line",
|
|
||||||
color: "black",
|
|
||||||
strokeWidth: 1,
|
|
||||||
tooltip: true,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
name: "table",
|
|
||||||
},
|
|
||||||
selection: {
|
|
||||||
grid: {
|
|
||||||
type: "interval",
|
|
||||||
bind: "scales",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
encoding: {
|
|
||||||
x: {
|
|
||||||
field: xAxis,
|
|
||||||
timeUnit: "year",
|
|
||||||
type: "temporal",
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
field: yAxis,
|
|
||||||
type: "quantitative",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (typeof data === 'string') {
|
|
||||||
spec.data = { "url": data } as any
|
|
||||||
return <VegaLite fullWidth={fullWidth} spec={spec} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <VegaLite fullWidth={fullWidth} data={vegaData} spec={spec} />;
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
import {
|
|
||||||
createColumnHelper,
|
|
||||||
FilterFn,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ArrowDownIcon,
|
|
||||||
ArrowUpIcon,
|
|
||||||
ChevronDoubleLeftIcon,
|
|
||||||
ChevronDoubleRightIcon,
|
|
||||||
ChevronLeftIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
} from "@heroicons/react/24/solid";
|
|
||||||
|
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
|
||||||
|
|
||||||
import parseCsv from "../lib/parseCsv";
|
|
||||||
import DebouncedInput from "./DebouncedInput";
|
|
||||||
import loadData from "../lib/loadData";
|
|
||||||
|
|
||||||
const Table = ({
|
|
||||||
data: ogData = [],
|
|
||||||
cols: ogCols = [],
|
|
||||||
csv = "",
|
|
||||||
url = "",
|
|
||||||
fullWidth = false,
|
|
||||||
}) => {
|
|
||||||
if (csv) {
|
|
||||||
const out = parseCsv(csv);
|
|
||||||
ogData = out.rows;
|
|
||||||
ogCols = out.fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [data, setData] = React.useState(ogData);
|
|
||||||
const [cols, setCols] = React.useState(ogCols);
|
|
||||||
const [error, setError] = React.useState(""); // TODO: add error handling
|
|
||||||
|
|
||||||
const tableCols = useMemo(() => {
|
|
||||||
const columnHelper = createColumnHelper();
|
|
||||||
return cols.map((c) =>
|
|
||||||
columnHelper.accessor(c.key, {
|
|
||||||
header: () => c.name,
|
|
||||||
cell: (info) => info.getValue(),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [data, cols]);
|
|
||||||
|
|
||||||
const [globalFilter, setGlobalFilter] = useState("");
|
|
||||||
|
|
||||||
const table = useReactTable({
|
|
||||||
data,
|
|
||||||
columns: tableCols,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
state: {
|
|
||||||
globalFilter,
|
|
||||||
},
|
|
||||||
globalFilterFn: globalFilterFn,
|
|
||||||
onGlobalFilterChange: setGlobalFilter,
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (url) {
|
|
||||||
loadData(url).then((data) => {
|
|
||||||
const { rows, fields } = parseCsv(data);
|
|
||||||
setData(rows);
|
|
||||||
setCols(fields);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [url]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`${fullWidth ? "w-[90vw] ml-[calc(50%-45vw)]" : "w-full"}`}>
|
|
||||||
<DebouncedInput
|
|
||||||
value={globalFilter ?? ""}
|
|
||||||
onChange={(value) => setGlobalFilter(String(value))}
|
|
||||||
className="p-2 text-sm shadow border border-block"
|
|
||||||
placeholder="Search all columns..."
|
|
||||||
/>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
{table.getHeaderGroups().map((hg) => (
|
|
||||||
<tr key={hg.id}>
|
|
||||||
{hg.headers.map((h) => (
|
|
||||||
<th key={h.id}>
|
|
||||||
<div
|
|
||||||
{...{
|
|
||||||
className: h.column.getCanSort()
|
|
||||||
? "cursor-pointer select-none"
|
|
||||||
: "",
|
|
||||||
onClick: h.column.getToggleSortingHandler(),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{flexRender(h.column.columnDef.header, h.getContext())}
|
|
||||||
{{
|
|
||||||
asc: (
|
|
||||||
<ArrowUpIcon className="inline-block ml-2 h-4 w-4" />
|
|
||||||
),
|
|
||||||
desc: (
|
|
||||||
<ArrowDownIcon className="inline-block ml-2 h-4 w-4" />
|
|
||||||
),
|
|
||||||
}[h.column.getIsSorted() as string] ?? (
|
|
||||||
<div className="inline-block ml-2 h-4 w-4" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{table.getRowModel().rows.map((r) => (
|
|
||||||
<tr key={r.id}>
|
|
||||||
{r.getVisibleCells().map((c) => (
|
|
||||||
<td key={c.id}>
|
|
||||||
{flexRender(c.column.columnDef.cell, c.getContext())}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div className="flex gap-2 items-center justify-center">
|
|
||||||
<button
|
|
||||||
className={`w-6 h-6 ${
|
|
||||||
!table.getCanPreviousPage() ? "opacity-25" : "opacity-100"
|
|
||||||
}`}
|
|
||||||
onClick={() => table.setPageIndex(0)}
|
|
||||||
disabled={!table.getCanPreviousPage()}
|
|
||||||
>
|
|
||||||
<ChevronDoubleLeftIcon />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`w-6 h-6 ${
|
|
||||||
!table.getCanPreviousPage() ? "opacity-25" : "opacity-100"
|
|
||||||
}`}
|
|
||||||
onClick={() => table.previousPage()}
|
|
||||||
disabled={!table.getCanPreviousPage()}
|
|
||||||
>
|
|
||||||
<ChevronLeftIcon />
|
|
||||||
</button>
|
|
||||||
<span className="flex items-center gap-1">
|
|
||||||
<div>Page</div>
|
|
||||||
<strong>
|
|
||||||
{table.getState().pagination.pageIndex + 1} of{" "}
|
|
||||||
{table.getPageCount()}
|
|
||||||
</strong>
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
className={`w-6 h-6 ${
|
|
||||||
!table.getCanNextPage() ? "opacity-25" : "opacity-100"
|
|
||||||
}`}
|
|
||||||
onClick={() => table.nextPage()}
|
|
||||||
disabled={!table.getCanNextPage()}
|
|
||||||
>
|
|
||||||
<ChevronRightIcon />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`w-6 h-6 ${
|
|
||||||
!table.getCanNextPage() ? "opacity-25" : "opacity-100"
|
|
||||||
}`}
|
|
||||||
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
|
||||||
disabled={!table.getCanNextPage()}
|
|
||||||
>
|
|
||||||
<ChevronDoubleRightIcon />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const globalFilterFn: FilterFn<any> = (row, columnId, filterValue: string) => {
|
|
||||||
const search = filterValue.toLowerCase();
|
|
||||||
|
|
||||||
let value = row.getValue(columnId) as string;
|
|
||||||
if (typeof value === "number") value = String(value);
|
|
||||||
|
|
||||||
return value?.toLowerCase().includes(search);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Table;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// Wrapper for the Vega component
|
|
||||||
import { Vega as VegaOg } from "react-vega";
|
|
||||||
|
|
||||||
export default function Vega(props) {
|
|
||||||
return <VegaOg {...props} />;
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// Wrapper for the Vega Lite component
|
|
||||||
import { VegaLite as VegaLiteOg } from "react-vega";
|
|
||||||
import applyFullWidthDirective from "../lib/applyFullWidthDirective";
|
|
||||||
|
|
||||||
export default function VegaLite(props) {
|
|
||||||
const Component = applyFullWidthDirective({ Component: VegaLiteOg });
|
|
||||||
|
|
||||||
return <Component {...props} />;
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
export default function applyFullWidthDirective({
|
|
||||||
Component,
|
|
||||||
defaultWFull = true,
|
|
||||||
}) {
|
|
||||||
return (props) => {
|
|
||||||
const newProps = { ...props };
|
|
||||||
|
|
||||||
let newClassName = newProps.className || "";
|
|
||||||
if (newProps.fullWidth === true) {
|
|
||||||
newClassName += " w-[90vw] ml-[calc(50%-45vw)] max-w-none";
|
|
||||||
} else if (defaultWFull) {
|
|
||||||
// So that charts and tables will have the
|
|
||||||
// same width as the text content, but images
|
|
||||||
// can have its width set using the width prop
|
|
||||||
newClassName += " w-full";
|
|
||||||
}
|
|
||||||
newProps.className = newClassName;
|
|
||||||
|
|
||||||
return <Component {...newProps} />;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export default async function loadData(url: string) {
|
|
||||||
const response = await fetch(url)
|
|
||||||
const data = await response.text()
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import papa from "papaparse";
|
|
||||||
|
|
||||||
const parseCsv = (csv) => {
|
|
||||||
csv = csv.trim();
|
|
||||||
const rawdata = papa.parse(csv, { header: true });
|
|
||||||
const cols = rawdata.meta.fields.map((r, i) => {
|
|
||||||
return { key: r, name: r };
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
rows: rawdata.data,
|
|
||||||
fields: cols,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default parseCsv;
|
|
||||||
10
examples/learn-example/package-lock.json
generated
10
examples/learn-example/package-lock.json
generated
@@ -15,6 +15,7 @@
|
|||||||
"@flowershow/remark-wiki-link": "^1.1.2",
|
"@flowershow/remark-wiki-link": "^1.1.2",
|
||||||
"@heroicons/react": "^2.0.17",
|
"@heroicons/react": "^2.0.17",
|
||||||
"@opentelemetry/api": "^1.4.0",
|
"@opentelemetry/api": "^1.4.0",
|
||||||
|
"@portaljs/components": "^0.0.3",
|
||||||
"@tanstack/react-table": "^8.8.5",
|
"@tanstack/react-table": "^8.8.5",
|
||||||
"@types/node": "18.16.0",
|
"@types/node": "18.16.0",
|
||||||
"@types/react": "18.2.0",
|
"@types/react": "18.2.0",
|
||||||
@@ -1976,6 +1977,15 @@
|
|||||||
"url": "https://opencollective.com/unts"
|
"url": "https://opencollective.com/unts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@portaljs/components": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@portaljs/components/-/components-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-SRinOO800oA58akBlni5ahs3PxE+AyqFqgtYN/3ZqYt3CT1JhXOfeXxb+Kbz2QHr/GsAZcUr3DMneB4EzvBx7g==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@protobufjs/aspromise": {
|
"node_modules/@protobufjs/aspromise": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"@flowershow/remark-wiki-link": "^1.1.2",
|
"@flowershow/remark-wiki-link": "^1.1.2",
|
||||||
"@heroicons/react": "^2.0.17",
|
"@heroicons/react": "^2.0.17",
|
||||||
"@opentelemetry/api": "^1.4.0",
|
"@opentelemetry/api": "^1.4.0",
|
||||||
|
"@portaljs/components": "^0.0.3",
|
||||||
"@tanstack/react-table": "^8.8.5",
|
"@tanstack/react-table": "^8.8.5",
|
||||||
"@types/node": "18.16.0",
|
"@types/node": "18.16.0",
|
||||||
"@types/react": "18.2.0",
|
"@types/react": "18.2.0",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { existsSync, promises as fs } from 'fs';
|
import { existsSync, promises as fs } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import parse from '../lib/markdown';
|
import parse from '../lib/markdown';
|
||||||
import DRD from '../components/DRD';
|
|
||||||
|
import DataRichDocument from '../components/DataRichDocument';
|
||||||
import clientPromise from '../lib/mddb';
|
import clientPromise from '../lib/mddb';
|
||||||
|
|
||||||
export const getStaticPaths = async () => {
|
export const getStaticPaths = async () => {
|
||||||
@@ -73,7 +74,7 @@ export default function DatasetPage({ mdxSource, frontMatter }) {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<DRD source={mdxSource} />
|
<DataRichDocument source={mdxSource} />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import '../styles/globals.css'
|
import '../styles/globals.css'
|
||||||
|
import '@portaljs/components/styles.css'
|
||||||
|
|
||||||
import type { AppProps } from 'next/app'
|
import type { AppProps } from 'next/app'
|
||||||
|
|
||||||
export default function App({ Component, pageProps }: AppProps) {
|
export default function App({ Component, pageProps }: AppProps) {
|
||||||
|
|||||||
@@ -1,32 +1,29 @@
|
|||||||
# `components` package
|
# PortalJS React Components
|
||||||
|
|
||||||
**See live:** https://storybook.portaljs.org
|
**Storybook:** https://storybook.portaljs.org
|
||||||
|
**Docs**: https://portaljs.org/docs
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To install this package on your project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm i @portaljs/components
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note: React 18 is required.
|
||||||
|
|
||||||
|
You'll also have to import the styles CSS file in your project:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// E.g.: Next.js => pages/_app.tsx
|
||||||
|
import '@portaljs/components/styles.css'
|
||||||
|
```
|
||||||
|
|
||||||
## Dev
|
## Dev
|
||||||
|
|
||||||
Use Storybook to work on components by running:
|
Use Storybook to work on components by running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn storybook
|
npm run storybook
|
||||||
```
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
To build this project, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
TODO: command to install the package
|
|
||||||
|
|
||||||
### Next.js
|
|
||||||
|
|
||||||
Add this line at the start of `_app.tsx`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// pages/_app.tsx
|
|
||||||
import "dist/styles.css";
|
|
||||||
```
|
```
|
||||||
24752
packages/components/package-lock.json
generated
Normal file
24752
packages/components/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "components",
|
"name": "@portaljs/components",
|
||||||
"private": true,
|
"version": "0.0.3",
|
||||||
"version": "0.0.1",
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"description": "https://portaljs.org",
|
||||||
|
"keywords": [
|
||||||
|
"data portal",
|
||||||
|
"data catalog",
|
||||||
|
"table",
|
||||||
|
"charts",
|
||||||
|
"visualization"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "yarn storybook",
|
"dev": "npm run storybook",
|
||||||
"build": "tsc && vite build && yarn build-tailwind",
|
"build": "tsc && vite build && npm run build-tailwind",
|
||||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"prepack": "json -f package.json -I -e \"delete this.devDependencies; delete this.dependencies\"",
|
"prepack": "json -f package.json -I -e \"delete this.devDependencies; delete this.dependencies\"",
|
||||||
"storybook": "storybook dev -p 6006",
|
"storybook": "storybook dev -p 6006",
|
||||||
@@ -65,6 +72,12 @@
|
|||||||
".": {
|
".": {
|
||||||
"import": "./dist/components.es.js",
|
"import": "./dist/components.es.js",
|
||||||
"require": "./dist/components.umd.js"
|
"require": "./dist/components.umd.js"
|
||||||
}
|
},
|
||||||
|
"./styles.css": {
|
||||||
|
"import": "./dist/styles.css"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user