From bece5155e49ae41691aa5a87ac3a79a80ed62dde Mon Sep 17 00:00:00 2001 From: Ben Price Date: Mon, 25 Oct 2021 18:28:18 +0100 Subject: [PATCH 1/3] feat(components): a Tree "outline" component We render it in a simple nested list format. This is intended to be simple to maintain and a good basis for an accessibility-focussed component. --- .../src/Tree/Tree.stories.tsx | 105 ++++++++++++++++++ packages/primer-components/src/Tree/Tree.tsx | 30 +++++ packages/primer-components/src/Tree/index.tsx | 3 + packages/primer-components/src/index.ts | 1 + 4 files changed, 139 insertions(+) create mode 100644 packages/primer-components/src/Tree/Tree.stories.tsx create mode 100644 packages/primer-components/src/Tree/Tree.tsx create mode 100644 packages/primer-components/src/Tree/index.tsx diff --git a/packages/primer-components/src/Tree/Tree.stories.tsx b/packages/primer-components/src/Tree/Tree.stories.tsx new file mode 100644 index 00000000..775768db --- /dev/null +++ b/packages/primer-components/src/Tree/Tree.stories.tsx @@ -0,0 +1,105 @@ +import { ComponentStory, ComponentMeta } from "@storybook/react"; + +import { Tree, TreeI } from "./Tree"; + +export default { + title: "Application/Component Library/Tree", + component: Tree, + argTypes: { + tree: { control: "object", name: "Tree to display" }, + }, +} as ComponentMeta; + +/* We have this indirection so storybook renders the whole json description of + * a tree as one control. (As having three separate controls for 'label', 'id' + * and 'subtrees' is not particularly useful.) + */ +interface TreeArgs { + tree: TreeI; +} + +const Template: ComponentStory<(args: TreeArgs) => JSX.Element> = (args) => ( + +); + +export const Tree1 = Template.bind({}); +Tree1.args = { tree: { nodeId: 0, childTrees: [], label: "EmptyHole" } }; + +export const Tree2 = Template.bind({}); +Tree2.args = { + tree: { + nodeId: 0, + childTrees: [{ nodeId: 1, childTrees: [], label: "Var x" }], + label: "Lam x", + }, +}; + +export const Tree3 = Template.bind({}); +Tree3.args = { + tree: { + nodeId: 0, + childTrees: [{ nodeId: 1, childTrees: [], label: "Var y" }], + label: "Lam y", + }, +}; + +export const Tree4 = Template.bind({}); +Tree4.args = { + tree: { + nodeId: 0, + childTrees: [ + { + nodeId: 1, + childTrees: [ + { + nodeId: 2, + childTrees: [ + { + nodeId: 3, + childTrees: [{ nodeId: 4, childTrees: [], label: "Var x" }], + label: "Lam x", + }, + ], + label: "LAM a", + }, + { + nodeId: 5, + childTrees: [ + { + nodeId: 6, + childTrees: [ + { nodeId: 7, childTrees: [], label: "TVar a" }, + { nodeId: 8, childTrees: [], label: "TVar a" }, + ], + label: "TFun", + }, + ], + label: "TForall", + }, + ], + label: "Ann", + }, + { nodeId: 9, childTrees: [], label: "Con Unit" }, + ], + label: "App", + }, +}; + +export const Tree5 = Template.bind({}); +Tree5.args = { + tree: { + nodeId: 0, + childTrees: [ + { + nodeId: 1, + childTrees: [ + { nodeId: 2, childTrees: [], label: "Var x" }, + { nodeId: 3, childTrees: [], label: "EmptyHole" }, + { nodeId: 6, childTrees: [], label: "EmptyHole" }, + ], + label: "Case", + }, + ], + label: "Lam x", + }, +}; diff --git a/packages/primer-components/src/Tree/Tree.tsx b/packages/primer-components/src/Tree/Tree.tsx new file mode 100644 index 00000000..349b8e54 --- /dev/null +++ b/packages/primer-components/src/Tree/Tree.tsx @@ -0,0 +1,30 @@ +import "@/index.css"; + +export interface TreeI { + nodeId: number; + label: string; + /* NB: 'children' is treated specially by react, leading to weird errors. + * Let's avoid that word. + */ + childTrees: TreeI[]; +} + +export const Tree = (tree: TreeI): JSX.Element => ( +
    +
  • {tree.label}
  • + +
+); + +function ChildTrees({ trees }: { trees: TreeI[] }): JSX.Element { + if (trees.length > 0) { + return ( +
    + {trees.map((t) => ( + + ))} +
+ ); + } + return <>; +} diff --git a/packages/primer-components/src/Tree/index.tsx b/packages/primer-components/src/Tree/index.tsx new file mode 100644 index 00000000..efc9829d --- /dev/null +++ b/packages/primer-components/src/Tree/index.tsx @@ -0,0 +1,3 @@ +import { Tree } from "./Tree"; + +export default Tree; diff --git a/packages/primer-components/src/index.ts b/packages/primer-components/src/index.ts index 4923650d..06c5581f 100644 --- a/packages/primer-components/src/index.ts +++ b/packages/primer-components/src/index.ts @@ -9,4 +9,5 @@ export { default as SearchBar } from "@/SearchBar"; export { default as SessionList } from "@/SessionList"; export { default as SessionPreview } from "@/SessionPreview"; export { default as SessionsNavBar } from "@/SessionsNavBar"; +export { default as Tree } from "@/Tree"; export { default as UIButton } from "@/UIButton"; From 96cbfe92cc053511efd9d5b31daabae576f00857 Mon Sep 17 00:00:00 2001 From: Ben Price Date: Wed, 17 Nov 2021 12:03:53 +0000 Subject: [PATCH 2/3] refactor: move TreeI type to primer-types --- packages/primer-components/src/Tree/Tree.stories.tsx | 3 ++- packages/primer-components/src/Tree/Tree.tsx | 9 +-------- packages/primer-types/src/Tree.ts | 9 +++++++++ packages/primer-types/src/index.ts | 1 + 4 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 packages/primer-types/src/Tree.ts diff --git a/packages/primer-components/src/Tree/Tree.stories.tsx b/packages/primer-components/src/Tree/Tree.stories.tsx index 775768db..4fcfae47 100644 --- a/packages/primer-components/src/Tree/Tree.stories.tsx +++ b/packages/primer-components/src/Tree/Tree.stories.tsx @@ -1,6 +1,7 @@ import { ComponentStory, ComponentMeta } from "@storybook/react"; -import { Tree, TreeI } from "./Tree"; +import { TreeI } from "@hackworthltd/primer-types"; +import { Tree } from "./Tree"; export default { title: "Application/Component Library/Tree", diff --git a/packages/primer-components/src/Tree/Tree.tsx b/packages/primer-components/src/Tree/Tree.tsx index 349b8e54..e55b502a 100644 --- a/packages/primer-components/src/Tree/Tree.tsx +++ b/packages/primer-components/src/Tree/Tree.tsx @@ -1,13 +1,6 @@ import "@/index.css"; -export interface TreeI { - nodeId: number; - label: string; - /* NB: 'children' is treated specially by react, leading to weird errors. - * Let's avoid that word. - */ - childTrees: TreeI[]; -} +import { TreeI } from "@hackworthltd/primer-types"; export const Tree = (tree: TreeI): JSX.Element => (
    diff --git a/packages/primer-types/src/Tree.ts b/packages/primer-types/src/Tree.ts new file mode 100644 index 00000000..63531ca6 --- /dev/null +++ b/packages/primer-types/src/Tree.ts @@ -0,0 +1,9 @@ +export interface TreeI { + nodeId: number; + label: string; + /* NB: 'children' is treated specially by react, leading to weird errors. + * Let's avoid that word. + */ + childTrees: TreeI[]; +} + diff --git a/packages/primer-types/src/index.ts b/packages/primer-types/src/index.ts index 895f0f7d..8fd1a086 100644 --- a/packages/primer-types/src/index.ts +++ b/packages/primer-types/src/index.ts @@ -1,2 +1,3 @@ export * from "./Account"; export * from "./Session"; +export * from "./Tree"; From 708100f8a96de2e8ed8d83237fcf413af49b8764 Mon Sep 17 00:00:00 2001 From: Ben Price Date: Wed, 17 Nov 2021 12:54:34 +0000 Subject: [PATCH 3/3] Use Tree API type auto-generated by backend This unfortunately involves a lot of renaming churn, since the openapi description calls it 'Tree', rather than 'TreeI'. However, this is a good thing to do anyway, since having the bare-bones outline component being called 'Tree' would be confusing when we have some other, fancier, tree component as well. --- packages/primer-components/src/Tree/index.tsx | 3 -- .../TreeOutline.stories.tsx} | 14 ++++---- .../Tree.tsx => TreeOutline/TreeOutline.tsx} | 8 ++--- .../src/TreeOutline/index.tsx | 3 ++ packages/primer-components/src/index.ts | 2 +- packages/primer-types/src/Tree.ts | 32 ++++++++++++++----- 6 files changed, 39 insertions(+), 23 deletions(-) delete mode 100644 packages/primer-components/src/Tree/index.tsx rename packages/primer-components/src/{Tree/Tree.stories.tsx => TreeOutline/TreeOutline.stories.tsx} (89%) rename packages/primer-components/src/{Tree/Tree.tsx => TreeOutline/TreeOutline.tsx} (59%) create mode 100644 packages/primer-components/src/TreeOutline/index.tsx diff --git a/packages/primer-components/src/Tree/index.tsx b/packages/primer-components/src/Tree/index.tsx deleted file mode 100644 index efc9829d..00000000 --- a/packages/primer-components/src/Tree/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { Tree } from "./Tree"; - -export default Tree; diff --git a/packages/primer-components/src/Tree/Tree.stories.tsx b/packages/primer-components/src/TreeOutline/TreeOutline.stories.tsx similarity index 89% rename from packages/primer-components/src/Tree/Tree.stories.tsx rename to packages/primer-components/src/TreeOutline/TreeOutline.stories.tsx index 4fcfae47..61727c71 100644 --- a/packages/primer-components/src/Tree/Tree.stories.tsx +++ b/packages/primer-components/src/TreeOutline/TreeOutline.stories.tsx @@ -1,26 +1,26 @@ import { ComponentStory, ComponentMeta } from "@storybook/react"; -import { TreeI } from "@hackworthltd/primer-types"; -import { Tree } from "./Tree"; +import { Tree } from "@hackworthltd/primer-types"; +import { TreeOutline } from "./TreeOutline"; export default { - title: "Application/Component Library/Tree", - component: Tree, + title: "Application/Component Library/TreeOutline", + component: TreeOutline, argTypes: { tree: { control: "object", name: "Tree to display" }, }, -} as ComponentMeta; +} as ComponentMeta; /* We have this indirection so storybook renders the whole json description of * a tree as one control. (As having three separate controls for 'label', 'id' * and 'subtrees' is not particularly useful.) */ interface TreeArgs { - tree: TreeI; + tree: Tree; } const Template: ComponentStory<(args: TreeArgs) => JSX.Element> = (args) => ( - + ); export const Tree1 = Template.bind({}); diff --git a/packages/primer-components/src/Tree/Tree.tsx b/packages/primer-components/src/TreeOutline/TreeOutline.tsx similarity index 59% rename from packages/primer-components/src/Tree/Tree.tsx rename to packages/primer-components/src/TreeOutline/TreeOutline.tsx index e55b502a..b82f62e8 100644 --- a/packages/primer-components/src/Tree/Tree.tsx +++ b/packages/primer-components/src/TreeOutline/TreeOutline.tsx @@ -1,20 +1,20 @@ import "@/index.css"; -import { TreeI } from "@hackworthltd/primer-types"; +import { Tree } from "@hackworthltd/primer-types"; -export const Tree = (tree: TreeI): JSX.Element => ( +export const TreeOutline = (tree: Tree): JSX.Element => (
    • {tree.label}
    ); -function ChildTrees({ trees }: { trees: TreeI[] }): JSX.Element { +function ChildTrees({ trees }: { trees: Tree[] }): JSX.Element { if (trees.length > 0) { return (
      {trees.map((t) => ( - + ))}
    ); diff --git a/packages/primer-components/src/TreeOutline/index.tsx b/packages/primer-components/src/TreeOutline/index.tsx new file mode 100644 index 00000000..c03785a6 --- /dev/null +++ b/packages/primer-components/src/TreeOutline/index.tsx @@ -0,0 +1,3 @@ +import { TreeOutline } from "./TreeOutline"; + +export default TreeOutline; diff --git a/packages/primer-components/src/index.ts b/packages/primer-components/src/index.ts index 06c5581f..4c8077d7 100644 --- a/packages/primer-components/src/index.ts +++ b/packages/primer-components/src/index.ts @@ -9,5 +9,5 @@ export { default as SearchBar } from "@/SearchBar"; export { default as SessionList } from "@/SessionList"; export { default as SessionPreview } from "@/SessionPreview"; export { default as SessionsNavBar } from "@/SessionsNavBar"; -export { default as Tree } from "@/Tree"; +export { default as TreeOutline } from "@/TreeOutline"; export { default as UIButton } from "@/UIButton"; diff --git a/packages/primer-types/src/Tree.ts b/packages/primer-types/src/Tree.ts index 63531ca6..751072e8 100644 --- a/packages/primer-types/src/Tree.ts +++ b/packages/primer-types/src/Tree.ts @@ -1,9 +1,25 @@ -export interface TreeI { - nodeId: number; - label: string; - /* NB: 'children' is treated specially by react, leading to weird errors. - * Let's avoid that word. - */ - childTrees: TreeI[]; +/** + * + * @export + * @interface Tree + */ +export interface Tree { + /** + * + * @type {Array} + * @memberof Tree + */ + childTrees: Array; + /** + * + * @type {number} + * @memberof Tree + */ + nodeId: number; + /** + * + * @type {string} + * @memberof Tree + */ + label: string; } -