From 574fb0dd0ba16b4c9f1c0b79164f22d140692b3f Mon Sep 17 00:00:00 2001 From: Jeremy Bernier Date: Fri, 15 Dec 2023 13:02:25 -0800 Subject: [PATCH 1/2] headlessui react select box --- example/package-lock.json | 8 +- example/package.json | 2 +- react/package-lock.json | 30 +++++++ react/package.json | 2 + react/src/Element.tsx | 2 +- react/src/components/MultiSelectInput.tsx | 99 ++++++++++++++++++----- react/src/filter/Filters.tsx | 15 ++-- 7 files changed, 126 insertions(+), 32 deletions(-) diff --git a/example/package-lock.json b/example/package-lock.json index 0adacba..176eb7d 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "canvas-embed": "^1.0.27" + "canvas-embed": "^1.0.29" }, "devDependencies": { "@babel/core": "^7.23.6", @@ -3911,9 +3911,9 @@ "license": "CC-BY-4.0" }, "node_modules/canvas-embed": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/canvas-embed/-/canvas-embed-1.0.27.tgz", - "integrity": "sha512-Hsj5vw1L9RVaBOpbglR/Q2omAbb8KaftetZT1vQW3uu1YICVucuLjkW6zP2afFlhuSD+7z2nP9Q0hHn4LXunyg==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/canvas-embed/-/canvas-embed-1.0.38.tgz", + "integrity": "sha512-ykJ0D+yDymnlkuxq/qIsvKnmdbOiCqUtsoEzqHYRKdw/5ZPZJ8h0+cPTj7PEYoSdIB3B3QLoeB++6Yc4TyK2jg==", "dependencies": { "antd": "^5.12.2", "chroma-js": "^2.4.2", diff --git a/example/package.json b/example/package.json index f9d045f..0c1042d 100644 --- a/example/package.json +++ b/example/package.json @@ -39,4 +39,4 @@ "react": "^18.2.0", "react-dom": "^18.2.0" } -} \ No newline at end of file +} diff --git a/react/package-lock.json b/react/package-lock.json index 55d45a5..183b539 100644 --- a/react/package-lock.json +++ b/react/package-lock.json @@ -9,6 +9,8 @@ "version": "1.0.38", "license": "MIT", "dependencies": { + "@headlessui/react": "^1.7.17", + "@heroicons/react": "^2.0.18", "antd": "^5.12.2", "chroma-js": "^2.4.2", "highcharts": "^11.2.0", @@ -2898,6 +2900,29 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "node_modules/@headlessui/react": { + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.17.tgz", + "integrity": "sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow==", + "dependencies": { + "client-only": "^0.0.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18", + "react-dom": "^16 || ^17 || ^18" + } + }, + "node_modules/@heroicons/react": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.0.18.tgz", + "integrity": "sha512-7TyMjRrZZMBPa+/5Y8lN0iyvUU/01PeMGX2+RE7cQWpEUIcb4QotzUObFkJDejj/HUH4qjP/eQ0gzzKs2f+6Yw==", + "peerDependencies": { + "react": ">= 16" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -4460,6 +4485,11 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", diff --git a/react/package.json b/react/package.json index 87ebe7d..c1326fe 100644 --- a/react/package.json +++ b/react/package.json @@ -64,6 +64,8 @@ "react-dom": "^18.2.0" }, "dependencies": { + "@headlessui/react": "^1.7.17", + "@heroicons/react": "^2.0.18", "antd": "^5.12.2", "chroma-js": "^2.4.2", "highcharts": "^11.2.0", diff --git a/react/src/Element.tsx b/react/src/Element.tsx index 4077306..363b074 100644 --- a/react/src/Element.tsx +++ b/react/src/Element.tsx @@ -20,7 +20,7 @@ export function Element({ title, children, elementId }: ElementProps): React.Rea }} id={elementId} > -
+
{title}
diff --git a/react/src/components/MultiSelectInput.tsx b/react/src/components/MultiSelectInput.tsx index b99b9e4..b9eed06 100644 --- a/react/src/components/MultiSelectInput.tsx +++ b/react/src/components/MultiSelectInput.tsx @@ -1,28 +1,89 @@ -import React from 'react'; +import React, { useState, Fragment } from 'react'; +import { Listbox, Transition } from '@headlessui/react'; +import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid'; + +type Value = string; +type Label = string; +export type SelectOption = [Value, Label]; type MultiSelectInputProps = { - value: string; + value: string; // value === '' when default option is selected onChange: (value: string) => void; - options: string[][]; + options: SelectOption[]; + defaultOption?: string; +}; + +function getLabel(item: SelectOption) { + if (item[0] === '') return item[1]; + return `${item[0]}${item[1] ? ` (${item[1]})` : ''}`; +} + +// taken from https://headlessui.com/react/listbox +const MultiSelectInputDisplay = ({ value, onChange, options }: MultiSelectInputProps) => { + return ( +
+ onChange(item[0])}> +
+ + + {getLabel(options.find((item) => item[0] === value) as SelectOption)} + + + + + + + {options?.map((option, index) => ( + + `relative cursor-pointer select-none py-2 pl-10 pr-4 ${ + active ? 'bg-amber-100 text-amber-900' : 'text-gray-900' + }` + } + value={option} + > + {({ selected }) => ( + <> + + {getLabel(option)} + + {selected ? ( + + + ) : null} + + )} + + ))} + + +
+
+
+ ); }; -const MultiSelectInput = ({ value, onChange, options }: MultiSelectInputProps) => { +// Our wrapper that adds default option +const MultiSelectInput = ({ value, onChange, options, defaultOption }: MultiSelectInputProps) => { + const optionsFinal = defaultOption ? [['', defaultOption] as SelectOption, ...options] : options; + const valueFinal = value || ''; return ( - + ); }; diff --git a/react/src/filter/Filters.tsx b/react/src/filter/Filters.tsx index 3dd0511..eabf9b0 100644 --- a/react/src/filter/Filters.tsx +++ b/react/src/filter/Filters.tsx @@ -1,8 +1,7 @@ import React from 'react'; +import isEmpty from 'lodash/isEmpty'; import MultiSelectInput from '../components/MultiSelectInput'; import useCanvasState from '../state/useCanvasState'; -import isEmpty from 'lodash/isEmpty'; - import { GetCanvasEmbedResponse } from '@/src/rust_types/GetCanvasEmbedResponse'; export function Filters({ canvasData }: { canvasData: GetCanvasEmbedResponse }) { @@ -10,7 +9,8 @@ export function Filters({ canvasData }: { canvasData: GetCanvasEmbedResponse }) const filtersVisible = filters?.filter((filter) => filter?.filterType?.type === 'select'); const updateFilter = useCanvasState((state) => state.updateFilter); const selectedFilters = useCanvasState((state) => state.filters); - const valueSelected = !isEmpty(selectedFilters); + const valueIsSelected = !isEmpty(selectedFilters); + return (
{filtersVisible?.map((filter) => ( @@ -18,18 +18,19 @@ export function Filters({ canvasData }: { canvasData: GetCanvasEmbedResponse }) { - if (value === '') { + onChange={(item: string) => { + if (item === '' || item == null) { updateFilter({}); return; } const variable = filter.variable; - updateFilter({ [variable]: value }); + updateFilter({ [variable]: item }); }} + defaultOption="Select Filter" // @ts-ignore options={canvasData.filters.uniqueValues[filter.filterType.storeId]} /> - {valueSelected && ( + {valueIsSelected && ( From 9ad11e8a4418fb87b95e0e7e95c60f221e8a22f8 Mon Sep 17 00:00:00 2001 From: Jeremy Bernier Date: Fri, 15 Dec 2023 13:24:11 -0800 Subject: [PATCH 2/2] readme update --- example/README.md | 4 ++++ react/README.md | 28 ++++------------------------ 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/example/README.md b/example/README.md index 9fee2ad..fa6a3ee 100644 --- a/example/README.md +++ b/example/README.md @@ -8,3 +8,7 @@ This sample application demonstrates how to use Canvas embeds in your applicatio npm i npm run dev ``` + +## Local development with React repo + +The `canvas-embed` repo is hosted in the `react` folder. You can link to the local version via: `npm link ../react` for local development \ No newline at end of file diff --git a/react/README.md b/react/README.md index 4f53f11..c5d4645 100644 --- a/react/README.md +++ b/react/README.md @@ -11,30 +11,10 @@ npm install canvas-embed ## Usage ``` -import { Chart } from "canvas-embed"; +import { Canvas } from "canvas-embed"; - -``` - -## Internal - -### Development - -Copy over Rust types from main repo with (adjust relative path in script as necessary): - -``` -npm run copy-rust-types -``` - -### Publishing - -``` -npm version patch -npm run build -npm run publish --access public ``` \ No newline at end of file