From 5b09c2ea8fa4486691f0d61f683d601e68f4aaa6 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 4 Jun 2025 11:25:34 -0400 Subject: [PATCH 001/217] finish up code cleanup and make sure that all styling is close to figma design --- .../ui/src/viewer-table/DictionaryHeader.tsx | 150 +++++++++++++----- 1 file changed, 107 insertions(+), 43 deletions(-) diff --git a/packages/ui/src/viewer-table/DictionaryHeader.tsx b/packages/ui/src/viewer-table/DictionaryHeader.tsx index 119ccee8..367282c3 100644 --- a/packages/ui/src/viewer-table/DictionaryHeader.tsx +++ b/packages/ui/src/viewer-table/DictionaryHeader.tsx @@ -20,61 +20,125 @@ */ /** @jsxImportSource @emotion/react */ - +import React, { useState } from 'react'; import { css } from '@emotion/react'; -import { ComponentType } from 'react'; import colours from './styles/colours'; - import { useThemeContext } from '../theme/ThemeContext'; -export type DictionaryHeaderProps = { +type DictionaryHeaderProps = { name: string; description?: string; version?: string; }; -const DictionaryHeader: ComponentType = ({ description, name }) => { +const getChevronStyle = (isExpanded: boolean) => css` + margin-left: 4px; + ${isExpanded && `transform: rotate(180deg);`} +`; + +const linkStyle = (theme: any) => css` + ${theme.typography?.label2} + color: white; + cursor: pointer; + margin-left: 6px; + display: inline-flex; + align-items: center; + + &:hover { + text-decoration: underline; + } +`; +const descriptionStyle = (theme: any) => css` + ${theme.typography?.data} + color: white; + margin: 0; + display: inline; +`; + +const containerStyle = (theme: any) => css` + background-color: ${colours.accent1_1}; + ${theme.typography.heading} + display: flex; + width: 100%; + margin-bottom: 1rem; + padding: 2.5rem; +`; + +const rowLayoutStyle = css` + display: flex; + flex-direction: row; + width: 100%; +`; + +const titleColumnStyle = css` + display: flex; + flex-direction: column; + flex: 1; + margin-right: 2rem; +`; + +const titleStyle = css` + font-weight: 700; + font-size: 40px; + color: white; + line-height: 100%; + margin: 0; + margin-bottom: 0.5rem; +`; + +const versionStyle = (theme: any) => css` + ${theme.typography.data} + color: white; +`; + +const descriptionColumnStyle = css` + flex: 2; + display: flex; + flex-direction: column; + justify-content: center; +`; + +const descriptionContainerStyle = css` + margin: 0; +`; + +// These constants can be adjusted based on design requirements +const DESCRIPTION_THRESHOLD = 220; +const MAX_CHARS_VISIBLE = 240; + +const DictionaryHeader = ({ name, description, version }: DictionaryHeaderProps) => { const theme = useThemeContext(); + const { ChevronDown } = theme.icons; + const [isExpanded, setIsExpanded] = useState(false); + + // Determine if the description is long enough to need a toggle, based off of how many characters we want to show by default + // according to the figma styling + const needsToggle = description && description.length > DESCRIPTION_THRESHOLD; + // We want to show all the text if it is not long or if it is already expanded via state variable + const showFull = isExpanded || !needsToggle; + // Based off of showFull, we determine the text to show, either its the full description or a truncated version + const textToShow = showFull ? description : description.slice(0, MAX_CHARS_VISIBLE) + '...'; + return ( -
-
-

- {name} -

+
+
+
+

{name}

+ {version && v{version}} +
+ {description && ( -

- {description} -

+
+
+ {textToShow} + {needsToggle && ( + setIsExpanded((prev) => !prev)}> + {isExpanded ? 'Read less' : 'Show more'} + + + )} +
+
)}
From 0fa14d9fbacbaacc5198794037cf0d639630087c Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 4 Jun 2025 11:27:22 -0400 Subject: [PATCH 002/217] type theme appropriately instead of settling for any --- packages/ui/src/viewer-table/DictionaryHeader.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/viewer-table/DictionaryHeader.tsx b/packages/ui/src/viewer-table/DictionaryHeader.tsx index 367282c3..c188c201 100644 --- a/packages/ui/src/viewer-table/DictionaryHeader.tsx +++ b/packages/ui/src/viewer-table/DictionaryHeader.tsx @@ -24,7 +24,7 @@ import React, { useState } from 'react'; import { css } from '@emotion/react'; import colours from './styles/colours'; import { useThemeContext } from '../theme/ThemeContext'; - +import type { Theme } from '../theme'; type DictionaryHeaderProps = { name: string; description?: string; @@ -36,7 +36,7 @@ const getChevronStyle = (isExpanded: boolean) => css` ${isExpanded && `transform: rotate(180deg);`} `; -const linkStyle = (theme: any) => css` +const linkStyle = (theme: Theme) => css` ${theme.typography?.label2} color: white; cursor: pointer; @@ -48,14 +48,14 @@ const linkStyle = (theme: any) => css` text-decoration: underline; } `; -const descriptionStyle = (theme: any) => css` +const descriptionStyle = (theme: Theme) => css` ${theme.typography?.data} color: white; margin: 0; display: inline; `; -const containerStyle = (theme: any) => css` +const containerStyle = (theme: Theme) => css` background-color: ${colours.accent1_1}; ${theme.typography.heading} display: flex; @@ -86,7 +86,7 @@ const titleStyle = css` margin-bottom: 0.5rem; `; -const versionStyle = (theme: any) => css` +const versionStyle = (theme: Theme) => css` ${theme.typography.data} color: white; `; From 62b5ec4b155c95b956c5d7b1b85e1ac81aa9ed2c Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 4 Jun 2025 11:41:19 -0400 Subject: [PATCH 003/217] fix styling and add comments for fonts --- .../ui/src/viewer-table/DictionaryHeader.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/viewer-table/DictionaryHeader.tsx b/packages/ui/src/viewer-table/DictionaryHeader.tsx index c188c201..cbb6d3a0 100644 --- a/packages/ui/src/viewer-table/DictionaryHeader.tsx +++ b/packages/ui/src/viewer-table/DictionaryHeader.tsx @@ -37,10 +37,9 @@ const getChevronStyle = (isExpanded: boolean) => css` `; const linkStyle = (theme: Theme) => css` - ${theme.typography?.label2} + ${theme.typography?.subheading} color: white; cursor: pointer; - margin-left: 6px; display: inline-flex; align-items: center; @@ -48,8 +47,13 @@ const linkStyle = (theme: Theme) => css` text-decoration: underline; } `; + +// Was unable to find the appropriate font size for the version numbering in the current design system, that matches +// Figma mockup so we are using something that is somewhat close with a hard coded font size + const descriptionStyle = (theme: Theme) => css` ${theme.typography?.data} + font-size: 16px; color: white; margin: 0; display: inline; @@ -77,18 +81,25 @@ const titleColumnStyle = css` margin-right: 2rem; `; +// Was unable to find the appropriate font size for the title in the current design system, that matches +// Figma mockup so it is HARDCODED for now + const titleStyle = css` font-weight: 700; - font-size: 40px; + font-size: 30px; color: white; line-height: 100%; margin: 0; margin-bottom: 0.5rem; `; +// Was unable to find the appropriate font size for the version numbering in the current design system, that matches +// Figma mockup so we are using something that is somewhat close + const versionStyle = (theme: Theme) => css` ${theme.typography.data} color: white; + font-size: 17px; `; const descriptionColumnStyle = css` @@ -126,7 +137,6 @@ const DictionaryHeader = ({ name, description, version }: DictionaryHeaderProps)

{name}

{version && v{version}}
- {description && (
From 376f887abf930a3275d12d8689d319e72c5818c3 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 4 Jun 2025 11:46:25 -0400 Subject: [PATCH 004/217] add some permuations of testing and reduce the threshold constants --- packages/ui/src/viewer-table/DictionaryHeader.tsx | 4 ++-- .../stories/viewer-table/DictionaryHeader.stories.tsx | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/viewer-table/DictionaryHeader.tsx b/packages/ui/src/viewer-table/DictionaryHeader.tsx index cbb6d3a0..26882a1b 100644 --- a/packages/ui/src/viewer-table/DictionaryHeader.tsx +++ b/packages/ui/src/viewer-table/DictionaryHeader.tsx @@ -114,8 +114,8 @@ const descriptionContainerStyle = css` `; // These constants can be adjusted based on design requirements -const DESCRIPTION_THRESHOLD = 220; -const MAX_CHARS_VISIBLE = 240; +const DESCRIPTION_THRESHOLD = 140; +const MAX_CHARS_VISIBLE = 160; const DictionaryHeader = ({ name, description, version }: DictionaryHeaderProps) => { const theme = useThemeContext(); diff --git a/packages/ui/stories/viewer-table/DictionaryHeader.stories.tsx b/packages/ui/stories/viewer-table/DictionaryHeader.stories.tsx index df1e0561..9b674a10 100644 --- a/packages/ui/stories/viewer-table/DictionaryHeader.stories.tsx +++ b/packages/ui/stories/viewer-table/DictionaryHeader.stories.tsx @@ -22,6 +22,10 @@ export const AllHeaderProperties: Story = { args: { ...pick(biosampleDictionary, 'name', 'version', 'description') }, }; +export const NoVersion: Story = { + // args: { name: sampleDictionary.name, version: sampleDictionary.name, description: sampleDictionary.description }, + args: { ...pick(biosampleDictionary, 'name', 'description') }, +}; export const NoDescription: Story = { args: { ...pick(biosampleDictionary, 'name', 'version') }, }; @@ -32,3 +36,10 @@ export const LongName: Story = { name: 'This is a really really reallt reallty long dictionary name! wow!', }, }; +export const LongDescription: Story = { + args: { + ...pick(biosampleDictionary, 'name', 'version', 'description'), + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + }, +}; From afe4e1652430e9cf35a4c69422579312f9cbb3b3 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 4 Jun 2025 11:50:01 -0400 Subject: [PATCH 005/217] final cleanup before pr, remove uneccesary styling --- packages/ui/src/viewer-table/DictionaryHeader.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/viewer-table/DictionaryHeader.tsx b/packages/ui/src/viewer-table/DictionaryHeader.tsx index 26882a1b..12bded5a 100644 --- a/packages/ui/src/viewer-table/DictionaryHeader.tsx +++ b/packages/ui/src/viewer-table/DictionaryHeader.tsx @@ -20,11 +20,12 @@ */ /** @jsxImportSource @emotion/react */ -import React, { useState } from 'react'; import { css } from '@emotion/react'; -import colours from './styles/colours'; -import { useThemeContext } from '../theme/ThemeContext'; +import { useState } from 'react'; import type { Theme } from '../theme'; +import { useThemeContext } from '../theme/ThemeContext'; +import colours from './styles/colours'; + type DictionaryHeaderProps = { name: string; description?: string; @@ -109,10 +110,6 @@ const descriptionColumnStyle = css` justify-content: center; `; -const descriptionContainerStyle = css` - margin: 0; -`; - // These constants can be adjusted based on design requirements const DESCRIPTION_THRESHOLD = 140; const MAX_CHARS_VISIBLE = 160; @@ -139,7 +136,7 @@ const DictionaryHeader = ({ name, description, version }: DictionaryHeaderProps)
{description && (
-
+
{textToShow} {needsToggle && ( setIsExpanded((prev) => !prev)}> From 8e7cc5ef654ef81145a107943991bc75263cba20 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 4 Jun 2025 12:06:10 -0400 Subject: [PATCH 006/217] fix logic flaw --- packages/ui/src/viewer-table/DictionaryHeader.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/ui/src/viewer-table/DictionaryHeader.tsx b/packages/ui/src/viewer-table/DictionaryHeader.tsx index 12bded5a..9a14c667 100644 --- a/packages/ui/src/viewer-table/DictionaryHeader.tsx +++ b/packages/ui/src/viewer-table/DictionaryHeader.tsx @@ -112,7 +112,6 @@ const descriptionColumnStyle = css` // These constants can be adjusted based on design requirements const DESCRIPTION_THRESHOLD = 140; -const MAX_CHARS_VISIBLE = 160; const DictionaryHeader = ({ name, description, version }: DictionaryHeaderProps) => { const theme = useThemeContext(); @@ -125,7 +124,7 @@ const DictionaryHeader = ({ name, description, version }: DictionaryHeaderProps) // We want to show all the text if it is not long or if it is already expanded via state variable const showFull = isExpanded || !needsToggle; // Based off of showFull, we determine the text to show, either its the full description or a truncated version - const textToShow = showFull ? description : description.slice(0, MAX_CHARS_VISIBLE) + '...'; + const textToShow = showFull ? description : description.slice(0, DESCRIPTION_THRESHOLD) + '...'; return (
From 296af83831f7efda1f799d3de56d25107b9b9146 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 4 Jun 2025 12:08:16 -0400 Subject: [PATCH 007/217] add minor spacing --- packages/ui/src/viewer-table/DictionaryHeader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/viewer-table/DictionaryHeader.tsx b/packages/ui/src/viewer-table/DictionaryHeader.tsx index 9a14c667..887ad97c 100644 --- a/packages/ui/src/viewer-table/DictionaryHeader.tsx +++ b/packages/ui/src/viewer-table/DictionaryHeader.tsx @@ -124,7 +124,7 @@ const DictionaryHeader = ({ name, description, version }: DictionaryHeaderProps) // We want to show all the text if it is not long or if it is already expanded via state variable const showFull = isExpanded || !needsToggle; // Based off of showFull, we determine the text to show, either its the full description or a truncated version - const textToShow = showFull ? description : description.slice(0, DESCRIPTION_THRESHOLD) + '...'; + const textToShow = showFull ? description : description.slice(0, DESCRIPTION_THRESHOLD) + '... '; return (
@@ -139,7 +139,7 @@ const DictionaryHeader = ({ name, description, version }: DictionaryHeaderProps) {textToShow} {needsToggle && ( setIsExpanded((prev) => !prev)}> - {isExpanded ? 'Read less' : 'Show more'} + {isExpanded ? ' Read less' : ' Show more'} )} From b12375200f8d251b064aac35833acf7fb0675c7c Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 4 Jun 2025 13:25:22 -0400 Subject: [PATCH 008/217] remove tacky v --- packages/ui/src/viewer-table/DictionaryHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/viewer-table/DictionaryHeader.tsx b/packages/ui/src/viewer-table/DictionaryHeader.tsx index 887ad97c..1ba84664 100644 --- a/packages/ui/src/viewer-table/DictionaryHeader.tsx +++ b/packages/ui/src/viewer-table/DictionaryHeader.tsx @@ -131,7 +131,7 @@ const DictionaryHeader = ({ name, description, version }: DictionaryHeaderProps)

{name}

- {version && v{version}} + {version && {version}}
{description && (
From 532260f9e75d5fbcfad5f5c34cf1641b202e169e Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 4 Jun 2025 14:34:10 -0400 Subject: [PATCH 009/217] port over the button implementation from stage into here in order to use it for action items --- packages/ui/src/common/Button.tsx | 117 ++++++++++++++++++++++++ packages/ui/src/theme/icons/Spinner.tsx | 54 +++++++++++ packages/ui/src/theme/styles/icons.ts | 2 + 3 files changed, 173 insertions(+) create mode 100644 packages/ui/src/common/Button.tsx create mode 100644 packages/ui/src/theme/icons/Spinner.tsx diff --git a/packages/ui/src/common/Button.tsx b/packages/ui/src/common/Button.tsx new file mode 100644 index 00000000..d6feab3c --- /dev/null +++ b/packages/ui/src/common/Button.tsx @@ -0,0 +1,117 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** @jsxImportSource @emotion/react */ +import React, { ReactNode } from 'react'; +import { css } from '@emotion/react'; + +import { Theme } from '../theme'; +import { useThemeContext } from '../theme/ThemeContext'; + +type ButtonProps = { + children?: ReactNode; + disabled?: boolean; + onClick?: ( + e: React.SyntheticEvent, + ) => any | ((e: React.SyntheticEvent) => Promise); + isAsync?: boolean; + className?: string; + isLoading?: boolean; +}; + +const getButtonStyles = (theme: Theme) => css` + color: ${theme.colors.white}; + background-color: ${theme.colors.accent}; + ${theme.typography.subheading2}; + line-height: 24px; + border-radius: 5px; + border: 1px solid ${theme.colors.accent}; + padding: 6px 15px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + position: relative; + + &:hover { + background-color: ${theme.colors.accent_dark}; + } + + &:disabled, + &:disabled:hover { + background-color: ${theme.colors.grey_4}; + cursor: not-allowed; + color: ${theme.colors.white}; + border: 1px solid ${theme.colors.grey_4}; + } +`; + +const getContentStyles = (shouldShowLoading: boolean) => css` + visibility: ${shouldShowLoading ? 'hidden' : 'visible'}; +`; + +const getSpinnerStyles = (theme: Theme, shouldShowLoading: boolean) => css` + position: absolute; + visibility: ${shouldShowLoading ? 'visible' : 'hidden'}; + bottom: 1px; +`; + +const Button = React.forwardRef( + ( + { + children, + onClick = () => {}, + disabled = false, + isAsync = false, + className, + isLoading: controlledLoading, + }: ButtonProps, + ref, + ) => { + const [internalLoading, setInternalLoading] = React.useState(false); + + const shouldShowLoading = !!controlledLoading || (internalLoading && isAsync); + + const handleClick = async (event: React.SyntheticEvent) => { + setInternalLoading(true); + await onClick(event); + setInternalLoading(false); + }; + const theme = useThemeContext(); + const { Spinner } = theme.icons; + return ( + + ); + }, +); + +export default Button; diff --git a/packages/ui/src/theme/icons/Spinner.tsx b/packages/ui/src/theme/icons/Spinner.tsx new file mode 100644 index 00000000..e6fc6c49 --- /dev/null +++ b/packages/ui/src/theme/icons/Spinner.tsx @@ -0,0 +1,54 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** @jsxImportSource @emotion/react */ + +import { css, keyframes } from '@emotion/react'; +import IconProps from './IconProps'; + +// Animation +const spin = keyframes` + 100% { + transform: rotate(360deg); + } +`; + +const Spinner = ({ fill, height, width }: IconProps) => { + return ( + + + + ); +}; + +export default Spinner; diff --git a/packages/ui/src/theme/styles/icons.ts b/packages/ui/src/theme/styles/icons.ts index d09423d3..c6d9988c 100644 --- a/packages/ui/src/theme/styles/icons.ts +++ b/packages/ui/src/theme/styles/icons.ts @@ -1,4 +1,6 @@ import ChevronDown from '../icons/ChevronDown'; +import Spinner from '../icons/Spinner'; export default { ChevronDown, + Spinner, }; From 28a164fb8177527fe9437f342a419153c120c6f3 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 4 Jun 2025 15:09:25 -0400 Subject: [PATCH 010/217] fix minor css styling --- packages/ui/src/common/Button.tsx | 68 +++++++++++++------ packages/ui/stories/common/Button.stories.tsx | 20 ++++++ 2 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 packages/ui/stories/common/Button.stories.tsx diff --git a/packages/ui/src/common/Button.tsx b/packages/ui/src/common/Button.tsx index d6feab3c..b51178fe 100644 --- a/packages/ui/src/common/Button.tsx +++ b/packages/ui/src/common/Button.tsx @@ -20,6 +20,7 @@ */ /** @jsxImportSource @emotion/react */ +// This is a slightly refactored version of the stage button import React, { ReactNode } from 'react'; import { css } from '@emotion/react'; @@ -35,45 +36,71 @@ type ButtonProps = { isAsync?: boolean; className?: string; isLoading?: boolean; + leftIcon?: ReactNode; + width?: string; }; -const getButtonStyles = (theme: Theme) => css` - color: ${theme.colors.white}; - background-color: ${theme.colors.accent}; - ${theme.typography.subheading2}; - line-height: 24px; - border-radius: 5px; - border: 1px solid ${theme.colors.accent}; - padding: 6px 15px; +const getButtonContainerStyles = (theme: any, width?: string) => css` display: flex; - justify-content: center; + flex-wrap: nowrap; align-items: center; + justify-content: center; + gap: 11px; + width: ${width || 'auto'}; + min-width: fit-content; + padding: 8px 16px; + background-color: #f7f7f7; + color: ${theme.colors.black}; + border: 1px solid #beb2b294; + border-radius: 9px; + font-size: 14px; + height: 43px; + box-sizing: border-box; cursor: pointer; position: relative; + transition: all 0.2s ease; &:hover { - background-color: ${theme.colors.accent_dark}; + background-color: ${theme.colors.grey_1}; } - &:disabled, - &:disabled:hover { - background-color: ${theme.colors.grey_4}; + &:disabled { cursor: not-allowed; - color: ${theme.colors.white}; - border: 1px solid ${theme.colors.grey_4}; + opacity: 0.7; } `; const getContentStyles = (shouldShowLoading: boolean) => css` + display: flex; + align-items: center; + font-weight: 400; + gap: 8px; + font-size: 16px; + line-height: 1.2; + color: inherit; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; visibility: ${shouldShowLoading ? 'hidden' : 'visible'}; `; -const getSpinnerStyles = (theme: Theme, shouldShowLoading: boolean) => css` +const getSpinnerStyles = (shouldShowLoading: boolean) => css` position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); visibility: ${shouldShowLoading ? 'visible' : 'hidden'}; - bottom: 1px; `; +const getIconStyles = () => css` + display: flex; + align-items: center; +`; + +/** + * This is the generic button component used throughout stage, however it has + * the styling that is specific to the current theme that is being used. + */ const Button = React.forwardRef( ( { @@ -83,6 +110,8 @@ const Button = React.forwardRef( isAsync = false, className, isLoading: controlledLoading, + leftIcon, + width, }: ButtonProps, ref, ) => { @@ -103,10 +132,11 @@ const Button = React.forwardRef( onClick={isAsync ? handleClick : onClick} disabled={disabled || shouldShowLoading} className={className} - css={getButtonStyles(theme)} + css={getButtonContainerStyles(theme, width)} > + {leftIcon && !shouldShowLoading && {leftIcon}} {children} - + diff --git a/packages/ui/stories/common/Button.stories.tsx b/packages/ui/stories/common/Button.stories.tsx new file mode 100644 index 00000000..f3bc38e9 --- /dev/null +++ b/packages/ui/stories/common/Button.stories.tsx @@ -0,0 +1,20 @@ +/** @jsxImportSource @emotion/react */ +import type { Meta, StoryObj } from '@storybook/react'; +import themeDecorator from '../themeDecorator'; +import Button from '../../src/common/Button'; +const meta = { + component: Button, + title: 'Common/Button', + tags: ['autodocs'], + decorators: [themeDecorator()], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { children: 'Click Me', onClick: () => alert('I have been clicked') }, +}; +export const Empty: Story = { + args: {}, +}; From 8d86be95dc42ec4d0bc3eb4a1bde39c1523bedfc Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 4 Jun 2025 15:18:23 -0400 Subject: [PATCH 011/217] add more configuations to the story --- packages/ui/stories/common/Button.stories.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/ui/stories/common/Button.stories.tsx b/packages/ui/stories/common/Button.stories.tsx index f3bc38e9..65578fdb 100644 --- a/packages/ui/stories/common/Button.stories.tsx +++ b/packages/ui/stories/common/Button.stories.tsx @@ -1,7 +1,9 @@ /** @jsxImportSource @emotion/react */ + import type { Meta, StoryObj } from '@storybook/react'; import themeDecorator from '../themeDecorator'; import Button from '../../src/common/Button'; + const meta = { component: Button, title: 'Common/Button', @@ -13,7 +15,13 @@ export default meta; type Story = StoryObj; export const Default: Story = { - args: { children: 'Click Me', onClick: () => alert('I have been clicked') }, + args: { children: 'Click Me', onClick: () => alert('I have been clicked'), className: 'my-button', leftIcon: '👍' }, +}; +export const Disabled: Story = { + args: { children: 'Disabled', disabled: true }, +}; +export const Loading: Story = { + args: { isLoading: true, children: 'Loading...' }, }; export const Empty: Story = { args: {}, From ed90b723ac43eb5f5d3f64d7770a11cb37ed34c8 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 4 Jun 2025 15:55:50 -0400 Subject: [PATCH 012/217] finish adding all neccasry components for expansion and collapseable button --- packages/ui/src/theme/icons/Collapse.tsx | 49 +++++++++++++++++++ packages/ui/src/theme/icons/Eye.tsx | 46 +++++++++++++++++ packages/ui/src/theme/styles/icons.ts | 4 ++ .../InteractionPanel/CollapseAllButton.tsx | 25 ++++++++++ .../InteractionPanel/ExpandAllButton.tsx | 25 ++++++++++ .../CollapseAllButton.stories.tsx | 19 +++++++ .../viewer-table/ExpandAllButton.stories.tsx | 18 +++++++ 7 files changed, 186 insertions(+) create mode 100644 packages/ui/src/theme/icons/Collapse.tsx create mode 100644 packages/ui/src/theme/icons/Eye.tsx create mode 100644 packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx create mode 100644 packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx create mode 100644 packages/ui/stories/viewer-table/CollapseAllButton.stories.tsx create mode 100644 packages/ui/stories/viewer-table/ExpandAllButton.stories.tsx diff --git a/packages/ui/src/theme/icons/Collapse.tsx b/packages/ui/src/theme/icons/Collapse.tsx new file mode 100644 index 00000000..f6d6eca6 --- /dev/null +++ b/packages/ui/src/theme/icons/Collapse.tsx @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** @jsxImportSource @emotion/react */ + +import IconProps from './IconProps'; + +const Collapse = ({ fill, width, height, style }: IconProps) => { + return ( + + + + + + + + ); +}; + +export default Collapse; diff --git a/packages/ui/src/theme/icons/Eye.tsx b/packages/ui/src/theme/icons/Eye.tsx new file mode 100644 index 00000000..20797742 --- /dev/null +++ b/packages/ui/src/theme/icons/Eye.tsx @@ -0,0 +1,46 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** @jsxImportSource @emotion/react */ + +import IconProps from './IconProps'; + +const Eye = ({ fill, width, height, style }: IconProps) => { + return ( + + + + + ); +}; + +export default Eye; diff --git a/packages/ui/src/theme/styles/icons.ts b/packages/ui/src/theme/styles/icons.ts index c6d9988c..2b107574 100644 --- a/packages/ui/src/theme/styles/icons.ts +++ b/packages/ui/src/theme/styles/icons.ts @@ -1,6 +1,10 @@ import ChevronDown from '../icons/ChevronDown'; import Spinner from '../icons/Spinner'; +import Collapse from '../icons/Collapse'; +import Eye from '../icons/Eye'; export default { ChevronDown, Spinner, + Collapse, + Eye, }; diff --git a/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx new file mode 100644 index 00000000..a6fce125 --- /dev/null +++ b/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx @@ -0,0 +1,25 @@ +import { useEffect } from 'react'; +import Button from '../../common/Button'; +import { useThemeContext } from '../../theme/ThemeContext'; + +interface CollapseAllButtonProps { + setIsCollapsed: (isCollapsed: boolean) => void; + collapsedOnLoad?: boolean; // This prop is optional and defaults to false +} + +const CollapseAllButton = ({ setIsCollapsed, collapsedOnLoad = false }: CollapseAllButtonProps) => { + const theme = useThemeContext(); + const { Collapse } = theme.icons; + + useEffect(() => { + setIsCollapsed(collapsedOnLoad); + }, [collapsedOnLoad, setIsCollapsed]); + + return ( + + ); +}; + +export default CollapseAllButton; diff --git a/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx new file mode 100644 index 00000000..33e4d541 --- /dev/null +++ b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx @@ -0,0 +1,25 @@ +import { useEffect } from 'react'; +import Button from '../../common/Button'; +import { useThemeContext } from '../../theme/ThemeContext'; + +interface ExpandAllButtonProps { + setIsCollapsed: (isCollapsed: boolean) => void; + expandOnLoad?: boolean; // This prop is optional and defaults to false +} + +const ExpandAllButton = ({ setIsCollapsed, expandOnLoad = false }: ExpandAllButtonProps) => { + const theme = useThemeContext(); + const { Eye } = theme.icons; + + useEffect(() => { + setIsCollapsed(expandOnLoad); + }, [expandOnLoad, setIsCollapsed]); + + return ( + + ); +}; + +export default ExpandAllButton; diff --git a/packages/ui/stories/viewer-table/CollapseAllButton.stories.tsx b/packages/ui/stories/viewer-table/CollapseAllButton.stories.tsx new file mode 100644 index 00000000..73fc96bf --- /dev/null +++ b/packages/ui/stories/viewer-table/CollapseAllButton.stories.tsx @@ -0,0 +1,19 @@ +/** @jsxImportSource @emotion/react */ + +import type { Meta, StoryObj } from '@storybook/react'; +import themeDecorator from '../themeDecorator'; +import CollapseAllButton from '../../src/viewer-table/InteractionPanel/CollapseAllButton'; + +const meta = { + component: CollapseAllButton, + title: 'Viewer - Table/CollapseAllButton', + tags: ['autodocs'], + decorators: [themeDecorator()], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { setIsCollapsed: (isCollapsed: boolean) => console.log('all collapsable components are collapsed') }, +}; diff --git a/packages/ui/stories/viewer-table/ExpandAllButton.stories.tsx b/packages/ui/stories/viewer-table/ExpandAllButton.stories.tsx new file mode 100644 index 00000000..1a150099 --- /dev/null +++ b/packages/ui/stories/viewer-table/ExpandAllButton.stories.tsx @@ -0,0 +1,18 @@ +/** @jsxImportSource @emotion/react */ + +import type { Meta, StoryObj } from '@storybook/react'; +import themeDecorator from '../themeDecorator'; +import ExpandAllButton from '../../src/viewer-table/InteractionPanel/ExpandAllButton'; +const meta = { + component: ExpandAllButton, + title: 'Viewer - Table/ExpandAllButton', + tags: ['autodocs'], + decorators: [themeDecorator()], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { setIsCollapsed: (isCollapsed: boolean) => console.log('all collapsable components are expanded') }, +}; From cc05e977b299261741b62719be46f3bdfa651ba5 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 5 Jun 2025 08:47:45 -0400 Subject: [PATCH 013/217] fix horizontal scrolling issue with css --- packages/ui/src/viewer-table/DictionaryHeader.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/ui/src/viewer-table/DictionaryHeader.tsx b/packages/ui/src/viewer-table/DictionaryHeader.tsx index 1ba84664..9d2d1f30 100644 --- a/packages/ui/src/viewer-table/DictionaryHeader.tsx +++ b/packages/ui/src/viewer-table/DictionaryHeader.tsx @@ -64,7 +64,6 @@ const containerStyle = (theme: Theme) => css` background-color: ${colours.accent1_1}; ${theme.typography.heading} display: flex; - width: 100%; margin-bottom: 1rem; padding: 2.5rem; `; @@ -127,7 +126,7 @@ const DictionaryHeader = ({ name, description, version }: DictionaryHeaderProps) const textToShow = showFull ? description : description.slice(0, DESCRIPTION_THRESHOLD) + '... '; return ( -
+

{name}

From c0b3dc436a25e469db3e3896755ca84c69f531e8 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 5 Jun 2025 10:21:48 -0400 Subject: [PATCH 014/217] initialize the table of contents component --- packages/ui/src/theme/icons/List.tsx | 54 +++++++++++++++++++ packages/ui/src/theme/styles/icons.ts | 2 + .../TableOfContentsDropdown.tsx | 21 ++++++++ .../TableOfContentDropdown.stories.tsx | 31 +++++++++++ 4 files changed, 108 insertions(+) create mode 100644 packages/ui/src/theme/icons/List.tsx create mode 100644 packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx create mode 100644 packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx diff --git a/packages/ui/src/theme/icons/List.tsx b/packages/ui/src/theme/icons/List.tsx new file mode 100644 index 00000000..c051eedc --- /dev/null +++ b/packages/ui/src/theme/icons/List.tsx @@ -0,0 +1,54 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** @jsxImportSource @emotion/react */ + +import { css } from '@emotion/react'; + +import IconProps from './IconProps'; + +const ChevronDown = ({ fill, width, height, style }: IconProps) => { + return ( + + + + + + + + + ); +}; + +export default ChevronDown; diff --git a/packages/ui/src/theme/styles/icons.ts b/packages/ui/src/theme/styles/icons.ts index d09423d3..ca4167f4 100644 --- a/packages/ui/src/theme/styles/icons.ts +++ b/packages/ui/src/theme/styles/icons.ts @@ -1,4 +1,6 @@ import ChevronDown from '../icons/ChevronDown'; +import List from '../icons/List'; export default { ChevronDown, + List, }; diff --git a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx new file mode 100644 index 00000000..70345348 --- /dev/null +++ b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx @@ -0,0 +1,21 @@ +import type { Schema } from '@overture-stack/lectern-dictionary'; +import Dropdown from '../../common/Dropdown/Dropdown'; +import List from '../../theme/icons/List'; + +type TableOfContentsDropdownProps = { + schemas: Schema[]; +}; + +const TableOfContentsDropdown = ({ schemas }: TableOfContentsDropdownProps) => { + const generateOptionsFromSchemas = () => { + return schemas.map((schema) => ({ + label: schema.name, + action: () => { + console.log(`Selected schema: ${schema.name}`); + }, + })); + }; + return } title="Table of Contents" menuItems={generateOptionsFromSchemas()} />; +}; + +export default TableOfContentsDropdown; diff --git a/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx b/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx new file mode 100644 index 00000000..7021478a --- /dev/null +++ b/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx @@ -0,0 +1,31 @@ +/** @jsxImportSource @emotion/react */ + +import { Schema } from '@overture-stack/lectern-dictionary'; +import type { Meta, StoryObj } from '@storybook/react'; +import primitiveJson from '../../../../samples/schemas/primitives.json'; +import TableOfContentsDropdown from '../../src/viewer-table/InteractionPanel/TableOfContentsDropdown'; +import themeDecorator from '../themeDecorator'; + +// Using the primitiveJson as a mock schema for demonstration purposes + +const schema: Schema = primitiveJson as Schema; + +const meta = { + component: TableOfContentsDropdown, + title: 'Viewer - Table/Table of Contents Dropdown', + tags: ['autodocs'], + decorators: [themeDecorator()], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + schemas: [schema], + }, +}; + +export const Empty: Story = { + args: { schemas: [] }, +}; From 8d9f78dbab38cde06e6fe178b2d2db281214241a Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 5 Jun 2025 10:38:44 -0400 Subject: [PATCH 015/217] handle logic for the actual scrolling into view for scheams --- .../InteractionPanel/TableOfContentsDropdown.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx index 70345348..e44531f6 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx @@ -4,17 +4,29 @@ import List from '../../theme/icons/List'; type TableOfContentsDropdownProps = { schemas: Schema[]; + onSchemaSelect: (schema: Schema) => void; + onAccordionToggle: (schemaName: string, isOpen: boolean) => void; }; -const TableOfContentsDropdown = ({ schemas }: TableOfContentsDropdownProps) => { +const TableOfContentsDropdown = ({ schemas, onSchemaSelect, onAccordionToggle }: TableOfContentsDropdownProps) => { + const handleAction = (schema: Schema) => { + const anchorId = `${schema.name}`; + onAccordionToggle(schema.name, true); + onSchemaSelect(schema); + setTimeout(() => { + window.location.hash = anchorId; + }, 100); + }; + const generateOptionsFromSchemas = () => { return schemas.map((schema) => ({ label: schema.name, action: () => { - console.log(`Selected schema: ${schema.name}`); + handleAction(schema); }, })); }; + return } title="Table of Contents" menuItems={generateOptionsFromSchemas()} />; }; From ee3a2c912089d895b5515779e86a3f2293503a9b Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 5 Jun 2025 10:59:34 -0400 Subject: [PATCH 016/217] clean up the table of contents, add the relevant functions --- .../TableOfContentsDropdown.tsx | 16 +++++++------- .../TableOfContentDropdown.stories.tsx | 21 ++++++++++++++----- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx index e44531f6..32cc3159 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx @@ -18,16 +18,14 @@ const TableOfContentsDropdown = ({ schemas, onSchemaSelect, onAccordionToggle }: }, 100); }; - const generateOptionsFromSchemas = () => { - return schemas.map((schema) => ({ - label: schema.name, - action: () => { - handleAction(schema); - }, - })); - }; + const menuItemsFromSchemas = schemas.map((schema) => ({ + label: schema.name, + action: () => { + handleAction(schema); + }, + })); - return } title="Table of Contents" menuItems={generateOptionsFromSchemas()} />; + return } title="Table of Contents" menuItems={menuItemsFromSchemas} />; }; export default TableOfContentsDropdown; diff --git a/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx b/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx index 7021478a..f536c6d6 100644 --- a/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx @@ -6,10 +6,6 @@ import primitiveJson from '../../../../samples/schemas/primitives.json'; import TableOfContentsDropdown from '../../src/viewer-table/InteractionPanel/TableOfContentsDropdown'; import themeDecorator from '../themeDecorator'; -// Using the primitiveJson as a mock schema for demonstration purposes - -const schema: Schema = primitiveJson as Schema; - const meta = { component: TableOfContentsDropdown, title: 'Viewer - Table/Table of Contents Dropdown', @@ -20,12 +16,27 @@ const meta = { export default meta; type Story = StoryObj; +// Using the primitiveJson as a mock schema for demonstration purposes +const schema: Schema = primitiveJson as Schema; + +// Mock functions for the story just to demonstrate interaction + +const onSchemaSelect = (schema: Schema) => { + console.log('Selected schema:', schema); +}; + +const onAccordionToggle = (schemaName: string, isOpen: boolean) => { + console.log(`Accordion ${isOpen ? 'opened' : 'closed'} for schema:`, schemaName); +}; + export const Default: Story = { args: { schemas: [schema], + onSchemaSelect, + onAccordionToggle, }, }; export const Empty: Story = { - args: { schemas: [] }, + args: { schemas: [], onSchemaSelect, onAccordionToggle }, }; From 9f2bd1dbffeb7a0c6f33c3a68584ce45ba321ff4 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 5 Jun 2025 13:21:35 -0400 Subject: [PATCH 017/217] cleanup css styling such that it is uniform and has no unessesary attributes --- packages/ui/src/common/Dropdown/Dropdown.tsx | 42 +++++++------------ .../ui/src/common/Dropdown/DropdownItem.tsx | 11 +---- 2 files changed, 17 insertions(+), 36 deletions(-) diff --git a/packages/ui/src/common/Dropdown/Dropdown.tsx b/packages/ui/src/common/Dropdown/Dropdown.tsx index 3ee365cc..987060cd 100644 --- a/packages/ui/src/common/Dropdown/Dropdown.tsx +++ b/packages/ui/src/common/Dropdown/Dropdown.tsx @@ -31,21 +31,20 @@ const dropdownButtonStyle = (theme: Theme, width?: string) => css` display: flex; flex-wrap: nowrap; align-items: center; - justify-content: space-between; + justify-content: center; gap: 11px; - min-width: ${width || '200px'}; - max-width: 400px; - width: 100%; - padding: 8px; + width: ${width || 'auto'}; + min-width: fit-content; + padding: 8px 16px; background-color: #f7f7f7; color: ${theme.colors.black}; border: 1px solid #beb2b294; border-radius: 9px; font-size: 14px; - max-height: 42px; + height: 42px; + box-sizing: border-box; cursor: pointer; - transition: background-color 0.2s ease; - + transition: all 0.2s ease; &:hover { background-color: ${theme.colors.grey_1}; } @@ -60,29 +59,22 @@ const chevronStyle = (open: boolean) => css` transform: ${open ? 'rotate(180deg)' : 'none'}; transition: transform 0.2s ease; `; -const dropDownTitleStyle = (theme: any) => css` - padding: 5px 10px; - font-weight: 400; - line-height: 100%; - letter-spacing: 0%; + +const dropDownTitleStyle = (theme: Theme) => css` + ${theme.typography?.heading}; color: ${theme.colors.accent_dark}; `; const dropdownMenuStyle = (theme: Theme) => css` + ${theme.typography?.heading}; position: absolute; top: calc(100% + 5px); - left: 0; width: 100%; background-color: #f7f7f7; - border: 1px solid ${theme.colors?.grey_1}; - border-radius: 4px; - box-shadow: - 0 1px 6px rgba(0, 0, 0, 0.1), - 0 1px 5px rgba(0, 0, 0, 0.08); - list-style: none; - padding: 4px 0; - margin: 0; - z-index: 1000; + border: 1px solid #beb2b294; + border-radius: 9px; + padding-top: 5px; + padding-bottom: 5px; `; type MenuItem = { @@ -103,8 +95,6 @@ const Dropdown = ({ menuItems = [], title, leftIcon }: DropDownProps) => { const { ChevronDown } = theme.icons; - const hasMenuItems = menuItems.length > 0; - useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { @@ -141,7 +131,7 @@ const Dropdown = ({ menuItems = [], title, leftIcon }: DropDownProps) => {
- {open &&
    {renderMenuItems()}
} + {open &&
{renderMenuItems()}
}
); diff --git a/packages/ui/src/common/Dropdown/DropdownItem.tsx b/packages/ui/src/common/Dropdown/DropdownItem.tsx index 342aacec..ffafc26a 100644 --- a/packages/ui/src/common/Dropdown/DropdownItem.tsx +++ b/packages/ui/src/common/Dropdown/DropdownItem.tsx @@ -37,21 +37,12 @@ type DropDownItemProps = { const styledListItemStyle = (theme: Theme, customStyles?: any) => css` display: flex; - max-height: 42px; min-height: 100%; height: 100%; align-items: center; - padding: 8px; justify-content: center; - color: ${theme.colors?.black}; - background-color: #f7f7f7; - border: 1px solid ${theme.colors.grey_1}; - text-decoration: none; + color: ${theme.colors.accent_dark}; cursor: pointer; - border: none; - &:hover { - background-color: ${theme.colors.grey_2}; - } ${customStyles?.base} `; From 5414669a00e1aa477cc07d425411e5c39d212b48 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 5 Jun 2025 13:40:16 -0400 Subject: [PATCH 018/217] fix subpar styling for dropdown --- packages/ui/src/common/Dropdown/Dropdown.tsx | 10 ++++------ packages/ui/src/common/Dropdown/DropdownItem.tsx | 8 ++++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/common/Dropdown/Dropdown.tsx b/packages/ui/src/common/Dropdown/Dropdown.tsx index 987060cd..63c64dd4 100644 --- a/packages/ui/src/common/Dropdown/Dropdown.tsx +++ b/packages/ui/src/common/Dropdown/Dropdown.tsx @@ -37,7 +37,7 @@ const dropdownButtonStyle = (theme: Theme, width?: string) => css` min-width: fit-content; padding: 8px 16px; background-color: #f7f7f7; - color: ${theme.colors.black}; + color: ${theme.colors.accent_dark}; border: 1px solid #beb2b294; border-radius: 9px; font-size: 14px; @@ -45,9 +45,6 @@ const dropdownButtonStyle = (theme: Theme, width?: string) => css` box-sizing: border-box; cursor: pointer; transition: all 0.2s ease; - &:hover { - background-color: ${theme.colors.grey_1}; - } `; const parentStyle = css` @@ -72,8 +69,8 @@ const dropdownMenuStyle = (theme: Theme) => css` width: 100%; background-color: #f7f7f7; border: 1px solid #beb2b294; - border-radius: 9px; padding-top: 5px; + border-radius: 9px; padding-bottom: 5px; `; @@ -128,9 +125,10 @@ const Dropdown = ({ menuItems = [], title, leftIcon }: DropDownProps) => {
{leftIcon} {title} - +
+ {/* {open && <>{renderMenuItems()}} */} {open &&
{renderMenuItems()}
}
diff --git a/packages/ui/src/common/Dropdown/DropdownItem.tsx b/packages/ui/src/common/Dropdown/DropdownItem.tsx index ffafc26a..645c3b3f 100644 --- a/packages/ui/src/common/Dropdown/DropdownItem.tsx +++ b/packages/ui/src/common/Dropdown/DropdownItem.tsx @@ -40,10 +40,14 @@ const styledListItemStyle = (theme: Theme, customStyles?: any) => css` min-height: 100%; height: 100%; align-items: center; + border-radius: 9px; justify-content: center; color: ${theme.colors.accent_dark}; cursor: pointer; ${customStyles?.base} + &:hover { + background-color: ${theme.colors.grey_1}; + } `; const DropDownItem = ({ children, action, customStyles }: DropDownItemProps) => { @@ -51,9 +55,9 @@ const DropDownItem = ({ children, action, customStyles }: DropDownItemProps) => const content =
{children}
; if (typeof action === 'function') { return ( - + ); } From c10619cdcf786c993f8eeb2efa5ebcd3798eef6b Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 5 Jun 2025 13:52:19 -0400 Subject: [PATCH 019/217] code cleanup --- .../InteractionPanel/TableOfContentsDropdown.tsx | 6 ++---- .../viewer-table/TableOfContentDropdown.stories.tsx | 9 ++------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx index 32cc3159..904c438d 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx @@ -4,15 +4,13 @@ import List from '../../theme/icons/List'; type TableOfContentsDropdownProps = { schemas: Schema[]; - onSchemaSelect: (schema: Schema) => void; onAccordionToggle: (schemaName: string, isOpen: boolean) => void; }; -const TableOfContentsDropdown = ({ schemas, onSchemaSelect, onAccordionToggle }: TableOfContentsDropdownProps) => { +const TableOfContentsDropdown = ({ schemas, onAccordionToggle }: TableOfContentsDropdownProps) => { const handleAction = (schema: Schema) => { const anchorId = `${schema.name}`; - onAccordionToggle(schema.name, true); - onSchemaSelect(schema); + onAccordionToggle(schema.name, true); // Ensuring that the accordion for the associated schema is open, via the state handler that will be defined in parent component setTimeout(() => { window.location.hash = anchorId; }, 100); diff --git a/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx b/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx index f536c6d6..c7cefccf 100644 --- a/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx @@ -21,22 +21,17 @@ const schema: Schema = primitiveJson as Schema; // Mock functions for the story just to demonstrate interaction -const onSchemaSelect = (schema: Schema) => { - console.log('Selected schema:', schema); -}; - const onAccordionToggle = (schemaName: string, isOpen: boolean) => { - console.log(`Accordion ${isOpen ? 'opened' : 'closed'} for schema:`, schemaName); + console.log('Accordion has been toggled for the following schema: ', schemaName); }; export const Default: Story = { args: { schemas: [schema], - onSchemaSelect, onAccordionToggle, }, }; export const Empty: Story = { - args: { schemas: [], onSchemaSelect, onAccordionToggle }, + args: { schemas: [], onAccordionToggle }, }; From ffa06e0d3d9df19f66166106424b147c42410441 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 5 Jun 2025 14:07:06 -0400 Subject: [PATCH 020/217] add an advanced.json to better facilitate visualation of table of contents and update the stories --- .../TableOfContentDropdown.stories.tsx | 6 ++-- samples/dictionary/advanced.json | 35 +++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 samples/dictionary/advanced.json diff --git a/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx b/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx index c7cefccf..4a5f4506 100644 --- a/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx @@ -2,7 +2,7 @@ import { Schema } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; -import primitiveJson from '../../../../samples/schemas/primitives.json'; +import Dictionary from '../../../../samples/dictionary/advanced.json'; import TableOfContentsDropdown from '../../src/viewer-table/InteractionPanel/TableOfContentsDropdown'; import themeDecorator from '../themeDecorator'; @@ -17,7 +17,7 @@ export default meta; type Story = StoryObj; // Using the primitiveJson as a mock schema for demonstration purposes -const schema: Schema = primitiveJson as Schema; +const schemas: Schema[] = Dictionary.schemas as Schema[]; // Mock functions for the story just to demonstrate interaction @@ -27,7 +27,7 @@ const onAccordionToggle = (schemaName: string, isOpen: boolean) => { export const Default: Story = { args: { - schemas: [schema], + schemas: schemas, onAccordionToggle, }, }; diff --git a/samples/dictionary/advanced.json b/samples/dictionary/advanced.json new file mode 100644 index 00000000..875347ca --- /dev/null +++ b/samples/dictionary/advanced.json @@ -0,0 +1,35 @@ +{ + "name": "Simple", + "version": "1.0", + "schemas": [ + { + "name": "empty", + "description": "An empty schema with no fields.", + "fields": [] + }, + { + "name": "single_field", + "description": "A schema with a single field of type string.", + "fields": [{ "name": "single_string_field", "valueType": "string" }] + }, + { + "name": "multiple_fields", + "description": "A schema with multiple fields of different types.", + "fields": [ + { "name": "string_field", "valueType": "string" }, + { "name": "integer_field", "valueType": "integer" }, + { "name": "boolean_field", "valueType": "boolean" } + ] + }, + { + "name": "primitives", + "description": "Includes one field of each primitive type without any restrictions. No Frills.", + "fields": [ + { "name": "boolean_field", "valueType": "boolean" }, + { "name": "integer_field", "valueType": "integer" }, + { "name": "number_field", "valueType": "number" }, + { "name": "string_field", "valueType": "string" } + ] + } + ] +} From 3f5549eda79c3bcafd67040de248b5808cc7856a Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 5 Jun 2025 15:41:11 -0400 Subject: [PATCH 021/217] port over the attribute filter from stage --- packages/ui/src/theme/icons/ListFilter.tsx | 50 +++++++++++++++++ packages/ui/src/theme/styles/icons.ts | 1 + .../AttributeFilterDropdown.tsx | 55 +++++++++++++++++++ .../AttributeFilterDropdown.stories.tsx | 24 ++++++++ 4 files changed, 130 insertions(+) create mode 100644 packages/ui/src/theme/icons/ListFilter.tsx create mode 100644 packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx create mode 100644 packages/ui/stories/viewer-table/AttributeFilterDropdown.stories.tsx diff --git a/packages/ui/src/theme/icons/ListFilter.tsx b/packages/ui/src/theme/icons/ListFilter.tsx new file mode 100644 index 00000000..01e0c03f --- /dev/null +++ b/packages/ui/src/theme/icons/ListFilter.tsx @@ -0,0 +1,50 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** @jsxImportSource @emotion/react */ + +import { css } from '@emotion/react'; + +import IconProps from './IconProps'; + +const ListFilter = ({ fill, width, height, style }: IconProps) => { + return ( + + + + + + ); +}; + +export default ListFilter; diff --git a/packages/ui/src/theme/styles/icons.ts b/packages/ui/src/theme/styles/icons.ts index ca4167f4..31a1c19f 100644 --- a/packages/ui/src/theme/styles/icons.ts +++ b/packages/ui/src/theme/styles/icons.ts @@ -3,4 +3,5 @@ import List from '../icons/List'; export default { ChevronDown, List, + LIst, }; diff --git a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx new file mode 100644 index 00000000..76796077 --- /dev/null +++ b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx @@ -0,0 +1,55 @@ +import type { Schema, Dictionary } from '@overture-stack/lectern-dictionary'; +import Dropdown from '../../common/Dropdown/Dropdown'; +import List from '../../theme/icons/List'; + +type FilterDropdownProps = { + data: Dictionary; + isFiltered: boolean; + setFilteredData: (dict: Dictionary) => void; + setIsFiltered: (bool: boolean) => void; +}; + +const AttributeFilter = ({ data, isFiltered, setFilteredData, setIsFiltered }: FilterDropdownProps) => { + const handleFilterSelect = (selectedFilterName: string) => { + if (isFiltered) { + setFilteredData(data); + setIsFiltered(false); + return; + } + + if (selectedFilterName === 'Required') { + const filteredDictionary: Dictionary = { + ...data, + schemas: data.schemas.map((schema) => ({ + ...schema, + fields: schema.fields.filter((field: any) => field?.restrictions?.required === true), + })), + }; + setFilteredData(filteredDictionary); + } + // Add more filters here as needed + // Follow the same pattern as above for additional filters + // If we have a lot of filtering logic, we might want to consider moving this logic into a separate utility function + else { + setFilteredData(data); + } + + setIsFiltered(true); + }; + + // We can easily define more filters here in the future + const menuItems = [ + { + label: 'All Fields', + action: () => handleFilterSelect('All Fields'), + }, + { + label: 'Required Only', + action: () => handleFilterSelect('Required'), + }, + ]; + + return } title="Filter By" menuItems={menuItems} />; +}; + +export default AttributeFilter; diff --git a/packages/ui/stories/viewer-table/AttributeFilterDropdown.stories.tsx b/packages/ui/stories/viewer-table/AttributeFilterDropdown.stories.tsx new file mode 100644 index 00000000..837f1e4e --- /dev/null +++ b/packages/ui/stories/viewer-table/AttributeFilterDropdown.stories.tsx @@ -0,0 +1,24 @@ +/** @jsxImportSource @emotion/react */ +import type { Meta, StoryObj } from '@storybook/react'; +import AttributeFilter from '../../src/viewer-table/InteractionPanel/AttributeFilterDropdown'; +import themeDecorator from '../themeDecorator'; +import AdvancedDictionary from '../../../../samples/dictionary/advanced.json'; +import { Dictionary } from '@overture-stack/lectern-dictionary'; +const meta = { + component: AttributeFilter, + title: 'Viewer - Table/AttributeFilterDropdown', + tags: ['autodocs'], + decorators: [themeDecorator()], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + data: AdvancedDictionary as Dictionary, + isFiltered: false, + setFilteredData: () => {}, + setIsFiltered: () => {}, + }, +}; From ce914945800ebb314470f3c751d3668f99d90e6e Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 5 Jun 2025 15:42:09 -0400 Subject: [PATCH 022/217] fix naming issue with list --- packages/ui/src/theme/icons/List.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/theme/icons/List.tsx b/packages/ui/src/theme/icons/List.tsx index c051eedc..1beeaa26 100644 --- a/packages/ui/src/theme/icons/List.tsx +++ b/packages/ui/src/theme/icons/List.tsx @@ -25,7 +25,7 @@ import { css } from '@emotion/react'; import IconProps from './IconProps'; -const ChevronDown = ({ fill, width, height, style }: IconProps) => { +const List = ({ fill, width, height, style }: IconProps) => { return ( { ); }; -export default ChevronDown; +export default List; From 6a68b7a817a92c415eaa3d303102287f0a4624b6 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 5 Jun 2025 15:45:31 -0400 Subject: [PATCH 023/217] finish up attribute filter dropdown implementation --- packages/ui/src/theme/styles/icons.ts | 3 ++- .../InteractionPanel/AttributeFilterDropdown.tsx | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/theme/styles/icons.ts b/packages/ui/src/theme/styles/icons.ts index 31a1c19f..7b42aeb8 100644 --- a/packages/ui/src/theme/styles/icons.ts +++ b/packages/ui/src/theme/styles/icons.ts @@ -1,7 +1,8 @@ import ChevronDown from '../icons/ChevronDown'; import List from '../icons/List'; +import ListFilter from '../icons/ListFilter'; export default { ChevronDown, List, - LIst, + ListFilter, }; diff --git a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx index 76796077..153f7bcb 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx @@ -1,6 +1,6 @@ -import type { Schema, Dictionary } from '@overture-stack/lectern-dictionary'; +import type { Dictionary } from '@overture-stack/lectern-dictionary'; import Dropdown from '../../common/Dropdown/Dropdown'; -import List from '../../theme/icons/List'; +import ListFilter from '../../theme/icons/ListFilter'; type FilterDropdownProps = { data: Dictionary; @@ -49,7 +49,7 @@ const AttributeFilter = ({ data, isFiltered, setFilteredData, setIsFiltered }: F }, ]; - return } title="Filter By" menuItems={menuItems} />; + return } title="Filter By" menuItems={menuItems} />; }; export default AttributeFilter; From eab689f32480e9e016e48583909b6240c0b5c648 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 6 Jun 2025 09:40:06 -0400 Subject: [PATCH 024/217] relocate all interaction panel based things in a different folder for better organization --- packages/ui/src/common/Button.tsx | 2 +- .../{ => interaction-panel}/CollapseAllButton.stories.tsx | 6 +++--- .../{ => interaction-panel}/ExpandAllButton.stories.tsx | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) rename packages/ui/stories/viewer-table/{ => interaction-panel}/CollapseAllButton.stories.tsx (68%) rename packages/ui/stories/viewer-table/{ => interaction-panel}/ExpandAllButton.stories.tsx (68%) diff --git a/packages/ui/src/common/Button.tsx b/packages/ui/src/common/Button.tsx index b51178fe..1edbbfff 100644 --- a/packages/ui/src/common/Button.tsx +++ b/packages/ui/src/common/Button.tsx @@ -54,7 +54,7 @@ const getButtonContainerStyles = (theme: any, width?: string) => css` border: 1px solid #beb2b294; border-radius: 9px; font-size: 14px; - height: 43px; + height: 42px; box-sizing: border-box; cursor: pointer; position: relative; diff --git a/packages/ui/stories/viewer-table/CollapseAllButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx similarity index 68% rename from packages/ui/stories/viewer-table/CollapseAllButton.stories.tsx rename to packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx index 73fc96bf..80a60dd2 100644 --- a/packages/ui/stories/viewer-table/CollapseAllButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx @@ -1,12 +1,12 @@ /** @jsxImportSource @emotion/react */ import type { Meta, StoryObj } from '@storybook/react'; -import themeDecorator from '../themeDecorator'; -import CollapseAllButton from '../../src/viewer-table/InteractionPanel/CollapseAllButton'; +import themeDecorator from '../../themeDecorator'; +import CollapseAllButton from '../../../src/viewer-table/InteractionPanel/CollapseAllButton'; const meta = { component: CollapseAllButton, - title: 'Viewer - Table/CollapseAllButton', + title: 'Viewer - Table/Interaction - Panel/CollapseAllButton', tags: ['autodocs'], decorators: [themeDecorator()], } satisfies Meta; diff --git a/packages/ui/stories/viewer-table/ExpandAllButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx similarity index 68% rename from packages/ui/stories/viewer-table/ExpandAllButton.stories.tsx rename to packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx index 1a150099..f706163d 100644 --- a/packages/ui/stories/viewer-table/ExpandAllButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx @@ -1,11 +1,11 @@ /** @jsxImportSource @emotion/react */ import type { Meta, StoryObj } from '@storybook/react'; -import themeDecorator from '../themeDecorator'; -import ExpandAllButton from '../../src/viewer-table/InteractionPanel/ExpandAllButton'; +import themeDecorator from '../../themeDecorator'; +import ExpandAllButton from '../../../src/viewer-table/InteractionPanel/ExpandAllButton'; const meta = { component: ExpandAllButton, - title: 'Viewer - Table/ExpandAllButton', + title: 'Viewer - Table/Interaction - Panel/ExpandAllButton', tags: ['autodocs'], decorators: [themeDecorator()], } satisfies Meta; From edc090232951a84be24cbf3be6a61e59364b5fda Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 6 Jun 2025 09:45:09 -0400 Subject: [PATCH 025/217] moving dropdown into the interaction panel --- .../TableOfContentDropdown.stories.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename packages/ui/stories/viewer-table/{ => interaction-panel}/TableOfContentDropdown.stories.tsx (74%) diff --git a/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx similarity index 74% rename from packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx rename to packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx index 4a5f4506..bc79d10e 100644 --- a/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx @@ -2,13 +2,13 @@ import { Schema } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; -import Dictionary from '../../../../samples/dictionary/advanced.json'; -import TableOfContentsDropdown from '../../src/viewer-table/InteractionPanel/TableOfContentsDropdown'; -import themeDecorator from '../themeDecorator'; +import Dictionary from '../../../../../samples/dictionary/advanced.json'; +import TableOfContentsDropdown from '../../../src/viewer-table/InteractionPanel/TableOfContentsDropdown'; +import themeDecorator from '../../themeDecorator'; const meta = { component: TableOfContentsDropdown, - title: 'Viewer - Table/Table of Contents Dropdown', + title: 'Viewer - Table/Interaction - Panel/Table of Contents Dropdown', tags: ['autodocs'], decorators: [themeDecorator()], } satisfies Meta; From 2b1c714013bd87a152ea510e0fca155996db85c7 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 6 Jun 2025 10:22:55 -0400 Subject: [PATCH 026/217] initial commit, initializing the component --- packages/ui/src/theme/icons/FileDownload.tsx | 52 ++++++++ packages/ui/src/theme/styles/icons.ts | 2 + .../DownloadTemplatesButton.tsx | 124 ++++++++++++++++++ .../DictionaryDownloadButton.stories.tsx | 18 +++ 4 files changed, 196 insertions(+) create mode 100644 packages/ui/src/theme/icons/FileDownload.tsx create mode 100644 packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx create mode 100644 packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx diff --git a/packages/ui/src/theme/icons/FileDownload.tsx b/packages/ui/src/theme/icons/FileDownload.tsx new file mode 100644 index 00000000..710f7de0 --- /dev/null +++ b/packages/ui/src/theme/icons/FileDownload.tsx @@ -0,0 +1,52 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** @jsxImportSource @emotion/react */ + +import { css } from '@emotion/react'; +import IconProps from './IconProps'; + +const FileDownload = ({ style, height, width }: IconProps) => { + return ( + + + + + + + ); +}; +export default FileDownload; diff --git a/packages/ui/src/theme/styles/icons.ts b/packages/ui/src/theme/styles/icons.ts index 2b107574..f31206cb 100644 --- a/packages/ui/src/theme/styles/icons.ts +++ b/packages/ui/src/theme/styles/icons.ts @@ -2,9 +2,11 @@ import ChevronDown from '../icons/ChevronDown'; import Spinner from '../icons/Spinner'; import Collapse from '../icons/Collapse'; import Eye from '../icons/Eye'; +import FileDownload from '../icons/FileDownload'; export default { ChevronDown, Spinner, Collapse, Eye, + FileDownload, }; diff --git a/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx new file mode 100644 index 00000000..03a3a206 --- /dev/null +++ b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx @@ -0,0 +1,124 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** @jsxImportSource @emotion/react */ + +import { css } from '@emotion/react'; +import { useState } from 'react'; +import Button from '../../common/Button'; +import { useThemeContext } from '../../theme/ThemeContext'; + +export const actionItemStyle = (theme: any, width?: string) => css` + display: flex; + flex-wrap: nowrap; + align-items: center; + justify-content: space-between; + gap: 11px; + min-width: ${width || '200px'}; + max-width: 400px; + width: 100%; + padding: 8px; + background-color: #f7f7f7; + color: ${theme.colors.black}; + border: 1px solid #beb2b294; + border-radius: 9px; + font-size: 14px; + height: 43px; + box-sizing: border-box; + cursor: pointer; + transition: background-color 0.2s ease; + + &:hover { + background-color: ${theme.colors.grey_1}; + } + + span, + div { + display: flex; + align-items: center; + font-weight: 400; + gap: 8px; + font-size: 16px; + line-height: 1.2; + color: ${theme.colors.accent_dark}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +`; + +type DictionaryDownloadButtonProps = { + version: string; + name: string; + lecternUrl: string; + fileType?: 'tsv' | 'csv'; +}; + +const DictionaryDownloadButton = ({ version, name, lecternUrl, fileType = 'tsv' }: DictionaryDownloadButtonProps) => { + const [isLoading, setIsLoading] = useState(false); + const theme = useThemeContext(); + const { FileDownload } = theme.icons; + + const fetchUrl = `${lecternUrl}/dictionaries/template/download?${new URLSearchParams({ + name, + version, + fileType, + })}`; + + const downloadDictionary = async () => { + try { + setIsLoading(true); + const res = await fetch(fetchUrl); + + if (!res.ok) { + throw new Error(`Failed with status ${res.status}`); + } + + //Triggers a file download in the browser by creating a temporary link to a Blob + // and simulating a click. + + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = `${name}_${version}_templates.zip`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + + URL.revokeObjectURL(url); + } catch (error) { + console.error('Error downloading dictionary:', error); + } finally { + setIsLoading(false); + } + }; + + return ( + Submission Templates + + ); +}; + +export default DictionaryDownloadButton; diff --git a/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx new file mode 100644 index 00000000..deb40ea5 --- /dev/null +++ b/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx @@ -0,0 +1,18 @@ +/** @jsxImportSource @emotion/react */ + +import type { Meta, StoryObj } from '@storybook/react'; +import themeDecorator from '../../themeDecorator'; +import DictionaryDownloadButton from '../../../src/viewer-table/InteractionPanel/DownloadTemplatesButton'; +const meta = { + component: DictionaryDownloadButton, + title: 'Viewer - Table/Interaction - Panel/DictionaryDownloadButton', + tags: ['autodocs'], + decorators: [themeDecorator()], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: {}, +}; From 41baefa95cb39160dcd448727767c0cbd7219001 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 6 Jun 2025 10:24:20 -0400 Subject: [PATCH 027/217] use theme context instead of individually importing the icon --- .../viewer-table/InteractionPanel/TableOfContentsDropdown.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx index 904c438d..a91a5551 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx @@ -1,6 +1,6 @@ import type { Schema } from '@overture-stack/lectern-dictionary'; import Dropdown from '../../common/Dropdown/Dropdown'; -import List from '../../theme/icons/List'; +import { useThemeContext } from '../../theme/ThemeContext'; type TableOfContentsDropdownProps = { schemas: Schema[]; @@ -8,6 +8,8 @@ type TableOfContentsDropdownProps = { }; const TableOfContentsDropdown = ({ schemas, onAccordionToggle }: TableOfContentsDropdownProps) => { + const theme = useThemeContext(); + const { List } = theme.icons; const handleAction = (schema: Schema) => { const anchorId = `${schema.name}`; onAccordionToggle(schema.name, true); // Ensuring that the accordion for the associated schema is open, via the state handler that will be defined in parent component From 24aeabdc00501bd34c9b72048cb17b2735002b60 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 6 Jun 2025 10:38:39 -0400 Subject: [PATCH 028/217] complete implementation of the dictionary download button --- .../DownloadTemplatesButton.tsx | 45 +------------------ .../DictionaryDownloadButton.stories.tsx | 7 ++- 2 files changed, 8 insertions(+), 44 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx index 03a3a206..af77d5cd 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx @@ -21,50 +21,10 @@ /** @jsxImportSource @emotion/react */ -import { css } from '@emotion/react'; import { useState } from 'react'; import Button from '../../common/Button'; import { useThemeContext } from '../../theme/ThemeContext'; -export const actionItemStyle = (theme: any, width?: string) => css` - display: flex; - flex-wrap: nowrap; - align-items: center; - justify-content: space-between; - gap: 11px; - min-width: ${width || '200px'}; - max-width: 400px; - width: 100%; - padding: 8px; - background-color: #f7f7f7; - color: ${theme.colors.black}; - border: 1px solid #beb2b294; - border-radius: 9px; - font-size: 14px; - height: 43px; - box-sizing: border-box; - cursor: pointer; - transition: background-color 0.2s ease; - - &:hover { - background-color: ${theme.colors.grey_1}; - } - - span, - div { - display: flex; - align-items: center; - font-weight: 400; - gap: 8px; - font-size: 16px; - line-height: 1.2; - color: ${theme.colors.accent_dark}; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } -`; - type DictionaryDownloadButtonProps = { version: string; name: string; @@ -114,9 +74,8 @@ const DictionaryDownloadButton = ({ version, name, lecternUrl, fileType = 'tsv' }; return ( - ); }; diff --git a/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx index deb40ea5..1e1b3a2e 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx @@ -14,5 +14,10 @@ export default meta; type Story = StoryObj; export const Default: Story = { - args: {}, + args: { + version: '1.0.0', + name: 'Sample Dictionary', + lecternUrl: 'https://example.com', + fileType: 'tsv', + }, }; From 70115329457c8fb56c3de0ddb62a9df9791df749 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 6 Jun 2025 10:52:44 -0400 Subject: [PATCH 029/217] make the download template button actually interactive --- .../interaction-panel/DictionaryDownloadButton.stories.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx index 1e1b3a2e..3aab598a 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx @@ -15,9 +15,9 @@ type Story = StoryObj; export const Default: Story = { args: { - version: '1.0.0', - name: 'Sample Dictionary', - lecternUrl: 'https://example.com', + version: '1.0', + name: 'example-dictionary', + lecternUrl: 'http://localhost:3031', fileType: 'tsv', }, }; From 109773c7949d096d589b6294851e954bab599f2a Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 6 Jun 2025 11:00:47 -0400 Subject: [PATCH 030/217] reoganize the components all together as one interaction panel story --- .../AttributeFilterDropdown.stories.tsx | 8 ++++---- .../TableOfContentDropdown.stories.tsx | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) rename packages/ui/stories/viewer-table/{ => interaction-panel}/AttributeFilterDropdown.stories.tsx (63%) rename packages/ui/stories/viewer-table/{ => interaction-panel}/TableOfContentDropdown.stories.tsx (74%) diff --git a/packages/ui/stories/viewer-table/AttributeFilterDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx similarity index 63% rename from packages/ui/stories/viewer-table/AttributeFilterDropdown.stories.tsx rename to packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx index 837f1e4e..85355761 100644 --- a/packages/ui/stories/viewer-table/AttributeFilterDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx @@ -1,12 +1,12 @@ /** @jsxImportSource @emotion/react */ import type { Meta, StoryObj } from '@storybook/react'; -import AttributeFilter from '../../src/viewer-table/InteractionPanel/AttributeFilterDropdown'; -import themeDecorator from '../themeDecorator'; -import AdvancedDictionary from '../../../../samples/dictionary/advanced.json'; +import AttributeFilter from '../../../src/viewer-table/InteractionPanel/AttributeFilterDropdown'; +import themeDecorator from '../../themeDecorator'; +import AdvancedDictionary from '../../../../../samples/dictionary/advanced.json'; import { Dictionary } from '@overture-stack/lectern-dictionary'; const meta = { component: AttributeFilter, - title: 'Viewer - Table/AttributeFilterDropdown', + title: 'Viewer - Table/Interaction - Panel/AttributeFilterDropdown', tags: ['autodocs'], decorators: [themeDecorator()], } satisfies Meta; diff --git a/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx similarity index 74% rename from packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx rename to packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx index 4a5f4506..a0a29cd4 100644 --- a/packages/ui/stories/viewer-table/TableOfContentDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx @@ -2,13 +2,13 @@ import { Schema } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; -import Dictionary from '../../../../samples/dictionary/advanced.json'; -import TableOfContentsDropdown from '../../src/viewer-table/InteractionPanel/TableOfContentsDropdown'; -import themeDecorator from '../themeDecorator'; +import Dictionary from '../../../../../samples/dictionary/advanced.json'; +import TableOfContentsDropdown from '../../../src/viewer-table/InteractionPanel/TableOfContentsDropdown'; +import themeDecorator from '../../themeDecorator'; const meta = { component: TableOfContentsDropdown, - title: 'Viewer - Table/Table of Contents Dropdown', + title: 'Viewer - Table/Interaction-Panel/Table of Contents Dropdown', tags: ['autodocs'], decorators: [themeDecorator()], } satisfies Meta; From 0c97a594fda4c68d811e2b69f4e8fe48624a316a Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 6 Jun 2025 11:17:52 -0400 Subject: [PATCH 031/217] finalizing the styling according to the figma --- packages/ui/src/common/Dropdown/Dropdown.tsx | 5 ++--- packages/ui/src/common/Dropdown/DropdownItem.tsx | 1 + packages/ui/src/theme/styles/typography.ts | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/common/Dropdown/Dropdown.tsx b/packages/ui/src/common/Dropdown/Dropdown.tsx index 63c64dd4..648d6f23 100644 --- a/packages/ui/src/common/Dropdown/Dropdown.tsx +++ b/packages/ui/src/common/Dropdown/Dropdown.tsx @@ -40,7 +40,6 @@ const dropdownButtonStyle = (theme: Theme, width?: string) => css` color: ${theme.colors.accent_dark}; border: 1px solid #beb2b294; border-radius: 9px; - font-size: 14px; height: 42px; box-sizing: border-box; cursor: pointer; @@ -58,12 +57,12 @@ const chevronStyle = (open: boolean) => css` `; const dropDownTitleStyle = (theme: Theme) => css` - ${theme.typography?.heading}; + ${theme.typography?.button}; color: ${theme.colors.accent_dark}; `; const dropdownMenuStyle = (theme: Theme) => css` - ${theme.typography?.heading}; + ${theme.typography?.button}; position: absolute; top: calc(100% + 5px); width: 100%; diff --git a/packages/ui/src/common/Dropdown/DropdownItem.tsx b/packages/ui/src/common/Dropdown/DropdownItem.tsx index 645c3b3f..370d3374 100644 --- a/packages/ui/src/common/Dropdown/DropdownItem.tsx +++ b/packages/ui/src/common/Dropdown/DropdownItem.tsx @@ -38,6 +38,7 @@ type DropDownItemProps = { const styledListItemStyle = (theme: Theme, customStyles?: any) => css` display: flex; min-height: 100%; + padding-bottom: 5px; height: 100%; align-items: center; border-radius: 9px; diff --git a/packages/ui/src/theme/styles/typography.ts b/packages/ui/src/theme/styles/typography.ts index e02454de..e4df0422 100644 --- a/packages/ui/src/theme/styles/typography.ts +++ b/packages/ui/src/theme/styles/typography.ts @@ -35,11 +35,11 @@ const regular = css` const button = css` ${baseFont} - font-size: 16px; - font-weight: bold; + font-size: 20px; + font-weight: 700; font-style: normal; font-stretch: normal; - line-height: 18px; + line-height: 100%; letter-spacing: normal; `; From 54d02439800e73cc2ae435d193afc78e2561e453 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 6 Jun 2025 11:26:08 -0400 Subject: [PATCH 032/217] fix styling for buttons --- packages/ui/src/common/Button.tsx | 10 +++------- packages/ui/src/theme/styles/typography.ts | 6 +++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/ui/src/common/Button.tsx b/packages/ui/src/common/Button.tsx index 1edbbfff..6eddd363 100644 --- a/packages/ui/src/common/Button.tsx +++ b/packages/ui/src/common/Button.tsx @@ -53,7 +53,6 @@ const getButtonContainerStyles = (theme: any, width?: string) => css` color: ${theme.colors.black}; border: 1px solid #beb2b294; border-radius: 9px; - font-size: 14px; height: 42px; box-sizing: border-box; cursor: pointer; @@ -70,13 +69,11 @@ const getButtonContainerStyles = (theme: any, width?: string) => css` } `; -const getContentStyles = (shouldShowLoading: boolean) => css` +const getContentStyles = (theme: Theme, shouldShowLoading: boolean) => css` display: flex; align-items: center; - font-weight: 400; gap: 8px; - font-size: 16px; - line-height: 1.2; + ${theme.typography.button}; color: inherit; white-space: nowrap; overflow: hidden; @@ -118,7 +115,6 @@ const Button = React.forwardRef( const [internalLoading, setInternalLoading] = React.useState(false); const shouldShowLoading = !!controlledLoading || (internalLoading && isAsync); - const handleClick = async (event: React.SyntheticEvent) => { setInternalLoading(true); await onClick(event); @@ -135,7 +131,7 @@ const Button = React.forwardRef( css={getButtonContainerStyles(theme, width)} > {leftIcon && !shouldShowLoading && {leftIcon}} - {children} + {children} diff --git a/packages/ui/src/theme/styles/typography.ts b/packages/ui/src/theme/styles/typography.ts index e02454de..e4df0422 100644 --- a/packages/ui/src/theme/styles/typography.ts +++ b/packages/ui/src/theme/styles/typography.ts @@ -35,11 +35,11 @@ const regular = css` const button = css` ${baseFont} - font-size: 16px; - font-weight: bold; + font-size: 20px; + font-weight: 700; font-style: normal; font-stretch: normal; - line-height: 18px; + line-height: 100%; letter-spacing: normal; `; From 5598d094e54d2d380aa8af0782e3ee22066bb3b1 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 6 Jun 2025 15:07:10 -0400 Subject: [PATCH 033/217] dictionary version implementation finished --- packages/ui/src/theme/icons/History.tsx | 49 ++++++++++++++++ packages/ui/src/theme/styles/icons.ts | 2 + .../DictionaryVersionSwitcher.tsx | 56 +++++++++++++++++++ .../DictionaryVersionSwitcher.stories.tsx | 28 ++++++++++ 4 files changed, 135 insertions(+) create mode 100644 packages/ui/src/theme/icons/History.tsx create mode 100644 packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx create mode 100644 packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx diff --git a/packages/ui/src/theme/icons/History.tsx b/packages/ui/src/theme/icons/History.tsx new file mode 100644 index 00000000..b5342ece --- /dev/null +++ b/packages/ui/src/theme/icons/History.tsx @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** @jsxImportSource @emotion/react */ +import IconProps from './IconProps'; +import { css } from '@emotion/react'; + +const History = ({ width, height, style }: IconProps) => { + return ( + + + + + + ); +}; +export default History; diff --git a/packages/ui/src/theme/styles/icons.ts b/packages/ui/src/theme/styles/icons.ts index 1a65c9ad..a0ee858a 100644 --- a/packages/ui/src/theme/styles/icons.ts +++ b/packages/ui/src/theme/styles/icons.ts @@ -5,6 +5,7 @@ import Eye from '../icons/Eye'; import FileDownload from '../icons/FileDownload'; import List from '../icons/List'; import ListFilter from '../icons/ListFilter'; +import History from '../icons/History'; export default { ChevronDown, Spinner, @@ -13,4 +14,5 @@ export default { FileDownload, List, ListFilter, + History, }; diff --git a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx new file mode 100644 index 00000000..f9fd1d4b --- /dev/null +++ b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx @@ -0,0 +1,56 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** @jsxImportSource @emotion/react */ +import { Dictionary } from '@overture-stack/lectern-dictionary'; +import Dropdown from '../../common/Dropdown/Dropdown'; +import { useThemeContext } from '../../theme/ThemeContext'; + +type VersionSwitcherProps = { + dictionaryData: Dictionary[]; + onVersionChange: (index: number) => void; + dictionaryIndex: number; +}; + +const VersionSwitcher = ({ dictionaryIndex, dictionaryData, onVersionChange }: VersionSwitcherProps) => { + const theme = useThemeContext(); + const { History } = theme.icons; + + const versionSwitcherObject = dictionaryData?.map((dictionary: Dictionary, index: number) => { + // TODO: We should either remove the version date stamp requirement or update the date to be dynamic via + // lectern-client + return { + label: 'Version ' + dictionary?.version, + action: () => { + onVersionChange(index); + }, + }; + }); + return ( + } + menuItems={versionSwitcherObject} + title={`Version ${dictionaryData?.[dictionaryIndex].version}`} + /> + ); +}; + +export default VersionSwitcher; diff --git a/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx new file mode 100644 index 00000000..7eb45ea5 --- /dev/null +++ b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx @@ -0,0 +1,28 @@ +/** @jsxImportSource @emotion/react */ + +import { Dictionary, Schema } from '@overture-stack/lectern-dictionary'; +import type { Meta, StoryObj } from '@storybook/react'; +import DictionarySample from '../../../../../samples/dictionary/advanced.json'; +import VersionSwitcher from '../../../src/viewer-table/InteractionPanel/DictionaryVersionSwitcher'; +import themeDecorator from '../../themeDecorator'; + +const meta = { + component: VersionSwitcher, + title: 'Viewer - Table/Interaction - Panel/Dictionary Version Switcher', + tags: ['autodocs'], + decorators: [themeDecorator()], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// Using the primitiveJson as a mock schema for demonstration purposes +const DictionaryData: Dictionary[] = [DictionarySample as Dictionary]; + +export const Default: Story = { + args: { + dictionaryData: DictionaryData, + onVersionChange: (index: number) => console.log(`Version changed to index: ${index}`), + dictionaryIndex: 0, + }, +}; From 84b5619cf9e78944c4becd7b022d5a4dec45e21a Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 6 Jun 2025 15:47:49 -0400 Subject: [PATCH 034/217] initialize the interaction component --- packages/ui/src/common/Dropdown/Dropdown.tsx | 1 + .../InteractionPanel/InteractionPanel.tsx | 86 +++++++++++++++++++ .../InteractionPanel.stories.tsx | 50 +++++++++++ 3 files changed, 137 insertions(+) create mode 100644 packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx create mode 100644 packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx diff --git a/packages/ui/src/common/Dropdown/Dropdown.tsx b/packages/ui/src/common/Dropdown/Dropdown.tsx index 648d6f23..4f1ce2a7 100644 --- a/packages/ui/src/common/Dropdown/Dropdown.tsx +++ b/packages/ui/src/common/Dropdown/Dropdown.tsx @@ -30,6 +30,7 @@ import type { Theme } from '../../theme'; const dropdownButtonStyle = (theme: Theme, width?: string) => css` display: flex; flex-wrap: nowrap; + white-space: nowrap; align-items: center; justify-content: center; gap: 11px; diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx new file mode 100644 index 00000000..033b0221 --- /dev/null +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -0,0 +1,86 @@ +/** @jsxImportSource @emotion/react */ + +import { css } from '@emotion/react'; +import TableOfContentsDropdown from './TableOfContentsDropdown'; +import ExpandAllButton from './ExpandAllButton'; +import CollapseAllButton from './CollapseAllButton'; +import AttributeFilterDropdown from './AttributeFilterDropdown'; +import DictionaryVersionSwitcher from './DictionaryVersionSwitcher'; +import DownloadTemplatesButton from './DownloadTemplatesButton'; +import { useThemeContext } from '../../theme/ThemeContext'; +import type { Schema, Dictionary } from '@overture-stack/lectern-dictionary'; + +type InteractionPanelProps = { + schemas: Schema[]; + dictionary: Dictionary; + filteredData: Dictionary; + isFiltered: boolean; + version: string; + name: string; + lecternUrl: string; + setIsCollapsed: (isCollapsed: boolean) => void; + setFilteredData: (dict: Dictionary) => void; + setIsFiltered: (bool: boolean) => void; + onAccordionToggle: (schemaName: string, isOpen: boolean) => void; + onVersionChange?: (version: number) => void; + dictionaryVersions?: Dictionary[]; + currentVersionIndex?: number; +}; + +const InteractionPanel = ({ + schemas, + dictionary, + filteredData, + isFiltered, + version, + name, + lecternUrl, + setIsCollapsed, + setFilteredData, + setIsFiltered, + onAccordionToggle, + onVersionChange, + dictionaryVersions, + currentVersionIndex = 0, +}: InteractionPanelProps) => { + const theme = useThemeContext(); + + const panelStyles = css` + display: flex; + align-items: center; + gap: 16px; + padding: 12px 16px; + border-bottom: 1px solid ${theme.colors.grey_4}; + background-color: ${theme.colors.white}; + min-height: 70px; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: visible; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + position: relative; + `; + + return ( +
+ + + + + {onVersionChange && dictionaryVersions && ( + + )} + +
+ ); +}; + +export default InteractionPanel; diff --git a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx new file mode 100644 index 00000000..a3c0ec70 --- /dev/null +++ b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx @@ -0,0 +1,50 @@ +/** @jsxImportSource @emotion/react */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { Dictionary } from '@overture-stack/lectern-dictionary'; +import InteractionPanel from '../../../src/viewer-table/InteractionPanel/InteractionPanel'; +import themeDecorator from '../../themeDecorator'; +import AdvancedDictionary from '../../../../../samples/dictionary/advanced.json'; +import biosampleDictionary from '../../fixtures/minimalBiosampleModel'; + +const meta = { + component: InteractionPanel, + title: 'Viewer - Table/Interaction - Panel/InteractionPanel', + tags: ['autodocs'], + decorators: [themeDecorator()], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const mockSetIsCollapsed = (isCollapsed: boolean) => { + console.log('setIsCollapsed called with:', isCollapsed); +}; + +const mockSetFilteredData = (dict: Dictionary) => { + console.log('setFilteredData called with dictionary:', dict.name); +}; + +const mockSetIsFiltered = (bool: boolean) => { + console.log('setIsFiltered called with:', bool); +}; + +const mockOnAccordionToggle = (schemaName: string, isOpen: boolean) => { + console.log('onAccordionToggle called for schema:', schemaName, 'isOpen:', isOpen); +}; + +export const Default: Story = { + args: { + schemas: (AdvancedDictionary as Dictionary).schemas, + dictionary: AdvancedDictionary as Dictionary, + filteredData: AdvancedDictionary as Dictionary, + isFiltered: false, + version: '1.0', + name: 'advanced-dictionary', + lecternUrl: 'http://localhost:3031', + setIsCollapsed: mockSetIsCollapsed, + setFilteredData: mockSetFilteredData, + setIsFiltered: mockSetIsFiltered, + onAccordionToggle: mockOnAccordionToggle, + }, +}; From f58ec3fedecb46cd2314f49dea28bca6f331a40c Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 9 Jun 2025 11:23:16 -0400 Subject: [PATCH 035/217] interaction bar rearrangement and left and right row addition --- packages/ui/src/common/Dropdown/Dropdown.tsx | 1 + .../InteractionPanel/InteractionPanel.tsx | 50 ++++++++++++------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/packages/ui/src/common/Dropdown/Dropdown.tsx b/packages/ui/src/common/Dropdown/Dropdown.tsx index 4f1ce2a7..12115ef4 100644 --- a/packages/ui/src/common/Dropdown/Dropdown.tsx +++ b/packages/ui/src/common/Dropdown/Dropdown.tsx @@ -45,6 +45,7 @@ const dropdownButtonStyle = (theme: Theme, width?: string) => css` box-sizing: border-box; cursor: pointer; transition: all 0.2s ease; + z-index: 1000; `; const parentStyle = css` diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index 033b0221..a3f3ff26 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -48,7 +48,7 @@ const InteractionPanel = ({ const panelStyles = css` display: flex; align-items: center; - gap: 16px; + justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid ${theme.colors.grey_4}; background-color: ${theme.colors.white}; @@ -60,25 +60,41 @@ const InteractionPanel = ({ position: relative; `; + const leftSectionStyles = css` + display: flex; + align-items: center; + gap: 16px; + `; + + const rightSectionStyles = css` + display: flex; + align-items: center; + gap: 16px; + `; + return (
- - - - - {onVersionChange && dictionaryVersions && ( - + + - )} - + + +
+
+ {onVersionChange && dictionaryVersions && ( + + )} + +
); }; From f4ccb2906c9d3fa033da36ad9ae4e677ecfe4204 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 9 Jun 2025 14:14:01 -0400 Subject: [PATCH 036/217] make updates to the package.json to make the lectern client available --- packages/ui/package.json | 1 + pnpm-lock.yaml | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 8c2a8bba..c3f2969e 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -24,6 +24,7 @@ "@emotion/styled": "^11.14.0", "@overture-stack/lectern-dictionary": "workspace:*", "@overture-stack/lectern-validation": "workspace:*", + "@overture-stack/lectern-client": "workspace:*", "@tanstack/react-table": "^8.21.3", "react": "^19.1.0", "react-dom": "^19.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e6828851..280864b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -266,6 +266,9 @@ importers: '@emotion/styled': specifier: ^11.14.0 version: 11.14.0(@emotion/react@11.14.0(@types/react@19.1.5)(react@19.1.0))(@types/react@19.1.5)(react@19.1.0) + '@overture-stack/lectern-client': + specifier: workspace:* + version: link:../client '@overture-stack/lectern-dictionary': specifier: workspace:* version: link:../dictionary @@ -302,7 +305,7 @@ importers: version: 8.6.14(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.3.3)) '@storybook/experimental-addon-test': specifier: ^8.6.14 - version: 8.6.14(@vitest/browser@3.1.4(playwright@1.52.0)(vite@6.3.5(@types/node@22.0.0)(yaml@2.8.0))(vitest@3.1.4))(@vitest/runner@3.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.3.3))(vitest@3.1.4(@types/node@22.0.0)(@vitest/browser@3.1.4)(yaml@2.8.0)) + version: 8.6.14(@vitest/browser@3.1.4)(@vitest/runner@3.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.3.3))(vitest@3.1.4) '@storybook/icons': specifier: ^1.4.0 version: 1.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -338,7 +341,7 @@ importers: version: 3.1.4(playwright@1.52.0)(vite@6.3.5(@types/node@22.0.0)(yaml@2.8.0))(vitest@3.1.4) '@vitest/coverage-v8': specifier: ^3.1.4 - version: 3.1.4(@vitest/browser@3.1.4(playwright@1.52.0)(vite@6.3.5(@types/node@22.0.0)(yaml@2.8.0))(vitest@3.1.4))(vitest@3.1.4(@types/node@22.0.0)(@vitest/browser@3.1.4)(yaml@2.8.0)) + version: 3.1.4(@vitest/browser@3.1.4)(vitest@3.1.4) immer: specifier: ^10.1.1 version: 10.1.1 @@ -5524,7 +5527,7 @@ snapshots: storybook: 8.6.14(prettier@3.3.3) unplugin: 1.16.1 - '@storybook/experimental-addon-test@8.6.14(@vitest/browser@3.1.4(playwright@1.52.0)(vite@6.3.5(@types/node@22.0.0)(yaml@2.8.0))(vitest@3.1.4))(@vitest/runner@3.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.3.3))(vitest@3.1.4(@types/node@22.0.0)(@vitest/browser@3.1.4)(yaml@2.8.0))': + '@storybook/experimental-addon-test@8.6.14(@vitest/browser@3.1.4)(@vitest/runner@3.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.3.3))(vitest@3.1.4)': dependencies: '@storybook/global': 5.0.0 '@storybook/icons': 1.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -5993,7 +5996,7 @@ snapshots: - utf-8-validate - vite - '@vitest/coverage-v8@3.1.4(@vitest/browser@3.1.4(playwright@1.52.0)(vite@6.3.5(@types/node@22.0.0)(yaml@2.8.0))(vitest@3.1.4))(vitest@3.1.4(@types/node@22.0.0)(@vitest/browser@3.1.4)(yaml@2.8.0))': + '@vitest/coverage-v8@3.1.4(@vitest/browser@3.1.4)(vitest@3.1.4)': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 From 97a4f6156dcbe3182042b3aaddba67857d87c18d Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 9 Jun 2025 14:23:41 -0400 Subject: [PATCH 037/217] implement disasbled state for dropdown --- packages/ui/src/common/Dropdown/Dropdown.tsx | 25 ++++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/ui/src/common/Dropdown/Dropdown.tsx b/packages/ui/src/common/Dropdown/Dropdown.tsx index 12115ef4..4e20d058 100644 --- a/packages/ui/src/common/Dropdown/Dropdown.tsx +++ b/packages/ui/src/common/Dropdown/Dropdown.tsx @@ -27,7 +27,7 @@ import DropDownItem from './DropdownItem'; import { useThemeContext } from '../../theme/ThemeContext'; import type { Theme } from '../../theme'; -const dropdownButtonStyle = (theme: Theme, width?: string) => css` +const dropdownButtonStyle = (theme: Theme, width?: string, disabled?: boolean) => css` display: flex; flex-wrap: nowrap; white-space: nowrap; @@ -43,9 +43,10 @@ const dropdownButtonStyle = (theme: Theme, width?: string) => css` border-radius: 9px; height: 42px; box-sizing: border-box; - cursor: pointer; + cursor: ${disabled ? 'not-allowed' : 'pointer'}; transition: all 0.2s ease; z-index: 1000; + opacity: ${disabled ? 0.7 : 1}; `; const parentStyle = css` @@ -84,9 +85,10 @@ type DropDownProps = { title?: string; leftIcon?: ReactNode; menuItems?: MenuItem[]; + disabled?: boolean; }; -const Dropdown = ({ menuItems = [], title, leftIcon }: DropDownProps) => { +const Dropdown = ({ menuItems = [], title, leftIcon, disabled = false }: DropDownProps) => { const [open, setOpen] = useState(false); const dropdownRef = useRef(null); const theme = useThemeContext(); @@ -107,10 +109,14 @@ const Dropdown = ({ menuItems = [], title, leftIcon }: DropDownProps) => { }; }, [open]); - const handleToggle = useCallback((e: React.MouseEvent) => { - e.stopPropagation(); - setOpen((prev) => !prev); - }, []); + const handleToggle = useCallback( + (e: React.MouseEvent) => { + if (disabled) return; + e.stopPropagation(); + setOpen((prev) => !prev); + }, + [disabled], + ); const renderMenuItems = () => { return menuItems.map(({ label, action }) => ( @@ -123,14 +129,13 @@ const Dropdown = ({ menuItems = [], title, leftIcon }: DropDownProps) => { return (
-
+
{leftIcon} {title}
- {/* {open && <>{renderMenuItems()}} */} - {open &&
{renderMenuItems()}
} + {open && !disabled &&
{renderMenuItems()}
}
); From fc4cee3b84b8ef81da8bbfcfc48b20cbb8a2109c Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 9 Jun 2025 14:26:45 -0400 Subject: [PATCH 038/217] add a disasbled state as part of the demo for the dropdown, going to need it for the interaction component --- .../ui/stories/common/DropDown.stories.tsx | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/ui/stories/common/DropDown.stories.tsx b/packages/ui/stories/common/DropDown.stories.tsx index 7f3cdf10..75d943b1 100644 --- a/packages/ui/stories/common/DropDown.stories.tsx +++ b/packages/ui/stories/common/DropDown.stories.tsx @@ -1,10 +1,8 @@ /** @jsxImportSource @emotion/react */ -// import { DictionaryHeader } from '#/viewer-table/DictionaryHeader'; import type { Meta, StoryObj } from '@storybook/react'; -import { pick } from 'lodash'; -import themeDecorator from '../themeDecorator'; import Dropdown from '../../src/common/Dropdown/Dropdown'; +import themeDecorator from '../themeDecorator'; const meta = { component: Dropdown, @@ -36,6 +34,29 @@ export const Default: Story = { ], }, }; + +export const Disabled: Story = { + args: { + title: 'Juice', + leftIcon: '!', + disabled: true, + menuItems: [ + { + label: 'Apple', + action: () => { + alert('apple juice!'); + }, + }, + { + label: 'Orange', + action: () => { + alert('orange juice :('); + }, + }, + ], + }, +}; + export const Empty: Story = { args: {}, }; From ccaf112392e37c1cc96dc75ce53050cb7fa317cb Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 9 Jun 2025 14:33:28 -0400 Subject: [PATCH 039/217] implement disabled state for interaction panel --- .../InteractionPanel/AttributeFilterDropdown.tsx | 11 +++++++++-- .../InteractionPanel/CollapseAllButton.tsx | 5 +++-- .../InteractionPanel/DictionaryVersionSwitcher.tsx | 9 ++++++++- .../InteractionPanel/DownloadTemplatesButton.tsx | 11 +++++++++-- .../InteractionPanel/ExpandAllButton.tsx | 5 +++-- .../InteractionPanel/InteractionPanel.tsx | 12 ++++++++---- .../InteractionPanel/TableOfContentsDropdown.tsx | 7 +++++-- 7 files changed, 45 insertions(+), 15 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx index 153f7bcb..f862a58f 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx @@ -7,9 +7,16 @@ type FilterDropdownProps = { isFiltered: boolean; setFilteredData: (dict: Dictionary) => void; setIsFiltered: (bool: boolean) => void; + disabled?: boolean; }; -const AttributeFilter = ({ data, isFiltered, setFilteredData, setIsFiltered }: FilterDropdownProps) => { +const AttributeFilter = ({ + data, + isFiltered, + setFilteredData, + setIsFiltered, + disabled = false, +}: FilterDropdownProps) => { const handleFilterSelect = (selectedFilterName: string) => { if (isFiltered) { setFilteredData(data); @@ -49,7 +56,7 @@ const AttributeFilter = ({ data, isFiltered, setFilteredData, setIsFiltered }: F }, ]; - return } title="Filter By" menuItems={menuItems} />; + return } title="Filter By" menuItems={menuItems} disabled={disabled} />; }; export default AttributeFilter; diff --git a/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx index a6fce125..f6b3b5f8 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx @@ -5,9 +5,10 @@ import { useThemeContext } from '../../theme/ThemeContext'; interface CollapseAllButtonProps { setIsCollapsed: (isCollapsed: boolean) => void; collapsedOnLoad?: boolean; // This prop is optional and defaults to false + disabled?: boolean; } -const CollapseAllButton = ({ setIsCollapsed, collapsedOnLoad = false }: CollapseAllButtonProps) => { +const CollapseAllButton = ({ setIsCollapsed, collapsedOnLoad = false, disabled = false }: CollapseAllButtonProps) => { const theme = useThemeContext(); const { Collapse } = theme.icons; @@ -16,7 +17,7 @@ const CollapseAllButton = ({ setIsCollapsed, collapsedOnLoad = false }: Collapse }, [collapsedOnLoad, setIsCollapsed]); return ( - ); diff --git a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx index f9fd1d4b..ba5aeedf 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx @@ -28,9 +28,15 @@ type VersionSwitcherProps = { dictionaryData: Dictionary[]; onVersionChange: (index: number) => void; dictionaryIndex: number; + disabled?: boolean; }; -const VersionSwitcher = ({ dictionaryIndex, dictionaryData, onVersionChange }: VersionSwitcherProps) => { +const VersionSwitcher = ({ + dictionaryIndex, + dictionaryData, + onVersionChange, + disabled = false, +}: VersionSwitcherProps) => { const theme = useThemeContext(); const { History } = theme.icons; @@ -49,6 +55,7 @@ const VersionSwitcher = ({ dictionaryIndex, dictionaryData, onVersionChange }: V leftIcon={} menuItems={versionSwitcherObject} title={`Version ${dictionaryData?.[dictionaryIndex].version}`} + disabled={disabled} /> ); }; diff --git a/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx index af77d5cd..8f744c70 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx @@ -30,9 +30,16 @@ type DictionaryDownloadButtonProps = { name: string; lecternUrl: string; fileType?: 'tsv' | 'csv'; + disabled?: boolean; }; -const DictionaryDownloadButton = ({ version, name, lecternUrl, fileType = 'tsv' }: DictionaryDownloadButtonProps) => { +const DictionaryDownloadButton = ({ + version, + name, + lecternUrl, + fileType = 'tsv', + disabled = false, +}: DictionaryDownloadButtonProps) => { const [isLoading, setIsLoading] = useState(false); const theme = useThemeContext(); const { FileDownload } = theme.icons; @@ -74,7 +81,7 @@ const DictionaryDownloadButton = ({ version, name, lecternUrl, fileType = 'tsv' }; return ( - ); diff --git a/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx index 33e4d541..acda35dd 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx @@ -5,9 +5,10 @@ import { useThemeContext } from '../../theme/ThemeContext'; interface ExpandAllButtonProps { setIsCollapsed: (isCollapsed: boolean) => void; expandOnLoad?: boolean; // This prop is optional and defaults to false + disabled?: boolean; } -const ExpandAllButton = ({ setIsCollapsed, expandOnLoad = false }: ExpandAllButtonProps) => { +const ExpandAllButton = ({ setIsCollapsed, expandOnLoad = false, disabled = false }: ExpandAllButtonProps) => { const theme = useThemeContext(); const { Eye } = theme.icons; @@ -16,7 +17,7 @@ const ExpandAllButton = ({ setIsCollapsed, expandOnLoad = false }: ExpandAllButt }, [expandOnLoad, setIsCollapsed]); return ( - ); diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index a3f3ff26..08d73d5a 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -14,6 +14,7 @@ type InteractionPanelProps = { schemas: Schema[]; dictionary: Dictionary; filteredData: Dictionary; + disabled?: boolean; isFiltered: boolean; version: string; name: string; @@ -31,6 +32,7 @@ const InteractionPanel = ({ schemas, dictionary, filteredData, + disabled = false, isFiltered, version, name, @@ -75,15 +77,16 @@ const InteractionPanel = ({ return (
- + - - + +
{onVersionChange && dictionaryVersions && ( @@ -91,9 +94,10 @@ const InteractionPanel = ({ dictionaryData={dictionaryVersions} dictionaryIndex={currentVersionIndex} onVersionChange={onVersionChange} + disabled={disabled} /> )} - +
); diff --git a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx index a91a5551..27431f2c 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx @@ -5,9 +5,10 @@ import { useThemeContext } from '../../theme/ThemeContext'; type TableOfContentsDropdownProps = { schemas: Schema[]; onAccordionToggle: (schemaName: string, isOpen: boolean) => void; + disabled?: boolean; }; -const TableOfContentsDropdown = ({ schemas, onAccordionToggle }: TableOfContentsDropdownProps) => { +const TableOfContentsDropdown = ({ schemas, onAccordionToggle, disabled = false }: TableOfContentsDropdownProps) => { const theme = useThemeContext(); const { List } = theme.icons; const handleAction = (schema: Schema) => { @@ -25,7 +26,9 @@ const TableOfContentsDropdown = ({ schemas, onAccordionToggle }: TableOfContents }, })); - return } title="Table of Contents" menuItems={menuItemsFromSchemas} />; + return ( + } title="Table of Contents" menuItems={menuItemsFromSchemas} disabled={disabled} /> + ); }; export default TableOfContentsDropdown; From ae26b10ab57051a622c52a1e7aa204e99201313e Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 9 Jun 2025 14:34:39 -0400 Subject: [PATCH 040/217] interaction panel implement disasbled state in stories --- .../InteractionPanel.stories.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx index a3c0ec70..6d19df15 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx @@ -48,3 +48,19 @@ export const Default: Story = { onAccordionToggle: mockOnAccordionToggle, }, }; +export const Disabled: Story = { + args: { + schemas: (AdvancedDictionary as Dictionary).schemas, + dictionary: AdvancedDictionary as Dictionary, + filteredData: AdvancedDictionary as Dictionary, + isFiltered: false, + version: '1.0', + name: 'advanced-dictionary', + lecternUrl: 'http://localhost:3031', + disabled: true, + setIsCollapsed: mockSetIsCollapsed, + setFilteredData: mockSetFilteredData, + setIsFiltered: mockSetIsFiltered, + onAccordionToggle: mockOnAccordionToggle, + }, +}; From 00cf2faf4160e5287d1729656d144220699e61bd Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 9 Jun 2025 15:16:13 -0400 Subject: [PATCH 041/217] add testing for the version switcher --- .../DictionaryVersionSwitcher.tsx | 18 ++++++++---- .../DictionaryVersionSwitcher.stories.tsx | 29 ++++++++++++++++--- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx index f9fd1d4b..1d8be1bc 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx @@ -34,7 +34,7 @@ const VersionSwitcher = ({ dictionaryIndex, dictionaryData, onVersionChange }: V const theme = useThemeContext(); const { History } = theme.icons; - const versionSwitcherObject = dictionaryData?.map((dictionary: Dictionary, index: number) => { + const versionSwitcherObjectArray = dictionaryData?.map((dictionary: Dictionary, index: number) => { // TODO: We should either remove the version date stamp requirement or update the date to be dynamic via // lectern-client return { @@ -44,12 +44,18 @@ const VersionSwitcher = ({ dictionaryIndex, dictionaryData, onVersionChange }: V }, }; }); + + // If there is only one version, we don't need to show the dropdown as per specifications + const displayVersionSwitcher = versionSwitcherObjectArray && versionSwitcherObjectArray.length > 1; + return ( - } - menuItems={versionSwitcherObject} - title={`Version ${dictionaryData?.[dictionaryIndex].version}`} - /> + displayVersionSwitcher && ( + } + menuItems={versionSwitcherObjectArray} + title={`Version ${dictionaryData?.[dictionaryIndex].version}`} + /> + ) ); }; diff --git a/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx index 7eb45ea5..2376467e 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx @@ -16,12 +16,33 @@ const meta = { export default meta; type Story = StoryObj; -// Using the primitiveJson as a mock schema for demonstration purposes -const DictionaryData: Dictionary[] = [DictionarySample as Dictionary]; +// Create multiple dictionary versions for testing +const SingleDictionaryData: Dictionary[] = [DictionarySample as Dictionary]; -export const Default: Story = { +const MultipleDictionaryData: Dictionary[] = [ + { ...DictionarySample, version: '1.0' } as Dictionary, + { ...DictionarySample, version: '2.0' } as Dictionary, + { ...DictionarySample, version: '3.0' } as Dictionary, +]; + +export const MultipleVersions: Story = { + args: { + dictionaryData: MultipleDictionaryData, + onVersionChange: (index: number) => console.log(`Version changed to index: ${index}`), + dictionaryIndex: 0, + }, +}; +export const SingleVersion: Story = { + args: { + dictionaryData: SingleDictionaryData, + onVersionChange: (index: number) => console.log(`Version changed to index: ${index}`), + dictionaryIndex: 0, + }, +}; + +export const EmptyArray: Story = { args: { - dictionaryData: DictionaryData, + dictionaryData: [], onVersionChange: (index: number) => console.log(`Version changed to index: ${index}`), dictionaryIndex: 0, }, From 9280d6ae1d23c71def472cc330a41c3e88c5bf4f Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 9 Jun 2025 15:41:39 -0400 Subject: [PATCH 042/217] Added some different cases for the interaction panel --- .../InteractionPanel.stories.tsx | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx index 6d19df15..7fa09f04 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx @@ -17,10 +17,22 @@ const meta = { export default meta; type Story = StoryObj; +const SingleDictionaryData: Dictionary[] = [AdvancedDictionary as Dictionary]; + +const MultipleDictionaryData: Dictionary[] = [ + { ...AdvancedDictionary, version: '1.0' } as Dictionary, + { ...AdvancedDictionary, version: '2.0' } as Dictionary, + { ...AdvancedDictionary, version: '3.0' } as Dictionary, +]; + const mockSetIsCollapsed = (isCollapsed: boolean) => { console.log('setIsCollapsed called with:', isCollapsed); }; +const mockOnVersionChange = (index: number) => { + console.log('onVersionChange called with index:', index); +}; + const mockSetFilteredData = (dict: Dictionary) => { console.log('setFilteredData called with dictionary:', dict.name); }; @@ -64,3 +76,41 @@ export const Disabled: Story = { onAccordionToggle: mockOnAccordionToggle, }, }; + +export const WithMultipleVersions: Story = { + args: { + schemas: (AdvancedDictionary as Dictionary).schemas, + dictionary: AdvancedDictionary as Dictionary, + filteredData: AdvancedDictionary as Dictionary, + isFiltered: false, + version: '1.0', + name: 'advanced-dictionary', + lecternUrl: 'http://localhost:3031', + setIsCollapsed: mockSetIsCollapsed, + setFilteredData: mockSetFilteredData, + setIsFiltered: mockSetIsFiltered, + onAccordionToggle: mockOnAccordionToggle, + onVersionChange: mockOnVersionChange, + dictionaryVersions: MultipleDictionaryData, + currentVersionIndex: 0, + }, +}; + +export const WithSingleVersion: Story = { + args: { + schemas: (AdvancedDictionary as Dictionary).schemas, + dictionary: AdvancedDictionary as Dictionary, + filteredData: AdvancedDictionary as Dictionary, + isFiltered: false, + version: '1.0', + name: 'advanced-dictionary', + lecternUrl: 'http://localhost:3031', + setIsCollapsed: mockSetIsCollapsed, + setFilteredData: mockSetFilteredData, + setIsFiltered: mockSetIsFiltered, + onAccordionToggle: mockOnAccordionToggle, + onVersionChange: mockOnVersionChange, + dictionaryVersions: SingleDictionaryData, + currentVersionIndex: 0, + }, +}; From 3d598de8f1f4b22fa9b7918456177beeacade49b Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 09:20:59 -0400 Subject: [PATCH 043/217] add licencing --- .../AttributeFilterDropdown.tsx | 21 +++++++++++++++++++ .../InteractionPanel/CollapseAllButton.tsx | 21 +++++++++++++++++++ .../InteractionPanel/ExpandAllButton.tsx | 21 +++++++++++++++++++ .../InteractionPanel/InteractionPanel.tsx | 21 +++++++++++++++++++ .../TableOfContentsDropdown.tsx | 21 +++++++++++++++++++ 5 files changed, 105 insertions(+) diff --git a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx index f862a58f..e4852c2f 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx @@ -1,3 +1,24 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + import type { Dictionary } from '@overture-stack/lectern-dictionary'; import Dropdown from '../../common/Dropdown/Dropdown'; import ListFilter from '../../theme/icons/ListFilter'; diff --git a/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx index f6b3b5f8..34d43149 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx @@ -1,3 +1,24 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + import { useEffect } from 'react'; import Button from '../../common/Button'; import { useThemeContext } from '../../theme/ThemeContext'; diff --git a/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx index acda35dd..11aa729e 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx @@ -1,3 +1,24 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + import { useEffect } from 'react'; import Button from '../../common/Button'; import { useThemeContext } from '../../theme/ThemeContext'; diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index 08d73d5a..dbb3afdb 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -1,3 +1,24 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; diff --git a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx index 27431f2c..adaf55a1 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx @@ -1,3 +1,24 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + import type { Schema } from '@overture-stack/lectern-dictionary'; import Dropdown from '../../common/Dropdown/Dropdown'; import { useThemeContext } from '../../theme/ThemeContext'; From 89ed33ffd340673b5b206a8ee3e91105fcb4fde3 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 10:59:17 -0400 Subject: [PATCH 044/217] initial component initialization --- .../ui/src/common/Accordion/Accordion.tsx | 44 +++++++ .../ui/src/common/Accordion/AccordionItem.tsx | 109 ++++++++++++++++++ .../ui/stories/common/Accordion.stories.tsx | 27 +++++ 3 files changed, 180 insertions(+) create mode 100644 packages/ui/src/common/Accordion/Accordion.tsx create mode 100644 packages/ui/src/common/Accordion/AccordionItem.tsx create mode 100644 packages/ui/stories/common/Accordion.stories.tsx diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx new file mode 100644 index 00000000..d15e07bb --- /dev/null +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -0,0 +1,44 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; +import { useState } from 'react'; +import AccordionItem, { AccordionData } from './AccordionItem'; + +type AccordionProps = { + accordionItems: Array; +}; + +const accordionStyle = css` + list-style: none; + padding: 0; + margin: 0; +`; + +/** + * Accordion component to display collapsible items with titles and content. + * @param {AccordionProps} props - The properties for the Accordion component. + * @param {Array} props.accordionItems - An array of accordion items, each containing a title, description and content. + * @returns {JSX.Element} The rendered Accordion component. + * @example + * const accordionItems = [ + * { title: 'Item 1', description: 'Description 1', content: 'Content for item 1' }, + * { title: 'Item 2', description: 'Description 2', content: 'Content for item 2' }, + * ]; + * + * Essentially pass in an an array of objects that are of type AccordionData, and it will render an accordion with those items. + */ + +const Accordion = ({ accordionItems }: AccordionProps) => { + const [currentIdx, setCurrentIdx] = useState(-1); + const btnOnClick = (idx: number) => { + setCurrentIdx((currentValue) => (currentValue !== idx ? idx : -1)); + }; + + return ( +
    + {accordionItems.map((item, idx) => ( + btnOnClick(idx)} /> + ))} +
+ ); +}; +export default Accordion; diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx new file mode 100644 index 00000000..a0599d9a --- /dev/null +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -0,0 +1,109 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; +import { ReactNode, useEffect, useRef, useState } from 'react'; +import type { Theme } from '../../theme'; +import { useThemeContext } from '../../theme/ThemeContext'; + +export type AccordionData = { + title: string; + description: string; + content: ReactNode | string; +}; +type AccordionItemProps = { + data: AccordionData; + isOpen: boolean; + onClick: () => void; +}; + +const accordionItemStyle = css` + list-style: none; + border: 1px solid #beb2b294; + border-radius: 9px; + margin-bottom: 8px; + overflow: hidden; + transition: all 0.2s ease; +`; + +const accordionItemTitleStyle = (theme: Theme) => css` + margin: 0; + ${theme.typography?.button}; +`; + +const accordionItemButtonStyle = (theme: Theme, isOpen: boolean) => css` + display: flex; + flex-wrap: nowrap; + white-space: nowrap; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 12px 16px; + background-color: #f7f7f7; + color: ${theme.colors.accent_dark}; + border: none; + cursor: pointer; + transition: all 0.2s ease; + ${theme.typography?.button}; + + &:hover { + background-color: ${theme.colors.grey_1}; + } + + &:focus { + outline: none; + background-color: ${theme.colors.grey_1}; + } +`; + +const chevronStyle = (isOpen: boolean) => css` + transform: ${!isOpen ? 'rotate(-90deg)' : 'none'}; + transition: transform 0.2s ease; + margin-left: 8px; +`; + +const accordionItemContainerStyle = (height: number) => css` + height: ${height}px; + overflow: hidden; + transition: height 0.2s ease; +`; + +const accordionItemContentStyle = css` + padding: 16px; + background-color: #ffffff; +`; + +const AccordionItem = ({ data, isOpen, onClick }: AccordionItemProps) => { + const contentRef = useRef(null); + const [height, setHeight] = useState(0); + const theme = useThemeContext(); + + const { ChevronDown } = theme.icons; + + useEffect(() => { + if (isOpen) { + const contentEl = contentRef.current; + if (contentEl) { + setHeight(contentEl.scrollHeight); + } + } else { + setHeight(0); + } + }, [isOpen]); + + return ( +
  • +

    + +

    +
    +
    + {data.content} +
    +
    +
  • + ); +}; +export default AccordionItem; diff --git a/packages/ui/stories/common/Accordion.stories.tsx b/packages/ui/stories/common/Accordion.stories.tsx new file mode 100644 index 00000000..e28909b9 --- /dev/null +++ b/packages/ui/stories/common/Accordion.stories.tsx @@ -0,0 +1,27 @@ +/** @jsxImportSource @emotion/react */ + +import type { Meta, StoryObj } from '@storybook/react'; +import themeDecorator from '../themeDecorator'; +import Accordion from '../../src/common/Accordion/Accordion'; +const meta = { + component: Accordion, + title: 'Common/Accordion', + tags: ['autodocs'], + decorators: [themeDecorator()], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + accordionItems: [ + { title: 'Item 1', description: 'Description for item 1', content: 'Content for item 1' }, + { title: 'Item 2', description: 'Description for item 2', content: 'Content for item 2' }, + { title: 'Item 3', description: 'Description for item 3', content: 'Content for item 3' }, + ], + }, +}; +export const Empty: Story = { + args: { accordionItems: [] }, +}; From 30f6bee0400d1b0afb5ea2991d3af4c73238d3d7 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 11:14:26 -0400 Subject: [PATCH 045/217] allow the ability to actually initalize the openInit --- packages/ui/src/common/Accordion/Accordion.tsx | 16 ++++++++++------ .../ui/src/common/Accordion/AccordionItem.tsx | 1 + packages/ui/stories/common/Accordion.stories.tsx | 6 +++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index d15e07bb..73f4e1d3 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -20,23 +20,27 @@ const accordionStyle = css` * @returns {JSX.Element} The rendered Accordion component. * @example * const accordionItems = [ - * { title: 'Item 1', description: 'Description 1', content: 'Content for item 1' }, - * { title: 'Item 2', description: 'Description 2', content: 'Content for item 2' }, + * { title: 'Item 1', description: 'Description 1', openOnInit: true, content: 'Content for item 1' }, + * { title: 'Item 2', description: 'Description 2', openOnInit: false, content: 'Content for item 2' }, * ]; * * Essentially pass in an an array of objects that are of type AccordionData, and it will render an accordion with those items. */ const Accordion = ({ accordionItems }: AccordionProps) => { - const [currentIdx, setCurrentIdx] = useState(-1); - const btnOnClick = (idx: number) => { - setCurrentIdx((currentValue) => (currentValue !== idx ? idx : -1)); + // This state keeps track of the currently open accordion item index via a boolean array, since each item can be opened or closed independently. + const [openStates, setOpenStates] = useState( + accordionItems.map((accordionItem) => accordionItem.openOnInit), // Initialize with the openOnInit property of each item + ); + + const onClick = (idx: number) => { + setOpenStates((prev) => prev.map((isOpen, index) => (index === idx ? !isOpen : isOpen))); }; return (
      {accordionItems.map((item, idx) => ( - btnOnClick(idx)} /> + onClick(idx)} /> ))}
    ); diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index a0599d9a..a2623fb9 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -6,6 +6,7 @@ import { useThemeContext } from '../../theme/ThemeContext'; export type AccordionData = { title: string; + openOnInit: boolean; description: string; content: ReactNode | string; }; diff --git a/packages/ui/stories/common/Accordion.stories.tsx b/packages/ui/stories/common/Accordion.stories.tsx index e28909b9..c014ecce 100644 --- a/packages/ui/stories/common/Accordion.stories.tsx +++ b/packages/ui/stories/common/Accordion.stories.tsx @@ -16,9 +16,9 @@ type Story = StoryObj; export const Default: Story = { args: { accordionItems: [ - { title: 'Item 1', description: 'Description for item 1', content: 'Content for item 1' }, - { title: 'Item 2', description: 'Description for item 2', content: 'Content for item 2' }, - { title: 'Item 3', description: 'Description for item 3', content: 'Content for item 3' }, + { title: 'Item 1', openOnInit: true, description: 'Description for item 1', content: 'Content for item 1' }, + { title: 'Item 2', openOnInit: false, description: 'Description for item 2', content: 'Content for item 2' }, + { title: 'Item 3', openOnInit: false, description: 'Description for item 3', content: 'Content for item 3' }, ], }, }; From 694931f5dc3c431a233ed8cd2d6548a017044231 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 13:29:46 -0400 Subject: [PATCH 046/217] add line seperation, fix up styling --- .../ui/src/common/Accordion/Accordion.tsx | 3 + .../ui/src/common/Accordion/AccordionItem.tsx | 94 ++++++++++++------- .../ui/stories/common/Accordion.stories.tsx | 26 ++++- 3 files changed, 88 insertions(+), 35 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index 73f4e1d3..7683ae0d 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -11,6 +11,9 @@ const accordionStyle = css` list-style: none; padding: 0; margin: 0; + display: flex; + flex-direction: column; + gap: 24px; `; /** diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index a2623fb9..a52c4c30 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -9,6 +9,7 @@ export type AccordionData = { openOnInit: boolean; description: string; content: ReactNode | string; + iconButton?: ReactNode; // Optional icon button for additional actions }; type AccordionItemProps = { data: AccordionData; @@ -16,13 +17,14 @@ type AccordionItemProps = { onClick: () => void; }; -const accordionItemStyle = css` +const accordionItemStyle = (theme: Theme) => css` list-style: none; - border: 1px solid #beb2b294; - border-radius: 9px; - margin-bottom: 8px; + border: 0.25px solid ${theme.colors.black}; + border-radius: 8px; + margin-bottom: 1px; overflow: hidden; transition: all 0.2s ease; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); `; const accordionItemTitleStyle = (theme: Theme) => css` @@ -32,51 +34,76 @@ const accordionItemTitleStyle = (theme: Theme) => css` const accordionItemButtonStyle = (theme: Theme, isOpen: boolean) => css` display: flex; - flex-wrap: nowrap; - white-space: nowrap; align-items: center; - justify-content: space-between; + justify-content: flex-start; width: 100%; - padding: 12px 16px; - background-color: #f7f7f7; + padding: 24px 20px; + background-color: ${'#ffffff'}; color: ${theme.colors.accent_dark}; - border: none; cursor: pointer; transition: all 0.2s ease; ${theme.typography?.button}; - - &:hover { - background-color: ${theme.colors.grey_1}; - } - - &:focus { - outline: none; - background-color: ${theme.colors.grey_1}; - } + text-align: left; `; const chevronStyle = (isOpen: boolean) => css` - transform: ${!isOpen ? 'rotate(-90deg)' : 'none'}; + transform: ${isOpen ? 'rotate(0deg)' : 'rotate(-90deg)'}; transition: transform 0.2s ease; - margin-left: 8px; + margin-right: 12px; + flex-shrink: 0; `; const accordionItemContainerStyle = (height: number) => css` height: ${height}px; overflow: hidden; - transition: height 0.2s ease; + transition: height 0.3s ease; `; -const accordionItemContentStyle = css` - padding: 16px; +const accordionItemContentStyle = (theme: Theme) => css` + padding: 30px; background-color: #ffffff; `; +const contentContainerStyle = css` + display: flex; + flex-direction: row; + algin-items: center; + gap: 43px; + flex: 1; + width: 100%; +`; + +const titleStyle = (theme: Theme) => css` + ${theme.typography?.subheading2}; + color: ${theme.colors?.accent_dark}; + margin: 0; +`; + +const descriptionStyle = (theme: Theme) => css` + ${theme.typography?.label2}; + color: ${theme.colors?.grey_5}; + margin-top: 2px; +`; + +const iconButtonContainerStyle = css` + margin-left: auto; +`; +const contentInnerContainerStyle = (theme: Theme) => css` + display: flex; + padding: 30px; + border-left: 1px solid ${theme.colors.grey_3}; + height: fit-content; + ${theme.typography?.data}; +`; + const AccordionItem = ({ data, isOpen, onClick }: AccordionItemProps) => { const contentRef = useRef(null); + const [height, setHeight] = useState(0); + const theme = useThemeContext(); + const { iconButton, description, title, content } = data; const { ChevronDown } = theme.icons; useEffect(() => { @@ -91,17 +118,20 @@ const AccordionItem = ({ data, isOpen, onClick }: AccordionItemProps) => { }, [isOpen]); return ( -
  • +
  • - +
    + +
    + {title} + {description && {description}} +
    + {iconButton && {iconButton}} +

    -
    - {data.content} +
    +
    {content}
  • diff --git a/packages/ui/stories/common/Accordion.stories.tsx b/packages/ui/stories/common/Accordion.stories.tsx index c014ecce..f1a8c9a5 100644 --- a/packages/ui/stories/common/Accordion.stories.tsx +++ b/packages/ui/stories/common/Accordion.stories.tsx @@ -16,9 +16,29 @@ type Story = StoryObj; export const Default: Story = { args: { accordionItems: [ - { title: 'Item 1', openOnInit: true, description: 'Description for item 1', content: 'Content for item 1' }, - { title: 'Item 2', openOnInit: false, description: 'Description for item 2', content: 'Content for item 2' }, - { title: 'Item 3', openOnInit: false, description: 'Description for item 3', content: 'Content for item 3' }, + { + title: '{Schema:name}', + openOnInit: true, + description: + '{Schema: description} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + content: 'Content for item 1', + }, + { + title: '{Schema:name}', + openOnInit: false, + description: + '{Schema: description} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + content: + 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + }, + { + title: '{Schema:name}', + openOnInit: false, + description: + '{Schema: description} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + content: + 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + }, ], }, }; From dc2949ba2bd82dc43ef98438fdfbaa9f38971108 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 14:01:33 -0400 Subject: [PATCH 047/217] make it so that we have appropriate aligning --- .../ui/src/common/Accordion/AccordionItem.tsx | 63 +++++++++++++++++-- .../ui/stories/common/Accordion.stories.tsx | 4 +- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index a52c4c30..fecd05c7 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -67,10 +67,11 @@ const accordionItemContentStyle = (theme: Theme) => css` const contentContainerStyle = css` display: flex; flex-direction: row; - algin-items: center; - gap: 43px; + align-items: center; + gap: 16px; flex: 1; - width: 100%; + min-width: 0; + max-width: calc(100% - 100px); `; const titleStyle = (theme: Theme) => css` @@ -82,7 +83,11 @@ const titleStyle = (theme: Theme) => css` const descriptionStyle = (theme: Theme) => css` ${theme.typography?.label2}; color: ${theme.colors?.grey_5}; - margin-top: 2px; + padding: 4px 8px; + word-wrap: break-word; + overflow-wrap: break-word; + flex: 1; + max-width: 60%; `; const iconButtonContainerStyle = css` @@ -96,16 +101,49 @@ const contentInnerContainerStyle = (theme: Theme) => css` ${theme.typography?.data}; `; +const getChevronStyle = (isExpanded: boolean) => css` + margin-left: 4px; + ${isExpanded && `transform: rotate(180deg);`} +`; + +const linkStyle = (theme: Theme) => css` + ${theme.typography?.label2} + color: ${theme.colors?.accent_dark}; + cursor: pointer; + display: inline-flex; + align-items: center; + + &:hover { + text-decoration: underline; + } +`; + +// These constants can be adjusted based on design requirements +const DESCRIPTION_THRESHOLD = 240; + const AccordionItem = ({ data, isOpen, onClick }: AccordionItemProps) => { const contentRef = useRef(null); const [height, setHeight] = useState(0); + const [isExpanded, setIsExpanded] = useState(false); const theme = useThemeContext(); const { iconButton, description, title, content } = data; const { ChevronDown } = theme.icons; + // Determine if the description is long enough to need a toggle, based off of how many characters we want to show by default + // according to the figma styling + const needsToggle = description && description.length > DESCRIPTION_THRESHOLD; + // We want to show all the text if it is not long or if it is already expanded via state variable + const showFull = isExpanded || !needsToggle; + // Based off of showFull, we determine the text to show, either its the full description or a truncated version + const textToShow = showFull ? description : description.slice(0, DESCRIPTION_THRESHOLD) + '... '; + + const showMoreEventHandler = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsExpanded((prev) => !prev); + }; useEffect(() => { if (isOpen) { const contentEl = contentRef.current; @@ -124,7 +162,22 @@ const AccordionItem = ({ data, isOpen, onClick }: AccordionItemProps) => {
    {title} - {description && {description}} + {description && ( +
    + {textToShow} + {needsToggle && ( + showMoreEventHandler(e)}> + {isExpanded ? ' Read less' : ' Show more'} + + + )} +
    + )}
    {iconButton && {iconButton}}
    diff --git a/packages/ui/stories/common/Accordion.stories.tsx b/packages/ui/stories/common/Accordion.stories.tsx index f1a8c9a5..a895c230 100644 --- a/packages/ui/stories/common/Accordion.stories.tsx +++ b/packages/ui/stories/common/Accordion.stories.tsx @@ -29,13 +29,13 @@ export const Default: Story = { description: '{Schema: description} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', content: - 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna.Adipisicing ea sunt ullamco voluptate tempor eu.Sint ex officiaLorem laborum mollit proident sunt culpa deserunt. officia enim. ', }, { title: '{Schema:name}', openOnInit: false, description: - '{Schema: description} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + '{Schema: description} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', content: 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', }, From 69c2ec692166a523b470e0f7dd70e0a7861ec0db Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 14:02:33 -0400 Subject: [PATCH 048/217] address preceeding text spacing comment from pr --- packages/ui/src/viewer-table/DictionaryHeader.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/viewer-table/DictionaryHeader.tsx b/packages/ui/src/viewer-table/DictionaryHeader.tsx index 9d2d1f30..47d74a3f 100644 --- a/packages/ui/src/viewer-table/DictionaryHeader.tsx +++ b/packages/ui/src/viewer-table/DictionaryHeader.tsx @@ -138,7 +138,8 @@ const DictionaryHeader = ({ name, description, version }: DictionaryHeaderProps) {textToShow} {needsToggle && ( setIsExpanded((prev) => !prev)}> - {isExpanded ? ' Read less' : ' Show more'} + {' '} + {isExpanded ? 'Read less' : 'Show more'} )} From 174922fc05dea2df16c893ef055817cbdea0c7af Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 14:03:16 -0400 Subject: [PATCH 049/217] exporting component props --- packages/ui/src/viewer-table/DictionaryHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/viewer-table/DictionaryHeader.tsx b/packages/ui/src/viewer-table/DictionaryHeader.tsx index 47d74a3f..c1c2370d 100644 --- a/packages/ui/src/viewer-table/DictionaryHeader.tsx +++ b/packages/ui/src/viewer-table/DictionaryHeader.tsx @@ -26,7 +26,7 @@ import type { Theme } from '../theme'; import { useThemeContext } from '../theme/ThemeContext'; import colours from './styles/colours'; -type DictionaryHeaderProps = { +export type DictionaryHeaderProps = { name: string; description?: string; version?: string; From 60dab2d702ba9eda69e1b1565bfae6e39d9e09b7 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 14:06:06 -0400 Subject: [PATCH 050/217] rename the constant to fit the naming standards better and add comments for clarity --- packages/ui/src/viewer-table/DictionaryHeader.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/viewer-table/DictionaryHeader.tsx b/packages/ui/src/viewer-table/DictionaryHeader.tsx index c1c2370d..8cc07255 100644 --- a/packages/ui/src/viewer-table/DictionaryHeader.tsx +++ b/packages/ui/src/viewer-table/DictionaryHeader.tsx @@ -109,8 +109,7 @@ const descriptionColumnStyle = css` justify-content: center; `; -// These constants can be adjusted based on design requirements -const DESCRIPTION_THRESHOLD = 140; +const DESCRIPTION_LENGTH_THRESHOLD = 140; // Chosen to display ~2-3 lines of text before truncation based on typical container width const DictionaryHeader = ({ name, description, version }: DictionaryHeaderProps) => { const theme = useThemeContext(); @@ -119,11 +118,11 @@ const DictionaryHeader = ({ name, description, version }: DictionaryHeaderProps) // Determine if the description is long enough to need a toggle, based off of how many characters we want to show by default // according to the figma styling - const needsToggle = description && description.length > DESCRIPTION_THRESHOLD; + const needsToggle = description && description.length > DESCRIPTION_LENGTH_THRESHOLD; // We want to show all the text if it is not long or if it is already expanded via state variable const showFull = isExpanded || !needsToggle; // Based off of showFull, we determine the text to show, either its the full description or a truncated version - const textToShow = showFull ? description : description.slice(0, DESCRIPTION_THRESHOLD) + '... '; + const textToShow = showFull ? description : description.slice(0, DESCRIPTION_LENGTH_THRESHOLD) + '... '; return (
    From 6bfcc691e531033f73301970e545c8952a125000 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 14:21:32 -0400 Subject: [PATCH 051/217] move the state management inside of the accordion, pt1 --- packages/ui/src/common/Accordion/Accordion.tsx | 18 +++++++++++++++++- .../ui/src/common/Accordion/AccordionItem.tsx | 16 +++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index 7683ae0d..8dc10c3d 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -36,14 +36,30 @@ const Accordion = ({ accordionItems }: AccordionProps) => { accordionItems.map((accordionItem) => accordionItem.openOnInit), // Initialize with the openOnInit property of each item ); + // This state keeps track of which accordion items have expanded descriptions + const [descriptionExpandedStates, setDescriptionExpandedStates] = useState( + accordionItems.map(() => false), // Initialize all descriptions as collapsed + ); + const onClick = (idx: number) => { setOpenStates((prev) => prev.map((isOpen, index) => (index === idx ? !isOpen : isOpen))); }; + const onDescriptionToggle = (idx: number) => { + setDescriptionExpandedStates((prev) => prev.map((isExpanded, index) => (index === idx ? !isExpanded : isExpanded))); + }; + return (
      {accordionItems.map((item, idx) => ( - onClick(idx)} /> + onClick(idx)} + isDescriptionExpanded={descriptionExpandedStates[idx]} + onDescriptionToggle={() => onDescriptionToggle(idx)} + /> ))}
    ); diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index fecd05c7..0c4935cb 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -15,6 +15,8 @@ type AccordionItemProps = { data: AccordionData; isOpen: boolean; onClick: () => void; + isDescriptionExpanded: boolean; + onDescriptionToggle: () => void; }; const accordionItemStyle = (theme: Theme) => css` @@ -119,13 +121,12 @@ const linkStyle = (theme: Theme) => css` `; // These constants can be adjusted based on design requirements -const DESCRIPTION_THRESHOLD = 240; +const DESCRIPTION_THRESHOLD = 240; // Allows for ~4-5 lines of description text in accordion items -const AccordionItem = ({ data, isOpen, onClick }: AccordionItemProps) => { +const AccordionItem = ({ data, isOpen, onClick, isDescriptionExpanded, onDescriptionToggle }: AccordionItemProps) => { const contentRef = useRef(null); const [height, setHeight] = useState(0); - const [isExpanded, setIsExpanded] = useState(false); const theme = useThemeContext(); @@ -136,13 +137,13 @@ const AccordionItem = ({ data, isOpen, onClick }: AccordionItemProps) => { // according to the figma styling const needsToggle = description && description.length > DESCRIPTION_THRESHOLD; // We want to show all the text if it is not long or if it is already expanded via state variable - const showFull = isExpanded || !needsToggle; + const showFull = isDescriptionExpanded || !needsToggle; // Based off of showFull, we determine the text to show, either its the full description or a truncated version const textToShow = showFull ? description : description.slice(0, DESCRIPTION_THRESHOLD) + '... '; const showMoreEventHandler = (e: React.MouseEvent) => { e.stopPropagation(); - setIsExpanded((prev) => !prev); + onDescriptionToggle(); }; useEffect(() => { if (isOpen) { @@ -167,9 +168,10 @@ const AccordionItem = ({ data, isOpen, onClick }: AccordionItemProps) => { {textToShow} {needsToggle && ( showMoreEventHandler(e)}> - {isExpanded ? ' Read less' : ' Show more'} + {' '} + {isDescriptionExpanded ? 'Read less' : 'Show more'} Date: Tue, 10 Jun 2025 14:22:21 -0400 Subject: [PATCH 052/217] whitespace --- packages/ui/src/common/Accordion/Accordion.tsx | 2 +- packages/ui/src/common/Accordion/AccordionItem.tsx | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index 8dc10c3d..b23e11b4 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -38,7 +38,7 @@ const Accordion = ({ accordionItems }: AccordionProps) => { // This state keeps track of which accordion items have expanded descriptions const [descriptionExpandedStates, setDescriptionExpandedStates] = useState( - accordionItems.map(() => false), // Initialize all descriptions as collapsed + accordionItems.map(() => false), ); const onClick = (idx: number) => { diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index 0c4935cb..6c47df86 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -125,11 +125,8 @@ const DESCRIPTION_THRESHOLD = 240; // Allows for ~4-5 lines of description text const AccordionItem = ({ data, isOpen, onClick, isDescriptionExpanded, onDescriptionToggle }: AccordionItemProps) => { const contentRef = useRef(null); - const [height, setHeight] = useState(0); - const theme = useThemeContext(); - const { iconButton, description, title, content } = data; const { ChevronDown } = theme.icons; From 0d98f8958fedcf4ccfb963a035e6f4b3b07ea17b Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 14:39:06 -0400 Subject: [PATCH 053/217] hover state --- packages/ui/src/common/Accordion/AccordionItem.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index 6c47df86..dc7f9a14 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -25,8 +25,13 @@ const accordionItemStyle = (theme: Theme) => css` border-radius: 8px; margin-bottom: 1px; overflow: hidden; - transition: all 0.2s ease; + transition: all 0.3s ease; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + + &:hover { + border-width: 1.2px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + } `; const accordionItemTitleStyle = (theme: Theme) => css` From 8601b5a47e36ebd338dee37e452ec945c801c998 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 14:40:38 -0400 Subject: [PATCH 054/217] export all props --- .../ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx | 2 +- .../ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx index a6fce125..f0bc1dd0 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx @@ -2,7 +2,7 @@ import { useEffect } from 'react'; import Button from '../../common/Button'; import { useThemeContext } from '../../theme/ThemeContext'; -interface CollapseAllButtonProps { +export interface CollapseAllButtonProps { setIsCollapsed: (isCollapsed: boolean) => void; collapsedOnLoad?: boolean; // This prop is optional and defaults to false } diff --git a/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx index 33e4d541..a6ac6400 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx @@ -2,7 +2,7 @@ import { useEffect } from 'react'; import Button from '../../common/Button'; import { useThemeContext } from '../../theme/ThemeContext'; -interface ExpandAllButtonProps { +export interface ExpandAllButtonProps { setIsCollapsed: (isCollapsed: boolean) => void; expandOnLoad?: boolean; // This prop is optional and defaults to false } From 36e5af97e5aee150e51b3d0a60abec98e1467a63 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 14:42:43 -0400 Subject: [PATCH 055/217] defaulted spinner width height and fill values --- packages/ui/src/theme/icons/Spinner.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/theme/icons/Spinner.tsx b/packages/ui/src/theme/icons/Spinner.tsx index e6fc6c49..6c0de99a 100644 --- a/packages/ui/src/theme/icons/Spinner.tsx +++ b/packages/ui/src/theme/icons/Spinner.tsx @@ -35,15 +35,15 @@ const Spinner = ({ fill, height, width }: IconProps) => { return ( From d0f4631efefd0d1b90ad1780fea77cbb6f048f74 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 14:43:35 -0400 Subject: [PATCH 056/217] change console.log to alerts --- .../interaction-panel/CollapseAllButton.stories.tsx | 2 +- .../viewer-table/interaction-panel/ExpandAllButton.stories.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx index 80a60dd2..5e744b48 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx @@ -15,5 +15,5 @@ export default meta; type Story = StoryObj; export const Default: Story = { - args: { setIsCollapsed: (isCollapsed: boolean) => console.log('all collapsable components are collapsed') }, + args: { setIsCollapsed: (isCollapsed: boolean) => alert('all collapsable components are collapsed') }, }; diff --git a/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx index f706163d..fff42a43 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx @@ -14,5 +14,5 @@ export default meta; type Story = StoryObj; export const Default: Story = { - args: { setIsCollapsed: (isCollapsed: boolean) => console.log('all collapsable components are expanded') }, + args: { setIsCollapsed: (isCollapsed: boolean) => alert('all collapsable components are expanded') }, }; From 9605e9faf1f3ecd46b7fd68174538b86ae89f088 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 15:21:47 -0400 Subject: [PATCH 057/217] do not control state of other components, addresses pr feedback --- .../InteractionPanel/CollapseAllButton.tsx | 11 +++-------- .../viewer-table/InteractionPanel/ExpandAllButton.tsx | 11 +++-------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx index f0bc1dd0..604b9fba 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx @@ -3,20 +3,15 @@ import Button from '../../common/Button'; import { useThemeContext } from '../../theme/ThemeContext'; export interface CollapseAllButtonProps { - setIsCollapsed: (isCollapsed: boolean) => void; - collapsedOnLoad?: boolean; // This prop is optional and defaults to false + onClick: () => void; } -const CollapseAllButton = ({ setIsCollapsed, collapsedOnLoad = false }: CollapseAllButtonProps) => { +const CollapseAllButton = ({ onClick }: CollapseAllButtonProps) => { const theme = useThemeContext(); const { Collapse } = theme.icons; - useEffect(() => { - setIsCollapsed(collapsedOnLoad); - }, [collapsedOnLoad, setIsCollapsed]); - return ( - } onClick={() => setIsCollapsed(true)}> + } onClick={onClick}> Collapse All ); diff --git a/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx index a6ac6400..42a81ec3 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx @@ -3,20 +3,15 @@ import Button from '../../common/Button'; import { useThemeContext } from '../../theme/ThemeContext'; export interface ExpandAllButtonProps { - setIsCollapsed: (isCollapsed: boolean) => void; - expandOnLoad?: boolean; // This prop is optional and defaults to false + onClick: () => void; } -const ExpandAllButton = ({ setIsCollapsed, expandOnLoad = false }: ExpandAllButtonProps) => { +const ExpandAllButton = ({ onClick }: ExpandAllButtonProps) => { const theme = useThemeContext(); const { Eye } = theme.icons; - useEffect(() => { - setIsCollapsed(expandOnLoad); - }, [expandOnLoad, setIsCollapsed]); - return ( - } onClick={() => setIsCollapsed(false)}> + } onClick={onClick}> Expand All ); From 634107b507ce34aad26eb20d15c7c7ee8d7ec72d Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 15:33:29 -0400 Subject: [PATCH 058/217] add back disasbled state from interaction panel --- .../src/viewer-table/InteractionPanel/CollapseAllButton.tsx | 5 +++-- .../ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx | 5 +++-- .../src/viewer-table/InteractionPanel/InteractionPanel.tsx | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx index 3ae50b76..0cb2cb10 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/CollapseAllButton.tsx @@ -25,14 +25,15 @@ import { useThemeContext } from '../../theme/ThemeContext'; export interface CollapseAllButtonProps { onClick: () => void; + disabled?: boolean; } -const CollapseAllButton = ({ onClick }: CollapseAllButtonProps) => { +const CollapseAllButton = ({ onClick, disabled }: CollapseAllButtonProps) => { const theme = useThemeContext(); const { Collapse } = theme.icons; return ( - } onClick={onClick}> + } onClick={onClick} disabled={disabled}> Collapse All ); diff --git a/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx index 598ba6ff..18cf0591 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx @@ -25,14 +25,15 @@ import { useThemeContext } from '../../theme/ThemeContext'; export interface ExpandAllButtonProps { onClick: () => void; + disabled?: boolean; } -const ExpandAllButton = ({ onClick }: ExpandAllButtonProps) => { +const ExpandAllButton = ({ onClick, disabled }: ExpandAllButtonProps) => { const theme = useThemeContext(); const { Eye } = theme.icons; return ( - } onClick={onClick}> + } onClick={onClick} disabled={disabled}> Expand All ); diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index dbb3afdb..3cf5ff9c 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -106,8 +106,8 @@ const InteractionPanel = ({ setIsFiltered={setIsFiltered} disabled={disabled} /> - - + setIsCollapsed(false)} disabled={disabled} /> + setIsCollapsed(true)} disabled={disabled} />
    {onVersionChange && dictionaryVersions && ( From 659a456faf36e3a4cb2d8afebf342a156d156989 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 15:49:32 -0400 Subject: [PATCH 059/217] generated dictionarypage for now just to see everything working at once, need to figure out how we want to display this properly later. --- .../src/viewer-table/DataDictionaryPage.tsx | 149 ++++++++++++++++++ .../DataDictionaryPage.stories.tsx | 77 +++++++++ 2 files changed, 226 insertions(+) create mode 100644 packages/ui/src/viewer-table/DataDictionaryPage.tsx create mode 100644 packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx diff --git a/packages/ui/src/viewer-table/DataDictionaryPage.tsx b/packages/ui/src/viewer-table/DataDictionaryPage.tsx new file mode 100644 index 00000000..9e9769dc --- /dev/null +++ b/packages/ui/src/viewer-table/DataDictionaryPage.tsx @@ -0,0 +1,149 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** @jsxImportSource @emotion/react */ + +import { css } from '@emotion/react'; +import type { Dictionary } from '@overture-stack/lectern-dictionary'; +import { useState } from 'react'; +import Accordion from '../common/Accordion/Accordion'; +import { useThemeContext } from '../theme/ThemeContext'; +import DictionaryHeader from './DictionaryHeader'; +import InteractionPanel from './InteractionPanel/InteractionPanel'; + +export type DataDictionaryPageProps = { + dictionary: Dictionary; + lecternUrl?: string; + disabled?: boolean; + dictionaryVersions?: Dictionary[]; + currentVersionIndex?: number; + onVersionChange?: (version: number) => void; +}; + +const pageContainerStyle = css` + width: 100%; + min-height: 100vh; + background-color: #f8fafc; +`; + +const contentContainerStyle = css` + max-width: 1200px; + margin: 0 auto; + padding: 0 24px 48px 24px; +`; + +const accordionContainerStyle = css` + margin-top: 24px; +`; + +const DataDictionaryPage = ({ + dictionary, + lecternUrl = 'http://localhost:3031', + disabled = false, + dictionaryVersions, + currentVersionIndex = 0, + onVersionChange, +}: DataDictionaryPageProps) => { + const theme = useThemeContext(); + + const [filteredData, setFilteredData] = useState(dictionary); + const [isFiltered, setIsFiltered] = useState(false); + const [isCollapsed, setIsCollapsed] = useState(false); + const [accordionStates, setAccordionStates] = useState>(() => { + // Initialize all schemas as open by default + const initialStates: Record = {}; + dictionary.schemas.forEach((schema) => { + initialStates[schema.name] = true; + }); + return initialStates; + }); + + const handleAccordionToggle = (schemaName: string, isOpen: boolean) => { + setAccordionStates((prev) => ({ + ...prev, + [schemaName]: isOpen, + })); + }; + + // Handle expand/collapse all + const handleSetIsCollapsed = (collapsed: boolean) => { + setIsCollapsed(collapsed); + const newStates: Record = {}; + dictionary.schemas.forEach((schema) => { + newStates[schema.name] = !collapsed; + }); + setAccordionStates(newStates); + }; + + const accordionItems = filteredData.schemas.map((schema) => ({ + title: schema.name, + description: schema.description || '', + openOnInit: accordionStates[schema.name] ?? true, + content: + 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.', + })); + + return ( +
    + + + {/* Interaction Panel */} + + + {accordionItems.length > 0 && ( +
    + +
    + )} + + {accordionItems.length === 0 && ( +
    + No schemas found in this dictionary. +
    + )} +
    + ); +}; + +export default DataDictionaryPage; diff --git a/packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx b/packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx new file mode 100644 index 00000000..f2de15cc --- /dev/null +++ b/packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx @@ -0,0 +1,77 @@ +/** @jsxImportSource @emotion/react */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { Dictionary } from '@overture-stack/lectern-dictionary'; +import DataDictionaryPage from '../../src/viewer-table/DataDictionaryPage'; +import themeDecorator from '../themeDecorator'; + +const meta = { + component: DataDictionaryPage, + title: 'Viewer - Table/Data Dictionary Page', + tags: ['autodocs'], + decorators: [themeDecorator()], + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// Simple mock dictionary with lorem ipsum content +const mockDictionary: Dictionary = { + name: 'sample-dictionary', + version: '1.0', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + schemas: [ + { + name: 'sample-schema-1', + description: + 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do.', + fields: [ + { + name: 'field1', + valueType: 'string', + description: 'Lorem ipsum field description', + restrictions: {}, + }, + { + name: 'field2', + valueType: 'integer', + description: 'Another field with description', + restrictions: {}, + }, + ], + }, + { + name: 'sample-schema-2', + description: + 'Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi.', + fields: [ + { + name: 'field3', + valueType: 'string', + description: 'Et sint enim eu proident ipsum', + restrictions: {}, + }, + ], + }, + ], + references: {}, +}; + +export const Default: Story = { + args: { + dictionary: mockDictionary, + lecternUrl: 'http://localhost:3031', + }, +}; + +export const Disabled: Story = { + args: { + dictionary: mockDictionary, + lecternUrl: 'http://localhost:3031', + disabled: true, + }, +}; From fe1dd0e7d120c903413d1d67604bfc578ad1a43a Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 15:57:13 -0400 Subject: [PATCH 060/217] move advanced.json and fix import --- .../dictionary => packages/ui/stories/fixtures}/advanced.json | 2 +- .../interaction-panel/TableOfContentDropdown.stories.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename {samples/dictionary => packages/ui/stories/fixtures}/advanced.json (97%) diff --git a/samples/dictionary/advanced.json b/packages/ui/stories/fixtures/advanced.json similarity index 97% rename from samples/dictionary/advanced.json rename to packages/ui/stories/fixtures/advanced.json index 875347ca..2b77d1bc 100644 --- a/samples/dictionary/advanced.json +++ b/packages/ui/stories/fixtures/advanced.json @@ -1,5 +1,5 @@ { - "name": "Simple", + "name": "Advanced", "version": "1.0", "schemas": [ { diff --git a/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx index bc79d10e..790b1aee 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx @@ -2,7 +2,7 @@ import { Schema } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; -import Dictionary from '../../../../../samples/dictionary/advanced.json'; +import Dictionary from '../../fixtures/advanced.json'; import TableOfContentsDropdown from '../../../src/viewer-table/InteractionPanel/TableOfContentsDropdown'; import themeDecorator from '../../themeDecorator'; From 9f29bf9adc7419445f8fa171693e3a9a1711bf47 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 15:58:42 -0400 Subject: [PATCH 061/217] we want user inputted styles to be in highest priority --- packages/ui/src/common/Dropdown/DropdownItem.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/common/Dropdown/DropdownItem.tsx b/packages/ui/src/common/Dropdown/DropdownItem.tsx index 370d3374..3af76d31 100644 --- a/packages/ui/src/common/Dropdown/DropdownItem.tsx +++ b/packages/ui/src/common/Dropdown/DropdownItem.tsx @@ -45,10 +45,11 @@ const styledListItemStyle = (theme: Theme, customStyles?: any) => css` justify-content: center; color: ${theme.colors.accent_dark}; cursor: pointer; - ${customStyles?.base} &:hover { background-color: ${theme.colors.grey_1}; } + + ${customStyles?.base} `; const DropDownItem = ({ children, action, customStyles }: DropDownItemProps) => { From 35556bbd6321e0c57bccf75526a8c04e40f6d30a Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 15:59:13 -0400 Subject: [PATCH 062/217] export props --- .../viewer-table/InteractionPanel/TableOfContentsDropdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx index a91a5551..70eb7ed0 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx @@ -2,7 +2,7 @@ import type { Schema } from '@overture-stack/lectern-dictionary'; import Dropdown from '../../common/Dropdown/Dropdown'; import { useThemeContext } from '../../theme/ThemeContext'; -type TableOfContentsDropdownProps = { +export type TableOfContentsDropdownProps = { schemas: Schema[]; onAccordionToggle: (schemaName: string, isOpen: boolean) => void; }; From 902eccc5b18b3209b2cb160bb15b9f5b85176efb Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 10 Jun 2025 16:00:18 -0400 Subject: [PATCH 063/217] change from console.log to alert --- .../interaction-panel/TableOfContentDropdown.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx index 790b1aee..3a0014fe 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx @@ -22,7 +22,7 @@ const schemas: Schema[] = Dictionary.schemas as Schema[]; // Mock functions for the story just to demonstrate interaction const onAccordionToggle = (schemaName: string, isOpen: boolean) => { - console.log('Accordion has been toggled for the following schema: ', schemaName); + alert(`Accordion has been toggled for the following schema: ${schemaName}`); }; export const Default: Story = { From b0b0dab05e06c24a118f8c3db21facb4b15f2564 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 11 Jun 2025 10:04:06 -0400 Subject: [PATCH 064/217] instead of increasing the border width, add a box shadow instead --- packages/ui/src/common/Accordion/AccordionItem.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index dc7f9a14..47e4d539 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -25,13 +25,13 @@ const accordionItemStyle = (theme: Theme) => css` border-radius: 8px; margin-bottom: 1px; overflow: hidden; - transition: all 0.3s ease; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - &:hover { - border-width: 1.2px; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + box-shadow: + 0 2px 6px rgba(0, 0, 0, 0.15), + 0 0 0 1px ${theme.colors.black}; } + transition: all 0.3s ease; `; const accordionItemTitleStyle = (theme: Theme) => css` From 4b42736294dc5555e8aca99e3f2c98a95b4007d8 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 11 Jun 2025 10:09:14 -0400 Subject: [PATCH 065/217] make hover state less prominent --- packages/ui/src/common/Accordion/AccordionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index 47e4d539..a6cad168 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -29,7 +29,7 @@ const accordionItemStyle = (theme: Theme) => css` &:hover { box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15), - 0 0 0 1px ${theme.colors.black}; + 0 0 0 0.3px ${theme.colors.black}; } transition: all 0.3s ease; `; From 140eaf6907f0f6ea1fbb5d5ff6cf4acc23b3d00f Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 11 Jun 2025 10:55:21 -0400 Subject: [PATCH 066/217] add a new button variant, iconOnly --- packages/ui/src/common/Button.tsx | 11 +++++++---- .../InteractionPanel/DownloadTemplatesButton.tsx | 2 +- .../viewer-table/InteractionPanel/ExpandAllButton.tsx | 2 +- packages/ui/stories/common/Button.stories.tsx | 11 ++++++++++- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/common/Button.tsx b/packages/ui/src/common/Button.tsx index 6eddd363..6800cbff 100644 --- a/packages/ui/src/common/Button.tsx +++ b/packages/ui/src/common/Button.tsx @@ -36,8 +36,9 @@ type ButtonProps = { isAsync?: boolean; className?: string; isLoading?: boolean; - leftIcon?: ReactNode; + icon?: ReactNode; width?: string; + iconOnly?: boolean; }; const getButtonContainerStyles = (theme: any, width?: string) => css` @@ -107,8 +108,9 @@ const Button = React.forwardRef( isAsync = false, className, isLoading: controlledLoading, - leftIcon, + icon, width, + iconOnly = false, }: ButtonProps, ref, ) => { @@ -130,8 +132,9 @@ const Button = React.forwardRef( className={className} css={getButtonContainerStyles(theme, width)} > - {leftIcon && !shouldShowLoading && {leftIcon}} - {children} + {icon && !shouldShowLoading && {icon}} + {/* If iconOnly is true, we don't show the children */} + {!iconOnly && {children}} diff --git a/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx index 8f744c70..db5dc4b2 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx @@ -81,7 +81,7 @@ const DictionaryDownloadButton = ({ }; return ( - ); diff --git a/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx index 18cf0591..b90a5709 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx @@ -33,7 +33,7 @@ const ExpandAllButton = ({ onClick, disabled }: ExpandAllButtonProps) => { const { Eye } = theme.icons; return ( - ); diff --git a/packages/ui/stories/common/Button.stories.tsx b/packages/ui/stories/common/Button.stories.tsx index 65578fdb..c375091a 100644 --- a/packages/ui/stories/common/Button.stories.tsx +++ b/packages/ui/stories/common/Button.stories.tsx @@ -3,6 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import themeDecorator from '../themeDecorator'; import Button from '../../src/common/Button'; +import FileDownload from '../../src/theme/icons/FileDownload'; const meta = { component: Button, @@ -15,7 +16,7 @@ export default meta; type Story = StoryObj; export const Default: Story = { - args: { children: 'Click Me', onClick: () => alert('I have been clicked'), className: 'my-button', leftIcon: '👍' }, + args: { children: 'Click Me', onClick: () => alert('I have been clicked'), className: 'my-button', icon: '👍' }, }; export const Disabled: Story = { args: { children: 'Disabled', disabled: true }, @@ -23,6 +24,14 @@ export const Disabled: Story = { export const Loading: Story = { args: { isLoading: true, children: 'Loading...' }, }; +export const IconOnly: Story = { + args: { + icon: , + onClick: () => alert('I have been clicked'), + className: 'iconButton', + iconOnly: true, + }, +}; export const Empty: Story = { args: {}, }; From 229928fedf48bafd7d0eb7aa06247fb6750356eb Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 11 Jun 2025 14:38:56 -0400 Subject: [PATCH 067/217] styling buttons such that we have the ability to pass in custom styles for the accordion --- .../ui/src/common/Accordion/Accordion.tsx | 25 ++++-- .../ui/src/common/Accordion/AccordionItem.tsx | 13 +-- packages/ui/src/common/Button.tsx | 9 +- .../DownloadTemplatesButton.tsx | 86 ++++++++++++------- .../DictionaryDownloadButton.stories.tsx | 9 ++ 5 files changed, 96 insertions(+), 46 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index b23e11b4..a2f4f071 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -1,7 +1,8 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import { useState } from 'react'; +import { useState, useMemo } from 'react'; import AccordionItem, { AccordionData } from './AccordionItem'; +import DownloadTemplatesButton from '../../viewer-table/InteractionPanel/DownloadTemplatesButton'; type AccordionProps = { accordionItems: Array; @@ -31,14 +32,28 @@ const accordionStyle = css` */ const Accordion = ({ accordionItems }: AccordionProps) => { + const accordionItemsWithButtons = useMemo(() => { + return accordionItems.map((item) => ({ + ...item, + downloadButton: ( + + ), + })); + }, [accordionItems]); + // This state keeps track of the currently open accordion item index via a boolean array, since each item can be opened or closed independently. const [openStates, setOpenStates] = useState( - accordionItems.map((accordionItem) => accordionItem.openOnInit), // Initialize with the openOnInit property of each item + accordionItemsWithButtons.map((accordionItem) => accordionItem.openOnInit), // Initialize with the openOnInit property of each item ); - // This state keeps track of which accordion items have expanded descriptions const [descriptionExpandedStates, setDescriptionExpandedStates] = useState( - accordionItems.map(() => false), + accordionItemsWithButtons.map(() => false), ); const onClick = (idx: number) => { @@ -51,7 +66,7 @@ const Accordion = ({ accordionItems }: AccordionProps) => { return (
      - {accordionItems.map((item, idx) => ( + {accordionItemsWithButtons.map((item, idx) => ( css` const accordionItemTitleStyle = (theme: Theme) => css` margin: 0; + width: 100%; ${theme.typography?.button}; `; const accordionItemButtonStyle = (theme: Theme, isOpen: boolean) => css` display: flex; align-items: center; - justify-content: flex-start; - width: 100%; + justify-content: space-between; padding: 24px 20px; background-color: ${'#ffffff'}; color: ${theme.colors.accent_dark}; @@ -99,6 +99,9 @@ const descriptionStyle = (theme: Theme) => css` const iconButtonContainerStyle = css` margin-left: auto; + display: flex; + flex-direction: row; + flex-shrink: 0; `; const contentInnerContainerStyle = (theme: Theme) => css` display: flex; @@ -132,7 +135,7 @@ const AccordionItem = ({ data, isOpen, onClick, isDescriptionExpanded, onDescrip const contentRef = useRef(null); const [height, setHeight] = useState(0); const theme = useThemeContext(); - const { iconButton, description, title, content } = data; + const { downloadButton, description, title, content } = data; const { ChevronDown } = theme.icons; // Determine if the description is long enough to need a toggle, based off of how many characters we want to show by default @@ -183,7 +186,7 @@ const AccordionItem = ({ data, isOpen, onClick, isDescriptionExpanded, onDescrip
    )}
    - {iconButton && {iconButton}} + {downloadButton && {downloadButton}}
    diff --git a/packages/ui/src/common/Button.tsx b/packages/ui/src/common/Button.tsx index 6800cbff..6e1a9565 100644 --- a/packages/ui/src/common/Button.tsx +++ b/packages/ui/src/common/Button.tsx @@ -22,7 +22,7 @@ /** @jsxImportSource @emotion/react */ // This is a slightly refactored version of the stage button import React, { ReactNode } from 'react'; -import { css } from '@emotion/react'; +import { css, SerializedStyles } from '@emotion/react'; import { Theme } from '../theme'; import { useThemeContext } from '../theme/ThemeContext'; @@ -30,6 +30,7 @@ import { useThemeContext } from '../theme/ThemeContext'; type ButtonProps = { children?: ReactNode; disabled?: boolean; + styleOverride?: SerializedStyles; onClick?: ( e: React.SyntheticEvent, ) => any | ((e: React.SyntheticEvent) => Promise); @@ -41,7 +42,7 @@ type ButtonProps = { iconOnly?: boolean; }; -const getButtonContainerStyles = (theme: any, width?: string) => css` +const getButtonContainerStyles = (theme: any, width?: string, styleOverride?: SerializedStyles) => css` display: flex; flex-wrap: nowrap; align-items: center; @@ -68,6 +69,7 @@ const getButtonContainerStyles = (theme: any, width?: string) => css` cursor: not-allowed; opacity: 0.7; } + ${styleOverride} `; const getContentStyles = (theme: Theme, shouldShowLoading: boolean) => css` @@ -111,6 +113,7 @@ const Button = React.forwardRef( icon, width, iconOnly = false, + styleOverride, }: ButtonProps, ref, ) => { @@ -130,7 +133,7 @@ const Button = React.forwardRef( onClick={isAsync ? handleClick : onClick} disabled={disabled || shouldShowLoading} className={className} - css={getButtonContainerStyles(theme, width)} + css={getButtonContainerStyles(theme, width, styleOverride)} > {icon && !shouldShowLoading && {icon}} {/* If iconOnly is true, we don't show the children */} diff --git a/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx index db5dc4b2..14f4c79c 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx @@ -21,16 +21,53 @@ /** @jsxImportSource @emotion/react */ -import { useState } from 'react'; +import { useState, Dispatch, SetStateAction } from 'react'; import Button from '../../common/Button'; import { useThemeContext } from '../../theme/ThemeContext'; +import { css } from '@emotion/react'; -type DictionaryDownloadButtonProps = { +export type DictionaryDownloadButtonProps = { version: string; name: string; lecternUrl: string; fileType?: 'tsv' | 'csv'; disabled?: boolean; + iconOnly?: boolean; +}; + +const downloadDictionary = async ( + setIsLoading: Dispatch>, + fetchUrl: string, + name: string, + version: string, +) => { + try { + setIsLoading(true); + const res = await fetch(fetchUrl); + + if (!res.ok) { + throw new Error(`Failed with status ${res.status}`); + } + + //Triggers a file download in the browser by creating a temporary link to a Blob + // and simulating a click. + + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = `${name}_${version}_templates.zip`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + + URL.revokeObjectURL(url); + } catch (error) { + console.error('Error downloading dictionary:', error); + } finally { + setIsLoading(false); + } }; const DictionaryDownloadButton = ({ @@ -39,6 +76,7 @@ const DictionaryDownloadButton = ({ lecternUrl, fileType = 'tsv', disabled = false, + iconOnly = false, }: DictionaryDownloadButtonProps) => { const [isLoading, setIsLoading] = useState(false); const theme = useThemeContext(); @@ -50,38 +88,20 @@ const DictionaryDownloadButton = ({ fileType, })}`; - const downloadDictionary = async () => { - try { - setIsLoading(true); - const res = await fetch(fetchUrl); - - if (!res.ok) { - throw new Error(`Failed with status ${res.status}`); - } - - //Triggers a file download in the browser by creating a temporary link to a Blob - // and simulating a click. - - const blob = await res.blob(); - const url = URL.createObjectURL(blob); - - const a = document.createElement('a'); - a.href = url; - a.download = `${name}_${version}_templates.zip`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - - URL.revokeObjectURL(url); - } catch (error) { - console.error('Error downloading dictionary:', error); - } finally { - setIsLoading(false); - } - }; - return ( - ); diff --git a/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx index 3aab598a..594c3cbb 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx @@ -21,3 +21,12 @@ export const Default: Story = { fileType: 'tsv', }, }; +export const IconOnly: Story = { + args: { + version: '1.0', + name: 'example-dictionary', + lecternUrl: 'http://localhost:3031', + fileType: 'tsv', + iconOnly: true, + }, +}; From 6bb135534d1c817fa23d158e5971baf1d3574d60 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 11 Jun 2025 16:03:07 -0400 Subject: [PATCH 068/217] add the hash --- packages/ui/src/theme/icons/Hash.tsx | 48 ++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 packages/ui/src/theme/icons/Hash.tsx diff --git a/packages/ui/src/theme/icons/Hash.tsx b/packages/ui/src/theme/icons/Hash.tsx new file mode 100644 index 00000000..8291759b --- /dev/null +++ b/packages/ui/src/theme/icons/Hash.tsx @@ -0,0 +1,48 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** @jsxImportSource @emotion/react */ + +import IconProps from './IconProps'; + +const Hash = ({ fill, width, height, style }: IconProps) => { + return ( + + + + + + + ); +}; + +export default Hash; From 9b344edb3a39a041e1541fd91c54ef23af128c54 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 12 Jun 2025 08:44:00 -0400 Subject: [PATCH 069/217] fix some div hierarchy issues with the spans not properly being stack and an uneccessary div --- .../ui/src/common/Accordion/AccordionItem.tsx | 55 +++++++++++++------ packages/ui/src/theme/icons/Hash.tsx | 4 +- packages/ui/src/theme/styles/icons.ts | 2 + 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index f4a4eb6e..92d539c3 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -84,7 +84,19 @@ const contentContainerStyle = css` const titleStyle = (theme: Theme) => css` ${theme.typography?.subheading2}; color: ${theme.colors?.accent_dark}; - margin: 0; + display: inline-flex; + align-items: center; +`; + +const hashIconStyle = (theme: Theme) => css` + opacity: 0; + margin-left: 8px; + transition: opacity 0.2s ease; + border-bottom: 2px solid ${theme.colors?.secondary}; + + &:hover { + opacity: 1; + } `; const descriptionStyle = (theme: Theme) => css` @@ -136,7 +148,7 @@ const AccordionItem = ({ data, isOpen, onClick, isDescriptionExpanded, onDescrip const [height, setHeight] = useState(0); const theme = useThemeContext(); const { downloadButton, description, title, content } = data; - const { ChevronDown } = theme.icons; + const { ChevronDown, Hash } = theme.icons; // Determine if the description is long enough to need a toggle, based off of how many characters we want to show by default // according to the figma styling @@ -167,23 +179,30 @@ const AccordionItem = ({ data, isOpen, onClick, isDescriptionExpanded, onDescrip
    - {title} + + {title} + + + + {description && ( -
    - {textToShow} - {needsToggle && ( - showMoreEventHandler(e)}> - {' '} - {isDescriptionExpanded ? 'Read less' : 'Show more'} - - - )} -
    + <> + + {textToShow} + {needsToggle && ( + showMoreEventHandler(e)}> + {' '} + {isDescriptionExpanded ? 'Read less' : 'Show more'} + + + )} + + )}
    {downloadButton && {downloadButton}} diff --git a/packages/ui/src/theme/icons/Hash.tsx b/packages/ui/src/theme/icons/Hash.tsx index 8291759b..20fa4e3b 100644 --- a/packages/ui/src/theme/icons/Hash.tsx +++ b/packages/ui/src/theme/icons/Hash.tsx @@ -31,8 +31,8 @@ const Hash = ({ fill, width, height, style }: IconProps) => { height={height || '24'} viewBox="0 0 24 24" css={style} - fill={fill || 'none'} - stroke="currentColor" + stroke={fill || 'currentColor'} + fill="none" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" diff --git a/packages/ui/src/theme/styles/icons.ts b/packages/ui/src/theme/styles/icons.ts index a0ee858a..25b87046 100644 --- a/packages/ui/src/theme/styles/icons.ts +++ b/packages/ui/src/theme/styles/icons.ts @@ -6,6 +6,7 @@ import FileDownload from '../icons/FileDownload'; import List from '../icons/List'; import ListFilter from '../icons/ListFilter'; import History from '../icons/History'; +import Hash from '../icons/Hash'; export default { ChevronDown, Spinner, @@ -15,4 +16,5 @@ export default { List, ListFilter, History, + Hash, }; From 7de835c2e823809e7982c2aea296bd6d1065c9b0 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 12 Jun 2025 09:23:57 -0400 Subject: [PATCH 070/217] remove errors from stories, need to consider the case for a longer title accordion --- packages/ui/src/common/Accordion/AccordionItem.tsx | 2 +- packages/ui/stories/common/Accordion.stories.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index 92d539c3..0046c46c 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -9,7 +9,7 @@ export type AccordionData = { openOnInit: boolean; description: string; content: ReactNode | string; - downloadButton: ReactNode; + downloadButton?: ReactNode; }; type AccordionItemProps = { data: AccordionData; diff --git a/packages/ui/stories/common/Accordion.stories.tsx b/packages/ui/stories/common/Accordion.stories.tsx index a895c230..dc7f7ec3 100644 --- a/packages/ui/stories/common/Accordion.stories.tsx +++ b/packages/ui/stories/common/Accordion.stories.tsx @@ -32,7 +32,7 @@ export const Default: Story = { 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna.Adipisicing ea sunt ullamco voluptate tempor eu.Sint ex officiaLorem laborum mollit proident sunt culpa deserunt. officia enim. ', }, { - title: '{Schema:name}', + title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', openOnInit: false, description: '{Schema: description} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', From 7516587761a8ad104d4b25acb4760dd58b6ce5d2 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 12 Jun 2025 09:47:49 -0400 Subject: [PATCH 071/217] attempt 1 of getting the title to wrap when too big --- packages/ui/src/common/Accordion/AccordionItem.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index 0046c46c..73e678ed 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -78,6 +78,7 @@ const contentContainerStyle = css` gap: 16px; flex: 1; min-width: 0; + flex-wrap: wrap; max-width: calc(100% - 100px); `; From 1997c6c866184ce5563dd773df9b0a17d10a358b Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 12 Jun 2025 09:58:46 -0400 Subject: [PATCH 072/217] implement handling of a long title --- packages/ui/src/common/Accordion/AccordionItem.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index 73e678ed..cacb99bb 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -83,9 +83,8 @@ const contentContainerStyle = css` `; const titleStyle = (theme: Theme) => css` - ${theme.typography?.subheading2}; color: ${theme.colors?.accent_dark}; - display: inline-flex; + display: flex; align-items: center; `; @@ -106,8 +105,6 @@ const descriptionStyle = (theme: Theme) => css` padding: 4px 8px; word-wrap: break-word; overflow-wrap: break-word; - flex: 1; - max-width: 60%; `; const iconButtonContainerStyle = css` From 316750be228d8efdbc4c7ae4211861999d89847a Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 12 Jun 2025 11:09:12 -0400 Subject: [PATCH 073/217] refactor the read-more/read-less logic --- .../ui/src/common/Accordion/Accordion.tsx | 17 +-- .../ui/src/common/Accordion/AccordionItem.tsx | 121 ++++++------------ packages/ui/src/common/ReadMoreText.tsx | 121 ++++++++++++++++++ .../ui/stories/common/Accordion.stories.tsx | 2 +- 4 files changed, 163 insertions(+), 98 deletions(-) create mode 100644 packages/ui/src/common/ReadMoreText.tsx diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index a2f4f071..2bc7617e 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -52,29 +52,14 @@ const Accordion = ({ accordionItems }: AccordionProps) => { accordionItemsWithButtons.map((accordionItem) => accordionItem.openOnInit), // Initialize with the openOnInit property of each item ); // This state keeps track of which accordion items have expanded descriptions - const [descriptionExpandedStates, setDescriptionExpandedStates] = useState( - accordionItemsWithButtons.map(() => false), - ); - const onClick = (idx: number) => { setOpenStates((prev) => prev.map((isOpen, index) => (index === idx ? !isOpen : isOpen))); }; - const onDescriptionToggle = (idx: number) => { - setDescriptionExpandedStates((prev) => prev.map((isExpanded, index) => (index === idx ? !isExpanded : isExpanded))); - }; - return (
      {accordionItemsWithButtons.map((item, idx) => ( - onClick(idx)} - isDescriptionExpanded={descriptionExpandedStates[idx]} - onDescriptionToggle={() => onDescriptionToggle(idx)} - /> + onClick(idx)} /> ))}
    ); diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index cacb99bb..9b165d39 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -1,8 +1,30 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; import { ReactNode, useEffect, useRef, useState } from 'react'; import type { Theme } from '../../theme'; import { useThemeContext } from '../../theme/ThemeContext'; +import ReadMoreText from '../ReadMoreText'; export type AccordionData = { title: string; @@ -11,12 +33,11 @@ export type AccordionData = { content: ReactNode | string; downloadButton?: ReactNode; }; + type AccordionItemProps = { data: AccordionData; isOpen: boolean; onClick: () => void; - isDescriptionExpanded: boolean; - onDescriptionToggle: () => void; }; const accordionItemStyle = (theme: Theme) => css` @@ -45,7 +66,7 @@ const accordionItemButtonStyle = (theme: Theme, isOpen: boolean) => css` align-items: center; justify-content: space-between; padding: 24px 20px; - background-color: ${'#ffffff'}; + background-color: #ffffff; color: ${theme.colors.accent_dark}; cursor: pointer; transition: all 0.2s ease; @@ -83,7 +104,7 @@ const contentContainerStyle = css` `; const titleStyle = (theme: Theme) => css` - color: ${theme.colors?.accent_dark}; + color: ${theme.colors.accent_dark}; display: flex; align-items: center; `; @@ -92,82 +113,36 @@ const hashIconStyle = (theme: Theme) => css` opacity: 0; margin-left: 8px; transition: opacity 0.2s ease; - border-bottom: 2px solid ${theme.colors?.secondary}; + border-bottom: 2px solid ${theme.colors.secondary}; &:hover { opacity: 1; } `; -const descriptionStyle = (theme: Theme) => css` +const descriptionWrapperStyle = (theme: Theme) => css` ${theme.typography?.label2}; - color: ${theme.colors?.grey_5}; + color: ${theme.colors.grey_5}; padding: 4px 8px; word-wrap: break-word; overflow-wrap: break-word; `; -const iconButtonContainerStyle = css` - margin-left: auto; - display: flex; - flex-direction: row; - flex-shrink: 0; -`; -const contentInnerContainerStyle = (theme: Theme) => css` - display: flex; - padding: 30px; - border-left: 1px solid ${theme.colors.grey_3}; - height: fit-content; +const contentTextStyle = (theme: Theme) => css` ${theme.typography?.data}; + color: ${theme.colors.grey_5}; `; -const getChevronStyle = (isExpanded: boolean) => css` - margin-left: 4px; - ${isExpanded && `transform: rotate(180deg);`} -`; - -const linkStyle = (theme: Theme) => css` - ${theme.typography?.label2} - color: ${theme.colors?.accent_dark}; - cursor: pointer; - display: inline-flex; - align-items: center; - - &:hover { - text-decoration: underline; - } -`; - -// These constants can be adjusted based on design requirements -const DESCRIPTION_THRESHOLD = 240; // Allows for ~4-5 lines of description text in accordion items - -const AccordionItem = ({ data, isOpen, onClick, isDescriptionExpanded, onDescriptionToggle }: AccordionItemProps) => { +const AccordionItem = ({ data, isOpen, onClick }: AccordionItemProps) => { const contentRef = useRef(null); const [height, setHeight] = useState(0); const theme = useThemeContext(); const { downloadButton, description, title, content } = data; const { ChevronDown, Hash } = theme.icons; - // Determine if the description is long enough to need a toggle, based off of how many characters we want to show by default - // according to the figma styling - const needsToggle = description && description.length > DESCRIPTION_THRESHOLD; - // We want to show all the text if it is not long or if it is already expanded via state variable - const showFull = isDescriptionExpanded || !needsToggle; - // Based off of showFull, we determine the text to show, either its the full description or a truncated version - const textToShow = showFull ? description : description.slice(0, DESCRIPTION_THRESHOLD) + '... '; - - const showMoreEventHandler = (e: React.MouseEvent) => { - e.stopPropagation(); - onDescriptionToggle(); - }; useEffect(() => { - if (isOpen) { - const contentEl = contentRef.current; - if (contentEl) { - setHeight(contentEl.scrollHeight); - } - } else { - setHeight(0); + if (contentRef.current) { + setHeight(isOpen ? contentRef.current.scrollHeight : 0); } }, [isOpen]); @@ -175,43 +150,27 @@ const AccordionItem = ({ data, isOpen, onClick, isDescriptionExpanded, onDescrip
  • - +
    {title} - + - {description && ( - <> - - {textToShow} - {needsToggle && ( - showMoreEventHandler(e)}> - {' '} - {isDescriptionExpanded ? 'Read less' : 'Show more'} - - - )} - - - )} + {description && {description}}
    - {downloadButton && {downloadButton}} + {downloadButton && {downloadButton}}

    +
    -
    {content}
    +
    {content}
  • ); }; + export default AccordionItem; diff --git a/packages/ui/src/common/ReadMoreText.tsx b/packages/ui/src/common/ReadMoreText.tsx new file mode 100644 index 00000000..984097f8 --- /dev/null +++ b/packages/ui/src/common/ReadMoreText.tsx @@ -0,0 +1,121 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; +import { ReactNode, useEffect, useRef, useState } from 'react'; +import type { Theme } from '../theme'; +import { useThemeContext } from '../theme/ThemeContext'; + +type ReadMoreTextProps = { + children: ReactNode; + maxLines?: number; + wrapperStyle?: (theme: Theme) => any; + onToggleClick?: (e: React.MouseEvent) => void; + expandedText?: string; + collapsedText?: string; +}; + +const defaultWrapperStyle = (theme: Theme) => css` + ${theme.typography?.label2}; + color: ${theme.colors.grey_5}; + padding: 4px 8px; + word-wrap: break-word; + overflow-wrap: break-word; +`; + +const linkStyle = (theme: Theme) => css` + ${theme.typography?.label2}; + color: ${theme.colors.accent_dark}; + cursor: pointer; + display: inline-flex; + align-items: center; + background: none; + border: none; + padding: 0; + margin-top: 4px; + + &:hover { + text-decoration: underline; + } +`; + +const clampingLogic = (isExpanded: boolean, maxLines: number) => css` + ${!isExpanded && + ` + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: ${maxLines}; + overflow: hidden; + `} +`; + +const getChevronStyle = (isExpanded: boolean) => css` + margin-left: 4px; + ${isExpanded && `transform: rotate(180deg);`} +`; + +const ReadMoreText = ({ + children, + maxLines = 2, + wrapperStyle, + onToggleClick, + expandedText = 'Read less', + collapsedText = 'Show more', +}: ReadMoreTextProps) => { + const contentRef = useRef(null); + const [isExpanded, setIsExpanded] = useState(false); + const [needsToggle, setNeedsToggle] = useState(false); + const theme = useThemeContext(); + const { ChevronDown } = theme.icons; + + useEffect(() => { + if (contentRef.current) { + const div = contentRef.current; + setNeedsToggle(div.scrollHeight > div.clientHeight + 1); + } + }, [children]); + + const handleToggle = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsExpanded(!isExpanded); + onToggleClick?.(e); + }; + + const appliedWrapperStyle = wrapperStyle || defaultWrapperStyle; + + return ( +
    +
    + {children} +
    + + {needsToggle && ( + + )} +
    + ); +}; + +export default ReadMoreText; diff --git a/packages/ui/stories/common/Accordion.stories.tsx b/packages/ui/stories/common/Accordion.stories.tsx index dc7f7ec3..be8ecfbf 100644 --- a/packages/ui/stories/common/Accordion.stories.tsx +++ b/packages/ui/stories/common/Accordion.stories.tsx @@ -35,7 +35,7 @@ export const Default: Story = { title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', openOnInit: false, description: - '{Schema: description} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', + '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', content: 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', }, From 7744643ac265b5d24d731b6533fa13074e1d67c8 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 12 Jun 2025 11:22:41 -0400 Subject: [PATCH 074/217] make the transition smooth with css without the use of useEffect --- .../ui/src/common/Accordion/AccordionItem.tsx | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index 9b165d39..c115e0fb 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -21,11 +21,13 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import { ReactNode, useEffect, useRef, useState } from 'react'; +import type { ReactNode } from 'react'; import type { Theme } from '../../theme'; import { useThemeContext } from '../../theme/ThemeContext'; import ReadMoreText from '../ReadMoreText'; +const MAX_LINES_BEFORE_EXPAND = 2; + export type AccordionData = { title: string; openOnInit: boolean; @@ -34,7 +36,7 @@ export type AccordionData = { downloadButton?: ReactNode; }; -type AccordionItemProps = { +export type AccordionItemProps = { data: AccordionData; isOpen: boolean; onClick: () => void; @@ -81,17 +83,6 @@ const chevronStyle = (isOpen: boolean) => css` flex-shrink: 0; `; -const accordionItemContainerStyle = (height: number) => css` - height: ${height}px; - overflow: hidden; - transition: height 0.3s ease; -`; - -const accordionItemContentStyle = (theme: Theme) => css` - padding: 30px; - background-color: #ffffff; -`; - const contentContainerStyle = css` display: flex; flex-direction: row; @@ -114,7 +105,6 @@ const hashIconStyle = (theme: Theme) => css` margin-left: 8px; transition: opacity 0.2s ease; border-bottom: 2px solid ${theme.colors.secondary}; - &:hover { opacity: 1; } @@ -128,29 +118,35 @@ const descriptionWrapperStyle = (theme: Theme) => css` overflow-wrap: break-word; `; -const contentTextStyle = (theme: Theme) => css` +const accordionCollapseStyle = (isOpen: boolean) => css` + overflow: hidden; + max-height: ${isOpen ? '800px' : '0px'}; + transition: max-height 0.3s ease; +`; + +const accordionItemContentStyle = (theme: Theme) => css` + padding: 30px; + background-color: #ffffff; +`; + +const contentInnerContainerStyle = (theme: Theme) => css` + display: flex; + padding: 30px; + border-left: 1px solid ${theme.colors.grey_3}; ${theme.typography?.data}; - color: ${theme.colors.grey_5}; `; const AccordionItem = ({ data, isOpen, onClick }: AccordionItemProps) => { - const contentRef = useRef(null); - const [height, setHeight] = useState(0); const theme = useThemeContext(); const { downloadButton, description, title, content } = data; const { ChevronDown, Hash } = theme.icons; - useEffect(() => { - if (contentRef.current) { - setHeight(isOpen ? contentRef.current.scrollHeight : 0); - } - }, [isOpen]); - return (
  • +
    {title} @@ -158,15 +154,21 @@ const AccordionItem = ({ data, isOpen, onClick }: AccordionItemProps) => { - {description && {description}} + + {description && ( + + {description} + + )}
    + {downloadButton && {downloadButton}}

    -
    -
    -
    {content}
    +
    +
    +
    {content}
  • From 50503e65cce1b51c9e85a8c853de43419b91ac70 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 12 Jun 2025 11:27:29 -0400 Subject: [PATCH 075/217] clean up css finally --- .../ui/src/common/Accordion/AccordionItem.tsx | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index c115e0fb..0bec6f6c 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -57,13 +57,12 @@ const accordionItemStyle = (theme: Theme) => css` transition: all 0.3s ease; `; -const accordionItemTitleStyle = (theme: Theme) => css` +const accordionItemTitleStyle = css` margin: 0; width: 100%; - ${theme.typography?.button}; `; -const accordionItemButtonStyle = (theme: Theme, isOpen: boolean) => css` +const accordionItemButtonStyle = (theme: Theme) => css` display: flex; align-items: center; justify-content: space-between; @@ -94,8 +93,7 @@ const contentContainerStyle = css` max-width: calc(100% - 100px); `; -const titleStyle = (theme: Theme) => css` - color: ${theme.colors.accent_dark}; +const titleStyle = css` display: flex; align-items: center; `; @@ -130,9 +128,8 @@ const accordionItemContentStyle = (theme: Theme) => css` `; const contentInnerContainerStyle = (theme: Theme) => css` - display: flex; - padding: 30px; border-left: 1px solid ${theme.colors.grey_3}; + padding-left: 30px; ${theme.typography?.data}; `; @@ -143,26 +140,23 @@ const AccordionItem = ({ data, isOpen, onClick }: AccordionItemProps) => { return (
  • -

    -
    +

    +
    -
    - + {title} - {description && ( {description} )}
    - - {downloadButton && {downloadButton}} + {downloadButton}

    From b2bc3eb8d5e3026ab388852f0d0656ebf18338a2 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 12 Jun 2025 11:28:29 -0400 Subject: [PATCH 076/217] whitespace fix --- packages/ui/src/common/Accordion/AccordionItem.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index 0bec6f6c..c1da8c7b 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -159,7 +159,6 @@ const AccordionItem = ({ data, isOpen, onClick }: AccordionItemProps) => { {downloadButton}

    -
    {content}
    From 2da8a3457a528bdb0d9e55abcb1ff724477bdcf1 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 12 Jun 2025 12:04:04 -0400 Subject: [PATCH 077/217] general copying pattern --- .../ui/src/common/Accordion/Accordion.tsx | 50 ++++++++++++++++++- .../ui/src/common/Accordion/AccordionItem.tsx | 8 +-- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index 2bc7617e..5c222903 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -3,6 +3,7 @@ import { css } from '@emotion/react'; import { useState, useMemo } from 'react'; import AccordionItem, { AccordionData } from './AccordionItem'; import DownloadTemplatesButton from '../../viewer-table/InteractionPanel/DownloadTemplatesButton'; +import { use } from 'chai'; type AccordionProps = { accordionItems: Array; @@ -32,6 +33,8 @@ const accordionStyle = css` */ const Accordion = ({ accordionItems }: AccordionProps) => { + //Some random buttons that we want to add to the accordion items, we will actually need to figure out some schema filtering logic, + // and download based on that, but for now we just add a button to each item. const accordionItemsWithButtons = useMemo(() => { return accordionItems.map((item) => ({ ...item, @@ -47,6 +50,45 @@ const Accordion = ({ accordionItems }: AccordionProps) => { })); }, [accordionItems]); + // This state keeps track of the clipboard contents, which can be set by the accordion items. + // Each individual accordion item can set this state when it's tag has been clicked, however only one item can be set at a time. + + const [clipboardContents, setClipboardContents] = useState(null); + const [isCopying, setIsCopying] = useState(false); + const [copySuccess, setCopySuccess] = useState(false); + + const handleCopy = (text: string) => { + if (isCopying) { + return; // We don't wanna copy if we are already copying + } + setIsCopying(true); + navigator.clipboard + .writeText(text) + .then(() => { + setCopySuccess(true); + setTimeout(() => { + setIsCopying(false); + }, 2000); // Reset copy success after 2 seconds as well as the isCopying state + }) + .catch((err) => { + console.error('Failed to copy text: ', err); + setCopySuccess(false); + setIsCopying(false); + }); + setClipboardContents(text); + console.log('Copied to clipboard:', text); + if (copySuccess) { + //do stuff + } + setCopySuccess(false); + }; + + useMemo(() => { + if (clipboardContents) { + handleCopy(clipboardContents); + } + }, [clipboardContents]); + // This state keeps track of the currently open accordion item index via a boolean array, since each item can be opened or closed independently. const [openStates, setOpenStates] = useState( accordionItemsWithButtons.map((accordionItem) => accordionItem.openOnInit), // Initialize with the openOnInit property of each item @@ -59,7 +101,13 @@ const Accordion = ({ accordionItems }: AccordionProps) => { return (
      {accordionItemsWithButtons.map((item, idx) => ( - onClick(idx)} /> + onClick(idx)} + setClipboardContents={setClipboardContents} + /> ))}
    ); diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index c1da8c7b..1f752e42 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -37,6 +37,7 @@ export type AccordionData = { }; export type AccordionItemProps = { + setClipboardContents: (currentSchema: string) => void; data: AccordionData; isOpen: boolean; onClick: () => void; @@ -133,20 +134,19 @@ const contentInnerContainerStyle = (theme: Theme) => css` ${theme.typography?.data}; `; -const AccordionItem = ({ data, isOpen, onClick }: AccordionItemProps) => { +const AccordionItem = ({ data, isOpen, onClick, setClipboardContents }: AccordionItemProps) => { const theme = useThemeContext(); const { downloadButton, description, title, content } = data; const { ChevronDown, Hash } = theme.icons; - return ( -
  • +
  • {title} - + setClipboardContents(data.title)}> From 0c6f71031bd64a60110d400f301bd66021a7a1c9 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 12 Jun 2025 15:10:54 -0400 Subject: [PATCH 078/217] finalize the url hash navigation --- .../ui/src/common/Accordion/Accordion.tsx | 11 +++++---- .../ui/src/common/Accordion/AccordionItem.tsx | 23 ++++++++++++++++--- packages/ui/src/common/ReadMoreText.tsx | 2 +- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index 5c222903..6630ea57 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -3,7 +3,6 @@ import { css } from '@emotion/react'; import { useState, useMemo } from 'react'; import AccordionItem, { AccordionData } from './AccordionItem'; import DownloadTemplatesButton from '../../viewer-table/InteractionPanel/DownloadTemplatesButton'; -import { use } from 'chai'; type AccordionProps = { accordionItems: Array; @@ -75,10 +74,10 @@ const Accordion = ({ accordionItems }: AccordionProps) => { setCopySuccess(false); setIsCopying(false); }); - setClipboardContents(text); - console.log('Copied to clipboard:', text); if (copySuccess) { - //do stuff + // Update the clipboard contents + const currentURL = window.location.href; + setClipboardContents(currentURL); } setCopySuccess(false); }; @@ -93,7 +92,7 @@ const Accordion = ({ accordionItems }: AccordionProps) => { const [openStates, setOpenStates] = useState( accordionItemsWithButtons.map((accordionItem) => accordionItem.openOnInit), // Initialize with the openOnInit property of each item ); - // This state keeps track of which accordion items have expanded descriptions + // This state keeps track of which accordion items are open const onClick = (idx: number) => { setOpenStates((prev) => prev.map((isOpen, index) => (index === idx ? !isOpen : isOpen))); }; @@ -102,8 +101,10 @@ const Accordion = ({ accordionItems }: AccordionProps) => {
      {accordionItemsWithButtons.map((item, idx) => ( onClick(index)} isOpen={openStates[idx]} onClick={() => onClick(idx)} setClipboardContents={setClipboardContents} diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index 1f752e42..79cf5a8e 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -22,6 +22,7 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; import type { ReactNode } from 'react'; +import { useEffect } from 'react'; import type { Theme } from '../../theme'; import { useThemeContext } from '../../theme/ThemeContext'; import ReadMoreText from '../ReadMoreText'; @@ -41,6 +42,8 @@ export type AccordionItemProps = { data: AccordionData; isOpen: boolean; onClick: () => void; + index: number; // Index of the accordion item, used for accessibility and unique identification to avoid the issue with duplicates + setIsOpen: (index: number) => void; }; const accordionItemStyle = (theme: Theme) => css` @@ -134,19 +137,33 @@ const contentInnerContainerStyle = (theme: Theme) => css` ${theme.typography?.data}; `; -const AccordionItem = ({ data, isOpen, onClick, setClipboardContents }: AccordionItemProps) => { +const AccordionItem = ({ index, data, isOpen, onClick, setClipboardContents, setIsOpen }: AccordionItemProps) => { const theme = useThemeContext(); const { downloadButton, description, title, content } = data; const { ChevronDown, Hash } = theme.icons; + useEffect(() => { + if (window.location.hash === `#${index}`) { + if (!data.openOnInit) { + setIsOpen(index); + } + document.getElementById(index.toString())?.scrollIntoView({ behavior: 'smooth' }); + } + }, []); return ( -
    • +
    • {title} - setClipboardContents(data.title)}> + { + window.location.hash = `#${index}`; + setClipboardContents(window.location.href); + }} + > diff --git a/packages/ui/src/common/ReadMoreText.tsx b/packages/ui/src/common/ReadMoreText.tsx index 984097f8..d918796e 100644 --- a/packages/ui/src/common/ReadMoreText.tsx +++ b/packages/ui/src/common/ReadMoreText.tsx @@ -25,7 +25,7 @@ import { ReactNode, useEffect, useRef, useState } from 'react'; import type { Theme } from '../theme'; import { useThemeContext } from '../theme/ThemeContext'; -type ReadMoreTextProps = { +export type ReadMoreTextProps = { children: ReactNode; maxLines?: number; wrapperStyle?: (theme: Theme) => any; From 5973dd4e48d7279a6953d0c5c80b366eebbc7ef6 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 12 Jun 2025 15:45:22 -0400 Subject: [PATCH 079/217] rework the table of contents dropdown stuff, and chat gpt the dictionary page to work again with new props --- .../src/viewer-table/DataDictionaryPage.tsx | 14 ++-- .../InteractionPanel/InteractionPanel.tsx | 4 +- .../TableOfContentsDropdown.tsx | 14 ++-- .../ui/stories/common/Accordion.stories.tsx | 66 ++++++++++++++++++- .../TableOfContentDropdown.stories.tsx | 8 +-- 5 files changed, 87 insertions(+), 19 deletions(-) diff --git a/packages/ui/src/viewer-table/DataDictionaryPage.tsx b/packages/ui/src/viewer-table/DataDictionaryPage.tsx index 9e9769dc..d59e236f 100644 --- a/packages/ui/src/viewer-table/DataDictionaryPage.tsx +++ b/packages/ui/src/viewer-table/DataDictionaryPage.tsx @@ -76,11 +76,15 @@ const DataDictionaryPage = ({ return initialStates; }); - const handleAccordionToggle = (schemaName: string, isOpen: boolean) => { - setAccordionStates((prev) => ({ - ...prev, - [schemaName]: isOpen, - })); + const handleAccordionToggle = (schemaIndex: number) => { + const schema = filteredData.schemas[schemaIndex]; + if (schema) { + // Open the accordion for this schema + setAccordionStates((prev) => ({ + ...prev, + [schema.name]: true, + })); + } }; // Handle expand/collapse all diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index 3cf5ff9c..a14a8e6f 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -43,7 +43,7 @@ type InteractionPanelProps = { setIsCollapsed: (isCollapsed: boolean) => void; setFilteredData: (dict: Dictionary) => void; setIsFiltered: (bool: boolean) => void; - onAccordionToggle: (schemaName: string, isOpen: boolean) => void; + onAccordionToggle: (schemaIndex: number) => void; onVersionChange?: (version: number) => void; dictionaryVersions?: Dictionary[]; currentVersionIndex?: number; @@ -98,7 +98,7 @@ const InteractionPanel = ({ return (
      - + void; + onSelect: (schemaIndex: number) => void; disabled?: boolean; }; -const TableOfContentsDropdown = ({ schemas, onAccordionToggle, disabled = false }: TableOfContentsDropdownProps) => { +const TableOfContentsDropdown = ({ schemas, onSelect, disabled = false }: TableOfContentsDropdownProps) => { const theme = useThemeContext(); const { List } = theme.icons; - const handleAction = (schema: Schema) => { - const anchorId = `${schema.name}`; - onAccordionToggle(schema.name, true); // Ensuring that the accordion for the associated schema is open, via the state handler that will be defined in parent component + const handleAction = (index: number) => { + const anchorId = `#${index}`; + onSelect(index); setTimeout(() => { window.location.hash = anchorId; }, 100); }; - const menuItemsFromSchemas = schemas.map((schema) => ({ + const menuItemsFromSchemas = schemas.map((schema, index) => ({ label: schema.name, action: () => { - handleAction(schema); + handleAction(index); }, })); diff --git a/packages/ui/stories/common/Accordion.stories.tsx b/packages/ui/stories/common/Accordion.stories.tsx index be8ecfbf..5f94a775 100644 --- a/packages/ui/stories/common/Accordion.stories.tsx +++ b/packages/ui/stories/common/Accordion.stories.tsx @@ -18,7 +18,7 @@ export const Default: Story = { accordionItems: [ { title: '{Schema:name}', - openOnInit: true, + openOnInit: false, description: '{Schema: description} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', content: 'Content for item 1', @@ -39,6 +39,70 @@ export const Default: Story = { content: 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', }, + { + title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', + openOnInit: false, + description: + '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', + content: + 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + }, + { + title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', + openOnInit: false, + description: + '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', + content: + 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + }, + { + title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', + openOnInit: false, + description: + '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', + content: + 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + }, + { + title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', + openOnInit: false, + description: + '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', + content: + 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + }, + { + title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', + openOnInit: false, + description: + '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', + content: + 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + }, + { + title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', + openOnInit: false, + description: + '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', + content: + 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + }, + { + title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', + openOnInit: false, + description: + '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', + content: + 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + }, + { + title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', + openOnInit: false, + description: + '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', + content: + 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + }, ], }, }; diff --git a/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx index bc79d10e..8ab78f09 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx @@ -21,17 +21,17 @@ const schemas: Schema[] = Dictionary.schemas as Schema[]; // Mock functions for the story just to demonstrate interaction -const onAccordionToggle = (schemaName: string, isOpen: boolean) => { - console.log('Accordion has been toggled for the following schema: ', schemaName); +const onSelect = (schemaIndex: number) => { + console.log('Schema selected at index: ', schemaIndex, 'with name: ', schemas[schemaIndex]?.name); }; export const Default: Story = { args: { schemas: schemas, - onAccordionToggle, + onSelect, }, }; export const Empty: Story = { - args: { schemas: [], onAccordionToggle }, + args: { schemas: [], onSelect }, }; From 7db705879678c7045dbe9cf34db71bee2edeffcd Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 12 Jun 2025 15:52:08 -0400 Subject: [PATCH 080/217] implement pr review changes --- .../TableOfContentsDropdown.tsx | 37 +++++++++++++++---- .../TableOfContentDropdown.stories.tsx | 8 ++-- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx index 70eb7ed0..17dd7946 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx @@ -1,27 +1,48 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + import type { Schema } from '@overture-stack/lectern-dictionary'; import Dropdown from '../../common/Dropdown/Dropdown'; import { useThemeContext } from '../../theme/ThemeContext'; -export type TableOfContentsDropdownProps = { +type TableOfContentsDropdownProps = { schemas: Schema[]; - onAccordionToggle: (schemaName: string, isOpen: boolean) => void; + onSelect: (schemaIndex: number) => void; }; -const TableOfContentsDropdown = ({ schemas, onAccordionToggle }: TableOfContentsDropdownProps) => { +const TableOfContentsDropdown = ({ schemas, onSelect }: TableOfContentsDropdownProps) => { const theme = useThemeContext(); const { List } = theme.icons; - const handleAction = (schema: Schema) => { - const anchorId = `${schema.name}`; - onAccordionToggle(schema.name, true); // Ensuring that the accordion for the associated schema is open, via the state handler that will be defined in parent component + const handleAction = (index: number) => { + const anchorId = `#${index}`; + onSelect(index); setTimeout(() => { window.location.hash = anchorId; }, 100); }; - const menuItemsFromSchemas = schemas.map((schema) => ({ + const menuItemsFromSchemas = schemas.map((schema, index) => ({ label: schema.name, action: () => { - handleAction(schema); + handleAction(index); }, })); diff --git a/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx index 3a0014fe..f690b6c8 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx @@ -21,17 +21,17 @@ const schemas: Schema[] = Dictionary.schemas as Schema[]; // Mock functions for the story just to demonstrate interaction -const onAccordionToggle = (schemaName: string, isOpen: boolean) => { - alert(`Accordion has been toggled for the following schema: ${schemaName}`); +const onAccordionToggle = (schemaIndex: number) => { + alert(`Accordion has been toggled for the following schema: ${schemas[schemaIndex].name}`); }; export const Default: Story = { args: { schemas: schemas, - onAccordionToggle, + onSelect: onAccordionToggle, }, }; export const Empty: Story = { - args: { schemas: [], onAccordionToggle }, + args: { schemas: [], onSelect: onAccordionToggle }, }; From 69467f0a00369f2126d8022d7da0b1cd34ee1537 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 12 Jun 2025 16:05:06 -0400 Subject: [PATCH 081/217] add a todo for the buttons rendering --- packages/ui/src/common/Accordion/Accordion.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index 6630ea57..6925656b 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -32,7 +32,7 @@ const accordionStyle = css` */ const Accordion = ({ accordionItems }: AccordionProps) => { - //Some random buttons that we want to add to the accordion items, we will actually need to figure out some schema filtering logic, + //TODO: Some random buttons that we want to add to the accordion items, we will actually need to figure out some schema filtering logic, // and download based on that, but for now we just add a button to each item. const accordionItemsWithButtons = useMemo(() => { return accordionItems.map((item) => ({ From 34445e913599ad2acf2a1c813fd4632c88914534 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 12 Jun 2025 16:11:25 -0400 Subject: [PATCH 082/217] accidentally removed export --- .../viewer-table/InteractionPanel/TableOfContentsDropdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx index 17dd7946..f3e6140c 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx @@ -23,7 +23,7 @@ import type { Schema } from '@overture-stack/lectern-dictionary'; import Dropdown from '../../common/Dropdown/Dropdown'; import { useThemeContext } from '../../theme/ThemeContext'; -type TableOfContentsDropdownProps = { +export type TableOfContentsDropdownProps = { schemas: Schema[]; onSelect: (schemaIndex: number) => void; }; From e9a068ec481644b3dfc22130aeedf9dff5004755 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 12 Jun 2025 16:13:22 -0400 Subject: [PATCH 083/217] fix naming inside of stories to make it consistent with props --- .../interaction-panel/TableOfContentDropdown.stories.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx index f690b6c8..d11b24f0 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx @@ -21,17 +21,17 @@ const schemas: Schema[] = Dictionary.schemas as Schema[]; // Mock functions for the story just to demonstrate interaction -const onAccordionToggle = (schemaIndex: number) => { +const onSelect = (schemaIndex: number) => { alert(`Accordion has been toggled for the following schema: ${schemas[schemaIndex].name}`); }; export const Default: Story = { args: { schemas: schemas, - onSelect: onAccordionToggle, + onSelect, }, }; export const Empty: Story = { - args: { schemas: [], onSelect: onAccordionToggle }, + args: { schemas: [], onSelect: () => {} }, }; From 639334a4f636a3cc72acb2e4bb47a1703a5c930d Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 13 Jun 2025 12:54:49 -0400 Subject: [PATCH 084/217] move filter dropdown from stage to lectern --- .../AttributeFilterDropdown.tsx | 60 +++++++------------ 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx index 153f7bcb..192113de 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx @@ -1,55 +1,39 @@ -import type { Dictionary } from '@overture-stack/lectern-dictionary'; import Dropdown from '../../common/Dropdown/Dropdown'; -import ListFilter from '../../theme/icons/ListFilter'; -type FilterDropdownProps = { - data: Dictionary; - isFiltered: boolean; - setFilteredData: (dict: Dictionary) => void; - setIsFiltered: (bool: boolean) => void; +export type FilterDropdownProps = { + filters: FilterMapping; + setFilters: (filters: FilterMapping) => void; }; -const AttributeFilter = ({ data, isFiltered, setFilteredData, setIsFiltered }: FilterDropdownProps) => { - const handleFilterSelect = (selectedFilterName: string) => { - if (isFiltered) { - setFilteredData(data); - setIsFiltered(false); - return; - } +export type FilterMapping = { + constraints?: FilterOptions[]; + active: boolean; +}; - if (selectedFilterName === 'Required') { - const filteredDictionary: Dictionary = { - ...data, - schemas: data.schemas.map((schema) => ({ - ...schema, - fields: schema.fields.filter((field: any) => field?.restrictions?.required === true), - })), - }; - setFilteredData(filteredDictionary); - } - // Add more filters here as needed - // Follow the same pattern as above for additional filters - // If we have a lot of filtering logic, we might want to consider moving this logic into a separate utility function - else { - setFilteredData(data); - } +export type FilterOptions = 'Required' | 'All Fields'; - setIsFiltered(true); +const AttributeFilter = ({ filters, setFilters }: FilterDropdownProps) => { + const handleFilterSelect = (selectedFilterName: FilterOptions) => { + // If we click the filter again then we want to toggle it off, iff it is the same filter being clicked + // and it is currently active + if (filters.active && filters.constraints?.includes(selectedFilterName)) { + setFilters({ active: false, constraints: [] }); + return; + } + setFilters({ active: true, constraints: [selectedFilterName] }); }; - - // We can easily define more filters here in the future const menuItems = [ - { - label: 'All Fields', - action: () => handleFilterSelect('All Fields'), - }, { label: 'Required Only', action: () => handleFilterSelect('Required'), }, + { + label: 'All Fields', + action: () => handleFilterSelect('All Fields'), + }, ]; - return } title="Filter By" menuItems={menuItems} />; + return ; }; export default AttributeFilter; From e9b0eafb293ad6b8cfbe6936b4b0a7bc38468ab3 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 13 Jun 2025 13:06:00 -0400 Subject: [PATCH 085/217] fix states in the interaction panel component --- .../AttributeFilterDropdown.tsx | 5 +- .../InteractionPanel/InteractionPanel.tsx | 92 +++++++++---------- 2 files changed, 44 insertions(+), 53 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx index 192113de..0919ffac 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx @@ -3,6 +3,7 @@ import Dropdown from '../../common/Dropdown/Dropdown'; export type FilterDropdownProps = { filters: FilterMapping; setFilters: (filters: FilterMapping) => void; + disabled?: boolean; }; export type FilterMapping = { @@ -12,7 +13,7 @@ export type FilterMapping = { export type FilterOptions = 'Required' | 'All Fields'; -const AttributeFilter = ({ filters, setFilters }: FilterDropdownProps) => { +const AttributeFilter = ({ filters, setFilters, disabled }: FilterDropdownProps) => { const handleFilterSelect = (selectedFilterName: FilterOptions) => { // If we click the filter again then we want to toggle it off, iff it is the same filter being clicked // and it is currently active @@ -33,7 +34,7 @@ const AttributeFilter = ({ filters, setFilters }: FilterDropdownProps) => { }, ]; - return ; + return ; }; export default AttributeFilter; diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index dbb3afdb..fc1b38d0 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -22,14 +22,16 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import TableOfContentsDropdown from './TableOfContentsDropdown'; -import ExpandAllButton from './ExpandAllButton'; +import type { Dictionary, Schema } from '@overture-stack/lectern-dictionary'; +import { useState } from 'react'; +import { Theme } from '../../theme'; +import { useThemeContext } from '../../theme/ThemeContext'; +import AttributeFilterDropdown, { FilterMapping } from './AttributeFilterDropdown'; import CollapseAllButton from './CollapseAllButton'; -import AttributeFilterDropdown from './AttributeFilterDropdown'; import DictionaryVersionSwitcher from './DictionaryVersionSwitcher'; import DownloadTemplatesButton from './DownloadTemplatesButton'; -import { useThemeContext } from '../../theme/ThemeContext'; -import type { Schema, Dictionary } from '@overture-stack/lectern-dictionary'; +import ExpandAllButton from './ExpandAllButton'; +import TableOfContentsDropdown from './TableOfContentsDropdown'; type InteractionPanelProps = { schemas: Schema[]; @@ -43,71 +45,59 @@ type InteractionPanelProps = { setIsCollapsed: (isCollapsed: boolean) => void; setFilteredData: (dict: Dictionary) => void; setIsFiltered: (bool: boolean) => void; - onAccordionToggle: (schemaName: string, isOpen: boolean) => void; + onSelect: (schemaNameIndex: number) => void; onVersionChange?: (version: number) => void; dictionaryVersions?: Dictionary[]; currentVersionIndex?: number; }; +const panelStyles = (theme: Theme) => css` + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + border-bottom: 1px solid ${theme.colors.grey_4}; + background-color: ${theme.colors.white}; + min-height: 70px; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: visible; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + position: relative; +`; + +const leftSectionStyles = css` + display: flex; + align-items: center; + gap: 16px; +`; + +const rightSectionStyles = css` + display: flex; + align-items: center; + gap: 16px; +`; const InteractionPanel = ({ schemas, - dictionary, - filteredData, disabled = false, - isFiltered, version, name, lecternUrl, setIsCollapsed, - setFilteredData, - setIsFiltered, - onAccordionToggle, + onSelect, onVersionChange, dictionaryVersions, currentVersionIndex = 0, }: InteractionPanelProps) => { const theme = useThemeContext(); - - const panelStyles = css` - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 16px; - border-bottom: 1px solid ${theme.colors.grey_4}; - background-color: ${theme.colors.white}; - min-height: 70px; - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: visible; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - position: relative; - `; - - const leftSectionStyles = css` - display: flex; - align-items: center; - gap: 16px; - `; - - const rightSectionStyles = css` - display: flex; - align-items: center; - gap: 16px; - `; - + const [filters, setFilters] = useState({ active: false, constraints: [] }); return ( -
      +
      - - - - + + + setIsCollapsed(true)} disabled={disabled} /> + setIsCollapsed(false)} disabled={disabled} />
      {onVersionChange && dictionaryVersions && ( From b82cd39672c2cf551e0060c90ee7879d58834dd4 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 13 Jun 2025 15:47:02 -0400 Subject: [PATCH 086/217] remove all the extra props that weren't needed --- .../DictionaryVersionSwitcher.tsx | 24 ++++----- .../InteractionPanel/InteractionPanel.tsx | 49 ++++++------------- 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx index 44238403..750d00c5 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx @@ -23,30 +23,32 @@ import { Dictionary } from '@overture-stack/lectern-dictionary'; import Dropdown from '../../common/Dropdown/Dropdown'; import { useThemeContext } from '../../theme/ThemeContext'; +import { FilterMapping } from './AttributeFilterDropdown'; type VersionSwitcherProps = { + config: DictionaryConfig; + disabled?: boolean; +}; +export type DictionaryConfig = { + lecternUrl: string; + dictionaryIndex: number; dictionaryData: Dictionary[]; onVersionChange: (index: number) => void; - dictionaryIndex: number; - disabled?: boolean; + filters: FilterMapping; + setFilters: (filters: FilterMapping) => void; }; -const VersionSwitcher = ({ - dictionaryIndex, - dictionaryData, - onVersionChange, - disabled = false, -}: VersionSwitcherProps) => { +const VersionSwitcher = ({ config, disabled = false }: VersionSwitcherProps) => { const theme = useThemeContext(); const { History } = theme.icons; - const versionSwitcherObjectArray = dictionaryData?.map((dictionary: Dictionary, index: number) => { + const versionSwitcherObjectArray = config.dictionaryData?.map((dictionary: Dictionary, index: number) => { // TODO: We should either remove the version date stamp requirement or update the date to be dynamic via // lectern-client return { label: 'Version ' + dictionary?.version, action: () => { - onVersionChange(index); + config.onVersionChange(index); }, }; }); @@ -59,7 +61,7 @@ const VersionSwitcher = ({ } menuItems={versionSwitcherObjectArray} - title={`Version ${dictionaryData?.[dictionaryIndex].version}`} + title={`Version ${config.dictionaryData?.[config.dictionaryIndex].version}`} disabled={disabled} /> ) diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index fc1b38d0..a1880423 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -28,27 +28,17 @@ import { Theme } from '../../theme'; import { useThemeContext } from '../../theme/ThemeContext'; import AttributeFilterDropdown, { FilterMapping } from './AttributeFilterDropdown'; import CollapseAllButton from './CollapseAllButton'; -import DictionaryVersionSwitcher from './DictionaryVersionSwitcher'; +import DictionaryVersionSwitcher, { DictionaryConfig } from './DictionaryVersionSwitcher'; import DownloadTemplatesButton from './DownloadTemplatesButton'; import ExpandAllButton from './ExpandAllButton'; import TableOfContentsDropdown from './TableOfContentsDropdown'; type InteractionPanelProps = { - schemas: Schema[]; - dictionary: Dictionary; - filteredData: Dictionary; disabled?: boolean; - isFiltered: boolean; - version: string; - name: string; - lecternUrl: string; setIsCollapsed: (isCollapsed: boolean) => void; - setFilteredData: (dict: Dictionary) => void; - setIsFiltered: (bool: boolean) => void; onSelect: (schemaNameIndex: number) => void; - onVersionChange?: (version: number) => void; - dictionaryVersions?: Dictionary[]; - currentVersionIndex?: number; + currDictionary: DictionaryConfig; + setFilters: (filters: FilterMapping) => void; }; const panelStyles = (theme: Theme) => css` @@ -78,37 +68,30 @@ const rightSectionStyles = css` gap: 16px; `; const InteractionPanel = ({ - schemas, disabled = false, - version, - name, - lecternUrl, setIsCollapsed, onSelect, - onVersionChange, - dictionaryVersions, - currentVersionIndex = 0, + currDictionary, + setFilters, }: InteractionPanelProps) => { - const theme = useThemeContext(); - const [filters, setFilters] = useState({ active: false, constraints: [] }); + const theme: Theme = useThemeContext(); + const currDict: Dictionary = currDictionary.dictionaryData[currDictionary.dictionaryIndex]; return (
      - - + + setIsCollapsed(true)} disabled={disabled} /> setIsCollapsed(false)} disabled={disabled} />
      - {onVersionChange && dictionaryVersions && ( - - )} - + +
      ); From 36419fbca1d9e15f6adbf89f83ad30bb5aebd427 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 16 Jun 2025 10:12:29 -0400 Subject: [PATCH 087/217] fix the panel with the new props --- .../AttributeFilterDropdown.stories.tsx | 2 +- .../DictionaryVersionSwitcher.stories.tsx | 4 +- .../InteractionPanel.stories.tsx | 133 +++++++++--------- 3 files changed, 73 insertions(+), 66 deletions(-) diff --git a/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx index 85355761..a4f6a90b 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx @@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import AttributeFilter from '../../../src/viewer-table/InteractionPanel/AttributeFilterDropdown'; import themeDecorator from '../../themeDecorator'; -import AdvancedDictionary from '../../../../../samples/dictionary/advanced.json'; +import AdvancedDictionary from '../../fixtures/advanced.json'; import { Dictionary } from '@overture-stack/lectern-dictionary'; const meta = { component: AttributeFilter, diff --git a/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx index 2376467e..08241423 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx @@ -2,7 +2,7 @@ import { Dictionary, Schema } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; -import DictionarySample from '../../../../../samples/dictionary/advanced.json'; +import DictionarySample from '../../fixtures/advanced.json'; import VersionSwitcher from '../../../src/viewer-table/InteractionPanel/DictionaryVersionSwitcher'; import themeDecorator from '../../themeDecorator'; @@ -27,7 +27,7 @@ const MultipleDictionaryData: Dictionary[] = [ export const MultipleVersions: Story = { args: { - dictionaryData: MultipleDictionaryData, + dictionaryData: { MultipleDictionaryData }, onVersionChange: (index: number) => console.log(`Version changed to index: ${index}`), dictionaryIndex: 0, }, diff --git a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx index 7fa09f04..a174c2b8 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx @@ -1,11 +1,12 @@ /** @jsxImportSource @emotion/react */ -import type { Meta, StoryObj } from '@storybook/react'; import { Dictionary } from '@overture-stack/lectern-dictionary'; +import type { Meta, StoryObj } from '@storybook/react'; +import { FilterMapping } from '../../../src/viewer-table/InteractionPanel/AttributeFilterDropdown'; +import { DictionaryConfig } from '../../../src/viewer-table/InteractionPanel/DictionaryVersionSwitcher'; import InteractionPanel from '../../../src/viewer-table/InteractionPanel/InteractionPanel'; +import AdvancedDictionary from '../../fixtures/advanced.json'; import themeDecorator from '../../themeDecorator'; -import AdvancedDictionary from '../../../../../samples/dictionary/advanced.json'; -import biosampleDictionary from '../../fixtures/minimalBiosampleModel'; const meta = { component: InteractionPanel, @@ -26,91 +27,97 @@ const MultipleDictionaryData: Dictionary[] = [ ]; const mockSetIsCollapsed = (isCollapsed: boolean) => { - console.log('setIsCollapsed called with:', isCollapsed); + alert('setIsCollapsed called with:' + isCollapsed); }; const mockOnVersionChange = (index: number) => { - console.log('onVersionChange called with index:', index); -}; - -const mockSetFilteredData = (dict: Dictionary) => { - console.log('setFilteredData called with dictionary:', dict.name); -}; - -const mockSetIsFiltered = (bool: boolean) => { - console.log('setIsFiltered called with:', bool); -}; - -const mockOnAccordionToggle = (schemaName: string, isOpen: boolean) => { - console.log('onAccordionToggle called for schema:', schemaName, 'isOpen:', isOpen); + alert('onVersionChange called with index:' + index); }; export const Default: Story = { args: { - schemas: (AdvancedDictionary as Dictionary).schemas, - dictionary: AdvancedDictionary as Dictionary, - filteredData: AdvancedDictionary as Dictionary, - isFiltered: false, - version: '1.0', - name: 'advanced-dictionary', - lecternUrl: 'http://localhost:3031', setIsCollapsed: mockSetIsCollapsed, - setFilteredData: mockSetFilteredData, - setIsFiltered: mockSetIsFiltered, - onAccordionToggle: mockOnAccordionToggle, + onSelect(schemaNameIndex) { + alert('onSelect called with schemaNameIndex:' + schemaNameIndex); + }, + currDictionary: { + lecternUrl: 'http://localhost:3031', + dictionaryIndex: 0, + dictionaryData: SingleDictionaryData, + onVersionChange: mockOnVersionChange, + filters: { active: true }, + setFilters: (filters: FilterMapping) => { + alert('setFilters called with:' + filters); + }, + } as DictionaryConfig, + setFilters: (filters: FilterMapping) => { + alert('setFilters called with:' + filters); + }, }, }; export const Disabled: Story = { args: { - schemas: (AdvancedDictionary as Dictionary).schemas, - dictionary: AdvancedDictionary as Dictionary, - filteredData: AdvancedDictionary as Dictionary, - isFiltered: false, - version: '1.0', - name: 'advanced-dictionary', - lecternUrl: 'http://localhost:3031', disabled: true, setIsCollapsed: mockSetIsCollapsed, - setFilteredData: mockSetFilteredData, - setIsFiltered: mockSetIsFiltered, - onAccordionToggle: mockOnAccordionToggle, + onSelect(schemaNameIndex) { + alert('onSelect called with schemaNameIndex:' + schemaNameIndex); + }, + currDictionary: { + lecternUrl: 'http://localhost:3031', + dictionaryIndex: 0, + dictionaryData: SingleDictionaryData, + onVersionChange: mockOnVersionChange, + filters: { active: true }, + setFilters: (filters: FilterMapping) => { + alert('setFilters called with:' + filters); + }, + } as DictionaryConfig, + setFilters: (filters: FilterMapping) => { + alert('setFilters called with:' + filters); + }, }, }; export const WithMultipleVersions: Story = { args: { - schemas: (AdvancedDictionary as Dictionary).schemas, - dictionary: AdvancedDictionary as Dictionary, - filteredData: AdvancedDictionary as Dictionary, - isFiltered: false, - version: '1.0', - name: 'advanced-dictionary', - lecternUrl: 'http://localhost:3031', setIsCollapsed: mockSetIsCollapsed, - setFilteredData: mockSetFilteredData, - setIsFiltered: mockSetIsFiltered, - onAccordionToggle: mockOnAccordionToggle, - onVersionChange: mockOnVersionChange, - dictionaryVersions: MultipleDictionaryData, - currentVersionIndex: 0, + onSelect(schemaNameIndex) { + alert('onSelect called with schemaNameIndex:' + schemaNameIndex); + }, + currDictionary: { + lecternUrl: 'http://localhost:3031', + dictionaryIndex: 0, + dictionaryData: MultipleDictionaryData, + onVersionChange: mockOnVersionChange, + filters: { active: true }, + setFilters: (filters: FilterMapping) => { + alert('setFilters called with:' + filters); + }, + } as DictionaryConfig, + setFilters: (filters: FilterMapping) => { + alert('setFilters called with:' + filters); + }, }, }; export const WithSingleVersion: Story = { args: { - schemas: (AdvancedDictionary as Dictionary).schemas, - dictionary: AdvancedDictionary as Dictionary, - filteredData: AdvancedDictionary as Dictionary, - isFiltered: false, - version: '1.0', - name: 'advanced-dictionary', - lecternUrl: 'http://localhost:3031', setIsCollapsed: mockSetIsCollapsed, - setFilteredData: mockSetFilteredData, - setIsFiltered: mockSetIsFiltered, - onAccordionToggle: mockOnAccordionToggle, - onVersionChange: mockOnVersionChange, - dictionaryVersions: SingleDictionaryData, - currentVersionIndex: 0, + onSelect(schemaNameIndex) { + alert('onSelect called with schemaNameIndex:' + schemaNameIndex); + }, + currDictionary: { + lecternUrl: 'http://localhost:3031', + dictionaryIndex: 0, + dictionaryData: SingleDictionaryData, + onVersionChange: mockOnVersionChange, + filters: { active: true }, + setFilters: (filters: FilterMapping) => { + alert('setFilters called with:' + filters); + }, + } as DictionaryConfig, + setFilters: (filters: FilterMapping) => { + alert('setFilters called with:' + filters); + }, }, }; From e7a0b17d2b663665876eb532b644645e47adf683 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 16 Jun 2025 10:22:35 -0400 Subject: [PATCH 088/217] fix errors due to the new dictionary config --- .../AttributeFilterDropdown.stories.tsx | 6 ++-- .../CollapseAllButton.stories.tsx | 2 +- .../DictionaryVersionSwitcher.stories.tsx | 33 ++++++++++++++----- .../ExpandAllButton.stories.tsx | 2 +- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx index a4f6a90b..766f4b50 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx @@ -16,9 +16,7 @@ type Story = StoryObj; export const Default: Story = { args: { - data: AdvancedDictionary as Dictionary, - isFiltered: false, - setFilteredData: () => {}, - setIsFiltered: () => {}, + filters: { active: true }, + setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), }, }; diff --git a/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx index 5e744b48..98209504 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx @@ -15,5 +15,5 @@ export default meta; type Story = StoryObj; export const Default: Story = { - args: { setIsCollapsed: (isCollapsed: boolean) => alert('all collapsable components are collapsed') }, + args: { onClick: () => alert('All collapsible components are collapsed') }, }; diff --git a/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx index 08241423..072e7d08 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx @@ -27,23 +27,38 @@ const MultipleDictionaryData: Dictionary[] = [ export const MultipleVersions: Story = { args: { - dictionaryData: { MultipleDictionaryData }, - onVersionChange: (index: number) => console.log(`Version changed to index: ${index}`), - dictionaryIndex: 0, + config: { + lecternUrl: 'http://localhost:3031', + dictionaryIndex: 0, + dictionaryData: MultipleDictionaryData, + onVersionChange: (index: number) => alert(`Version changed to index: ${index}`), + filters: { active: true }, + setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), + }, }, }; export const SingleVersion: Story = { args: { - dictionaryData: SingleDictionaryData, - onVersionChange: (index: number) => console.log(`Version changed to index: ${index}`), - dictionaryIndex: 0, + config: { + lecternUrl: 'http://localhost:3031', + dictionaryIndex: 0, + dictionaryData: SingleDictionaryData, + onVersionChange: (index: number) => alert(`Version changed to index: ${index}`), + filters: { active: true }, + setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), + }, }, }; export const EmptyArray: Story = { args: { - dictionaryData: [], - onVersionChange: (index: number) => console.log(`Version changed to index: ${index}`), - dictionaryIndex: 0, + config: { + lecternUrl: 'http://localhost:3031', + dictionaryIndex: 0, + dictionaryData: [], + onVersionChange: (index: number) => alert(`Version changed to index: ${index}`), + filters: { active: true }, + setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), + }, }, }; diff --git a/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx index fff42a43..37fe169d 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx @@ -14,5 +14,5 @@ export default meta; type Story = StoryObj; export const Default: Story = { - args: { setIsCollapsed: (isCollapsed: boolean) => alert('all collapsable components are expanded') }, + args: { onClick: () => alert('All collapsible components are expanded') }, }; From 9bd6322751a5f070f3cdf8923a4f79750c841905 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 16 Jun 2025 10:27:58 -0400 Subject: [PATCH 089/217] add disasbled state --- .../AttributeFilterDropdown.stories.tsx | 7 +++++++ .../interaction-panel/CollapseAllButton.stories.tsx | 6 ++++++ .../DictionaryDownloadButton.stories.tsx | 10 ++++++++++ .../DictionaryVersionSwitcher.stories.tsx | 13 +++++++++++++ .../interaction-panel/ExpandAllButton.stories.tsx | 6 ++++++ 5 files changed, 42 insertions(+) diff --git a/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx index 766f4b50..f9492b61 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx @@ -20,3 +20,10 @@ export const Default: Story = { setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), }, }; +export const Disabled: Story = { + args: { + filters: { active: false }, + setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), + disabled: true, + }, +}; diff --git a/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx index 98209504..882b469a 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx @@ -17,3 +17,9 @@ type Story = StoryObj; export const Default: Story = { args: { onClick: () => alert('All collapsible components are collapsed') }, }; +export const Disabled: Story = { + args: { + onClick: () => alert('All collapsible components are collapsed'), + disabled: true, + }, +}; diff --git a/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx index 3aab598a..2015589b 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx @@ -21,3 +21,13 @@ export const Default: Story = { fileType: 'tsv', }, }; + +export const Disabled: Story = { + args: { + version: '1.0', + name: 'example-dictionary', + lecternUrl: 'http://localhost:3031', + fileType: 'tsv', + disabled: true, + }, +}; diff --git a/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx index 072e7d08..cf8c283f 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx @@ -62,3 +62,16 @@ export const EmptyArray: Story = { }, }, }; +export const DisabledWithMultipleVersions: Story = { + args: { + config: { + lecternUrl: 'http://localhost:3031', + dictionaryIndex: 0, + dictionaryData: MultipleDictionaryData, + onVersionChange: (index: number) => alert(`Version changed to index: ${index}`), + filters: { active: true }, + setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), + }, + disabled: true, + }, +}; diff --git a/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx index 37fe169d..6c09c110 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx @@ -16,3 +16,9 @@ type Story = StoryObj; export const Default: Story = { args: { onClick: () => alert('All collapsible components are expanded') }, }; +export const Disabled: Story = { + args: { + onClick: () => alert('All collapsible components are expanded'), + disabled: true, + }, +}; From d337d8eb2025987e0a4fd42d8e6462e040244a19 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 16 Jun 2025 10:30:21 -0400 Subject: [PATCH 090/217] fix filter label --- .../viewer-table/InteractionPanel/AttributeFilterDropdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx index 0919ffac..d640d9ca 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx @@ -34,7 +34,7 @@ const AttributeFilter = ({ filters, setFilters, disabled }: FilterDropdownProps) }, ]; - return ; + return ; }; export default AttributeFilter; From 344da2bffcfd234d0d4375da48b572db8feb2a20 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 16 Jun 2025 10:47:42 -0400 Subject: [PATCH 091/217] file download pattern following --- packages/ui/src/theme/icons/FileDownload.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/theme/icons/FileDownload.tsx b/packages/ui/src/theme/icons/FileDownload.tsx index 710f7de0..7c66435e 100644 --- a/packages/ui/src/theme/icons/FileDownload.tsx +++ b/packages/ui/src/theme/icons/FileDownload.tsx @@ -29,12 +29,10 @@ const FileDownload = ({ style, height, width }: IconProps) => { Date: Mon, 16 Jun 2025 11:01:17 -0400 Subject: [PATCH 092/217] make the stories more case handling --- .../InteractionPanel.stories.tsx | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx index a174c2b8..61b824ed 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx @@ -34,6 +34,7 @@ const mockOnVersionChange = (index: number) => { alert('onVersionChange called with index:' + index); }; +// When we are at multiple versions, then the version switcher is now rendered, this is to test that behavior export const Default: Story = { args: { setIsCollapsed: mockSetIsCollapsed, @@ -43,29 +44,7 @@ export const Default: Story = { currDictionary: { lecternUrl: 'http://localhost:3031', dictionaryIndex: 0, - dictionaryData: SingleDictionaryData, - onVersionChange: mockOnVersionChange, - filters: { active: true }, - setFilters: (filters: FilterMapping) => { - alert('setFilters called with:' + filters); - }, - } as DictionaryConfig, - setFilters: (filters: FilterMapping) => { - alert('setFilters called with:' + filters); - }, - }, -}; -export const Disabled: Story = { - args: { - disabled: true, - setIsCollapsed: mockSetIsCollapsed, - onSelect(schemaNameIndex) { - alert('onSelect called with schemaNameIndex:' + schemaNameIndex); - }, - currDictionary: { - lecternUrl: 'http://localhost:3031', - dictionaryIndex: 0, - dictionaryData: SingleDictionaryData, + dictionaryData: MultipleDictionaryData, onVersionChange: mockOnVersionChange, filters: { active: true }, setFilters: (filters: FilterMapping) => { @@ -78,7 +57,8 @@ export const Disabled: Story = { }, }; -export const WithMultipleVersions: Story = { +// The reason why this story exists is to test the behavior when the dictionary version switcher button is not rendered +export const WithSingleVersion: Story = { args: { setIsCollapsed: mockSetIsCollapsed, onSelect(schemaNameIndex) { @@ -87,7 +67,7 @@ export const WithMultipleVersions: Story = { currDictionary: { lecternUrl: 'http://localhost:3031', dictionaryIndex: 0, - dictionaryData: MultipleDictionaryData, + dictionaryData: SingleDictionaryData, onVersionChange: mockOnVersionChange, filters: { active: true }, setFilters: (filters: FilterMapping) => { @@ -100,8 +80,9 @@ export const WithMultipleVersions: Story = { }, }; -export const WithSingleVersion: Story = { +export const Disabled: Story = { args: { + disabled: true, setIsCollapsed: mockSetIsCollapsed, onSelect(schemaNameIndex) { alert('onSelect called with schemaNameIndex:' + schemaNameIndex); From 1de4f70bc7b524b25cabe2f8b6494a9cc843a1d3 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 16 Jun 2025 11:04:00 -0400 Subject: [PATCH 093/217] make the icon filedownload follow the common pattern --- packages/ui/src/theme/icons/FileDownload.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/theme/icons/FileDownload.tsx b/packages/ui/src/theme/icons/FileDownload.tsx index 7c66435e..6b30cd6c 100644 --- a/packages/ui/src/theme/icons/FileDownload.tsx +++ b/packages/ui/src/theme/icons/FileDownload.tsx @@ -21,20 +21,16 @@ /** @jsxImportSource @emotion/react */ -import { css } from '@emotion/react'; import IconProps from './IconProps'; -const FileDownload = ({ style, height, width }: IconProps) => { +const FileDownload = ({ fill, height, width }: IconProps) => { return ( Date: Mon, 16 Jun 2025 11:08:10 -0400 Subject: [PATCH 094/217] fix the icons --- packages/ui/src/theme/icons/FileDownload.tsx | 6 +++++- packages/ui/src/theme/icons/History.tsx | 12 +++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/theme/icons/FileDownload.tsx b/packages/ui/src/theme/icons/FileDownload.tsx index 6b30cd6c..779d974b 100644 --- a/packages/ui/src/theme/icons/FileDownload.tsx +++ b/packages/ui/src/theme/icons/FileDownload.tsx @@ -22,10 +22,14 @@ /** @jsxImportSource @emotion/react */ import IconProps from './IconProps'; +import { css } from '@emotion/react'; -const FileDownload = ({ fill, height, width }: IconProps) => { +const FileDownload = ({ fill, height, width, style }: IconProps) => { return ( { +const History = ({ width, height, fill, style }: IconProps) => { return ( Date: Mon, 16 Jun 2025 12:16:28 -0400 Subject: [PATCH 095/217] download templates pr review comments --- .../viewer-table/InteractionPanel/DownloadTemplatesButton.tsx | 2 +- .../ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx index 8f744c70..84e32fdb 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx @@ -29,7 +29,7 @@ type DictionaryDownloadButtonProps = { version: string; name: string; lecternUrl: string; - fileType?: 'tsv' | 'csv'; + fileType: 'tsv' | 'csv'; disabled?: boolean; }; diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index a1880423..a94be84e 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -87,6 +87,7 @@ const InteractionPanel = ({
      Date: Mon, 16 Jun 2025 15:57:10 -0400 Subject: [PATCH 096/217] data dictionary page was a mess and not needed currently --- .../src/viewer-table/DataDictionaryPage.tsx | 153 ------------------ .../DataDictionaryPage.stories.tsx | 77 --------- 2 files changed, 230 deletions(-) delete mode 100644 packages/ui/src/viewer-table/DataDictionaryPage.tsx delete mode 100644 packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx diff --git a/packages/ui/src/viewer-table/DataDictionaryPage.tsx b/packages/ui/src/viewer-table/DataDictionaryPage.tsx deleted file mode 100644 index d59e236f..00000000 --- a/packages/ui/src/viewer-table/DataDictionaryPage.tsx +++ /dev/null @@ -1,153 +0,0 @@ -/* - * - * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved - * - * This program and the accompanying materials are made available under the terms of - * the GNU Affero General Public License v3.0. You should have received a copy of the - * GNU Affero General Public License along with this program. - * If not, see . - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -/** @jsxImportSource @emotion/react */ - -import { css } from '@emotion/react'; -import type { Dictionary } from '@overture-stack/lectern-dictionary'; -import { useState } from 'react'; -import Accordion from '../common/Accordion/Accordion'; -import { useThemeContext } from '../theme/ThemeContext'; -import DictionaryHeader from './DictionaryHeader'; -import InteractionPanel from './InteractionPanel/InteractionPanel'; - -export type DataDictionaryPageProps = { - dictionary: Dictionary; - lecternUrl?: string; - disabled?: boolean; - dictionaryVersions?: Dictionary[]; - currentVersionIndex?: number; - onVersionChange?: (version: number) => void; -}; - -const pageContainerStyle = css` - width: 100%; - min-height: 100vh; - background-color: #f8fafc; -`; - -const contentContainerStyle = css` - max-width: 1200px; - margin: 0 auto; - padding: 0 24px 48px 24px; -`; - -const accordionContainerStyle = css` - margin-top: 24px; -`; - -const DataDictionaryPage = ({ - dictionary, - lecternUrl = 'http://localhost:3031', - disabled = false, - dictionaryVersions, - currentVersionIndex = 0, - onVersionChange, -}: DataDictionaryPageProps) => { - const theme = useThemeContext(); - - const [filteredData, setFilteredData] = useState(dictionary); - const [isFiltered, setIsFiltered] = useState(false); - const [isCollapsed, setIsCollapsed] = useState(false); - const [accordionStates, setAccordionStates] = useState>(() => { - // Initialize all schemas as open by default - const initialStates: Record = {}; - dictionary.schemas.forEach((schema) => { - initialStates[schema.name] = true; - }); - return initialStates; - }); - - const handleAccordionToggle = (schemaIndex: number) => { - const schema = filteredData.schemas[schemaIndex]; - if (schema) { - // Open the accordion for this schema - setAccordionStates((prev) => ({ - ...prev, - [schema.name]: true, - })); - } - }; - - // Handle expand/collapse all - const handleSetIsCollapsed = (collapsed: boolean) => { - setIsCollapsed(collapsed); - const newStates: Record = {}; - dictionary.schemas.forEach((schema) => { - newStates[schema.name] = !collapsed; - }); - setAccordionStates(newStates); - }; - - const accordionItems = filteredData.schemas.map((schema) => ({ - title: schema.name, - description: schema.description || '', - openOnInit: accordionStates[schema.name] ?? true, - content: - 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.', - })); - - return ( -
      - - - {/* Interaction Panel */} - - - {accordionItems.length > 0 && ( -
      - -
      - )} - - {accordionItems.length === 0 && ( -
      - No schemas found in this dictionary. -
      - )} -
      - ); -}; - -export default DataDictionaryPage; diff --git a/packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx b/packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx deleted file mode 100644 index f2de15cc..00000000 --- a/packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/** @jsxImportSource @emotion/react */ - -import type { Meta, StoryObj } from '@storybook/react'; -import { Dictionary } from '@overture-stack/lectern-dictionary'; -import DataDictionaryPage from '../../src/viewer-table/DataDictionaryPage'; -import themeDecorator from '../themeDecorator'; - -const meta = { - component: DataDictionaryPage, - title: 'Viewer - Table/Data Dictionary Page', - tags: ['autodocs'], - decorators: [themeDecorator()], - parameters: { - layout: 'fullscreen', - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -// Simple mock dictionary with lorem ipsum content -const mockDictionary: Dictionary = { - name: 'sample-dictionary', - version: '1.0', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', - schemas: [ - { - name: 'sample-schema-1', - description: - 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do.', - fields: [ - { - name: 'field1', - valueType: 'string', - description: 'Lorem ipsum field description', - restrictions: {}, - }, - { - name: 'field2', - valueType: 'integer', - description: 'Another field with description', - restrictions: {}, - }, - ], - }, - { - name: 'sample-schema-2', - description: - 'Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi.', - fields: [ - { - name: 'field3', - valueType: 'string', - description: 'Et sint enim eu proident ipsum', - restrictions: {}, - }, - ], - }, - ], - references: {}, -}; - -export const Default: Story = { - args: { - dictionary: mockDictionary, - lecternUrl: 'http://localhost:3031', - }, -}; - -export const Disabled: Story = { - args: { - dictionary: mockDictionary, - lecternUrl: 'http://localhost:3031', - disabled: true, - }, -}; From 4c474e3360ca1e49c53e404470f23e4db8577803 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 17 Jun 2025 09:36:54 -0400 Subject: [PATCH 097/217] the individual schema does not need the description or name --- .../ui/src/common/Accordion/Accordion.tsx | 1 + .../viewer-table/DataTable/SchemaTable.tsx | 20 ---------------- .../DataTable/SchemaTableWithAccordion.tsx | 18 ++++++++++++++ .../SchemaTableWithAccordion.stories.tsx | 24 +++++++++++++++++++ 4 files changed, 43 insertions(+), 20 deletions(-) create mode 100644 packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx create mode 100644 packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index 6925656b..e91cadba 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -44,6 +44,7 @@ const Accordion = ({ accordionItems }: AccordionProps) => { lecternUrl="http://localhost:3031" disabled={false} iconOnly={true} + fileType="tsv" /> ), })); diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx index 2f573f1c..a3ec87e6 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx @@ -52,26 +52,6 @@ const SchemaTable = ({ schema }: SchemaTableProps) => { return (
      -
      - {schema.name} -
      -
      - {schema.description} -
      {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx new file mode 100644 index 00000000..84f6fb72 --- /dev/null +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx @@ -0,0 +1,18 @@ +import Accordion from '../../common/Accordion/Accordion'; +import { AccordionData } from '../../common/Accordion/AccordionItem'; +import biosampleDictionary from '../../../stories/fixtures/minimalBiosampleModel'; +const schema = biosampleDictionary.schemas[0]; +import SchemaTable from './SchemaTable'; +const SchemaTableWithAccordion = () => { + const accordionItems: Array = [ + { + title: 'Hello World', + description: 'I am drinking coffee', + openOnInit: false, + content: , + }, + ]; + return ; +}; + +export default SchemaTableWithAccordion; diff --git a/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx b/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx new file mode 100644 index 00000000..5e7a4b09 --- /dev/null +++ b/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx @@ -0,0 +1,24 @@ +/** @jsxImportSource @emotion/react */ + +// import { DictionaryHeader } from '#/viewer-table/DictionaryHeader'; +import type { Meta, StoryObj } from '@storybook/react'; +import SchemaTableWithAccordion from '../../src/viewer-table/DataTable/SchemaTableWithAccordion'; +import biosampleDictionary from '../fixtures/minimalBiosampleModel'; +import themeDecorator from '../themeDecorator'; + +const schema = biosampleDictionary.schemas[0]; + +const meta = { + component: SchemaTableWithAccordion, + title: 'Viewer - Table/Schema Table With Accordion', + tags: ['autodocs'], + decorators: [themeDecorator()], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + // args: { name: sampleDictionary.name, version: sampleDictionary.name, description: sampleDictionary.description }, + args: { schema }, +}; From e58ee038007bc60f56c9280f0e64a2a19c952c65 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 17 Jun 2025 09:49:16 -0400 Subject: [PATCH 098/217] Remove max width from the table and make sure that the table is refactored nicely with the read more and readless functionality --- .../viewer-table/DataTable/SchemaTable.tsx | 2 - .../src/viewer-table/DataTable/TableRow.tsx | 71 +++++-------------- 2 files changed, 16 insertions(+), 57 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx index a3ec87e6..15fd6d1f 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx @@ -24,7 +24,6 @@ import { css } from '@emotion/react'; import type { Schema, SchemaField } from '@overture-stack/lectern-dictionary'; import { getCoreRowModel, HeaderGroup, useReactTable } from '@tanstack/react-table'; -import { Lato } from '../styles/typography'; import TableHeader from './TableHeader'; import TableRow from './TableRow'; import { schemaBaseColumns } from './tableInit'; @@ -35,7 +34,6 @@ type SchemaTableProps = { const sectionStyle = css` margin-bottom: 48px; - max-width: 1200px; `; const tableStyle = css` width: 100%; diff --git a/packages/ui/src/viewer-table/DataTable/TableRow.tsx b/packages/ui/src/viewer-table/DataTable/TableRow.tsx index 26392564..3681ee84 100644 --- a/packages/ui/src/viewer-table/DataTable/TableRow.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableRow.tsx @@ -21,12 +21,11 @@ /** @jsxImportSource @emotion/react */ -import { flexRender, Row } from '@tanstack/react-table'; -import { useState } from 'react'; import { css } from '@emotion/react'; -import { useThemeContext } from '../../theme/ThemeContext'; +import { Row } from '@tanstack/react-table'; +import ReadMoreText from '../../common/ReadMoreText'; +import { Theme } from '../../theme'; -//Styles const rowStyle = (index: number) => css` background-color: ${index % 2 === 0 ? '' : '#F5F7F8'}; `; @@ -40,44 +39,23 @@ const tdStyle = css` word-break: break-word; vertical-align: top; `; -const linkStyle = css` - color: #0b75a2; - font-size: 12px; - cursor: pointer; - margin-left: 6px; - &:hover { - text-decoration: underline; - } -`; -const chevronDownStyle = css` - margin-left: 4px; -`; - -const chevronUpStyle = css` - margin-left: 4px; - transform: rotate(180deg); +const descriptionWrapperStyle = (theme: Theme) => css` + ${theme.typography?.label2}; + color: ${theme.colors.grey_5}; + padding: 4px 8px; + word-wrap: break-word; + overflow-wrap: break-word; `; -// Component Implementation - -type TableRowProps = { - row: Row; +type TableRowProps = { + row: Row; index: number; }; -const TableRow = ({ row, index }: TableRowProps) => { - const [expandedCells, setExpandedCells] = useState>({}); - const theme = useThemeContext(); - const { ChevronDown } = theme.icons; - - const toggleExpand = (cellId: string) => { - setExpandedCells((prev) => ({ - ...prev, - [cellId]: !prev[cellId], - })); - }; +const MAX_LINES_BEFORE_EXPAND = 2; +const TableRow = ({ row, index }: TableRowProps) => { return ( {row.getVisibleCells().map((cell) => { @@ -88,29 +66,12 @@ const TableRow = ({ row, index }: TableRowProps) => { } const valueStr = cellValue.toString(); // concatenate a large string to test dropdown feature - const isLong = valueStr.length > 68; // Length for truncation, to best match the Figma design - const isExpanded = expandedCells[cell.id]; - - if (isLong) { - return ( - - ); - } return ( ); })} From 0115324380a2fdae8b1a97a0294339f357beac66 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 17 Jun 2025 10:18:24 -0400 Subject: [PATCH 099/217] fix up the table columns, schemaField Html is useless now --- .../src/viewer-table/DataTable/SchemaTable.tsx | 2 ++ .../DataTable/SchemaTableWithAccordion.tsx | 3 +-- .../ui/src/viewer-table/DataTable/tableInit.tsx | 17 +---------------- packages/ui/src/viewer-table/SchemaTable.tsx | 0 .../viewer-table/SchemaTable.stories.tsx | 6 ++---- .../SchemaTableWithAccordion.stories.tsx | 5 ++--- 6 files changed, 8 insertions(+), 25 deletions(-) delete mode 100644 packages/ui/src/viewer-table/SchemaTable.tsx diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx index 15fd6d1f..a3ec87e6 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx @@ -24,6 +24,7 @@ import { css } from '@emotion/react'; import type { Schema, SchemaField } from '@overture-stack/lectern-dictionary'; import { getCoreRowModel, HeaderGroup, useReactTable } from '@tanstack/react-table'; +import { Lato } from '../styles/typography'; import TableHeader from './TableHeader'; import TableRow from './TableRow'; import { schemaBaseColumns } from './tableInit'; @@ -34,6 +35,7 @@ type SchemaTableProps = { const sectionStyle = css` margin-bottom: 48px; + max-width: 1200px; `; const tableStyle = css` width: 100%; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx index 84f6fb72..e08f0d4b 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx @@ -1,9 +1,8 @@ import Accordion from '../../common/Accordion/Accordion'; import { AccordionData } from '../../common/Accordion/AccordionItem'; import biosampleDictionary from '../../../stories/fixtures/minimalBiosampleModel'; -const schema = biosampleDictionary.schemas[0]; import SchemaTable from './SchemaTable'; -const SchemaTableWithAccordion = () => { +const SchemaTableWithAccordion = ({ schema }: any) => { const accordionItems: Array = [ { title: 'Hello World', diff --git a/packages/ui/src/viewer-table/DataTable/tableInit.tsx b/packages/ui/src/viewer-table/DataTable/tableInit.tsx index 97714312..15ce8935 100644 --- a/packages/ui/src/viewer-table/DataTable/tableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/tableInit.tsx @@ -31,22 +31,7 @@ const columnHelper = createColumnHelper(); export const schemaBaseColumns = [ columnHelper.accessor('name', { - header: 'SchemaField', - cell: (field) => { - // TODO: Open issue in lectern to make displayName a known property of field - return ( -
      -
      {field.row.original.name}
      -
      {field.row.original.description}
      -
      - ); - }, + header: 'SchemaField ', }), columnHelper.accessor( (row) => { diff --git a/packages/ui/src/viewer-table/SchemaTable.tsx b/packages/ui/src/viewer-table/SchemaTable.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/ui/stories/viewer-table/SchemaTable.stories.tsx b/packages/ui/stories/viewer-table/SchemaTable.stories.tsx index 9f525ac4..90892aaa 100644 --- a/packages/ui/stories/viewer-table/SchemaTable.stories.tsx +++ b/packages/ui/stories/viewer-table/SchemaTable.stories.tsx @@ -3,11 +3,9 @@ // import { DictionaryHeader } from '#/viewer-table/DictionaryHeader'; import type { Meta, StoryObj } from '@storybook/react'; import SchemaTable from '../../src/viewer-table/DataTable/SchemaTable'; -import biosampleDictionary from '../fixtures/minimalBiosampleModel'; +import webUsers from '../fixtures/websiteUsersDataDictionary'; import themeDecorator from '../themeDecorator'; - -const schema = biosampleDictionary.schemas[0]; - +const schema = webUsers.schemas[0]; const meta = { component: SchemaTable, title: 'Viewer - Table/Schema Table', diff --git a/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx b/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx index 5e7a4b09..30c65b7e 100644 --- a/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx +++ b/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx @@ -3,10 +3,9 @@ // import { DictionaryHeader } from '#/viewer-table/DictionaryHeader'; import type { Meta, StoryObj } from '@storybook/react'; import SchemaTableWithAccordion from '../../src/viewer-table/DataTable/SchemaTableWithAccordion'; -import biosampleDictionary from '../fixtures/minimalBiosampleModel'; +import webUsers from '../fixtures/websiteUsersDataDictionary'; import themeDecorator from '../themeDecorator'; - -const schema = biosampleDictionary.schemas[0]; +const schema = webUsers.schemas[0]; const meta = { component: SchemaTableWithAccordion, From 42a6d1e560f0e45db47cff6367b3d43783914aaf Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 17 Jun 2025 10:41:23 -0400 Subject: [PATCH 100/217] add back the field's description name after accidental removal --- .../DataTable/SchemaTableWithAccordion.tsx | 11 +++++++---- .../ui/src/viewer-table/DataTable/tableInit.tsx | 15 +++++++++++++++ .../SchemaTableWithAccordion.stories.tsx | 3 ++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx index e08f0d4b..d23e10ab 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx @@ -1,12 +1,15 @@ +import { Schema } from '@overture-stack/lectern-dictionary'; import Accordion from '../../common/Accordion/Accordion'; import { AccordionData } from '../../common/Accordion/AccordionItem'; -import biosampleDictionary from '../../../stories/fixtures/minimalBiosampleModel'; import SchemaTable from './SchemaTable'; -const SchemaTableWithAccordion = ({ schema }: any) => { +type props = { + schema: Schema; +}; +const SchemaTableWithAccordion = ({ schema }: props) => { const accordionItems: Array = [ { - title: 'Hello World', - description: 'I am drinking coffee', + title: schema.name, + description: schema.description ?? '', openOnInit: false, content: , }, diff --git a/packages/ui/src/viewer-table/DataTable/tableInit.tsx b/packages/ui/src/viewer-table/DataTable/tableInit.tsx index 15ce8935..0b20f623 100644 --- a/packages/ui/src/viewer-table/DataTable/tableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/tableInit.tsx @@ -32,6 +32,21 @@ const columnHelper = createColumnHelper(); export const schemaBaseColumns = [ columnHelper.accessor('name', { header: 'SchemaField ', + cell: (field) => { + // TODO: Open issue in lectern to make displayName a known property of field + return ( +
      +
      {field.row.original.name}
      +
      {field.row.original.description}
      +
      + ); + }, }), columnHelper.accessor( (row) => { diff --git a/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx b/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx index 30c65b7e..43f94bfb 100644 --- a/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx +++ b/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx @@ -5,7 +5,8 @@ import type { Meta, StoryObj } from '@storybook/react'; import SchemaTableWithAccordion from '../../src/viewer-table/DataTable/SchemaTableWithAccordion'; import webUsers from '../fixtures/websiteUsersDataDictionary'; import themeDecorator from '../themeDecorator'; -const schema = webUsers.schemas[0]; +import { Schema } from '@overture-stack/lectern-dictionary'; +const schema: Schema = webUsers.schemas[0]; const meta = { component: SchemaTableWithAccordion, From 8bc0b7988f9313dec341036e2c9c63e2b029a8aa Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 17 Jun 2025 10:41:45 -0400 Subject: [PATCH 101/217] fix css --- packages/ui/src/viewer-table/DataTable/tableInit.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/tableInit.tsx b/packages/ui/src/viewer-table/DataTable/tableInit.tsx index 0b20f623..97714312 100644 --- a/packages/ui/src/viewer-table/DataTable/tableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/tableInit.tsx @@ -31,15 +31,15 @@ const columnHelper = createColumnHelper(); export const schemaBaseColumns = [ columnHelper.accessor('name', { - header: 'SchemaField ', + header: 'SchemaField', cell: (field) => { // TODO: Open issue in lectern to make displayName a known property of field return (
      {field.row.original.name}
      From 8b13278416192f3f51a41b5f32bfca29eab18ed4 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 17 Jun 2025 11:12:46 -0400 Subject: [PATCH 102/217] fix the rendering of the descriptions --- .../viewer-table/DataTable/SchemaTable.tsx | 1 - .../src/viewer-table/DataTable/TableRow.tsx | 25 ++----------------- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx index a3ec87e6..efe96ee0 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx @@ -24,7 +24,6 @@ import { css } from '@emotion/react'; import type { Schema, SchemaField } from '@overture-stack/lectern-dictionary'; import { getCoreRowModel, HeaderGroup, useReactTable } from '@tanstack/react-table'; -import { Lato } from '../styles/typography'; import TableHeader from './TableHeader'; import TableRow from './TableRow'; import { schemaBaseColumns } from './tableInit'; diff --git a/packages/ui/src/viewer-table/DataTable/TableRow.tsx b/packages/ui/src/viewer-table/DataTable/TableRow.tsx index 3681ee84..35514d67 100644 --- a/packages/ui/src/viewer-table/DataTable/TableRow.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableRow.tsx @@ -22,9 +22,8 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import { Row } from '@tanstack/react-table'; +import { Row, flexRender } from '@tanstack/react-table'; import ReadMoreText from '../../common/ReadMoreText'; -import { Theme } from '../../theme'; const rowStyle = (index: number) => css` background-color: ${index % 2 === 0 ? '' : '#F5F7F8'}; @@ -40,38 +39,18 @@ const tdStyle = css` vertical-align: top; `; -const descriptionWrapperStyle = (theme: Theme) => css` - ${theme.typography?.label2}; - color: ${theme.colors.grey_5}; - padding: 4px 8px; - word-wrap: break-word; - overflow-wrap: break-word; -`; - type TableRowProps = { row: Row; index: number; }; -const MAX_LINES_BEFORE_EXPAND = 2; - const TableRow = ({ row, index }: TableRowProps) => { return (
      {row.getVisibleCells().map((cell) => { - const cellValue = cell.getValue(); - - if (cellValue === undefined || cellValue === null || cellValue === '') { - return ; - } - - const valueStr = cellValue.toString(); // concatenate a large string to test dropdown feature - return ( ); })} From 480a31338edb2fc522cd70980c38a1196893f091 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 17 Jun 2025 11:57:39 -0400 Subject: [PATCH 103/217] render examples fields if available inside of the meta --- .../src/viewer-table/DataTable/tableInit.tsx | 52 ++++++++++++++----- .../stories/fixtures/minimalBiosampleModel.ts | 3 ++ .../SchemaTableWithAccordion.stories.tsx | 4 +- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/tableInit.tsx b/packages/ui/src/viewer-table/DataTable/tableInit.tsx index 97714312..aa6483db 100644 --- a/packages/ui/src/viewer-table/DataTable/tableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/tableInit.tsx @@ -23,29 +23,53 @@ import { css } from '@emotion/react'; import { SchemaField } from '@overture-stack/lectern-dictionary'; -import { createColumnHelper } from '@tanstack/react-table'; -import { Lato } from '../styles/typography'; +import { CellContext, createColumnHelper } from '@tanstack/react-table'; +import { useThemeContext } from '../../theme/ThemeContext'; // This file is responsible for defining the columns of the schema table, depending on user defined types and schemas. const columnHelper = createColumnHelper(); +const renderSchemaField = (field: CellContext) => { + const theme = useThemeContext(); + { + /* In a Dictionary, there is no such thing as an examples field, however it is commonly apart of the meta */ + } + { + /* due to project specs, we will render the examples here if they exist and have logic to handle it. */ + } + const renderExamples = () => { + const examples = field.row.original.meta?.examples; + const examplesLength = examples?.toString().length; + return ( + examples && ( +
      + {examplesLength && examplesLength > 1 ? 'Examples:' : 'Example:'}{' '} + {Array.isArray(examples) ? examples.join(', ') : String(examples)} +
      + ) + ); + }; + return ( +
      +
      {field.row.original.name}
      +
      {field.row.original.description}
      + {renderExamples()} +
      + ); +}; + export const schemaBaseColumns = [ columnHelper.accessor('name', { header: 'SchemaField', cell: (field) => { // TODO: Open issue in lectern to make displayName a known property of field - return ( -
      -
      {field.row.original.name}
      -
      {field.row.original.description}
      -
      - ); + return renderSchemaField(field); }, }), columnHelper.accessor( diff --git a/packages/ui/stories/fixtures/minimalBiosampleModel.ts b/packages/ui/stories/fixtures/minimalBiosampleModel.ts index c03973bc..4ebd29e3 100644 --- a/packages/ui/stories/fixtures/minimalBiosampleModel.ts +++ b/packages/ui/stories/fixtures/minimalBiosampleModel.ts @@ -19,6 +19,9 @@ const dictionary: Dictionary = { required: true, regex: '#/regex/submitter_id', }, + meta: { + examples: ['DONOR12345', 'DONOR67890'], + }, }, { name: 'gender', diff --git a/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx b/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx index 43f94bfb..01bd48b7 100644 --- a/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx +++ b/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx @@ -3,10 +3,10 @@ // import { DictionaryHeader } from '#/viewer-table/DictionaryHeader'; import type { Meta, StoryObj } from '@storybook/react'; import SchemaTableWithAccordion from '../../src/viewer-table/DataTable/SchemaTableWithAccordion'; -import webUsers from '../fixtures/websiteUsersDataDictionary'; +import Dictionary from '../fixtures/minimalBiosampleModel'; import themeDecorator from '../themeDecorator'; import { Schema } from '@overture-stack/lectern-dictionary'; -const schema: Schema = webUsers.schemas[0]; +const schema: Schema = Dictionary.schemas[0]; const meta = { component: SchemaTableWithAccordion, From e4d542fe4e9f0892688489c1884376b74250cbae Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 17 Jun 2025 12:01:10 -0400 Subject: [PATCH 104/217] render examples --- .../src/viewer-table/DataTable/tableInit.tsx | 20 ++++++++++++------- .../stories/fixtures/minimalBiosampleModel.ts | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/tableInit.tsx b/packages/ui/src/viewer-table/DataTable/tableInit.tsx index aa6483db..92155227 100644 --- a/packages/ui/src/viewer-table/DataTable/tableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/tableInit.tsx @@ -39,16 +39,22 @@ const renderSchemaField = (field: CellContext) => { } const renderExamples = () => { const examples = field.row.original.meta?.examples; - const examplesLength = examples?.toString().length; + if (!examples) { + return null; + } + // the only way we can have more than one example is if we have an array of examples + + const count = Array.isArray(examples) ? examples.length : 1; + const label = count > 1 ? 'Examples:' : 'Example:'; + const text = Array.isArray(examples) ? examples.join(', ') : String(examples); + return ( - examples && ( -
      - {examplesLength && examplesLength > 1 ? 'Examples:' : 'Example:'}{' '} - {Array.isArray(examples) ? examples.join(', ') : String(examples)} -
      - ) +
      + {label} {text} +
      ); }; + return (
      Date: Tue, 17 Jun 2025 15:36:44 -0400 Subject: [PATCH 105/217] clean up the handling for the hash --- .../viewer-table/DataTable/SchemaTable.tsx | 40 +++++++++++++- .../DataTable/SchemaTableWithAccordion.tsx | 2 +- .../src/viewer-table/DataTable/tableInit.tsx | 52 +++++++++++++++---- 3 files changed, 81 insertions(+), 13 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx index efe96ee0..86ced4c5 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx @@ -26,7 +26,8 @@ import type { Schema, SchemaField } from '@overture-stack/lectern-dictionary'; import { getCoreRowModel, HeaderGroup, useReactTable } from '@tanstack/react-table'; import TableHeader from './TableHeader'; import TableRow from './TableRow'; -import { schemaBaseColumns } from './tableInit'; +import { getSchemaBaseColumns } from './tableInit'; +import { useMemo, useState } from 'react'; type SchemaTableProps = { schema: Schema; @@ -43,11 +44,46 @@ const tableStyle = css` `; const SchemaTable = ({ schema }: SchemaTableProps) => { + const [clipboardContents, setClipboardContents] = useState(null); + const [isCopying, setIsCopying] = useState(false); + const [copySuccess, setCopySuccess] = useState(false); + + const handleCopy = (text: string) => { + if (isCopying) { + return; // We don't wanna copy if we are already copying + } + setIsCopying(true); + navigator.clipboard + .writeText(text) + .then(() => { + setCopySuccess(true); + setTimeout(() => { + setIsCopying(false); + }, 2000); // Reset copy success after 2 seconds as well as the isCopying state + }) + .catch((err) => { + console.error('Failed to copy text: ', err); + setCopySuccess(false); + setIsCopying(false); + }); + if (copySuccess) { + // Update the clipboard contents + const currentURL = window.location.href; + setClipboardContents(currentURL); + } + setCopySuccess(false); + }; + const table = useReactTable({ data: schema.fields || [], - columns: schemaBaseColumns, + columns: getSchemaBaseColumns(setClipboardContents), getCoreRowModel: getCoreRowModel(), }); + useMemo(() => { + if (clipboardContents) { + handleCopy(clipboardContents); + } + }, [clipboardContents]); return (
      diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx index d23e10ab..6b92517c 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx @@ -10,7 +10,7 @@ const SchemaTableWithAccordion = ({ schema }: props) => { { title: schema.name, description: schema.description ?? '', - openOnInit: false, + openOnInit: true, content: , }, ]; diff --git a/packages/ui/src/viewer-table/DataTable/tableInit.tsx b/packages/ui/src/viewer-table/DataTable/tableInit.tsx index 92155227..0e0e4dc2 100644 --- a/packages/ui/src/viewer-table/DataTable/tableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/tableInit.tsx @@ -25,18 +25,29 @@ import { css } from '@emotion/react'; import { SchemaField } from '@overture-stack/lectern-dictionary'; import { CellContext, createColumnHelper } from '@tanstack/react-table'; import { useThemeContext } from '../../theme/ThemeContext'; +import { useEffect } from 'react'; +import { Theme } from '../../theme'; // This file is responsible for defining the columns of the schema table, depending on user defined types and schemas. +const hashIconStyle = (theme: Theme) => css` + opacity: 0; + margin-left: 8px; + transition: opacity 0.2s ease; + border-bottom: 2px solid ${theme.colors.secondary}; + &:hover { + opacity: 1; + } +`; const columnHelper = createColumnHelper(); -const renderSchemaField = (field: CellContext) => { +const renderSchemaField = (field: CellContext, setClipboardContents: (curr: string) => void) => { const theme = useThemeContext(); - { - /* In a Dictionary, there is no such thing as an examples field, however it is commonly apart of the meta */ - } - { - /* due to project specs, we will render the examples here if they exist and have logic to handle it. */ - } + const fieldName = field.row.original.name; + const fieldIndex = field.row.index; + + // In a Dictionary, there is no such thing as an examples field, however it is commonly apart of the meta + // due to project specs, we will render the examples here if they exist and have logic to handle it. + const renderExamples = () => { const examples = field.row.original.meta?.examples; if (!examples) { @@ -55,27 +66,48 @@ const renderSchemaField = (field: CellContext) => { ); }; + const { Hash } = theme.icons; + + useEffect(() => { + const hashTarget = `field-${fieldIndex}`; + if (window.location.hash === `#${hashTarget}`) { + document.getElementById(hashTarget)?.scrollIntoView({ behavior: 'smooth' }); + } + }, []); + + const handleClick = () => { + const hashTarget = `field-${fieldIndex}`; + window.location.hash = `#${hashTarget}`; + setClipboardContents(window.location.href); + }; + return (
      -
      {field.row.original.name}
      +
      + {fieldName} + + + +
      {field.row.original.description}
      {renderExamples()}
      ); }; -export const schemaBaseColumns = [ +export const getSchemaBaseColumns = (setClipboardContents: (curr: string) => void) => [ columnHelper.accessor('name', { header: 'SchemaField', cell: (field) => { // TODO: Open issue in lectern to make displayName a known property of field - return renderSchemaField(field); + return renderSchemaField(field, setClipboardContents); }, }), columnHelper.accessor( From 3f61b33b8db1aeb5a2df2e5ee6a0784f2e0deace Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 18 Jun 2025 10:43:02 -0400 Subject: [PATCH 106/217] add the filter dropdown from stage --- .../AttributeFilterDropdown.tsx | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx index d640d9ca..3b49d06e 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx @@ -1,27 +1,21 @@ import Dropdown from '../../common/Dropdown/Dropdown'; export type FilterDropdownProps = { - filters: FilterMapping; - setFilters: (filters: FilterMapping) => void; - disabled?: boolean; -}; - -export type FilterMapping = { - constraints?: FilterOptions[]; - active: boolean; + filters: FilterOptions[]; + setFilters: (filters: FilterOptions[]) => void; }; export type FilterOptions = 'Required' | 'All Fields'; -const AttributeFilter = ({ filters, setFilters, disabled }: FilterDropdownProps) => { +const FilterDropdown = ({ filters, setFilters }: FilterDropdownProps) => { const handleFilterSelect = (selectedFilterName: FilterOptions) => { // If we click the filter again then we want to toggle it off, iff it is the same filter being clicked // and it is currently active - if (filters.active && filters.constraints?.includes(selectedFilterName)) { - setFilters({ active: false, constraints: [] }); + if (filters?.includes(selectedFilterName)) { + setFilters([]); return; } - setFilters({ active: true, constraints: [selectedFilterName] }); + setFilters([selectedFilterName]); }; const menuItems = [ { @@ -34,7 +28,7 @@ const AttributeFilter = ({ filters, setFilters, disabled }: FilterDropdownProps) }, ]; - return ; + return ; }; -export default AttributeFilter; +export default FilterDropdown; From a02c93bd46f12c2b125f28040e9bec30c9321905 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 18 Jun 2025 10:55:05 -0400 Subject: [PATCH 107/217] finishing up code clean-up --- .../InteractionPanel/AttributeFilterDropdown.tsx | 6 +++--- .../InteractionPanel/InteractionPanel.tsx | 8 ++++---- .../AttributeFilterDropdown.stories.tsx | 6 ++---- .../CollapseAllButton.stories.tsx | 2 +- .../DictionaryDownloadButton.stories.tsx | 2 +- .../DictionaryVersionSwitcher.stories.tsx | 4 ++-- .../interaction-panel/ExpandAllButton.stories.tsx | 2 +- .../InteractionPanel.stories.tsx | 15 +++++++-------- .../TableOfContentDropdown.stories.tsx | 2 +- 9 files changed, 22 insertions(+), 25 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx index 3b49d06e..745a1ed8 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx @@ -3,14 +3,14 @@ import Dropdown from '../../common/Dropdown/Dropdown'; export type FilterDropdownProps = { filters: FilterOptions[]; setFilters: (filters: FilterOptions[]) => void; + disabled?: boolean; }; export type FilterOptions = 'Required' | 'All Fields'; -const FilterDropdown = ({ filters, setFilters }: FilterDropdownProps) => { +const FilterDropdown = ({ filters, setFilters, disabled }: FilterDropdownProps) => { const handleFilterSelect = (selectedFilterName: FilterOptions) => { // If we click the filter again then we want to toggle it off, iff it is the same filter being clicked - // and it is currently active if (filters?.includes(selectedFilterName)) { setFilters([]); return; @@ -28,7 +28,7 @@ const FilterDropdown = ({ filters, setFilters }: FilterDropdownProps) => { }, ]; - return ; + return ; }; export default FilterDropdown; diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index a94be84e..000d04c5 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -22,11 +22,10 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import type { Dictionary, Schema } from '@overture-stack/lectern-dictionary'; -import { useState } from 'react'; +import type { Dictionary } from '@overture-stack/lectern-dictionary'; import { Theme } from '../../theme'; import { useThemeContext } from '../../theme/ThemeContext'; -import AttributeFilterDropdown, { FilterMapping } from './AttributeFilterDropdown'; +import AttributeFilterDropdown, { FilterOptions } from './AttributeFilterDropdown'; import CollapseAllButton from './CollapseAllButton'; import DictionaryVersionSwitcher, { DictionaryConfig } from './DictionaryVersionSwitcher'; import DownloadTemplatesButton from './DownloadTemplatesButton'; @@ -38,7 +37,7 @@ type InteractionPanelProps = { setIsCollapsed: (isCollapsed: boolean) => void; onSelect: (schemaNameIndex: number) => void; currDictionary: DictionaryConfig; - setFilters: (filters: FilterMapping) => void; + setFilters: (filters: FilterOptions[]) => void; }; const panelStyles = (theme: Theme) => css` @@ -67,6 +66,7 @@ const rightSectionStyles = css` align-items: center; gap: 16px; `; + const InteractionPanel = ({ disabled = false, setIsCollapsed, diff --git a/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx index f9492b61..699ce5aa 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx @@ -2,8 +2,6 @@ import type { Meta, StoryObj } from '@storybook/react'; import AttributeFilter from '../../../src/viewer-table/InteractionPanel/AttributeFilterDropdown'; import themeDecorator from '../../themeDecorator'; -import AdvancedDictionary from '../../fixtures/advanced.json'; -import { Dictionary } from '@overture-stack/lectern-dictionary'; const meta = { component: AttributeFilter, title: 'Viewer - Table/Interaction - Panel/AttributeFilterDropdown', @@ -16,13 +14,13 @@ type Story = StoryObj; export const Default: Story = { args: { - filters: { active: true }, + filters: ['Required'], setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), }, }; export const Disabled: Story = { args: { - filters: { active: false }, + filters: ['Required'], setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), disabled: true, }, diff --git a/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx index 882b469a..b8c8608e 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx @@ -1,8 +1,8 @@ /** @jsxImportSource @emotion/react */ import type { Meta, StoryObj } from '@storybook/react'; -import themeDecorator from '../../themeDecorator'; import CollapseAllButton from '../../../src/viewer-table/InteractionPanel/CollapseAllButton'; +import themeDecorator from '../../themeDecorator'; const meta = { component: CollapseAllButton, diff --git a/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx index 2015589b..acd40e57 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx @@ -1,8 +1,8 @@ /** @jsxImportSource @emotion/react */ import type { Meta, StoryObj } from '@storybook/react'; -import themeDecorator from '../../themeDecorator'; import DictionaryDownloadButton from '../../../src/viewer-table/InteractionPanel/DownloadTemplatesButton'; +import themeDecorator from '../../themeDecorator'; const meta = { component: DictionaryDownloadButton, title: 'Viewer - Table/Interaction - Panel/DictionaryDownloadButton', diff --git a/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx index cf8c283f..969bb5f1 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx @@ -1,9 +1,9 @@ /** @jsxImportSource @emotion/react */ -import { Dictionary, Schema } from '@overture-stack/lectern-dictionary'; +import { Dictionary } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; -import DictionarySample from '../../fixtures/advanced.json'; import VersionSwitcher from '../../../src/viewer-table/InteractionPanel/DictionaryVersionSwitcher'; +import DictionarySample from '../../fixtures/advanced.json'; import themeDecorator from '../../themeDecorator'; const meta = { diff --git a/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx index 6c09c110..bd33e9b0 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/ExpandAllButton.stories.tsx @@ -1,8 +1,8 @@ /** @jsxImportSource @emotion/react */ import type { Meta, StoryObj } from '@storybook/react'; -import themeDecorator from '../../themeDecorator'; import ExpandAllButton from '../../../src/viewer-table/InteractionPanel/ExpandAllButton'; +import themeDecorator from '../../themeDecorator'; const meta = { component: ExpandAllButton, title: 'Viewer - Table/Interaction - Panel/ExpandAllButton', diff --git a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx index 61b824ed..c79fe28b 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx @@ -2,12 +2,11 @@ import { Dictionary } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; -import { FilterMapping } from '../../../src/viewer-table/InteractionPanel/AttributeFilterDropdown'; +import type { FilterOptions } from '../../../src/viewer-table/InteractionPanel/AttributeFilterDropdown'; import { DictionaryConfig } from '../../../src/viewer-table/InteractionPanel/DictionaryVersionSwitcher'; import InteractionPanel from '../../../src/viewer-table/InteractionPanel/InteractionPanel'; import AdvancedDictionary from '../../fixtures/advanced.json'; import themeDecorator from '../../themeDecorator'; - const meta = { component: InteractionPanel, title: 'Viewer - Table/Interaction - Panel/InteractionPanel', @@ -47,11 +46,11 @@ export const Default: Story = { dictionaryData: MultipleDictionaryData, onVersionChange: mockOnVersionChange, filters: { active: true }, - setFilters: (filters: FilterMapping) => { + setFilters: (filters: FilterOptions[]) => { alert('setFilters called with:' + filters); }, } as DictionaryConfig, - setFilters: (filters: FilterMapping) => { + setFilters: (filters: FilterOptions[]) => { alert('setFilters called with:' + filters); }, }, @@ -70,11 +69,11 @@ export const WithSingleVersion: Story = { dictionaryData: SingleDictionaryData, onVersionChange: mockOnVersionChange, filters: { active: true }, - setFilters: (filters: FilterMapping) => { + setFilters: (filters: FilterOptions[]) => { alert('setFilters called with:' + filters); }, } as DictionaryConfig, - setFilters: (filters: FilterMapping) => { + setFilters: (filters: FilterOptions[]) => { alert('setFilters called with:' + filters); }, }, @@ -93,11 +92,11 @@ export const Disabled: Story = { dictionaryData: SingleDictionaryData, onVersionChange: mockOnVersionChange, filters: { active: true }, - setFilters: (filters: FilterMapping) => { + setFilters: (filters: FilterOptions[]) => { alert('setFilters called with:' + filters); }, } as DictionaryConfig, - setFilters: (filters: FilterMapping) => { + setFilters: (filters: FilterOptions[]) => { alert('setFilters called with:' + filters); }, }, diff --git a/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx index d11b24f0..79066097 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx @@ -2,8 +2,8 @@ import { Schema } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; -import Dictionary from '../../fixtures/advanced.json'; import TableOfContentsDropdown from '../../../src/viewer-table/InteractionPanel/TableOfContentsDropdown'; +import Dictionary from '../../fixtures/advanced.json'; import themeDecorator from '../../themeDecorator'; const meta = { From 600f63f851a78afce88d1406694192545fcc01cf Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 18 Jun 2025 11:10:39 -0400 Subject: [PATCH 108/217] clean up code to meet standards better --- .../DictionaryVersionSwitcher.tsx | 6 +-- .../InteractionPanel/InteractionPanel.tsx | 15 +++---- .../AttributeFilterDropdown.stories.tsx | 4 +- .../InteractionPanel.stories.tsx | 40 +++++++------------ 4 files changed, 26 insertions(+), 39 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx index 750d00c5..fbe28ca6 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx @@ -23,7 +23,7 @@ import { Dictionary } from '@overture-stack/lectern-dictionary'; import Dropdown from '../../common/Dropdown/Dropdown'; import { useThemeContext } from '../../theme/ThemeContext'; -import { FilterMapping } from './AttributeFilterDropdown'; +import { FilterOptions } from './AttributeFilterDropdown'; type VersionSwitcherProps = { config: DictionaryConfig; @@ -34,8 +34,8 @@ export type DictionaryConfig = { dictionaryIndex: number; dictionaryData: Dictionary[]; onVersionChange: (index: number) => void; - filters: FilterMapping; - setFilters: (filters: FilterMapping) => void; + filters: FilterOptions[]; + setFilters: (filters: FilterOptions[]) => void; }; const VersionSwitcher = ({ config, disabled = false }: VersionSwitcherProps) => { diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index 000d04c5..9c165593 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -37,7 +37,6 @@ type InteractionPanelProps = { setIsCollapsed: (isCollapsed: boolean) => void; onSelect: (schemaNameIndex: number) => void; currDictionary: DictionaryConfig; - setFilters: (filters: FilterOptions[]) => void; }; const panelStyles = (theme: Theme) => css` @@ -67,20 +66,18 @@ const rightSectionStyles = css` gap: 16px; `; -const InteractionPanel = ({ - disabled = false, - setIsCollapsed, - onSelect, - currDictionary, - setFilters, -}: InteractionPanelProps) => { +const InteractionPanel = ({ disabled = false, setIsCollapsed, onSelect, currDictionary }: InteractionPanelProps) => { const theme: Theme = useThemeContext(); const currDict: Dictionary = currDictionary.dictionaryData[currDictionary.dictionaryIndex]; return (
      - + setIsCollapsed(true)} disabled={disabled} /> setIsCollapsed(false)} disabled={disabled} />
      diff --git a/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx index 699ce5aa..4f3c0c7f 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx @@ -14,13 +14,13 @@ type Story = StoryObj; export const Default: Story = { args: { - filters: ['Required'], + filters: [], setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), }, }; export const Disabled: Story = { args: { - filters: ['Required'], + filters: [], setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), disabled: true, }, diff --git a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx index c79fe28b..f183211e 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx @@ -3,10 +3,10 @@ import { Dictionary } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; import type { FilterOptions } from '../../../src/viewer-table/InteractionPanel/AttributeFilterDropdown'; -import { DictionaryConfig } from '../../../src/viewer-table/InteractionPanel/DictionaryVersionSwitcher'; import InteractionPanel from '../../../src/viewer-table/InteractionPanel/InteractionPanel'; import AdvancedDictionary from '../../fixtures/advanced.json'; import themeDecorator from '../../themeDecorator'; + const meta = { component: InteractionPanel, title: 'Viewer - Table/Interaction - Panel/InteractionPanel', @@ -15,6 +15,7 @@ const meta = { } satisfies Meta; export default meta; + type Story = StoryObj; const SingleDictionaryData: Dictionary[] = [AdvancedDictionary as Dictionary]; @@ -33,6 +34,10 @@ const mockOnVersionChange = (index: number) => { alert('onVersionChange called with index:' + index); }; +const mockSetFilters = (filters: FilterOptions[]) => { + alert('setFilters called with:' + filters); +}; + // When we are at multiple versions, then the version switcher is now rendered, this is to test that behavior export const Default: Story = { args: { @@ -41,17 +46,12 @@ export const Default: Story = { alert('onSelect called with schemaNameIndex:' + schemaNameIndex); }, currDictionary: { - lecternUrl: 'http://localhost:3031', + lecternUrl: '', dictionaryIndex: 0, dictionaryData: MultipleDictionaryData, onVersionChange: mockOnVersionChange, - filters: { active: true }, - setFilters: (filters: FilterOptions[]) => { - alert('setFilters called with:' + filters); - }, - } as DictionaryConfig, - setFilters: (filters: FilterOptions[]) => { - alert('setFilters called with:' + filters); + filters: [], + setFilters: mockSetFilters, }, }, }; @@ -64,17 +64,12 @@ export const WithSingleVersion: Story = { alert('onSelect called with schemaNameIndex:' + schemaNameIndex); }, currDictionary: { - lecternUrl: 'http://localhost:3031', + lecternUrl: '', dictionaryIndex: 0, dictionaryData: SingleDictionaryData, onVersionChange: mockOnVersionChange, - filters: { active: true }, - setFilters: (filters: FilterOptions[]) => { - alert('setFilters called with:' + filters); - }, - } as DictionaryConfig, - setFilters: (filters: FilterOptions[]) => { - alert('setFilters called with:' + filters); + filters: [], + setFilters: mockSetFilters, }, }, }; @@ -87,17 +82,12 @@ export const Disabled: Story = { alert('onSelect called with schemaNameIndex:' + schemaNameIndex); }, currDictionary: { - lecternUrl: 'http://localhost:3031', + lecternUrl: '', dictionaryIndex: 0, dictionaryData: SingleDictionaryData, onVersionChange: mockOnVersionChange, - filters: { active: true }, - setFilters: (filters: FilterOptions[]) => { - alert('setFilters called with:' + filters); - }, - } as DictionaryConfig, - setFilters: (filters: FilterOptions[]) => { - alert('setFilters called with:' + filters); + filters: [], + setFilters: mockSetFilters, }, }, }; From a6c9d47138ed894b540af50ceed65e6085931611 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 18 Jun 2025 11:47:33 -0400 Subject: [PATCH 109/217] clean up the stories and spread through the mockedproperties --- .../DownloadTemplatesButton.tsx | 2 +- .../AttributeFilterDropdown.stories.tsx | 8 ++- .../CollapseAllButton.stories.tsx | 7 +- .../DictionaryDownloadButton.stories.tsx | 23 ++++--- .../DictionaryVersionSwitcher.stories.tsx | 42 ++++++------ .../InteractionPanel.stories.tsx | 66 ++++++++----------- 6 files changed, 72 insertions(+), 76 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx index 84e32fdb..76ce56a1 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx @@ -25,7 +25,7 @@ import { useState } from 'react'; import Button from '../../common/Button'; import { useThemeContext } from '../../theme/ThemeContext'; -type DictionaryDownloadButtonProps = { +export type DictionaryDownloadButtonProps = { version: string; name: string; lecternUrl: string; diff --git a/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx index 4f3c0c7f..0fa8028e 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/AttributeFilterDropdown.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import AttributeFilter from '../../../src/viewer-table/InteractionPanel/AttributeFilterDropdown'; import themeDecorator from '../../themeDecorator'; + const meta = { component: AttributeFilter, title: 'Viewer - Table/Interaction - Panel/AttributeFilterDropdown', @@ -10,18 +11,21 @@ const meta = { } satisfies Meta; export default meta; + type Story = StoryObj; +const mockSetFilters = (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`); + export const Default: Story = { args: { filters: [], - setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), + setFilters: mockSetFilters, }, }; export const Disabled: Story = { args: { filters: [], - setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), + setFilters: mockSetFilters, disabled: true, }, }; diff --git a/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx index b8c8608e..c25f961d 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/CollapseAllButton.stories.tsx @@ -14,12 +14,15 @@ const meta = { export default meta; type Story = StoryObj; +const mockOnClick = () => alert('All collapsible components are collapsed'); + export const Default: Story = { - args: { onClick: () => alert('All collapsible components are collapsed') }, + args: { onClick: mockOnClick, disabled: false }, }; + export const Disabled: Story = { args: { - onClick: () => alert('All collapsible components are collapsed'), + onClick: mockOnClick, disabled: true, }, }; diff --git a/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx index acd40e57..723d9ced 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/DictionaryDownloadButton.stories.tsx @@ -1,8 +1,11 @@ /** @jsxImportSource @emotion/react */ import type { Meta, StoryObj } from '@storybook/react'; -import DictionaryDownloadButton from '../../../src/viewer-table/InteractionPanel/DownloadTemplatesButton'; +import DictionaryDownloadButton, { + DictionaryDownloadButtonProps, +} from '../../../src/viewer-table/InteractionPanel/DownloadTemplatesButton'; import themeDecorator from '../../themeDecorator'; + const meta = { component: DictionaryDownloadButton, title: 'Viewer - Table/Interaction - Panel/DictionaryDownloadButton', @@ -11,23 +14,25 @@ const meta = { } satisfies Meta; export default meta; + type Story = StoryObj; +const mockDictionaryDownloadButtonProps: DictionaryDownloadButtonProps = { + version: '1.0', + name: 'example-dictionary', + lecternUrl: 'http://localhost:3031', + fileType: 'tsv', +}; + export const Default: Story = { args: { - version: '1.0', - name: 'example-dictionary', - lecternUrl: 'http://localhost:3031', - fileType: 'tsv', + ...mockDictionaryDownloadButtonProps, }, }; export const Disabled: Story = { args: { - version: '1.0', - name: 'example-dictionary', - lecternUrl: 'http://localhost:3031', - fileType: 'tsv', + ...mockDictionaryDownloadButtonProps, disabled: true, }, }; diff --git a/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx index 969bb5f1..ddd9196c 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx @@ -16,61 +16,59 @@ const meta = { export default meta; type Story = StoryObj; -// Create multiple dictionary versions for testing const SingleDictionaryData: Dictionary[] = [DictionarySample as Dictionary]; - const MultipleDictionaryData: Dictionary[] = [ { ...DictionarySample, version: '1.0' } as Dictionary, { ...DictionarySample, version: '2.0' } as Dictionary, { ...DictionarySample, version: '3.0' } as Dictionary, ]; +const mockProps = { + config: { + lecternUrl: 'http://localhost:3031', + dictionaryIndex: 0, + onVersionChange: (index: number) => alert(`Version changed to index: ${index}`), + filters: [], + setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), + }, +}; + export const MultipleVersions: Story = { args: { + ...mockProps, config: { - lecternUrl: 'http://localhost:3031', - dictionaryIndex: 0, + ...mockProps.config, dictionaryData: MultipleDictionaryData, - onVersionChange: (index: number) => alert(`Version changed to index: ${index}`), - filters: { active: true }, - setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), }, }, }; + export const SingleVersion: Story = { args: { + ...mockProps, config: { - lecternUrl: 'http://localhost:3031', - dictionaryIndex: 0, + ...mockProps.config, dictionaryData: SingleDictionaryData, - onVersionChange: (index: number) => alert(`Version changed to index: ${index}`), - filters: { active: true }, - setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), }, }, }; export const EmptyArray: Story = { args: { + ...mockProps, config: { - lecternUrl: 'http://localhost:3031', - dictionaryIndex: 0, + ...mockProps.config, dictionaryData: [], - onVersionChange: (index: number) => alert(`Version changed to index: ${index}`), - filters: { active: true }, - setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), }, }, }; + export const DisabledWithMultipleVersions: Story = { args: { + ...mockProps, config: { - lecternUrl: 'http://localhost:3031', - dictionaryIndex: 0, + ...mockProps.config, dictionaryData: MultipleDictionaryData, - onVersionChange: (index: number) => alert(`Version changed to index: ${index}`), - filters: { active: true }, - setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), }, disabled: true, }, diff --git a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx index f183211e..cbe620c6 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx @@ -19,39 +19,39 @@ export default meta; type Story = StoryObj; const SingleDictionaryData: Dictionary[] = [AdvancedDictionary as Dictionary]; - const MultipleDictionaryData: Dictionary[] = [ { ...AdvancedDictionary, version: '1.0' } as Dictionary, { ...AdvancedDictionary, version: '2.0' } as Dictionary, { ...AdvancedDictionary, version: '3.0' } as Dictionary, ]; -const mockSetIsCollapsed = (isCollapsed: boolean) => { - alert('setIsCollapsed called with:' + isCollapsed); -}; - -const mockOnVersionChange = (index: number) => { - alert('onVersionChange called with index:' + index); -}; - -const mockSetFilters = (filters: FilterOptions[]) => { - alert('setFilters called with:' + filters); +const mockProps = { + setIsCollapsed: (isCollapsed: boolean) => { + alert('setIsCollapsed called with:' + isCollapsed); + }, + onSelect: (schemaNameIndex) => { + alert('onSelect called with schemaNameIndex:' + schemaNameIndex); + }, + currDictionary: { + lecternUrl: '', + dictionaryIndex: 0, + onVersionChange: (index: number) => { + alert('onVersionChange called with index:' + index); + }, + filters: [], + setFilters: (filters: FilterOptions[]) => { + alert('setFilters called with:' + filters); + }, + }, }; // When we are at multiple versions, then the version switcher is now rendered, this is to test that behavior export const Default: Story = { args: { - setIsCollapsed: mockSetIsCollapsed, - onSelect(schemaNameIndex) { - alert('onSelect called with schemaNameIndex:' + schemaNameIndex); - }, + ...mockProps, currDictionary: { - lecternUrl: '', - dictionaryIndex: 0, + ...mockProps.currDictionary, dictionaryData: MultipleDictionaryData, - onVersionChange: mockOnVersionChange, - filters: [], - setFilters: mockSetFilters, }, }, }; @@ -59,35 +59,21 @@ export const Default: Story = { // The reason why this story exists is to test the behavior when the dictionary version switcher button is not rendered export const WithSingleVersion: Story = { args: { - setIsCollapsed: mockSetIsCollapsed, - onSelect(schemaNameIndex) { - alert('onSelect called with schemaNameIndex:' + schemaNameIndex); - }, + ...mockProps, currDictionary: { - lecternUrl: '', - dictionaryIndex: 0, + ...mockProps.currDictionary, dictionaryData: SingleDictionaryData, - onVersionChange: mockOnVersionChange, - filters: [], - setFilters: mockSetFilters, }, }, }; export const Disabled: Story = { args: { - disabled: true, - setIsCollapsed: mockSetIsCollapsed, - onSelect(schemaNameIndex) { - alert('onSelect called with schemaNameIndex:' + schemaNameIndex); - }, + ...mockProps, currDictionary: { - lecternUrl: '', - dictionaryIndex: 0, - dictionaryData: SingleDictionaryData, - onVersionChange: mockOnVersionChange, - filters: [], - setFilters: mockSetFilters, + ...mockProps.currDictionary, + dictionaryData: MultipleDictionaryData, }, + disabled: true, }, }; From 9d6a0cba2ee1c8aefeaef8737fb219547a4ad7fc Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 19 Jun 2025 08:46:58 -0400 Subject: [PATCH 110/217] some slob generated to get some scroll behaviour working --- .../viewer-table/DataTable/SchemaTable.tsx | 114 ++++++++++++++---- .../viewer-table/DataTable/TableHeader.tsx | 12 +- .../src/viewer-table/DataTable/TableRow.tsx | 17 +-- .../src/viewer-table/DataTable/tableInit.tsx | 12 +- 4 files changed, 111 insertions(+), 44 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx index 86ced4c5..057c3eee 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx @@ -22,12 +22,13 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import type { Schema, SchemaField } from '@overture-stack/lectern-dictionary'; -import { getCoreRowModel, HeaderGroup, useReactTable } from '@tanstack/react-table'; +import type { Schema } from '@overture-stack/lectern-dictionary'; +import { getCoreRowModel, useReactTable } from '@tanstack/react-table'; +import { useMemo, useState } from 'react'; +import { useThemeContext } from '../../theme/ThemeContext'; import TableHeader from './TableHeader'; import TableRow from './TableRow'; import { getSchemaBaseColumns } from './tableInit'; -import { useMemo, useState } from 'react'; type SchemaTableProps = { schema: Schema; @@ -37,10 +38,57 @@ const sectionStyle = css` margin-bottom: 48px; max-width: 1200px; `; + +const tableContainerStyle = css` + display: flex; + max-width: 1200px; +`; + +const fixedColumnContainerStyle = css` + flex: 0 0 300px; +`; + +const scrollableColumnsContainerStyle = css` + overflow-x: auto; + flex: 1; +`; + const tableStyle = css` - width: 100%; - border-collapse: collapse; + border-collapse: separate; + border-spacing: 0; margin-top: 8px; + width: 100%; + td, + th { + vertical-align: top; + line-height: 1.5; + } +`; + +const fixedTableStyle = css` + ${tableStyle} + width: 300px; // Match the container width +`; + +const scrollableTableStyle = css` + ${tableStyle} + min-width: 800px; // Ensure table is wide enough to scroll +`; + +const thStyle = css` + padding: 8px; + text-align: left; + border-bottom: 1px solid #dcdcdc; +`; + +const tdStyle = css` + padding: 8px; + border-bottom: 1px solid #dcdcdc; + vertical-align: top; +`; + +const rowStyle = (index: number) => css` + background-color: ${index % 2 === 0 ? '#fff' : '#f9f9f9'}; `; const SchemaTable = ({ schema }: SchemaTableProps) => { @@ -49,9 +97,7 @@ const SchemaTable = ({ schema }: SchemaTableProps) => { const [copySuccess, setCopySuccess] = useState(false); const handleCopy = (text: string) => { - if (isCopying) { - return; // We don't wanna copy if we are already copying - } + if (isCopying) return; setIsCopying(true); navigator.clipboard .writeText(text) @@ -59,7 +105,7 @@ const SchemaTable = ({ schema }: SchemaTableProps) => { setCopySuccess(true); setTimeout(() => { setIsCopying(false); - }, 2000); // Reset copy success after 2 seconds as well as the isCopying state + }, 2000); }) .catch((err) => { console.error('Failed to copy text: ', err); @@ -67,9 +113,7 @@ const SchemaTable = ({ schema }: SchemaTableProps) => { setIsCopying(false); }); if (copySuccess) { - // Update the clipboard contents - const currentURL = window.location.href; - setClipboardContents(currentURL); + setClipboardContents(window.location.href); } setCopySuccess(false); }; @@ -79,26 +123,48 @@ const SchemaTable = ({ schema }: SchemaTableProps) => { columns: getSchemaBaseColumns(setClipboardContents), getCoreRowModel: getCoreRowModel(), }); + useMemo(() => { if (clipboardContents) { handleCopy(clipboardContents); } }, [clipboardContents]); + const theme = useThemeContext(); + return (
      -
      - {isExpanded ? valueStr : valueStr.slice(0, 68) + ' ...'} - toggleExpand(cell.id)}> - {!isExpanded ? '\nRead more' : '\nRead less'} - - - - {flexRender(cell.column.columnDef.cell, cell.getContext())} + + {valueStr} +
      - - {valueStr} - + {flexRender(cell.column.columnDef.cell, cell.getContext())}
      - - {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( - - ))} - - - {table.getRowModel().rows.map((row, i: number) => ( - - ))} - -
      +
      +
      + + + {table.getHeaderGroups().map((headerGroup) => ( + + ))} + + + {table.getRowModel().rows.map((row, i) => ( + + ))} + +
      +
      + +
      + + + {table.getHeaderGroups().map((headerGroup) => ( + + ))} + + + {table.getRowModel().rows.map((row, i) => ( + + ))} + +
      +
      +
      ); }; diff --git a/packages/ui/src/viewer-table/DataTable/TableHeader.tsx b/packages/ui/src/viewer-table/DataTable/TableHeader.tsx index ecd181ee..606ddb7a 100644 --- a/packages/ui/src/viewer-table/DataTable/TableHeader.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableHeader.tsx @@ -23,7 +23,7 @@ import { css } from '@emotion/react'; import { flexRender, HeaderGroup } from '@tanstack/react-table'; -import { Lato } from '../styles/typography'; +import { useThemeContext } from '../../theme/ThemeContext'; const thStyle = css` background: #e5edf3; @@ -34,18 +34,22 @@ const thStyle = css` type TableHeaderProps = { headerGroup: HeaderGroup; + columnSlice?: [number, number] | [number]; }; -const TableHeader = ({ headerGroup }: TableHeaderProps) => { +const TableHeader = ({ headerGroup, columnSlice }: TableHeaderProps) => { + const theme = useThemeContext(); + const headers = columnSlice ? headerGroup.headers.slice(...columnSlice) : headerGroup.headers; + return ( - {headerGroup.headers.map((header) => ( + {headers.map((header) => ( {flexRender(header.column.columnDef.header, header.getContext())} diff --git a/packages/ui/src/viewer-table/DataTable/TableRow.tsx b/packages/ui/src/viewer-table/DataTable/TableRow.tsx index 35514d67..5983a62e 100644 --- a/packages/ui/src/viewer-table/DataTable/TableRow.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableRow.tsx @@ -42,18 +42,19 @@ const tdStyle = css` type TableRowProps = { row: Row; index: number; + columnSlice?: [number, number] | [number]; }; -const TableRow = ({ row, index }: TableRowProps) => { +const TableRow = ({ row, index, columnSlice }: TableRowProps) => { + const cells = columnSlice ? row.getVisibleCells().slice(...columnSlice) : row.getVisibleCells(); + return ( - {row.getVisibleCells().map((cell) => { - return ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ); - })} + {cells.map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} ); }; diff --git a/packages/ui/src/viewer-table/DataTable/tableInit.tsx b/packages/ui/src/viewer-table/DataTable/tableInit.tsx index 0e0e4dc2..a0a97874 100644 --- a/packages/ui/src/viewer-table/DataTable/tableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/tableInit.tsx @@ -104,11 +104,8 @@ const renderSchemaField = (field: CellContext, setClipboard export const getSchemaBaseColumns = (setClipboardContents: (curr: string) => void) => [ columnHelper.accessor('name', { - header: 'SchemaField', - cell: (field) => { - // TODO: Open issue in lectern to make displayName a known property of field - return renderSchemaField(field, setClipboardContents); - }, + header: 'Fields', + cell: (field) => renderSchemaField(field, setClipboardContents), }), columnHelper.accessor( (row) => { @@ -120,12 +117,11 @@ export const getSchemaBaseColumns = (setClipboardContents: (curr: string) => voi }, { id: 'required', - header: 'Required', - cell: (required) => (required.getValue() ? 'Yes' : 'No'), + header: 'Attribute', }, ), columnHelper.accessor('valueType', { - header: 'Type', + header: 'Data Type', cell: (type) => { const { valueType, isArray, delimiter } = type.row.original; return ( From ab90868219e4bfe04bfa74c740dcb2866a5e78bb Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 19 Jun 2025 08:47:50 -0400 Subject: [PATCH 111/217] fix the fileType --- packages/ui/src/common/Accordion/Accordion.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index 6925656b..e91cadba 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -44,6 +44,7 @@ const Accordion = ({ accordionItems }: AccordionProps) => { lecternUrl="http://localhost:3031" disabled={false} iconOnly={true} + fileType="tsv" /> ), })); From 353e806e7107d1d54fa52ef35a01279c319ed526 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 19 Jun 2025 09:29:26 -0400 Subject: [PATCH 112/217] fixed code readibility, abstracted things into functions --- .../ui/src/common/Accordion/Accordion.tsx | 30 ++++++------- .../ui/src/common/Accordion/AccordionItem.tsx | 45 ++++++++++--------- 2 files changed, 40 insertions(+), 35 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index e91cadba..0fdc398c 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -1,6 +1,6 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import { useState, useMemo } from 'react'; +import { useState, useMemo, useCallback } from 'react'; import AccordionItem, { AccordionData } from './AccordionItem'; import DownloadTemplatesButton from '../../viewer-table/InteractionPanel/DownloadTemplatesButton'; @@ -32,6 +32,7 @@ const accordionStyle = css` */ const Accordion = ({ accordionItems }: AccordionProps) => { + // Some placeholder buttons just for testing, THIS IS NOT A SOLUTION. //TODO: Some random buttons that we want to add to the accordion items, we will actually need to figure out some schema filtering logic, // and download based on that, but for now we just add a button to each item. const accordionItemsWithButtons = useMemo(() => { @@ -59,7 +60,7 @@ const Accordion = ({ accordionItems }: AccordionProps) => { const handleCopy = (text: string) => { if (isCopying) { - return; // We don't wanna copy if we are already copying + return; // We don't want to copy if we are already copying } setIsCopying(true); navigator.clipboard @@ -90,24 +91,23 @@ const Accordion = ({ accordionItems }: AccordionProps) => { }, [clipboardContents]); // This state keeps track of the currently open accordion item index via a boolean array, since each item can be opened or closed independently. - const [openStates, setOpenStates] = useState( - accordionItemsWithButtons.map((accordionItem) => accordionItem.openOnInit), // Initialize with the openOnInit property of each item - ); - // This state keeps track of which accordion items are open - const onClick = (idx: number) => { - setOpenStates((prev) => prev.map((isOpen, index) => (index === idx ? !isOpen : isOpen))); - }; + const [openStates, setOpenStates] = useState(accordionItemsWithButtons.map((item) => item.openOnInit)); // Inits the component with the openOnInit prop + + const handleToggle = useCallback((index: number) => { + setOpenStates((prev) => prev.map((isOpen, i) => (i === index ? !isOpen : isOpen))); + }, []); return (
        - {accordionItemsWithButtons.map((item, idx) => ( + {accordionItemsWithButtons.map((item, index) => ( onClick(index)} - isOpen={openStates[idx]} - onClick={() => onClick(idx)} + openState={{ + isOpen: openStates[index], + toggle: () => handleToggle(index), + }} setClipboardContents={setClipboardContents} /> ))} diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index 79cf5a8e..6471d394 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -22,7 +22,7 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; import type { ReactNode } from 'react'; -import { useEffect } from 'react'; +import { useEffect, MouseEvent } from 'react'; import type { Theme } from '../../theme'; import { useThemeContext } from '../../theme/ThemeContext'; import ReadMoreText from '../ReadMoreText'; @@ -34,16 +34,17 @@ export type AccordionData = { openOnInit: boolean; description: string; content: ReactNode | string; - downloadButton?: ReactNode; + downloadButton?: ReactNode; // Optional for now, just for testing, will need to be mandatory later due to }; export type AccordionItemProps = { setClipboardContents: (currentSchema: string) => void; data: AccordionData; - isOpen: boolean; - onClick: () => void; - index: number; // Index of the accordion item, used for accessibility and unique identification to avoid the issue with duplicates - setIsOpen: (index: number) => void; + index: number; + openState: { + isOpen: boolean; + toggle: () => void; + }; }; const accordionItemStyle = (theme: Theme) => css` @@ -137,33 +138,37 @@ const contentInnerContainerStyle = (theme: Theme) => css` ${theme.typography?.data}; `; -const AccordionItem = ({ index, data, isOpen, onClick, setClipboardContents, setIsOpen }: AccordionItemProps) => { +const AccordionItem = ({ index, data, openState, setClipboardContents }: AccordionItemProps) => { const theme = useThemeContext(); const { downloadButton, description, title, content } = data; const { ChevronDown, Hash } = theme.icons; + + const indexString = index.toString(); + useEffect(() => { if (window.location.hash === `#${index}`) { if (!data.openOnInit) { - setIsOpen(index); + openState.toggle(); } - document.getElementById(index.toString())?.scrollIntoView({ behavior: 'smooth' }); + document.getElementById(indexString)?.scrollIntoView({ behavior: 'smooth' }); } }, []); + + const hashOnClick = (event: MouseEvent) => { + event.stopPropagation(); + window.location.hash = `#${index}`; + setClipboardContents(window.location.href); + }; + return ( -
      • +
      • -
        - +
        +
        {title} - { - window.location.hash = `#${index}`; - setClipboardContents(window.location.href); - }} - > + @@ -176,7 +181,7 @@ const AccordionItem = ({ index, data, isOpen, onClick, setClipboardContents, set {downloadButton}

        -
        +
        {content}
        From 94d33388c497afe50f0ef7f46c2c3dc112ae2e72 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 19 Jun 2025 09:58:28 -0400 Subject: [PATCH 113/217] remove over engineering --- .../ui/src/common/Accordion/Accordion.tsx | 26 +++++++------------ .../DownloadTemplatesButton.tsx | 6 +++-- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index 0fdc398c..63a48f57 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -35,21 +35,13 @@ const Accordion = ({ accordionItems }: AccordionProps) => { // Some placeholder buttons just for testing, THIS IS NOT A SOLUTION. //TODO: Some random buttons that we want to add to the accordion items, we will actually need to figure out some schema filtering logic, // and download based on that, but for now we just add a button to each item. - const accordionItemsWithButtons = useMemo(() => { - return accordionItems.map((item) => ({ - ...item, - downloadButton: ( - - ), - })); - }, [accordionItems]); + + const accordionItemsWithButtons = accordionItems.map((item) => ({ + ...item, + downloadButton: ( + + ), + })); // This state keeps track of the clipboard contents, which can be set by the accordion items. // Each individual accordion item can set this state when it's tag has been clicked, however only one item can be set at a time. @@ -93,9 +85,9 @@ const Accordion = ({ accordionItems }: AccordionProps) => { // This state keeps track of the currently open accordion item index via a boolean array, since each item can be opened or closed independently. const [openStates, setOpenStates] = useState(accordionItemsWithButtons.map((item) => item.openOnInit)); // Inits the component with the openOnInit prop - const handleToggle = useCallback((index: number) => { + const handleToggle = (index: number) => { setOpenStates((prev) => prev.map((isOpen, i) => (i === index ? !isOpen : isOpen))); - }, []); + }; return (
          diff --git a/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx index 86b47eb1..a154f7c5 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/DownloadTemplatesButton.tsx @@ -21,7 +21,7 @@ /** @jsxImportSource @emotion/react */ -import { useState, Dispatch, SetStateAction } from 'react'; +import { useState, Dispatch, SetStateAction, SyntheticEvent } from 'react'; import Button from '../../common/Button'; import { useThemeContext } from '../../theme/ThemeContext'; import { css } from '@emotion/react'; @@ -40,7 +40,9 @@ const downloadDictionary = async ( fetchUrl: string, name: string, version: string, + event: SyntheticEvent, ) => { + event.stopPropagation(); try { setIsLoading(true); const res = await fetch(fetchUrl); @@ -99,7 +101,7 @@ const DictionaryDownloadButton = ({ : undefined } icon={} - onClick={() => downloadDictionary(setIsLoading, fetchUrl, name, version)} + onClick={(e) => downloadDictionary(setIsLoading, fetchUrl, name, version, e)} disabled={disabled || isLoading} > Submission Templates From 9879efbaffaccfefa4eda3a2accdbd30ca30d1b6 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 19 Jun 2025 10:50:30 -0400 Subject: [PATCH 114/217] abstract download button out of the component, shouldn't be needing to do anything with download button except for passing props inside this component --- .../ui/src/common/Accordion/Accordion.tsx | 15 ++----------- .../ui/src/common/Accordion/AccordionItem.tsx | 17 +++++++++------ .../ui/stories/common/Accordion.stories.tsx | 21 +++++++++++++++++++ 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index 63a48f57..bc4f5c03 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -32,17 +32,6 @@ const accordionStyle = css` */ const Accordion = ({ accordionItems }: AccordionProps) => { - // Some placeholder buttons just for testing, THIS IS NOT A SOLUTION. - //TODO: Some random buttons that we want to add to the accordion items, we will actually need to figure out some schema filtering logic, - // and download based on that, but for now we just add a button to each item. - - const accordionItemsWithButtons = accordionItems.map((item) => ({ - ...item, - downloadButton: ( - - ), - })); - // This state keeps track of the clipboard contents, which can be set by the accordion items. // Each individual accordion item can set this state when it's tag has been clicked, however only one item can be set at a time. @@ -83,7 +72,7 @@ const Accordion = ({ accordionItems }: AccordionProps) => { }, [clipboardContents]); // This state keeps track of the currently open accordion item index via a boolean array, since each item can be opened or closed independently. - const [openStates, setOpenStates] = useState(accordionItemsWithButtons.map((item) => item.openOnInit)); // Inits the component with the openOnInit prop + const [openStates, setOpenStates] = useState(accordionItems.map((item) => item.openOnInit)); // Inits the component with the openOnInit prop const handleToggle = (index: number) => { setOpenStates((prev) => prev.map((isOpen, i) => (i === index ? !isOpen : isOpen))); @@ -91,7 +80,7 @@ const Accordion = ({ accordionItems }: AccordionProps) => { return (
            - {accordionItemsWithButtons.map((item, index) => ( + {accordionItems.map((item, index) => ( css` const AccordionItem = ({ index, data, openState, setClipboardContents }: AccordionItemProps) => { const theme = useThemeContext(); - const { downloadButton, description, title, content } = data; + const { description, title, content, dictionaryDownloadButtonProps } = data; const { ChevronDown, Hash } = theme.icons; const indexString = index.toString(); + const windowLocationHash = `#${index}`; useEffect(() => { - if (window.location.hash === `#${index}`) { + if (window.location.hash === windowLocationHash) { if (!data.openOnInit) { openState.toggle(); } @@ -156,7 +161,7 @@ const AccordionItem = ({ index, data, openState, setClipboardContents }: Accordi const hashOnClick = (event: MouseEvent) => { event.stopPropagation(); - window.location.hash = `#${index}`; + window.location.hash = windowLocationHash; setClipboardContents(window.location.href); }; @@ -178,7 +183,7 @@ const AccordionItem = ({ index, data, openState, setClipboardContents }: Accordi )}
        - {downloadButton} +

      diff --git a/packages/ui/stories/common/Accordion.stories.tsx b/packages/ui/stories/common/Accordion.stories.tsx index 5f94a775..f1a4e93e 100644 --- a/packages/ui/stories/common/Accordion.stories.tsx +++ b/packages/ui/stories/common/Accordion.stories.tsx @@ -3,6 +3,8 @@ import type { Meta, StoryObj } from '@storybook/react'; import themeDecorator from '../themeDecorator'; import Accordion from '../../src/common/Accordion/Accordion'; +import { DictionaryDownloadButtonProps } from '../../src/viewer-table/InteractionPanel/DownloadTemplatesButton'; + const meta = { component: Accordion, title: 'Common/Accordion', @@ -13,6 +15,14 @@ const meta = { export default meta; type Story = StoryObj; +const mockDictionaryDownloadButtonProps: DictionaryDownloadButtonProps = { + version: '1.0', + name: 'example-dictionary', + lecternUrl: 'localhost:3031', + fileType: 'tsv', + iconOnly: true, + disabled: true, +}; export const Default: Story = { args: { accordionItems: [ @@ -22,6 +32,7 @@ export const Default: Story = { description: '{Schema: description} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', content: 'Content for item 1', + dictionaryDownloadButtonProps: mockDictionaryDownloadButtonProps, }, { title: '{Schema:name}', @@ -30,6 +41,7 @@ export const Default: Story = { '{Schema: description} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', content: 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna.Adipisicing ea sunt ullamco voluptate tempor eu.Sint ex officiaLorem laborum mollit proident sunt culpa deserunt. officia enim. ', + dictionaryDownloadButtonProps: mockDictionaryDownloadButtonProps, }, { title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', @@ -38,6 +50,7 @@ export const Default: Story = { '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', content: 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + dictionaryDownloadButtonProps: mockDictionaryDownloadButtonProps, }, { title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', @@ -46,6 +59,7 @@ export const Default: Story = { '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', content: 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + dictionaryDownloadButtonProps: mockDictionaryDownloadButtonProps, }, { title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', @@ -54,6 +68,7 @@ export const Default: Story = { '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', content: 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + dictionaryDownloadButtonProps: mockDictionaryDownloadButtonProps, }, { title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', @@ -62,6 +77,7 @@ export const Default: Story = { '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', content: 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + dictionaryDownloadButtonProps: mockDictionaryDownloadButtonProps, }, { title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', @@ -70,6 +86,7 @@ export const Default: Story = { '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', content: 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + dictionaryDownloadButtonProps: mockDictionaryDownloadButtonProps, }, { title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', @@ -78,6 +95,7 @@ export const Default: Story = { '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', content: 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + dictionaryDownloadButtonProps: mockDictionaryDownloadButtonProps, }, { title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', @@ -86,6 +104,7 @@ export const Default: Story = { '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', content: 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + dictionaryDownloadButtonProps: mockDictionaryDownloadButtonProps, }, { title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', @@ -94,6 +113,7 @@ export const Default: Story = { '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', content: 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + dictionaryDownloadButtonProps: mockDictionaryDownloadButtonProps, }, { title: '{An_unnecessarily_lengthy_name_for_a_schema:with_it’s_name_include_of_course}', @@ -102,6 +122,7 @@ export const Default: Story = { '{Schema: description} Do MagNisiDoAmet sit adipisicing dolore incididunt minim.lore pariatur sit ex eiusmod Lorem do voluptate id aliquip occaecat duis. laMinim fugiatEnim qui irure incididunt ex proident qui reprehenderit enim. deserunt irure mollit do aute do voluptate sint veniam commodo excepteur officia.borum mollit aute eu ea dolor adipisicing et.na Cupidatat nostrud adipisicing nulla fugiat laboris laborum aliquip adipisicing minim incididunt laboris.non ipsum dolore anim eiusmod velit amet enim veniam ut ad est.fugiat nisi elit nisi non aliqua qui duis et consectetur.Labore proident qui id ad laborum mollit amet do officia sint qui mollit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Cillum ex qui pariatur sint est elit amet commodo dolore duis exercitation cupidatat anim deserunt. Commodo mollit labore et qui sit consectetur laborum eiusmod.', content: 'Paritur Lorem sint commodo deserunt duis nostrud. Quis cillum veniam dolor amet elit nulla. Pariatur sunt ex non minim labore et exercitation quis velit duis. Nisi minim commodo anim aute quis incididunt proident enim adipisicing eu do. Mollit tempor minim anim deserunt adipisicing. Magna esse labore eiusmod irure sunt cupidatat non et labore. Pariatur consectetur cupidatat ullamco dolor sit commodo proident cupidatat nulla occaecat qui ea. Ad nostrud magna quis anim veniam laboris do sint cillum nisi. Et sint enim eu proident ipsum. Deserunt ad ex non aliquip fugiat eiusmod tempor fugiat est et excepteur consequat excepteur ipsum.Quis consectetur nostrud proident laboris veniam eiusmod ullamco culpa esse reprehenderit esse proident sint. Ea aliqua laboris veniam tempor eiusmod sunt consequat nisi mollit. Veniam enim est cillum mollit sunt in non. Elit voluptate aliquip sunt fugiat aliqua elit incididunt fugiat ipsum eu mollit tempor. Officia magna est est cillum qui amet fugiat quis. Consectetur quis deserunt enim eiusmod est est sint quis consectetur nulla ea non. Magna ipsum aute laborum nisi duis tempor dolor ad cupidatat quis et pariatur.Sit ad irure laborum minim deserunt aute mollit fugiat minim laboris. Laborum aliqua occaecat dolore esse exercitation do elit aliquip amet proident laborum sint. Aute do cupidatat ut ea ipsum nisi veniam nulla ea est ex irure adipisicing adipisicing. In dolor aliquip non magna mollit reprehenderit sit fugiat excepteur. Pariatur tempor excepteur nulla tempor commodo reprehenderit deserunt sint nisi aute. Dolore est aliquip eu nisi ea nisi mollit culpa magna. ', + dictionaryDownloadButtonProps: mockDictionaryDownloadButtonProps, }, ], }, From 4565336b1805f1b2283020aba41e4df9eb64211c Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 19 Jun 2025 10:55:13 -0400 Subject: [PATCH 115/217] fix error --- .../viewer-table/InteractionPanel/AttributeFilterDropdown.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx index 745a1ed8..1bf84dec 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import Dropdown from '../../common/Dropdown/Dropdown'; export type FilterDropdownProps = { From 1bf7e66e782805093ede17adf0d2c254bb5054ca Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 19 Jun 2025 11:33:02 -0400 Subject: [PATCH 116/217] make sure that there is no sharing of the types. --- .../DictionaryVersionSwitcher.tsx | 24 +++++++-------- .../InteractionPanel/InteractionPanel.tsx | 18 +++++++++-- .../DictionaryVersionSwitcher.stories.tsx | 30 +++++-------------- 3 files changed, 33 insertions(+), 39 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx index fbe28ca6..85416449 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx @@ -23,32 +23,30 @@ import { Dictionary } from '@overture-stack/lectern-dictionary'; import Dropdown from '../../common/Dropdown/Dropdown'; import { useThemeContext } from '../../theme/ThemeContext'; -import { FilterOptions } from './AttributeFilterDropdown'; type VersionSwitcherProps = { - config: DictionaryConfig; - disabled?: boolean; -}; -export type DictionaryConfig = { - lecternUrl: string; - dictionaryIndex: number; dictionaryData: Dictionary[]; + dictionaryIndex: number; onVersionChange: (index: number) => void; - filters: FilterOptions[]; - setFilters: (filters: FilterOptions[]) => void; + disabled?: boolean; }; -const VersionSwitcher = ({ config, disabled = false }: VersionSwitcherProps) => { +const VersionSwitcher = ({ + dictionaryData, + dictionaryIndex, + onVersionChange, + disabled = false, +}: VersionSwitcherProps) => { const theme = useThemeContext(); const { History } = theme.icons; - const versionSwitcherObjectArray = config.dictionaryData?.map((dictionary: Dictionary, index: number) => { + const versionSwitcherObjectArray = dictionaryData?.map((dictionary: Dictionary, index: number) => { // TODO: We should either remove the version date stamp requirement or update the date to be dynamic via // lectern-client return { label: 'Version ' + dictionary?.version, action: () => { - config.onVersionChange(index); + onVersionChange(index); }, }; }); @@ -61,7 +59,7 @@ const VersionSwitcher = ({ config, disabled = false }: VersionSwitcherProps) => } menuItems={versionSwitcherObjectArray} - title={`Version ${config.dictionaryData?.[config.dictionaryIndex].version}`} + title={`Version ${dictionaryData?.[dictionaryIndex].version}`} disabled={disabled} /> ) diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index 9c165593..77a33ca5 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -27,7 +27,7 @@ import { Theme } from '../../theme'; import { useThemeContext } from '../../theme/ThemeContext'; import AttributeFilterDropdown, { FilterOptions } from './AttributeFilterDropdown'; import CollapseAllButton from './CollapseAllButton'; -import DictionaryVersionSwitcher, { DictionaryConfig } from './DictionaryVersionSwitcher'; +import DictionaryVersionSwitcher from './DictionaryVersionSwitcher'; import DownloadTemplatesButton from './DownloadTemplatesButton'; import ExpandAllButton from './ExpandAllButton'; import TableOfContentsDropdown from './TableOfContentsDropdown'; @@ -36,7 +36,14 @@ type InteractionPanelProps = { disabled?: boolean; setIsCollapsed: (isCollapsed: boolean) => void; onSelect: (schemaNameIndex: number) => void; - currDictionary: DictionaryConfig; + currDictionary: { + lecternUrl: string; + dictionaryIndex: number; + dictionaryData: Dictionary[]; + onVersionChange: (index: number) => void; + filters: FilterOptions[]; + setFilters: (filters: FilterOptions[]) => void; + }; }; const panelStyles = (theme: Theme) => css` @@ -82,7 +89,12 @@ const InteractionPanel = ({ disabled = false, setIsCollapsed, onSelect, currDict setIsCollapsed(false)} disabled={disabled} />
      - + alert(`Version changed to index: ${index}`), - filters: [], - setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), - }, + dictionaryData: MultipleDictionaryData, + dictionaryIndex: 0, + onVersionChange: (index: number) => alert(`Version changed to index: ${index}`), }; export const MultipleVersions: Story = { args: { ...mockProps, - config: { - ...mockProps.config, - dictionaryData: MultipleDictionaryData, - }, + dictionaryData: MultipleDictionaryData, }, }; export const SingleVersion: Story = { args: { ...mockProps, - config: { - ...mockProps.config, - dictionaryData: SingleDictionaryData, - }, + dictionaryData: SingleDictionaryData, }, }; export const EmptyArray: Story = { args: { ...mockProps, - config: { - ...mockProps.config, - dictionaryData: [], - }, + dictionaryData: [], }, }; export const DisabledWithMultipleVersions: Story = { args: { ...mockProps, - config: { - ...mockProps.config, - dictionaryData: MultipleDictionaryData, - }, + dictionaryData: MultipleDictionaryData, disabled: true, }, }; From 6a5a487ac47ea5552c268e5060d08b8c87ab554e Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 19 Jun 2025 11:39:47 -0400 Subject: [PATCH 117/217] fix cross referencing issues --- .../DictionaryVersionSwitcher.tsx | 24 +++++++-------- .../InteractionPanel/InteractionPanel.tsx | 18 +++++++++-- .../DictionaryVersionSwitcher.stories.tsx | 30 +++++-------------- 3 files changed, 33 insertions(+), 39 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx index fbe28ca6..85416449 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx @@ -23,32 +23,30 @@ import { Dictionary } from '@overture-stack/lectern-dictionary'; import Dropdown from '../../common/Dropdown/Dropdown'; import { useThemeContext } from '../../theme/ThemeContext'; -import { FilterOptions } from './AttributeFilterDropdown'; type VersionSwitcherProps = { - config: DictionaryConfig; - disabled?: boolean; -}; -export type DictionaryConfig = { - lecternUrl: string; - dictionaryIndex: number; dictionaryData: Dictionary[]; + dictionaryIndex: number; onVersionChange: (index: number) => void; - filters: FilterOptions[]; - setFilters: (filters: FilterOptions[]) => void; + disabled?: boolean; }; -const VersionSwitcher = ({ config, disabled = false }: VersionSwitcherProps) => { +const VersionSwitcher = ({ + dictionaryData, + dictionaryIndex, + onVersionChange, + disabled = false, +}: VersionSwitcherProps) => { const theme = useThemeContext(); const { History } = theme.icons; - const versionSwitcherObjectArray = config.dictionaryData?.map((dictionary: Dictionary, index: number) => { + const versionSwitcherObjectArray = dictionaryData?.map((dictionary: Dictionary, index: number) => { // TODO: We should either remove the version date stamp requirement or update the date to be dynamic via // lectern-client return { label: 'Version ' + dictionary?.version, action: () => { - config.onVersionChange(index); + onVersionChange(index); }, }; }); @@ -61,7 +59,7 @@ const VersionSwitcher = ({ config, disabled = false }: VersionSwitcherProps) => } menuItems={versionSwitcherObjectArray} - title={`Version ${config.dictionaryData?.[config.dictionaryIndex].version}`} + title={`Version ${dictionaryData?.[dictionaryIndex].version}`} disabled={disabled} /> ) diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index 9c165593..77a33ca5 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -27,7 +27,7 @@ import { Theme } from '../../theme'; import { useThemeContext } from '../../theme/ThemeContext'; import AttributeFilterDropdown, { FilterOptions } from './AttributeFilterDropdown'; import CollapseAllButton from './CollapseAllButton'; -import DictionaryVersionSwitcher, { DictionaryConfig } from './DictionaryVersionSwitcher'; +import DictionaryVersionSwitcher from './DictionaryVersionSwitcher'; import DownloadTemplatesButton from './DownloadTemplatesButton'; import ExpandAllButton from './ExpandAllButton'; import TableOfContentsDropdown from './TableOfContentsDropdown'; @@ -36,7 +36,14 @@ type InteractionPanelProps = { disabled?: boolean; setIsCollapsed: (isCollapsed: boolean) => void; onSelect: (schemaNameIndex: number) => void; - currDictionary: DictionaryConfig; + currDictionary: { + lecternUrl: string; + dictionaryIndex: number; + dictionaryData: Dictionary[]; + onVersionChange: (index: number) => void; + filters: FilterOptions[]; + setFilters: (filters: FilterOptions[]) => void; + }; }; const panelStyles = (theme: Theme) => css` @@ -82,7 +89,12 @@ const InteractionPanel = ({ disabled = false, setIsCollapsed, onSelect, currDict setIsCollapsed(false)} disabled={disabled} />
      - + alert(`Version changed to index: ${index}`), - filters: [], - setFilters: (filters) => alert(`Filters updated: ${JSON.stringify(filters)}`), - }, + dictionaryData: MultipleDictionaryData, + dictionaryIndex: 0, + onVersionChange: (index: number) => alert(`Version changed to index: ${index}`), }; export const MultipleVersions: Story = { args: { ...mockProps, - config: { - ...mockProps.config, - dictionaryData: MultipleDictionaryData, - }, + dictionaryData: MultipleDictionaryData, }, }; export const SingleVersion: Story = { args: { ...mockProps, - config: { - ...mockProps.config, - dictionaryData: SingleDictionaryData, - }, + dictionaryData: SingleDictionaryData, }, }; export const EmptyArray: Story = { args: { ...mockProps, - config: { - ...mockProps.config, - dictionaryData: [], - }, + dictionaryData: [], }, }; export const DisabledWithMultipleVersions: Story = { args: { ...mockProps, - config: { - ...mockProps.config, - dictionaryData: MultipleDictionaryData, - }, + dictionaryData: MultipleDictionaryData, disabled: true, }, }; From 92b43c936ec38733d2aaab271ecfc7172a5b3997 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 19 Jun 2025 15:51:43 -0400 Subject: [PATCH 118/217] useable state for the table scrolling --- packages/ui/src/viewer-table/DataTable/SchemaTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx index 057c3eee..b240489a 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx @@ -67,12 +67,12 @@ const tableStyle = css` const fixedTableStyle = css` ${tableStyle} - width: 300px; // Match the container width + width: 300px; `; const scrollableTableStyle = css` ${tableStyle} - min-width: 800px; // Ensure table is wide enough to scroll + min-width: 800px; `; const thStyle = css` From 5ccf451628c7533a71fe4f1e73d2c188868a9330 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 20 Jun 2025 11:17:33 -0400 Subject: [PATCH 119/217] commit --- .../viewer-table/DataTable/SchemaTable.tsx | 10 +- .../DataTable/SchemaTableWithAccordion.tsx | 8 +- .../viewer-table/DataTable/TableHeader.tsx | 16 +- .../src/viewer-table/DataTable/TableRow.tsx | 8 +- .../src/viewer-table/DataTable/tableInit.tsx | 69 +++++-- packages/ui/stories/fixtures/advanced.json | 193 ++++++++++++++++-- .../viewer-table/SchemaTable.stories.tsx | 8 +- .../SchemaTableWithAccordion.stories.tsx | 2 +- 8 files changed, 256 insertions(+), 58 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx index 86ced4c5..7200018b 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx @@ -22,7 +22,8 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import type { Schema, SchemaField } from '@overture-stack/lectern-dictionary'; +import type { Schema, SchemaField, Dictionary } from '@overture-stack/lectern-dictionary'; +import { replaceReferences } from '@overture-stack/lectern-dictionary'; import { getCoreRowModel, HeaderGroup, useReactTable } from '@tanstack/react-table'; import TableHeader from './TableHeader'; import TableRow from './TableRow'; @@ -31,6 +32,7 @@ import { useMemo, useState } from 'react'; type SchemaTableProps = { schema: Schema; + dictionary: Dictionary; }; const sectionStyle = css` @@ -43,11 +45,13 @@ const tableStyle = css` margin-top: 8px; `; -const SchemaTable = ({ schema }: SchemaTableProps) => { +const SchemaTable = ({ schema, dictionary }: SchemaTableProps) => { const [clipboardContents, setClipboardContents] = useState(null); const [isCopying, setIsCopying] = useState(false); const [copySuccess, setCopySuccess] = useState(false); + const resolvedDictionary = replaceReferences(dictionary); + const handleCopy = (text: string) => { if (isCopying) { return; // We don't wanna copy if we are already copying @@ -75,7 +79,7 @@ const SchemaTable = ({ schema }: SchemaTableProps) => { }; const table = useReactTable({ - data: schema.fields || [], + data: resolvedDictionary.schemas, columns: getSchemaBaseColumns(setClipboardContents), getCoreRowModel: getCoreRowModel(), }); diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx index 6b92517c..2ff770fd 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx @@ -1,17 +1,19 @@ -import { Schema } from '@overture-stack/lectern-dictionary'; +import React from 'react'; +import { Schema, Dictionary } from '@overture-stack/lectern-dictionary'; import Accordion from '../../common/Accordion/Accordion'; import { AccordionData } from '../../common/Accordion/AccordionItem'; import SchemaTable from './SchemaTable'; type props = { schema: Schema; + dictionary: Dictionary; }; -const SchemaTableWithAccordion = ({ schema }: props) => { +const SchemaTableWithAccordion = ({ schema, dictionary }: props) => { const accordionItems: Array = [ { title: schema.name, description: schema.description ?? '', openOnInit: true, - content: , + content: , }, ]; return ; diff --git a/packages/ui/src/viewer-table/DataTable/TableHeader.tsx b/packages/ui/src/viewer-table/DataTable/TableHeader.tsx index ecd181ee..9c1ea300 100644 --- a/packages/ui/src/viewer-table/DataTable/TableHeader.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableHeader.tsx @@ -23,9 +23,11 @@ import { css } from '@emotion/react'; import { flexRender, HeaderGroup } from '@tanstack/react-table'; -import { Lato } from '../styles/typography'; +import { useThemeContext } from '../../theme/ThemeContext'; +import { Theme } from '../../theme'; -const thStyle = css` +const thStyle = (theme: Theme) => css` + ${theme.typography.heading}; background: #e5edf3; text-align: left; padding: 12px; @@ -37,17 +39,11 @@ type TableHeaderProps = { }; const TableHeader = ({ headerGroup }: TableHeaderProps) => { + const theme = useThemeContext(); return ( {headerGroup.headers.map((header) => ( - + {flexRender(header.column.columnDef.header, header.getContext())} ))} diff --git a/packages/ui/src/viewer-table/DataTable/TableRow.tsx b/packages/ui/src/viewer-table/DataTable/TableRow.tsx index 35514d67..903271f3 100644 --- a/packages/ui/src/viewer-table/DataTable/TableRow.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableRow.tsx @@ -24,12 +24,15 @@ import { css } from '@emotion/react'; import { Row, flexRender } from '@tanstack/react-table'; import ReadMoreText from '../../common/ReadMoreText'; +import { useThemeContext } from '../../theme/ThemeContext'; +import { Theme } from '../../theme'; const rowStyle = (index: number) => css` background-color: ${index % 2 === 0 ? '' : '#F5F7F8'}; `; -const tdStyle = css` +const tdStyle = (theme: Theme) => css` + ${theme.typography.data} padding: 12px; border-bottom: 1px solid #eaeaea; max-width: 30vw; @@ -45,11 +48,12 @@ type TableRowProps = { }; const TableRow = ({ row, index }: TableRowProps) => { + const theme = useThemeContext(); return ( {row.getVisibleCells().map((cell) => { return ( - + {flexRender(cell.column.columnDef.cell, cell.getContext())} ); diff --git a/packages/ui/src/viewer-table/DataTable/tableInit.tsx b/packages/ui/src/viewer-table/DataTable/tableInit.tsx index 0e0e4dc2..7ddf96b6 100644 --- a/packages/ui/src/viewer-table/DataTable/tableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/tableInit.tsx @@ -22,11 +22,12 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import { SchemaField } from '@overture-stack/lectern-dictionary'; +import { SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { CellContext, createColumnHelper } from '@tanstack/react-table'; import { useThemeContext } from '../../theme/ThemeContext'; import { useEffect } from 'react'; import { Theme } from '../../theme'; +import ReadMoreText from '../../common/ReadMoreText'; // This file is responsible for defining the columns of the schema table, depending on user defined types and schemas. const hashIconStyle = (theme: Theme) => css` @@ -60,7 +61,7 @@ const renderSchemaField = (field: CellContext, setClipboard const text = Array.isArray(examples) ? examples.join(', ') : String(examples); return ( -
      +
      {label} {text}
      ); @@ -90,13 +91,13 @@ const renderSchemaField = (field: CellContext, setClipboard gap: 10px; `} > -
      +
      {fieldName}
      -
      {field.row.original.description}
      +
      {field.row.original.description}
      {renderExamples()}
      ); @@ -104,7 +105,7 @@ const renderSchemaField = (field: CellContext, setClipboard export const getSchemaBaseColumns = (setClipboardContents: (curr: string) => void) => [ columnHelper.accessor('name', { - header: 'SchemaField', + header: 'Fields', cell: (field) => { // TODO: Open issue in lectern to make displayName a known property of field return renderSchemaField(field, setClipboardContents); @@ -120,16 +121,20 @@ export const getSchemaBaseColumns = (setClipboardContents: (curr: string) => voi }, { id: 'required', - header: 'Required', - cell: (required) => (required.getValue() ? 'Yes' : 'No'), + header: 'Attribute', + cell: (required) => { + const theme: Theme = useThemeContext(); + return
      {required.getValue() ? 'Required' : 'Optional'}
      ; + }, }, ), columnHelper.accessor('valueType', { header: 'Type', cell: (type) => { const { valueType, isArray, delimiter } = type.row.original; + const theme: Theme = useThemeContext(); return ( -
      +
      {valueType} {isArray} {delimiter} @@ -137,12 +142,48 @@ export const getSchemaBaseColumns = (setClipboardContents: (curr: string) => voi ); }, }), - columnHelper.accessor((row) => row.meta?.examples ?? [], { - id: 'examples', - header: 'Examples', - cell: (examples) => { - const value = examples.getValue(); - return Array.isArray(value) ? value.join(', ') : value; + columnHelper.accessor((row) => row.restrictions ?? {}, { + id: 'restrictions', + header: 'Restrictions', + cell: (restrictions) => { + const theme: Theme = useThemeContext(); + + const restrictionsObj: SchemaRestrictions = restrictions.getValue(); + if (!restrictionsObj || Object.keys(restrictionsObj).length === 0) { + return
      None
      ; + } + + const restrictionItems: string[] = []; // This is the array that we push everything into. + + if ('regex' in restrictionsObj && restrictionsObj.regex) { + const regexValue = + Array.isArray(restrictionsObj.regex) ? restrictionsObj.regex.join(', ') : restrictionsObj.regex; + restrictionItems.push(regexValue); + } + + if ('codeList' in restrictionsObj && restrictionsObj.codeList) { + const value = + Array.isArray(restrictionsObj.codeList) ? + restrictionsObj.codeList.join(', ') + : (restrictionsObj.codeList as any).$ref || restrictionsObj.codeList; + restrictionItems.push(value); + } + + if ('range' in restrictionsObj && restrictionsObj.range) { + restrictionItems.push(JSON.stringify(restrictionsObj.range)); + } + + if ('unique' in restrictionsObj && restrictionsObj.unique) { + restrictionItems.push('Unique'); + } + + return ( +
      + {restrictionItems.length > 0 ? + {restrictionItems.join('; ')} + : 'None'} +
      + ); }, }), ]; diff --git a/packages/ui/stories/fixtures/advanced.json b/packages/ui/stories/fixtures/advanced.json index 2b77d1bc..91fb916c 100644 --- a/packages/ui/stories/fixtures/advanced.json +++ b/packages/ui/stories/fixtures/advanced.json @@ -1,35 +1,184 @@ { - "name": "Advanced", - "version": "1.0", - "schemas": [ - { - "name": "empty", - "description": "An empty schema with no fields.", - "fields": [] + "name": "Hockey Game Stats", + "version": "1.0.0", + "meta": { + "description": "A comprehensive data dictionary for tracking statistics in a hockey game, with a focus on providing rich examples for all supported features.", + "author": "Claude AI" + }, + "references": { + "maple_leafs_roster": { + "description": "The official roster for the Toronto Maple Leafs.", + "values": [ + "Matthews", + "Nylander", + "Marner", + "Tavares", + "Rielly", + "Domi", + "Bertuzzi", + "Knies", + "McMann", + "Holmberg", + "Kampf", + "Reaves", + "Dewar", + "Lorentz", + "Robertson", + "Pacioretty", + "McCabe", + "Tanev", + "Ekman-Larsson", + "Benoit", + "Timmins", + "Hakanpaa", + "Woll", + "Stolarz", + "Murray" + ] }, + "gender_options": { + "description": "Standard gender identification options including missing data categories.", + "values": [ + "Man", + "Woman", + "Another Gender", + "Prefer not to answer", + "Not applicable", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + } + }, + "schemas": [ { - "name": "single_field", - "description": "A schema with a single field of type string.", - "fields": [{ "name": "single_string_field", "valueType": "string" }] + "name": "game_statistics", + "description": "A schema for recording the statistics of a single hockey game.", + "fields": [ + { + "name": "game_id", + "valueType": "string", + "description": "A unique identifier assigned to any given game played by the Toronto Maple Leafs. Example(s): BLF_67_2INF, BOS_GM7_4_0", + "restrictions": { + "required": true, + "regex": "^[A-Z]{3}_[A-Z0-9]{2,5}_[A-Z0-9]{1,5}$" + } + }, + { + "name": "three_stars_of_the_game", + "valueType": "string", + "isArray": true, + "delimiter": "|", + "description": "The 'three stars' is a long-standing hockey tradition to recognize the three best individual performances in a game. This honor system dates back to the 1930s and has become one of hockey's most cherished traditions, serving as both a way to acknowledge outstanding play and provide fans with a memorable conclusion to each game. Example: \"Nylander|Matthews|Marner\"", + "restrictions": { + "required": true, + "codeList": { "$ref": "maple_leafs_roster" } + }, + "meta": { + "selection_rule": "Choose exactly three players from the roster." + } + }, + { + "name": "first_star", + "valueType": "string", + "restrictions": { + "required": true, + "codeList": { "$ref": "maple_leafs_roster" } + } + }, + { + "name": "second_star", + "valueType": "string", + "restrictions": { + "required": true, + "codeList": { "$ref": "maple_leafs_roster" } + } + }, + { + "name": "third_star", + "valueType": "string", + "restrictions": { + "required": true, + "codeList": { "$ref": "maple_leafs_roster" } + } + } + ], + "restrictions": { + "uniqueKeys": [["first_star", "second_star", "third_star"]] + } }, { - "name": "multiple_fields", - "description": "A schema with multiple fields of different types.", + "name": "player_profiles", + "description": "Contains profile information for each player.", "fields": [ - { "name": "string_field", "valueType": "string" }, - { "name": "integer_field", "valueType": "integer" }, - { "name": "boolean_field", "valueType": "boolean" } + { + "name": "player_name", + "valueType": "string", + "unique": true, + "restrictions": { + "required": true, + "codeList": { "$ref": "maple_leafs_roster" } + } + }, + { + "name": "games_played", + "valueType": "integer", + "restrictions": { + "required": true, + "range": { "min": 0 } + } + }, + { + "name": "is_active", + "valueType": "boolean", + "restrictions": { + "required": true + } + } ] }, { - "name": "primitives", - "description": "Includes one field of each primitive type without any restrictions. No Frills.", + "name": "game_events", + "description": "Records specific events that occur during a game.", "fields": [ - { "name": "boolean_field", "valueType": "boolean" }, - { "name": "integer_field", "valueType": "integer" }, - { "name": "number_field", "valueType": "number" }, - { "name": "string_field", "valueType": "string" } - ] + { + "name": "event_id", + "valueType": "string", + "unique": true, + "restrictions": { "required": true } + }, + { + "name": "game_id", + "valueType": "string", + "restrictions": { "required": true } + }, + { + "name": "player_name", + "valueType": "string", + "restrictions": { "required": true } + }, + { + "name": "event_type", + "valueType": "string", + "restrictions": { + "required": true, + "codeList": ["goal", "assist", "penalty"] + } + } + ], + "restrictions": { + "foreignKeys": [ + { + "schema": "game_statistics", + "mappings": [{ "local": "game_id", "foreign": "game_id" }] + }, + { + "schema": "player_profiles", + "mappings": [{ "local": "player_name", "foreign": "player_name" }] + } + ] + } } ] } diff --git a/packages/ui/stories/viewer-table/SchemaTable.stories.tsx b/packages/ui/stories/viewer-table/SchemaTable.stories.tsx index 90892aaa..e201cc95 100644 --- a/packages/ui/stories/viewer-table/SchemaTable.stories.tsx +++ b/packages/ui/stories/viewer-table/SchemaTable.stories.tsx @@ -3,9 +3,11 @@ // import { DictionaryHeader } from '#/viewer-table/DictionaryHeader'; import type { Meta, StoryObj } from '@storybook/react'; import SchemaTable from '../../src/viewer-table/DataTable/SchemaTable'; -import webUsers from '../fixtures/websiteUsersDataDictionary'; +import Advanced from '../fixtures/advanced.json'; import themeDecorator from '../themeDecorator'; -const schema = webUsers.schemas[0]; +import { Dictionary, Schema } from '@overture-stack/lectern-dictionary'; +const dictionary: Dictionary = Advanced as Dictionary; +const schema: Schema = dictionary.schemas[0]; const meta = { component: SchemaTable, title: 'Viewer - Table/Schema Table', @@ -18,5 +20,5 @@ type Story = StoryObj; export const Default: Story = { // args: { name: sampleDictionary.name, version: sampleDictionary.name, description: sampleDictionary.description }, - args: { schema }, + args: { schema, dictionary }, }; diff --git a/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx b/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx index 01bd48b7..d03150ee 100644 --- a/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx +++ b/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx @@ -20,5 +20,5 @@ type Story = StoryObj; export const Default: Story = { // args: { name: sampleDictionary.name, version: sampleDictionary.name, description: sampleDictionary.description }, - args: { schema }, + args: { schema, dictionary: Dictionary }, }; From d564435f2f5e04b16c05f28cfe654da3d4790a12 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 20 Jun 2025 11:42:58 -0400 Subject: [PATCH 120/217] testing --- packages/ui/src/viewer-table/DataTable/SchemaTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx index 7200018b..fa289255 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx @@ -79,7 +79,7 @@ const SchemaTable = ({ schema, dictionary }: SchemaTableProps) => { }; const table = useReactTable({ - data: resolvedDictionary.schemas, + data: resolvedDictionary.schemas[0].fields, columns: getSchemaBaseColumns(setClipboardContents), getCoreRowModel: getCoreRowModel(), }); From 6a23c4f63d6a046158f2ea812ed8c534a4c3e521 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 20 Jun 2025 12:29:49 -0400 Subject: [PATCH 121/217] FIX VITE CONFIG ISSUES --- .../ui/src/viewer-table/DataTable/TableRow.tsx | 9 ++++++++- .../src/viewer-table/DataTable/tableInit.tsx | 17 ++++------------- packages/ui/stories/fixtures/advanced.json | 10 +++++----- packages/ui/vite.config.ts | 18 ++++++++++++++++++ 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/TableRow.tsx b/packages/ui/src/viewer-table/DataTable/TableRow.tsx index c70195e4..15d1cf79 100644 --- a/packages/ui/src/viewer-table/DataTable/TableRow.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableRow.tsx @@ -55,7 +55,14 @@ const TableRow = ({ row, index }: TableRowProps) => { {row.getVisibleCells().map((cell) => { return ( - {flexRender(cell.column.columnDef.cell, cell.getContext())} + css` + ${theme.typography.data} + `} + maxLines={4} + > + {flexRender(cell.column.columnDef.cell, cell.getContext())} + ); })} diff --git a/packages/ui/src/viewer-table/DataTable/tableInit.tsx b/packages/ui/src/viewer-table/DataTable/tableInit.tsx index b591535a..2a223c27 100644 --- a/packages/ui/src/viewer-table/DataTable/tableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/tableInit.tsx @@ -24,10 +24,9 @@ import { css } from '@emotion/react'; import { SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { CellContext, createColumnHelper } from '@tanstack/react-table'; -import { useThemeContext } from '../../theme/ThemeContext'; import { useEffect } from 'react'; import { Theme } from '../../theme'; -import ReadMoreText from '../../common/ReadMoreText'; +import { useThemeContext } from '../../theme/ThemeContext'; // This file is responsible for defining the columns of the schema table, depending on user defined types and schemas. const hashIconStyle = (theme: Theme) => css` @@ -132,9 +131,8 @@ export const getSchemaBaseColumns = (setClipboardContents: (curr: string) => voi header: 'Data Type', cell: (type) => { const { valueType, isArray, delimiter } = type.row.original; - const theme: Theme = useThemeContext(); return ( -
      +
      {valueType} {isArray} {delimiter} @@ -164,7 +162,7 @@ export const getSchemaBaseColumns = (setClipboardContents: (curr: string) => voi if ('codeList' in restrictionsObj && restrictionsObj.codeList) { const value = Array.isArray(restrictionsObj.codeList) ? - restrictionsObj.codeList.join(', ') + restrictionsObj.codeList.join(',\n ') : (restrictionsObj.codeList as any).$ref || restrictionsObj.codeList; restrictionItems.push(value); } @@ -176,14 +174,7 @@ export const getSchemaBaseColumns = (setClipboardContents: (curr: string) => voi if ('unique' in restrictionsObj && restrictionsObj.unique) { restrictionItems.push('Unique'); } - - return ( -
      - {restrictionItems.length > 0 ? - {restrictionItems.join('; ')} - : 'None'} -
      - ); + return restrictionItems.length > 0 ?
      {restrictionItems.join('; ')}
      : 'None'; }, }), ]; diff --git a/packages/ui/stories/fixtures/advanced.json b/packages/ui/stories/fixtures/advanced.json index 91fb916c..7df9c89d 100644 --- a/packages/ui/stories/fixtures/advanced.json +++ b/packages/ui/stories/fixtures/advanced.json @@ -73,7 +73,7 @@ "description": "The 'three stars' is a long-standing hockey tradition to recognize the three best individual performances in a game. This honor system dates back to the 1930s and has become one of hockey's most cherished traditions, serving as both a way to acknowledge outstanding play and provide fans with a memorable conclusion to each game. Example: \"Nylander|Matthews|Marner\"", "restrictions": { "required": true, - "codeList": { "$ref": "maple_leafs_roster" } + "codeList": "#/maple_leafs_roster/values" }, "meta": { "selection_rule": "Choose exactly three players from the roster." @@ -84,7 +84,7 @@ "valueType": "string", "restrictions": { "required": true, - "codeList": { "$ref": "maple_leafs_roster" } + "codeList": "#/maple_leafs_roster/values" } }, { @@ -92,7 +92,7 @@ "valueType": "string", "restrictions": { "required": true, - "codeList": { "$ref": "maple_leafs_roster" } + "codeList": "#/maple_leafs_roster/values" } }, { @@ -100,7 +100,7 @@ "valueType": "string", "restrictions": { "required": true, - "codeList": { "$ref": "maple_leafs_roster" } + "codeList": "#/maple_leafs_roster/values" } } ], @@ -118,7 +118,7 @@ "unique": true, "restrictions": { "required": true, - "codeList": { "$ref": "maple_leafs_roster" } + "codeList": "#/maple_leafs_roster/values" } }, { diff --git a/packages/ui/vite.config.ts b/packages/ui/vite.config.ts index d274e700..a7d73b18 100644 --- a/packages/ui/vite.config.ts +++ b/packages/ui/vite.config.ts @@ -10,6 +10,24 @@ const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(file // More info at: https://storybook.js.org/docs/writing-tests/test-addon export default defineConfig({ + // To enable import of commonJS modules from the monorepo + build: { + commonjsOptions: { + include: [ + /@overture-stack\/lectern-client/, + /@overture-stack\/lectern-dictionary/, + /@overture-stack\/lectern-validation/, + ], + }, + }, + optimizeDeps: { + include: [ + '@overture-stack/lectern-client', + '@overture-stack/lectern-dictionary', + '@overture-stack/lectern-validation', + ], + }, + plugins: [tsconfigPaths()], test: { workspace: [ From 8876368b89e632f38ecf95567067128571eb538f Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 20 Jun 2025 14:38:03 -0400 Subject: [PATCH 122/217] slight refactor --- .../viewer-table/DataTable/SchemaTable.tsx | 6 +- .../Columns/AllowedValues.tsx | 37 ++++ .../Columns/Attribute.tsx | 0 .../Columns/CustomColumns.tsx | 0 .../Columns/DataType.tsx | 0 .../Columns/Fields.tsx | 81 ++++++++ .../SchemaTableInitialization/TableInit.tsx | 78 ++++++++ .../src/viewer-table/DataTable/tableInit.tsx | 180 ------------------ 8 files changed, 199 insertions(+), 183 deletions(-) create mode 100644 packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/AllowedValues.tsx create mode 100644 packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Attribute.tsx create mode 100644 packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/CustomColumns.tsx create mode 100644 packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx create mode 100644 packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Fields.tsx create mode 100644 packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx delete mode 100644 packages/ui/src/viewer-table/DataTable/tableInit.tsx diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx index fa289255..0c278f77 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx @@ -22,13 +22,13 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import type { Schema, SchemaField, Dictionary } from '@overture-stack/lectern-dictionary'; +import type { Dictionary, Schema, SchemaField } from '@overture-stack/lectern-dictionary'; import { replaceReferences } from '@overture-stack/lectern-dictionary'; import { getCoreRowModel, HeaderGroup, useReactTable } from '@tanstack/react-table'; +import { useMemo, useState } from 'react'; +import { getSchemaBaseColumns } from './SchemaTableInitialization/TableInit'; import TableHeader from './TableHeader'; import TableRow from './TableRow'; -import { getSchemaBaseColumns } from './tableInit'; -import { useMemo, useState } from 'react'; type SchemaTableProps = { schema: Schema; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/AllowedValues.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/AllowedValues.tsx new file mode 100644 index 00000000..37b6cb65 --- /dev/null +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/AllowedValues.tsx @@ -0,0 +1,37 @@ +/** @jsxImportSource @emotion/react */ +import { ConditionalRestriction, SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; +import { CellContext } from '@tanstack/react-table'; +import { useThemeContext } from '../../../../theme/ThemeContext'; + +export const renderAllowedValuesColumn = (restrictions: CellContext) => { + const theme = useThemeContext(); + + const restrictionsValue: SchemaRestrictions = restrictions.getValue(); + + const restrictionItems: string[] = []; // This is the array that we push everything into. + // If we have nothing we can return NNone + if (!restrictionsValue || Object.keys(restrictionsValue).length === 0) { + return
      None
      ; + } + + if ('regex' in restrictionsValue && restrictionsValue.regex) { + const regexValue = + Array.isArray(restrictionsValue.regex) ? restrictionsValue.regex.join(', ') : restrictionsValue.regex; + restrictionItems.push(regexValue); + } + + if ('codeList' in restrictionsValue && restrictionsValue.codeList) { + restrictionItems.push( + Array.isArray(restrictionsValue.codeList) ? restrictionsValue.codeList.join(',\n ') : restrictionsValue.codeList, + ); + } + + if ('range' in restrictionsValue && restrictionsValue.range) { + restrictionItems.push(JSON.stringify(restrictionsValue.range)); + } + + if ('unique' in restrictionsValue && restrictionsValue.unique) { + restrictionItems.push('Unique'); + } + return restrictionItems.length > 0 ?
      {restrictionItems.join('; ')}
      : 'None'; +}; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Attribute.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Attribute.tsx new file mode 100644 index 00000000..e69de29b diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/CustomColumns.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/CustomColumns.tsx new file mode 100644 index 00000000..e69de29b diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx new file mode 100644 index 00000000..e69de29b diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Fields.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Fields.tsx new file mode 100644 index 00000000..78be7cac --- /dev/null +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Fields.tsx @@ -0,0 +1,81 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; +import { SchemaField } from '@overture-stack/lectern-dictionary'; +import { CellContext } from '@tanstack/react-table'; +import React, { useEffect } from 'react'; +import { Theme } from '../../../../theme'; +import { useThemeContext } from '../../../../theme/ThemeContext'; + +const hashIconStyle = (theme: Theme) => css` + opacity: 0; + margin-left: 8px; + transition: opacity 0.2s ease; + border-bottom: 2px solid ${theme.colors.secondary}; + &:hover { + opacity: 1; + } +`; +export const renderFieldsColumn = ( + field: CellContext, + setClipboardContents: (curr: string) => void, +) => { + const theme = useThemeContext(); + const fieldName = field.row.original.name; + const fieldIndex = field.row.index; + + // In a Dictionary, there is no such thing as an examples field, however it is commonly apart of the meta + // due to project specs, we will render the examples here if they exist and have logic to handle it. + + const renderExamples = () => { + const examples = field.row.original.meta?.examples; + if (!examples) { + return null; + } + // the only way we can have more than one example is if we have an array of examples + + const count = Array.isArray(examples) ? examples.length : 1; + const label = count > 1 ? 'Examples:' : 'Example:'; + const text = Array.isArray(examples) ? examples.join(', ') : String(examples); + + return ( +
      + {label} {text} +
      + ); + }; + + const { Hash } = theme.icons; + + useEffect(() => { + const hashTarget = `field-${fieldIndex}`; + if (window.location.hash === `#${hashTarget}`) { + document.getElementById(hashTarget)?.scrollIntoView({ behavior: 'smooth' }); + } + }, []); + + const handleClick = () => { + const hashTarget = `field-${fieldIndex}`; + window.location.hash = `#${hashTarget}`; + setClipboardContents(window.location.href); + }; + + return ( +
      +
      + {fieldName} + + + +
      +
      {field.row.original.description}
      + {renderExamples()} +
      + ); +}; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx new file mode 100644 index 00000000..2b88aeaa --- /dev/null +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx @@ -0,0 +1,78 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** @jsxImportSource @emotion/react */ + +import { SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; +import { createColumnHelper } from '@tanstack/react-table'; +import { Theme } from '../../../theme'; +import { useThemeContext } from '../../../theme/ThemeContext'; +import { renderFieldsColumn } from './Columns/Fields'; +import { renderAllowedValuesColumn } from './Columns/AllowedValues'; +// This file is responsible for defining the columns of the schema table, depending on user defined types and schemas. + +const columnHelper = createColumnHelper(); + +export const getSchemaBaseColumns = (setClipboardContents: (curr: string) => void) => [ + columnHelper.accessor('name', { + header: 'Fields', + cell: (field) => { + return renderFieldsColumn(field, setClipboardContents); + }, + }), + columnHelper.accessor( + (row) => { + const restrictions = row.restrictions || {}; + if ('required' in restrictions && typeof restrictions !== 'function') { + return restrictions.required ?? false; + } + return false; + }, + { + id: 'required', + header: 'Attribute', + cell: (required) => { + const theme: Theme = useThemeContext(); + return
      {required.getValue() ? 'Required' : 'Optional'}
      ; + }, + }, + ), + columnHelper.accessor('valueType', { + header: 'Data Type', + cell: (type) => { + const { valueType, isArray, delimiter } = type.row.original; + return ( +
      + {valueType} + {isArray} + {delimiter} +
      + ); + }, + }), + columnHelper.accessor((row) => row.restrictions, { + id: 'restrictions', + header: 'Restrictions', + cell: (restrictions) => { + return renderAllowedValuesColumn(restrictions); + }, + }), +]; diff --git a/packages/ui/src/viewer-table/DataTable/tableInit.tsx b/packages/ui/src/viewer-table/DataTable/tableInit.tsx deleted file mode 100644 index 2a223c27..00000000 --- a/packages/ui/src/viewer-table/DataTable/tableInit.tsx +++ /dev/null @@ -1,180 +0,0 @@ -/* - * - * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved - * - * This program and the accompanying materials are made available under the terms of - * the GNU Affero General Public License v3.0. You should have received a copy of the - * GNU Affero General Public License along with this program. - * If not, see . - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -/** @jsxImportSource @emotion/react */ - -import { css } from '@emotion/react'; -import { SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; -import { CellContext, createColumnHelper } from '@tanstack/react-table'; -import { useEffect } from 'react'; -import { Theme } from '../../theme'; -import { useThemeContext } from '../../theme/ThemeContext'; -// This file is responsible for defining the columns of the schema table, depending on user defined types and schemas. - -const hashIconStyle = (theme: Theme) => css` - opacity: 0; - margin-left: 8px; - transition: opacity 0.2s ease; - border-bottom: 2px solid ${theme.colors.secondary}; - &:hover { - opacity: 1; - } -`; -const columnHelper = createColumnHelper(); - -const renderSchemaField = (field: CellContext, setClipboardContents: (curr: string) => void) => { - const theme = useThemeContext(); - const fieldName = field.row.original.name; - const fieldIndex = field.row.index; - - // In a Dictionary, there is no such thing as an examples field, however it is commonly apart of the meta - // due to project specs, we will render the examples here if they exist and have logic to handle it. - - const renderExamples = () => { - const examples = field.row.original.meta?.examples; - if (!examples) { - return null; - } - // the only way we can have more than one example is if we have an array of examples - - const count = Array.isArray(examples) ? examples.length : 1; - const label = count > 1 ? 'Examples:' : 'Example:'; - const text = Array.isArray(examples) ? examples.join(', ') : String(examples); - - return ( -
      - {label} {text} -
      - ); - }; - - const { Hash } = theme.icons; - - useEffect(() => { - const hashTarget = `field-${fieldIndex}`; - if (window.location.hash === `#${hashTarget}`) { - document.getElementById(hashTarget)?.scrollIntoView({ behavior: 'smooth' }); - } - }, []); - - const handleClick = () => { - const hashTarget = `field-${fieldIndex}`; - window.location.hash = `#${hashTarget}`; - setClipboardContents(window.location.href); - }; - - return ( -
      -
      - {fieldName} - - - -
      -
      {field.row.original.description}
      - {renderExamples()} -
      - ); -}; - -export const getSchemaBaseColumns = (setClipboardContents: (curr: string) => void) => [ - columnHelper.accessor('name', { - header: 'Fields', - cell: (field) => { - // TODO: Open issue in lectern to make displayName a known property of field - return renderSchemaField(field, setClipboardContents); - }, - }), - columnHelper.accessor( - (row) => { - const restrictions = row.restrictions || {}; - if ('required' in restrictions && typeof restrictions !== 'function') { - return restrictions.required ?? false; - } - return false; - }, - { - id: 'required', - header: 'Attribute', - cell: (required) => { - const theme: Theme = useThemeContext(); - return
      {required.getValue() ? 'Required' : 'Optional'}
      ; - }, - }, - ), - columnHelper.accessor('valueType', { - header: 'Data Type', - cell: (type) => { - const { valueType, isArray, delimiter } = type.row.original; - return ( -
      - {valueType} - {isArray} - {delimiter} -
      - ); - }, - }), - columnHelper.accessor((row) => row.restrictions ?? {}, { - id: 'restrictions', - header: 'Restrictions', - cell: (restrictions) => { - const theme: Theme = useThemeContext(); - - const restrictionsObj: SchemaRestrictions = restrictions.getValue(); - if (!restrictionsObj || Object.keys(restrictionsObj).length === 0) { - return
      None
      ; - } - - const restrictionItems: string[] = []; // This is the array that we push everything into. - - if ('regex' in restrictionsObj && restrictionsObj.regex) { - const regexValue = - Array.isArray(restrictionsObj.regex) ? restrictionsObj.regex.join(', ') : restrictionsObj.regex; - restrictionItems.push(regexValue); - } - - if ('codeList' in restrictionsObj && restrictionsObj.codeList) { - const value = - Array.isArray(restrictionsObj.codeList) ? - restrictionsObj.codeList.join(',\n ') - : (restrictionsObj.codeList as any).$ref || restrictionsObj.codeList; - restrictionItems.push(value); - } - - if ('range' in restrictionsObj && restrictionsObj.range) { - restrictionItems.push(JSON.stringify(restrictionsObj.range)); - } - - if ('unique' in restrictionsObj && restrictionsObj.unique) { - restrictionItems.push('Unique'); - } - return restrictionItems.length > 0 ?
      {restrictionItems.join('; ')}
      : 'None'; - }, - }), -]; From 24cc1a4674735ab6249fd6ef818bbbc4d6998d91 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 20 Jun 2025 22:31:08 -0400 Subject: [PATCH 123/217] Allowed Values working... kinda --- .../Columns/AllowedValues.tsx | 58 +++++++++++++++---- .../SchemaTableInitialization/TableInit.tsx | 6 +- packages/ui/stories/fixtures/advanced.json | 2 +- 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/AllowedValues.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/AllowedValues.tsx index 37b6cb65..f776a220 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/AllowedValues.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/AllowedValues.tsx @@ -1,23 +1,44 @@ /** @jsxImportSource @emotion/react */ -import { ConditionalRestriction, SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; +import { RestrictionRange, SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { CellContext } from '@tanstack/react-table'; -import { useThemeContext } from '../../../../theme/ThemeContext'; -export const renderAllowedValuesColumn = (restrictions: CellContext) => { - const theme = useThemeContext(); +const handleRange = (range: RestrictionRange, restrictionItems: string[]) => { + if (range.min && range.max) { + restrictionItems.push('Min: ' + range.min + '\nMax: ' + range.max); + } else if (range.min) { + restrictionItems.push('Min: ' + range.min); + } else if (range.max) { + restrictionItems.push('Max: ' + range.max); + } else if (range.exclusiveMin) { + restrictionItems.push('Greater than ' + range.exclusiveMin); + } else if (range.exclusiveMax) { + restrictionItems.push('Less than ' + range.exclusiveMax); + } +}; +export const renderAllowedValuesColumn = (restrictions: CellContext) => { + const schemaField: SchemaField = restrictions.row.original; const restrictionsValue: SchemaRestrictions = restrictions.getValue(); + const restrictionItems: string[] = []; - const restrictionItems: string[] = []; // This is the array that we push everything into. - // If we have nothing we can return NNone + // If we have nothing we can return None if (!restrictionsValue || Object.keys(restrictionsValue).length === 0) { - return
      None
      ; + return 'None'; + } + + if ('if' in restrictionsValue && restrictionsValue.if) { + restrictionItems.push('CONDITIONAL RESTRICTION, PART 5'); + } + + // Type narrowing and pushing appropriate values from the restrictions into the restrictionItems array + if ('required' in restrictionsValue && restrictionsValue.required) { + restrictionItems.push('Required'); } if ('regex' in restrictionsValue && restrictionsValue.regex) { const regexValue = Array.isArray(restrictionsValue.regex) ? restrictionsValue.regex.join(', ') : restrictionsValue.regex; - restrictionItems.push(regexValue); + restrictionItems.push('Must Match Pattern: ' + regexValue + '\nSee field description for examples.'); } if ('codeList' in restrictionsValue && restrictionsValue.codeList) { @@ -27,11 +48,28 @@ export const renderAllowedValuesColumn = (restrictions: CellContext 0 ?
      {restrictionItems.join('; ')}
      : 'None'; + + if (schemaField.isArray && restrictionItems.length > 0) { + restrictionItems.push(`delimited by "${schemaField.delimiter ?? ','}"`); + } + + if (schemaField.unique) { + restrictionItems.push('Must be unique'); + } + + return restrictionItems.length > 0 ? restrictionItems.join('\n') : 'None'; }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx index 2b88aeaa..7b8a66f4 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx @@ -68,9 +68,9 @@ export const getSchemaBaseColumns = (setClipboardContents: (curr: string) => voi ); }, }), - columnHelper.accessor((row) => row.restrictions, { - id: 'restrictions', - header: 'Restrictions', + columnHelper.accessor((row: SchemaField) => row.restrictions, { + id: 'Attribute', + header: 'Attribute', cell: (restrictions) => { return renderAllowedValuesColumn(restrictions); }, diff --git a/packages/ui/stories/fixtures/advanced.json b/packages/ui/stories/fixtures/advanced.json index 7df9c89d..293e20d8 100644 --- a/packages/ui/stories/fixtures/advanced.json +++ b/packages/ui/stories/fixtures/advanced.json @@ -73,7 +73,7 @@ "description": "The 'three stars' is a long-standing hockey tradition to recognize the three best individual performances in a game. This honor system dates back to the 1930s and has become one of hockey's most cherished traditions, serving as both a way to acknowledge outstanding play and provide fans with a memorable conclusion to each game. Example: \"Nylander|Matthews|Marner\"", "restrictions": { "required": true, - "codeList": "#/maple_leafs_roster/values" + "codeList": ["A", "B", "C"] }, "meta": { "selection_rule": "Choose exactly three players from the roster." From 805925532a4f5c7d77dad897a2faab04ee600eac Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Sat, 21 Jun 2025 20:41:10 -0400 Subject: [PATCH 124/217] got a googled pill component, and pasted it in --- packages/ui/src/common/Pill.tsx | 122 ++++++++++++++++++ .../Columns/DataType.tsx | 9 ++ .../SchemaTableInitialization/TableInit.tsx | 41 ++---- 3 files changed, 139 insertions(+), 33 deletions(-) create mode 100644 packages/ui/src/common/Pill.tsx diff --git a/packages/ui/src/common/Pill.tsx b/packages/ui/src/common/Pill.tsx new file mode 100644 index 00000000..eafcffa0 --- /dev/null +++ b/packages/ui/src/common/Pill.tsx @@ -0,0 +1,122 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; +import React, { CSSProperties, MouseEvent, ReactNode } from 'react'; +import colors from '../theme/styles/colors'; + +export type PillVariant = 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info'; +export type PillSize = 'small' | 'medium' | 'large'; + +export interface PillProps { + children: ReactNode; + variant?: PillVariant; + size?: PillSize; + icon?: ReactNode; + onClick?: (event: MouseEvent) => void; + dark?: boolean; + style?: CSSProperties; +} + +const getVariantStyles = (dark: boolean) => { + return { + background: dark ? '#D9D9D9' : '#E5E7EA', + color: dark ? colors.black : colors.black, + }; +}; + +const getSizeStyles = (size: PillSize) => { + const sizeStyles = { + small: { + padding: '2px 8px', + fontSize: '10px', + lineHeight: '14px', + borderRadius: '12px', + gap: '4px', + }, + medium: { + padding: '4px 12px', + fontSize: '12px', + lineHeight: '16px', + borderRadius: '16px', + gap: '6px', + }, + large: { + padding: '6px 16px', + fontSize: '14px', + lineHeight: '20px', + borderRadius: '20px', + gap: '8px', + }, + }; + + return sizeStyles[size]; +}; + +const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, dark = false, style }: PillProps) => { + const variantStyles = getVariantStyles(dark); + const sizeStyles = getSizeStyles(size); + + const pillStyles = css` + display: inline-flex; + align-items: center; + justify-content: center; + gap: ${sizeStyles.gap}px; + padding: ${sizeStyles.padding}; + font-size: ${sizeStyles.fontSize}; + line-height: ${sizeStyles.lineHeight}; + font-weight: 500; + border-radius: ${sizeStyles.borderRadius}; + background-color: ${variantStyles.background}; + color: ${variantStyles.color}; + transition: all 0.2s ease-in-out; + user-select: none; + white-space: nowrap; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + + ${onClick ? + css` + cursor: pointer; + &:hover { + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + &:active { + transform: translateY(0); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + } + ` + : ''} + + ${icon ? + css` + .pill-icon { + display: flex; + align-items: center; + font-size: ${parseInt(sizeStyles.fontSize) - 2}px; + } + ` + : ''} + `; + + const handleClick = (event: React.MouseEvent) => { + if (onClick) { + onClick(event); + } + }; + + return ( +
      + {icon && {icon}} + {children} +
      + ); +}; + +export default Pill; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx index e69de29b..f1865b08 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx @@ -0,0 +1,9 @@ +/** @jsxImportSource @emotion/react */ +import { SchemaField } from '@overture-stack/lectern-dictionary'; +import { CellContext } from '@tanstack/react-table'; +import Pill from '../../../../common/Pill'; + +export const renderDataTypeColumn = (type: CellContext) => { + const { valueType, isArray } = type.row.original; + return {isArray ? 'Array' : valueType}; +}; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx index 7b8a66f4..6d806bd1 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx @@ -22,11 +22,10 @@ /** @jsxImportSource @emotion/react */ import { SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; -import { createColumnHelper } from '@tanstack/react-table'; -import { Theme } from '../../../theme'; -import { useThemeContext } from '../../../theme/ThemeContext'; -import { renderFieldsColumn } from './Columns/Fields'; +import { CellContext, createColumnHelper } from '@tanstack/react-table'; import { renderAllowedValuesColumn } from './Columns/AllowedValues'; +import { renderFieldsColumn } from './Columns/Fields'; +import { renderDataTypeColumn } from './Columns/DataType'; // This file is responsible for defining the columns of the schema table, depending on user defined types and schemas. const columnHelper = createColumnHelper(); @@ -38,40 +37,16 @@ export const getSchemaBaseColumns = (setClipboardContents: (curr: string) => voi return renderFieldsColumn(field, setClipboardContents); }, }), - columnHelper.accessor( - (row) => { - const restrictions = row.restrictions || {}; - if ('required' in restrictions && typeof restrictions !== 'function') { - return restrictions.required ?? false; - } - return false; - }, - { - id: 'required', - header: 'Attribute', - cell: (required) => { - const theme: Theme = useThemeContext(); - return
      {required.getValue() ? 'Required' : 'Optional'}
      ; - }, - }, - ), columnHelper.accessor('valueType', { header: 'Data Type', - cell: (type) => { - const { valueType, isArray, delimiter } = type.row.original; - return ( -
      - {valueType} - {isArray} - {delimiter} -
      - ); + cell: (type: CellContext) => { + return renderDataTypeColumn(type); }, }), columnHelper.accessor((row: SchemaField) => row.restrictions, { - id: 'Attribute', - header: 'Attribute', - cell: (restrictions) => { + id: 'allowedValues', + header: 'Allowed Values', + cell: (restrictions: CellContext) => { return renderAllowedValuesColumn(restrictions); }, }), From 7ace926c9d66a3886dbe0471b6c37d790278845a Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Sat, 21 Jun 2025 20:43:03 -0400 Subject: [PATCH 125/217] pill implementation complete --- packages/ui/src/common/Pill.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/common/Pill.tsx b/packages/ui/src/common/Pill.tsx index eafcffa0..d3d81714 100644 --- a/packages/ui/src/common/Pill.tsx +++ b/packages/ui/src/common/Pill.tsx @@ -99,8 +99,9 @@ const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, d : ''} `; - const handleClick = (event: React.MouseEvent) => { + const handleClick = (event: MouseEvent) => { if (onClick) { + event.stopPropagation; onClick(event); } }; From 830cf3361ba541255267293a20ded60ab090bfd6 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Sat, 21 Jun 2025 20:46:39 -0400 Subject: [PATCH 126/217] capitalize the first letter since that is required in the figma --- .../DataTable/SchemaTableInitialization/Columns/DataType.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx index f1865b08..5f3b230c 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx @@ -5,5 +5,5 @@ import Pill from '../../../../common/Pill'; export const renderDataTypeColumn = (type: CellContext) => { const { valueType, isArray } = type.row.original; - return {isArray ? 'Array' : valueType}; + return {isArray ? 'Array' : valueType.charAt(0).toUpperCase() + valueType.slice(1)}; }; From e032f3b9fabe75f47cd5afa9a55a35a3c7be9c28 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Sat, 21 Jun 2025 22:20:36 -0400 Subject: [PATCH 127/217] conditional restrictions schema table part c done --- .../Columns/Attribute.tsx | 21 +++++++++++++++++++ .../SchemaTableInitialization/TableInit.tsx | 8 +++++++ 2 files changed, 29 insertions(+) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Attribute.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Attribute.tsx index e69de29b..f748120c 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Attribute.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Attribute.tsx @@ -0,0 +1,21 @@ +/** @jsxImportSource @emotion/react */ +import { SchemaRestrictions } from '@overture-stack/lectern-dictionary'; +import Pill from '../../../../common/Pill'; + +export type Attributes = 'Required' | 'Optional' | 'Required When'; + +export const renderAttributesColumn = (schemaRestrictions: SchemaRestrictions | undefined) => { + //TODO: Implement this when specs next week arrive. + const handleRequiredWhen = () => { + return
      Conditional Restrictions
      ; + }; + return ( + + {schemaRestrictions && 'required' in schemaRestrictions && schemaRestrictions.required ? + 'Required' + : schemaRestrictions && 'if' in schemaRestrictions && schemaRestrictions.if ? + handleRequiredWhen() + : 'Optional'} + + ); +}; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx index 6d806bd1..fae3addd 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx @@ -26,6 +26,7 @@ import { CellContext, createColumnHelper } from '@tanstack/react-table'; import { renderAllowedValuesColumn } from './Columns/AllowedValues'; import { renderFieldsColumn } from './Columns/Fields'; import { renderDataTypeColumn } from './Columns/DataType'; +import { renderAttributesColumn } from './Columns/Attribute'; // This file is responsible for defining the columns of the schema table, depending on user defined types and schemas. const columnHelper = createColumnHelper(); @@ -37,6 +38,13 @@ export const getSchemaBaseColumns = (setClipboardContents: (curr: string) => voi return renderFieldsColumn(field, setClipboardContents); }, }), + columnHelper.accessor('restrictions.required', { + header: 'Attribute', + cell: (required: CellContext) => { + return renderAttributesColumn(required.row.original.restrictions); + }, + }), + columnHelper.accessor('valueType', { header: 'Data Type', cell: (type: CellContext) => { From 9792fa526d19fc5b75b3e5c8ffb3c7690c865849 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Sat, 21 Jun 2025 22:25:40 -0400 Subject: [PATCH 128/217] delimted requirement finish --- .../SchemaTableInitialization/Columns/DataType.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx index 5f3b230c..905acdd6 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx @@ -4,6 +4,10 @@ import { CellContext } from '@tanstack/react-table'; import Pill from '../../../../common/Pill'; export const renderDataTypeColumn = (type: CellContext) => { - const { valueType, isArray } = type.row.original; - return {isArray ? 'Array' : valueType.charAt(0).toUpperCase() + valueType.slice(1)}; + const { valueType, isArray, delimiter } = type.row.original; + return ( + + {isArray ? `Array delimited by "${delimiter}"` : valueType.charAt(0).toUpperCase() + valueType.slice(1)} + + ); }; From af66e7e518b977fcf61f8af28cf15ae662a99643 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Sun, 22 Jun 2025 00:17:25 -0400 Subject: [PATCH 129/217] add sticky header --- .../viewer-table/DataTable/SchemaTable.tsx | 51 ++++++++++++++----- .../viewer-table/DataTable/TableHeader.tsx | 13 +++-- .../src/viewer-table/DataTable/TableRow.tsx | 13 +++-- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx index 0c278f77..1ccebcdf 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx @@ -39,10 +39,33 @@ const sectionStyle = css` margin-bottom: 48px; max-width: 1200px; `; + +const tableContainerStyle = css` + overflow-x: auto; + max-width: 100%; + border: 1px solid #eaeaea; + border-radius: 4px; +`; + const tableStyle = css` - width: 100%; + min-width: 1200px; border-collapse: collapse; margin-top: 8px; + position: relative; +`; + +const stickyColumnStyle = css` + position: sticky; + left: 0; + z-index: 10; + background-color: inherit; +`; + +const stickyHeaderStyle = css` + position: sticky; + left: 0; + z-index: 20; + background-color: #e5edf3; `; const SchemaTable = ({ schema, dictionary }: SchemaTableProps) => { @@ -91,18 +114,20 @@ const SchemaTable = ({ schema, dictionary }: SchemaTableProps) => { return (
      - - - {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( - - ))} - - - {table.getRowModel().rows.map((row, i: number) => ( - - ))} - -
      +
      + + + {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( + + ))} + + + {table.getRowModel().rows.map((row, i: number) => ( + + ))} + +
      +
      ); }; diff --git a/packages/ui/src/viewer-table/DataTable/TableHeader.tsx b/packages/ui/src/viewer-table/DataTable/TableHeader.tsx index 27013f1e..18e11a70 100644 --- a/packages/ui/src/viewer-table/DataTable/TableHeader.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableHeader.tsx @@ -26,12 +26,19 @@ import { flexRender, HeaderGroup } from '@tanstack/react-table'; import { useThemeContext } from '../../theme/ThemeContext'; import { Theme } from '../../theme'; -const thStyle = (theme: Theme) => css` +const thStyle = (theme: Theme, index: number) => css` ${theme.typography.heading}; background: #e5edf3; text-align: left; padding: 12px; border-bottom: 1px solid #dcdcdc; + ${index === 0 && + ` + position: sticky; + left: 0; + z-index: 20; + background-color: #e5edf3; + `} `; type TableHeaderProps = { @@ -43,8 +50,8 @@ const TableHeader = ({ headerGroup }: TableHeaderProps) => { const theme = useThemeContext(); return ( - {headerGroup.headers.map((header) => ( - + {headerGroup.headers.map((header, index) => ( + {flexRender(header.column.columnDef.header, header.getContext())} ))} diff --git a/packages/ui/src/viewer-table/DataTable/TableRow.tsx b/packages/ui/src/viewer-table/DataTable/TableRow.tsx index 15d1cf79..1b443089 100644 --- a/packages/ui/src/viewer-table/DataTable/TableRow.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableRow.tsx @@ -31,7 +31,7 @@ const rowStyle = (index: number) => css` background-color: ${index % 2 === 0 ? '' : '#F5F7F8'}; `; -const tdStyle = (theme: Theme) => css` +const tdStyle = (theme: Theme, cellIndex: number, rowIndex: number) => css` ${theme.typography.data} padding: 12px; border-bottom: 1px solid #eaeaea; @@ -40,6 +40,13 @@ const tdStyle = (theme: Theme) => css` overflow-wrap: break-word; word-break: break-word; vertical-align: top; + ${cellIndex === 0 && + ` + position: sticky; + left: 0; + z-index: 10; + background-color: ${rowIndex % 2 === 0 ? 'white' : '#F5F7F8'}; + `} `; type TableRowProps = { @@ -52,9 +59,9 @@ const TableRow = ({ row, index }: TableRowProps) => { const theme = useThemeContext(); return ( - {row.getVisibleCells().map((cell) => { + {row.getVisibleCells().map((cell, cellIndex) => { return ( - + css` ${theme.typography.data} From 93cbadbe9a2a369c21c0e5a1d4097a8d409b19ac Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Sun, 22 Jun 2025 22:32:43 -0400 Subject: [PATCH 130/217] slight refactor --- .../Columns/AllowedValues.tsx | 0 .../Columns/Attribute.tsx | 0 .../Columns/CustomColumns.tsx | 0 .../Columns/DataType.tsx | 0 .../Columns/Fields.tsx | 7 +- .../DataTable/SchemaTable/SchemaTable.tsx | 13 +++ .../SchemaTableInit.tsx} | 8 +- .../DataTable/SchemaTableWithAccordion.tsx | 22 ----- .../DataTable/{SchemaTable.tsx => Table.tsx} | 81 ++++--------------- .../viewer-table/SchemaTable.stories.tsx | 15 +++- .../SchemaTableWithAccordion.stories.tsx | 24 ------ 11 files changed, 48 insertions(+), 122 deletions(-) rename packages/ui/src/viewer-table/DataTable/{SchemaTableInitialization => SchemaTable}/Columns/AllowedValues.tsx (100%) rename packages/ui/src/viewer-table/DataTable/{SchemaTableInitialization => SchemaTable}/Columns/Attribute.tsx (100%) rename packages/ui/src/viewer-table/DataTable/{SchemaTableInitialization => SchemaTable}/Columns/CustomColumns.tsx (100%) rename packages/ui/src/viewer-table/DataTable/{SchemaTableInitialization => SchemaTable}/Columns/DataType.tsx (100%) rename packages/ui/src/viewer-table/DataTable/{SchemaTableInitialization => SchemaTable}/Columns/Fields.tsx (92%) create mode 100644 packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTable.tsx rename packages/ui/src/viewer-table/DataTable/{SchemaTableInitialization/TableInit.tsx => SchemaTable/SchemaTableInit.tsx} (94%) delete mode 100644 packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx rename packages/ui/src/viewer-table/DataTable/{SchemaTable.tsx => Table.tsx} (52%) delete mode 100644 packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/AllowedValues.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx similarity index 100% rename from packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/AllowedValues.tsx rename to packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Attribute.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx similarity index 100% rename from packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Attribute.tsx rename to packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/CustomColumns.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/CustomColumns.tsx similarity index 100% rename from packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/CustomColumns.tsx rename to packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/CustomColumns.tsx diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx similarity index 100% rename from packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/DataType.tsx rename to packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Fields.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx similarity index 92% rename from packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Fields.tsx rename to packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx index 78be7cac..f658e7c4 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/Columns/Fields.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx @@ -15,10 +15,7 @@ const hashIconStyle = (theme: Theme) => css` opacity: 1; } `; -export const renderFieldsColumn = ( - field: CellContext, - setClipboardContents: (curr: string) => void, -) => { +export const renderFieldsColumn = (field: CellContext) => { const theme = useThemeContext(); const fieldName = field.row.original.name; const fieldIndex = field.row.index; @@ -56,7 +53,7 @@ export const renderFieldsColumn = ( const handleClick = () => { const hashTarget = `field-${fieldIndex}`; window.location.hash = `#${hashTarget}`; - setClipboardContents(window.location.href); + // setClipboardContents(window.location.href); }; return ( diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTable.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTable.tsx new file mode 100644 index 00000000..155bf609 --- /dev/null +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTable.tsx @@ -0,0 +1,13 @@ +import { Schema } from '@overture-stack/lectern-dictionary'; +import React from 'react'; +import Table from '../Table'; +import { getSchemaBaseColumns } from './SchemaTableInit'; + +type schemaTableProps = { + schema: Schema; +}; +const SchemaTable = ({ schema }: schemaTableProps) => { + return ; +}; + +export default SchemaTable; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTableInit.tsx similarity index 94% rename from packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx rename to packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTableInit.tsx index fae3addd..b6606f47 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTableInitialization/TableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTableInit.tsx @@ -24,18 +24,18 @@ import { SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { CellContext, createColumnHelper } from '@tanstack/react-table'; import { renderAllowedValuesColumn } from './Columns/AllowedValues'; -import { renderFieldsColumn } from './Columns/Fields'; -import { renderDataTypeColumn } from './Columns/DataType'; import { renderAttributesColumn } from './Columns/Attribute'; +import { renderDataTypeColumn } from './Columns/DataType'; +import { renderFieldsColumn } from './Columns/Fields'; // This file is responsible for defining the columns of the schema table, depending on user defined types and schemas. const columnHelper = createColumnHelper(); -export const getSchemaBaseColumns = (setClipboardContents: (curr: string) => void) => [ +export const getSchemaBaseColumns = () => [ columnHelper.accessor('name', { header: 'Fields', cell: (field) => { - return renderFieldsColumn(field, setClipboardContents); + return renderFieldsColumn(field); }, }), columnHelper.accessor('restrictions.required', { diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx deleted file mode 100644 index 2ff770fd..00000000 --- a/packages/ui/src/viewer-table/DataTable/SchemaTableWithAccordion.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { Schema, Dictionary } from '@overture-stack/lectern-dictionary'; -import Accordion from '../../common/Accordion/Accordion'; -import { AccordionData } from '../../common/Accordion/AccordionItem'; -import SchemaTable from './SchemaTable'; -type props = { - schema: Schema; - dictionary: Dictionary; -}; -const SchemaTableWithAccordion = ({ schema, dictionary }: props) => { - const accordionItems: Array = [ - { - title: schema.name, - description: schema.description ?? '', - openOnInit: true, - content: , - }, - ]; - return ; -}; - -export default SchemaTableWithAccordion; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx b/packages/ui/src/viewer-table/DataTable/Table.tsx similarity index 52% rename from packages/ui/src/viewer-table/DataTable/SchemaTable.tsx rename to packages/ui/src/viewer-table/DataTable/Table.tsx index 1ccebcdf..3ea384b4 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable.tsx +++ b/packages/ui/src/viewer-table/DataTable/Table.tsx @@ -22,17 +22,13 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import type { Dictionary, Schema, SchemaField } from '@overture-stack/lectern-dictionary'; -import { replaceReferences } from '@overture-stack/lectern-dictionary'; -import { getCoreRowModel, HeaderGroup, useReactTable } from '@tanstack/react-table'; -import { useMemo, useState } from 'react'; -import { getSchemaBaseColumns } from './SchemaTableInitialization/TableInit'; +import { ColumnDef, getCoreRowModel, HeaderGroup, useReactTable } from '@tanstack/react-table'; import TableHeader from './TableHeader'; import TableRow from './TableRow'; -type SchemaTableProps = { - schema: Schema; - dictionary: Dictionary; +export type GenericTableProps = { + data: R[]; + columns: ColumnDef[]; }; const sectionStyle = css` @@ -40,11 +36,19 @@ const sectionStyle = css` max-width: 1200px; `; +// We can keep the scrollbar which would mean it spans the whole table or just have the scrollbar hidden. const tableContainerStyle = css` overflow-x: auto; + -webkit-scrollbar: none; + -ms-overflow-style: none; + scrollbar-width: none; max-width: 100%; border: 1px solid #eaeaea; border-radius: 4px; + + &::-webkit-scrollbar { + display: none; + } `; const tableStyle = css` @@ -54,70 +58,19 @@ const tableStyle = css` position: relative; `; -const stickyColumnStyle = css` - position: sticky; - left: 0; - z-index: 10; - background-color: inherit; -`; - -const stickyHeaderStyle = css` - position: sticky; - left: 0; - z-index: 20; - background-color: #e5edf3; -`; - -const SchemaTable = ({ schema, dictionary }: SchemaTableProps) => { - const [clipboardContents, setClipboardContents] = useState(null); - const [isCopying, setIsCopying] = useState(false); - const [copySuccess, setCopySuccess] = useState(false); - - const resolvedDictionary = replaceReferences(dictionary); - - const handleCopy = (text: string) => { - if (isCopying) { - return; // We don't wanna copy if we are already copying - } - setIsCopying(true); - navigator.clipboard - .writeText(text) - .then(() => { - setCopySuccess(true); - setTimeout(() => { - setIsCopying(false); - }, 2000); // Reset copy success after 2 seconds as well as the isCopying state - }) - .catch((err) => { - console.error('Failed to copy text: ', err); - setCopySuccess(false); - setIsCopying(false); - }); - if (copySuccess) { - // Update the clipboard contents - const currentURL = window.location.href; - setClipboardContents(currentURL); - } - setCopySuccess(false); - }; - +const Table = ({ columns, data }: GenericTableProps) => { const table = useReactTable({ - data: resolvedDictionary.schemas[0].fields, - columns: getSchemaBaseColumns(setClipboardContents), + data: data, + columns, getCoreRowModel: getCoreRowModel(), }); - useMemo(() => { - if (clipboardContents) { - handleCopy(clipboardContents); - } - }, [clipboardContents]); return (
      - {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( + {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( ))} @@ -132,4 +85,4 @@ const SchemaTable = ({ schema, dictionary }: SchemaTableProps) => { ); }; -export default SchemaTable; +export default Table; diff --git a/packages/ui/stories/viewer-table/SchemaTable.stories.tsx b/packages/ui/stories/viewer-table/SchemaTable.stories.tsx index e201cc95..7e44f5b7 100644 --- a/packages/ui/stories/viewer-table/SchemaTable.stories.tsx +++ b/packages/ui/stories/viewer-table/SchemaTable.stories.tsx @@ -2,12 +2,14 @@ // import { DictionaryHeader } from '#/viewer-table/DictionaryHeader'; import type { Meta, StoryObj } from '@storybook/react'; -import SchemaTable from '../../src/viewer-table/DataTable/SchemaTable'; +import SchemaTable from '../../src/viewer-table/DataTable/SchemaTable/SchemaTable'; import Advanced from '../fixtures/advanced.json'; import themeDecorator from '../themeDecorator'; import { Dictionary, Schema } from '@overture-stack/lectern-dictionary'; + const dictionary: Dictionary = Advanced as Dictionary; const schema: Schema = dictionary.schemas[0]; + const meta = { component: SchemaTable, title: 'Viewer - Table/Schema Table', @@ -19,6 +21,13 @@ export default meta; type Story = StoryObj; export const Default: Story = { - // args: { name: sampleDictionary.name, version: sampleDictionary.name, description: sampleDictionary.description }, - args: { schema, dictionary }, + args: { schema }, +}; + +export const PlayerProfiles: Story = { + args: { schema: dictionary.schemas[1] }, +}; + +export const GameEvents: Story = { + args: { schema: dictionary.schemas[2] }, }; diff --git a/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx b/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx deleted file mode 100644 index d03150ee..00000000 --- a/packages/ui/stories/viewer-table/SchemaTableWithAccordion.stories.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/** @jsxImportSource @emotion/react */ - -// import { DictionaryHeader } from '#/viewer-table/DictionaryHeader'; -import type { Meta, StoryObj } from '@storybook/react'; -import SchemaTableWithAccordion from '../../src/viewer-table/DataTable/SchemaTableWithAccordion'; -import Dictionary from '../fixtures/minimalBiosampleModel'; -import themeDecorator from '../themeDecorator'; -import { Schema } from '@overture-stack/lectern-dictionary'; -const schema: Schema = Dictionary.schemas[0]; - -const meta = { - component: SchemaTableWithAccordion, - title: 'Viewer - Table/Schema Table With Accordion', - tags: ['autodocs'], - decorators: [themeDecorator()], -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - // args: { name: sampleDictionary.name, version: sampleDictionary.name, description: sampleDictionary.description }, - args: { schema, dictionary: Dictionary }, -}; From 69e690703224c176769d12a81f5cfee3b7508937 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 23 Jun 2025 11:46:18 -0400 Subject: [PATCH 131/217] schema a b and the other tickets almost done --- packages/ui/src/common/Pill.tsx | 4 +- .../SchemaTable/Columns/DataType.tsx | 15 +- .../DataTable/SchemaTable/Columns/Fields.tsx | 153 +++++++++++++----- .../DataTable/SchemaTable/SchemaTableInit.tsx | 4 +- packages/ui/stories/fixtures/advanced.json | 7 +- 5 files changed, 128 insertions(+), 55 deletions(-) diff --git a/packages/ui/src/common/Pill.tsx b/packages/ui/src/common/Pill.tsx index d3d81714..768d7045 100644 --- a/packages/ui/src/common/Pill.tsx +++ b/packages/ui/src/common/Pill.tsx @@ -36,7 +36,7 @@ const getSizeStyles = (size: PillSize) => { padding: '4px 12px', fontSize: '12px', lineHeight: '16px', - borderRadius: '16px', + borderRadius: '5px', gap: '6px', }, large: { @@ -69,7 +69,7 @@ const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, d color: ${variantStyles.color}; transition: all 0.2s ease-in-out; user-select: none; - white-space: nowrap; + text-align: center; max-width: 100%; overflow: hidden; text-overflow: ellipsis; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx index 905acdd6..d7b3ad9e 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx @@ -5,9 +5,14 @@ import Pill from '../../../../common/Pill'; export const renderDataTypeColumn = (type: CellContext) => { const { valueType, isArray, delimiter } = type.row.original; - return ( - - {isArray ? `Array delimited by "${delimiter}"` : valueType.charAt(0).toUpperCase() + valueType.slice(1)} - - ); + const renderContent = () => { + return isArray ? + + Array with +
      + Delimiter "{delimiter}" +
      + : valueType.charAt(0).toUpperCase() + valueType.slice(1); + }; + return {renderContent()}; }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx index f658e7c4..8e4e9a32 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx @@ -1,8 +1,8 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import { SchemaField } from '@overture-stack/lectern-dictionary'; +import { DictionaryMeta, SchemaField } from '@overture-stack/lectern-dictionary'; import { CellContext } from '@tanstack/react-table'; -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { Theme } from '../../../../theme'; import { useThemeContext } from '../../../../theme/ThemeContext'; @@ -15,64 +15,131 @@ const hashIconStyle = (theme: Theme) => css` opacity: 1; } `; -export const renderFieldsColumn = (field: CellContext) => { - const theme = useThemeContext(); - const fieldName = field.row.original.name; - const fieldIndex = field.row.index; - // In a Dictionary, there is no such thing as an examples field, however it is commonly apart of the meta - // due to project specs, we will render the examples here if they exist and have logic to handle it. +const fieldContainerStyle = css` + display: flex; + flex-direction: column; + gap: 10px; + scroll-margin: 40%; +`; - const renderExamples = () => { - const examples = field.row.original.meta?.examples; - if (!examples) { - return null; - } - // the only way we can have more than one example is if we have an array of examples +export type FieldExamplesProps = { + examples: string | number | boolean | string[] | number[] | DictionaryMeta | undefined; +}; - const count = Array.isArray(examples) ? examples.length : 1; - const label = count > 1 ? 'Examples:' : 'Example:'; - const text = Array.isArray(examples) ? examples.join(', ') : String(examples); +export type FieldNameProps = { + name: string; + index: number; + onHashClick: () => void; +}; - return ( -
      - {label} {text} -
      - ); - }; +export type FieldDescriptionProps = { + description: string; +}; +const FieldExamples = ({ examples }: FieldExamplesProps) => { + const theme = useThemeContext(); + if (!examples) { + return null; + } + const count = Array.isArray(examples) ? examples.length : 1; + const label = count > 1 ? 'Examples:' : 'Example:'; + const text = Array.isArray(examples) ? examples.join(', ') : String(examples); + + return ( +
      +

      + {label} {text} +

      +
      + ); +}; + +const FieldName = ({ name, onHashClick }: FieldNameProps) => { + const theme = useThemeContext(); const { Hash } = theme.icons; + return ( +
      + {name} + + + +
      + ); +}; + +const FieldDescription = ({ description }: FieldDescriptionProps) => { + const theme = useThemeContext(); + return
      {description}
      ; +}; +const useHashNavigation = (fieldIndex: number) => { useEffect(() => { const hashTarget = `field-${fieldIndex}`; if (window.location.hash === `#${hashTarget}`) { - document.getElementById(hashTarget)?.scrollIntoView({ behavior: 'smooth' }); + document.getElementById(hashTarget)?.scrollIntoView({ behavior: 'smooth', block: 'start' }); } - }, []); + }, [fieldIndex]); +}; - const handleClick = () => { +const useHashClickHandler = (fieldIndex: number, setClipboardContents: (clipboardContents: string) => void) => { + return () => { const hashTarget = `field-${fieldIndex}`; window.location.hash = `#${hashTarget}`; - // setClipboardContents(window.location.href); + setClipboardContents(window.location.href); }; +}; + +export const FieldsColumn = ({ field }: { field: CellContext }) => { + const fieldName = field.row.original.name; + const fieldIndex = field.row.index; + const fieldDescription = field.row.original.description; + const fieldExamples = field.row.original.meta?.examples; + + const [clipboardContents, setClipboardContents] = useState(null); + const [isCopying, setIsCopying] = useState(false); + const [copySuccess, setCopySuccess] = useState(false); + + const handleCopy = (text: string) => { + if (isCopying) { + return; // We don't wanna copy if we are already copying + } + setIsCopying(true); + navigator.clipboard + .writeText(text) + .then(() => { + setCopySuccess(true); + setTimeout(() => { + setIsCopying(false); + }, 2000); // Reset copy success after 2 seconds as well as the isCopying state + }) + .catch((err) => { + console.error('Failed to copy text: ', err); + setCopySuccess(false); + setIsCopying(false); + }); + if (copySuccess) { + // Update the clipboard contents + const currentURL = window.location.href; + setClipboardContents(currentURL); + } + setCopySuccess(false); + }; + + useMemo(() => { + if (clipboardContents) { + handleCopy(clipboardContents); + } + }, [clipboardContents]); + + useHashNavigation(fieldIndex); + const handleHashClick = useHashClickHandler(fieldIndex, setClipboardContents); return ( -
      -
      - {fieldName} - - - -
      -
      {field.row.original.description}
      - {renderExamples()} +
      + + {fieldDescription && } + {fieldExamples && }
      ); }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTableInit.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTableInit.tsx index b6606f47..7b046643 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTableInit.tsx @@ -26,7 +26,7 @@ import { CellContext, createColumnHelper } from '@tanstack/react-table'; import { renderAllowedValuesColumn } from './Columns/AllowedValues'; import { renderAttributesColumn } from './Columns/Attribute'; import { renderDataTypeColumn } from './Columns/DataType'; -import { renderFieldsColumn } from './Columns/Fields'; +import { FieldsColumn } from './Columns/Fields'; // This file is responsible for defining the columns of the schema table, depending on user defined types and schemas. const columnHelper = createColumnHelper(); @@ -35,7 +35,7 @@ export const getSchemaBaseColumns = () => [ columnHelper.accessor('name', { header: 'Fields', cell: (field) => { - return renderFieldsColumn(field); + return ; }, }), columnHelper.accessor('restrictions.required', { diff --git a/packages/ui/stories/fixtures/advanced.json b/packages/ui/stories/fixtures/advanced.json index 293e20d8..67c33ee9 100644 --- a/packages/ui/stories/fixtures/advanced.json +++ b/packages/ui/stories/fixtures/advanced.json @@ -59,7 +59,7 @@ { "name": "game_id", "valueType": "string", - "description": "A unique identifier assigned to any given game played by the Toronto Maple Leafs. Example(s): BLF_67_2INF, BOS_GM7_4_0", + "description": "A unique identifier assigned to any given game played by the Toronto Maple Leafs. ", "restrictions": { "required": true, "regex": "^[A-Z]{3}_[A-Z0-9]{2,5}_[A-Z0-9]{1,5}$" @@ -70,13 +70,14 @@ "valueType": "string", "isArray": true, "delimiter": "|", - "description": "The 'three stars' is a long-standing hockey tradition to recognize the three best individual performances in a game. This honor system dates back to the 1930s and has become one of hockey's most cherished traditions, serving as both a way to acknowledge outstanding play and provide fans with a memorable conclusion to each game. Example: \"Nylander|Matthews|Marner\"", + "description": "The 'three stars' is a long-standing hockey tradition to recognize the three best individual performances in a game. This honor system dates back to the 1930s and has become one of hockey's most cherished traditions, serving as both a way to acknowledge outstanding play and provide fans with a memorable conclusion to each game. ", "restrictions": { "required": true, "codeList": ["A", "B", "C"] }, "meta": { - "selection_rule": "Choose exactly three players from the roster." + "selection_rule": "Choose exactly three players from the roster.", + "examples": ["BLF_67_2INF", "BOS_GM7_4_0"] } }, { From f221ab77fc0e15944ff3770bdc80bbf852f93383 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 23 Jun 2025 13:30:17 -0400 Subject: [PATCH 132/217] implement the modal button --- .../SchemaTable/Columns/Attribute.tsx | 13 ++++++------- .../DataTable/SchemaTable/OpenModalButton.tsx | 12 ++++++++++++ packages/ui/stories/fixtures/advanced.json | 18 ++++++++++++++++-- 3 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalButton.tsx diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx index f748120c..69e0098e 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx @@ -1,21 +1,20 @@ /** @jsxImportSource @emotion/react */ import { SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import Pill from '../../../../common/Pill'; - +import OpenModalButton from '../OpenModalButton'; export type Attributes = 'Required' | 'Optional' | 'Required When'; export const renderAttributesColumn = (schemaRestrictions: SchemaRestrictions | undefined) => { //TODO: Implement this when specs next week arrive. const handleRequiredWhen = () => { - return
      Conditional Restrictions
      ; + return ; }; + if (schemaRestrictions && 'if' in schemaRestrictions && schemaRestrictions.if) { + return handleRequiredWhen(); + } return ( - {schemaRestrictions && 'required' in schemaRestrictions && schemaRestrictions.required ? - 'Required' - : schemaRestrictions && 'if' in schemaRestrictions && schemaRestrictions.if ? - handleRequiredWhen() - : 'Optional'} + {schemaRestrictions && 'required' in schemaRestrictions && schemaRestrictions.required ? 'Required' : 'Optional'} ); }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalButton.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalButton.tsx new file mode 100644 index 00000000..1977881c --- /dev/null +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalButton.tsx @@ -0,0 +1,12 @@ +/** @jsxImportSource @emotion/react */ +import React from 'react'; +import Button from '../../../common/Button'; +import Eye from '../../../theme/icons/Eye'; +import { css } from '@emotion/react'; +export type OpenModalButtonProps = { + title: string; +}; +const OpenModalButton = ({ title }: OpenModalButtonProps) => { + return ; +}; +export default OpenModalButton; diff --git a/packages/ui/stories/fixtures/advanced.json b/packages/ui/stories/fixtures/advanced.json index 67c33ee9..075a2a97 100644 --- a/packages/ui/stories/fixtures/advanced.json +++ b/packages/ui/stories/fixtures/advanced.json @@ -100,8 +100,22 @@ "name": "third_star", "valueType": "string", "restrictions": { - "required": true, - "codeList": "#/maple_leafs_roster/values" + "if": { + "conditions": [ + { + "fields": ["player_status"], + "match": { + "value": "Injured" + } + } + ] + }, + "then": { + "required": true + }, + "else": { + "empty": true + } } } ], From 97b3b5e07c4759a3136de82883684810ea7bc3bf Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 23 Jun 2025 15:31:00 -0400 Subject: [PATCH 133/217] some fonting changes --- packages/ui/src/common/Pill.tsx | 11 ++--- .../SchemaTable/Columns/AllowedValues.tsx | 46 ++++++++++++++----- .../SchemaTable/Columns/Attribute.tsx | 2 +- .../SchemaTable/Columns/DataType.tsx | 16 ++++++- .../DataTable/SchemaTable/OpenModalButton.tsx | 12 ----- .../DataTable/SchemaTable/OpenModalPill.tsx | 11 +++++ packages/ui/stories/fixtures/advanced.json | 11 +++-- 7 files changed, 72 insertions(+), 37 deletions(-) delete mode 100644 packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalButton.tsx create mode 100644 packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx diff --git a/packages/ui/src/common/Pill.tsx b/packages/ui/src/common/Pill.tsx index 768d7045..331fe658 100644 --- a/packages/ui/src/common/Pill.tsx +++ b/packages/ui/src/common/Pill.tsx @@ -34,7 +34,7 @@ const getSizeStyles = (size: PillSize) => { }, medium: { padding: '4px 12px', - fontSize: '12px', + fontSize: '16px', lineHeight: '16px', borderRadius: '5px', gap: '6px', @@ -59,9 +59,9 @@ const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, d display: inline-flex; align-items: center; justify-content: center; - gap: ${sizeStyles.gap}px; + gap: ${sizeStyles.gap}; padding: ${sizeStyles.padding}; - font-size: ${sizeStyles.fontSize}; + font-size: ${icon ? '11px' : sizeStyles.fontSize}; line-height: ${sizeStyles.lineHeight}; font-weight: 500; border-radius: ${sizeStyles.borderRadius}; @@ -69,10 +69,7 @@ const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, d color: ${variantStyles.color}; transition: all 0.2s ease-in-out; user-select: none; - text-align: center; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; + max-width: 73px !important; ${onClick ? css` diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx index f776a220..72748797 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx @@ -1,5 +1,5 @@ /** @jsxImportSource @emotion/react */ -import { RestrictionRange, SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; +import { MatchRuleCount, RestrictionRange, SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { CellContext } from '@tanstack/react-table'; const handleRange = (range: RestrictionRange, restrictionItems: string[]) => { @@ -16,6 +16,31 @@ const handleRange = (range: RestrictionRange, restrictionItems: string[]) => { } }; +const handleCodeListsWithCountRestrictions = ( + codeList: string | string[] | number[], + count: MatchRuleCount, + restrictionItems: string[], +) => { + const codeListDisplay = Array.isArray(codeList) ? codeList.join(', ') : codeList; + + if (typeof count === 'number') { + restrictionItems.push(`Exactly ${count} from: ${codeListDisplay}`); + } else { + const range = count; + if (range.min && range.max) { + restrictionItems.push(`Select ${range.min} to ${range.max} from: ${codeListDisplay}`); + } else if (range.min) { + restrictionItems.push(`At least ${range.min} from: ${codeListDisplay}`); + } else if (range.max) { + restrictionItems.push(`Up to ${range.max} from: ${codeListDisplay}`); + } else if (range.exclusiveMin) { + restrictionItems.push(`More than ${range.exclusiveMin} from: ${codeListDisplay}`); + } else if (range.exclusiveMax) { + restrictionItems.push(`Fewer than ${range.exclusiveMax} from: ${codeListDisplay}`); + } + } +}; + export const renderAllowedValuesColumn = (restrictions: CellContext) => { const schemaField: SchemaField = restrictions.row.original; const restrictionsValue: SchemaRestrictions = restrictions.getValue(); @@ -27,7 +52,7 @@ export const renderAllowedValuesColumn = (restrictions: CellContext 0) { diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx index 69e0098e..decca421 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx @@ -1,7 +1,7 @@ /** @jsxImportSource @emotion/react */ import { SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import Pill from '../../../../common/Pill'; -import OpenModalButton from '../OpenModalButton'; +import OpenModalButton from '../OpenModalPill'; export type Attributes = 'Required' | 'Optional' | 'Required When'; export const renderAttributesColumn = (schemaRestrictions: SchemaRestrictions | undefined) => { diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx index d7b3ad9e..195912c4 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx @@ -1,10 +1,17 @@ /** @jsxImportSource @emotion/react */ import { SchemaField } from '@overture-stack/lectern-dictionary'; import { CellContext } from '@tanstack/react-table'; +import React from 'react'; import Pill from '../../../../common/Pill'; +import { css } from '@emotion/react'; +const containerStyle = () => css` + display: flex; + flex-direction: column; + gap: 10px; +`; export const renderDataTypeColumn = (type: CellContext) => { - const { valueType, isArray, delimiter } = type.row.original; + const { valueType, isArray, delimiter, unique } = type.row.original; const renderContent = () => { return isArray ? @@ -14,5 +21,10 @@ export const renderDataTypeColumn = (type: CellContext) => : valueType.charAt(0).toUpperCase() + valueType.slice(1); }; - return {renderContent()}; + return ( +
      + {renderContent()} + {unique === true && Unique} +
      + ); }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalButton.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalButton.tsx deleted file mode 100644 index 1977881c..00000000 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalButton.tsx +++ /dev/null @@ -1,12 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import React from 'react'; -import Button from '../../../common/Button'; -import Eye from '../../../theme/icons/Eye'; -import { css } from '@emotion/react'; -export type OpenModalButtonProps = { - title: string; -}; -const OpenModalButton = ({ title }: OpenModalButtonProps) => { - return ; -}; -export default OpenModalButton; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx new file mode 100644 index 00000000..f68161d6 --- /dev/null +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx @@ -0,0 +1,11 @@ +/** @jsxImportSource @emotion/react */ +import React from 'react'; +import Eye from '../../../theme/icons/Eye'; +import Pill from '../../../common/Pill'; +export type OpenModalButtonProps = { + title: string; +}; +const OpenModalPill = ({ title }: OpenModalButtonProps) => { + return }>{title}; +}; +export default OpenModalPill; diff --git a/packages/ui/stories/fixtures/advanced.json b/packages/ui/stories/fixtures/advanced.json index 075a2a97..198b4cd0 100644 --- a/packages/ui/stories/fixtures/advanced.json +++ b/packages/ui/stories/fixtures/advanced.json @@ -55,12 +55,15 @@ { "name": "game_statistics", "description": "A schema for recording the statistics of a single hockey game.", + "fields": [ { "name": "game_id", + "unique": true, "valueType": "string", "description": "A unique identifier assigned to any given game played by the Toronto Maple Leafs. ", "restrictions": { + "uniqueKey": "HelloWorld", "required": true, "regex": "^[A-Z]{3}_[A-Z0-9]{2,5}_[A-Z0-9]{1,5}$" } @@ -70,10 +73,11 @@ "valueType": "string", "isArray": true, "delimiter": "|", - "description": "The 'three stars' is a long-standing hockey tradition to recognize the three best individual performances in a game. This honor system dates back to the 1930s and has become one of hockey's most cherished traditions, serving as both a way to acknowledge outstanding play and provide fans with a memorable conclusion to each game. ", + "description": "The 'ng stars' is a long-standing hockey tradition to recognize the three best individual performances in a game. This honor system dates back to the 1930s and has become one of hockey's most cherished traditions, serving as both a way to acknowledge outstanding play and provide fans with a memorable conclusion to each game. ", "restrictions": { "required": true, - "codeList": ["A", "B", "C"] + "codeList": ["A", "B", "C"], + "count": { "min": 2, "max": 3 } }, "meta": { "selection_rule": "Choose exactly three players from the roster.", @@ -93,7 +97,8 @@ "valueType": "string", "restrictions": { "required": true, - "codeList": "#/maple_leafs_roster/values" + "codeList": ["A", "B", "C"], + "count": { "min": 2 } } }, { From d0394c03e0b663468ebe893df59c35cb5c1f4599 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 23 Jun 2025 16:17:51 -0400 Subject: [PATCH 134/217] handle primary key --- packages/ui/src/common/Pill.tsx | 7 +++---- .../DataTable/SchemaTable/Columns/Fields.tsx | 14 +++++++++++--- .../DataTable/SchemaTable/OpenModalPill.tsx | 6 +++++- packages/ui/stories/fixtures/advanced.json | 3 ++- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/ui/src/common/Pill.tsx b/packages/ui/src/common/Pill.tsx index 331fe658..dda461a7 100644 --- a/packages/ui/src/common/Pill.tsx +++ b/packages/ui/src/common/Pill.tsx @@ -29,14 +29,14 @@ const getSizeStyles = (size: PillSize) => { padding: '2px 8px', fontSize: '10px', lineHeight: '14px', - borderRadius: '12px', + borderRadius: '9px', gap: '4px', }, medium: { padding: '4px 12px', fontSize: '16px', lineHeight: '16px', - borderRadius: '5px', + borderRadius: '9px', gap: '6px', }, large: { @@ -61,7 +61,7 @@ const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, d justify-content: center; gap: ${sizeStyles.gap}; padding: ${sizeStyles.padding}; - font-size: ${icon ? '11px' : sizeStyles.fontSize}; + font-size: ${sizeStyles.fontSize}; line-height: ${sizeStyles.lineHeight}; font-weight: 500; border-radius: ${sizeStyles.borderRadius}; @@ -69,7 +69,6 @@ const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, d color: ${variantStyles.color}; transition: all 0.2s ease-in-out; user-select: none; - max-width: 73px !important; ${onClick ? css` diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx index 8e4e9a32..604dbe21 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx @@ -1,10 +1,12 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import { DictionaryMeta, SchemaField } from '@overture-stack/lectern-dictionary'; +import { DictionaryMeta, SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { CellContext } from '@tanstack/react-table'; import React, { useEffect, useMemo, useState } from 'react'; import { Theme } from '../../../../theme'; import { useThemeContext } from '../../../../theme/ThemeContext'; +import Pill from '../../../../common/Pill'; +import OpenModalPill from '../OpenModalPill'; const hashIconStyle = (theme: Theme) => css` opacity: 0; @@ -29,6 +31,7 @@ export type FieldExamplesProps = { export type FieldNameProps = { name: string; + uniqueKeys: string[]; index: number; onHashClick: () => void; }; @@ -55,15 +58,18 @@ const FieldExamples = ({ examples }: FieldExamplesProps) => { ); }; -const FieldName = ({ name, onHashClick }: FieldNameProps) => { +const FieldName = ({ name, onHashClick, uniqueKeys }: FieldNameProps) => { const theme = useThemeContext(); const { Hash } = theme.icons; + const displayKeys = uniqueKeys.filter((value) => value !== ''); return (
      {name} + {displayKeys.length === 1 && {displayKeys}} + {displayKeys.length > 1 && }
      ); }; @@ -95,6 +101,8 @@ export const FieldsColumn = ({ field }: { field: CellContext(null); const [isCopying, setIsCopying] = useState(false); @@ -137,7 +145,7 @@ export const FieldsColumn = ({ field }: { field: CellContext - + {fieldDescription && } {fieldExamples && }
      diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx index f68161d6..c47b3dcb 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx @@ -6,6 +6,10 @@ export type OpenModalButtonProps = { title: string; }; const OpenModalPill = ({ title }: OpenModalButtonProps) => { - return }>{title}; + return ( + }> + {title} + + ); }; export default OpenModalPill; diff --git a/packages/ui/stories/fixtures/advanced.json b/packages/ui/stories/fixtures/advanced.json index 198b4cd0..1e24d07c 100644 --- a/packages/ui/stories/fixtures/advanced.json +++ b/packages/ui/stories/fixtures/advanced.json @@ -63,7 +63,7 @@ "valueType": "string", "description": "A unique identifier assigned to any given game played by the Toronto Maple Leafs. ", "restrictions": { - "uniqueKey": "HelloWorld", + "uniqueKey": ["HelloWorld"], "required": true, "regex": "^[A-Z]{3}_[A-Z0-9]{2,5}_[A-Z0-9]{1,5}$" } @@ -75,6 +75,7 @@ "delimiter": "|", "description": "The 'ng stars' is a long-standing hockey tradition to recognize the three best individual performances in a game. This honor system dates back to the 1930s and has become one of hockey's most cherished traditions, serving as both a way to acknowledge outstanding play and provide fans with a memorable conclusion to each game. ", "restrictions": { + "uniqueKey": ["HelloWorld", "Bye World"], "required": true, "codeList": ["A", "B", "C"], "count": { "min": 2, "max": 3 } From 3e0eeda360bcf46f2013cb14a1d48694568dd932 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 23 Jun 2025 16:23:49 -0400 Subject: [PATCH 135/217] appropriately type examples --- .../src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx index 604dbe21..125ef608 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx @@ -3,9 +3,9 @@ import { css } from '@emotion/react'; import { DictionaryMeta, SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { CellContext } from '@tanstack/react-table'; import React, { useEffect, useMemo, useState } from 'react'; +import Pill from '../../../../common/Pill'; import { Theme } from '../../../../theme'; import { useThemeContext } from '../../../../theme/ThemeContext'; -import Pill from '../../../../common/Pill'; import OpenModalPill from '../OpenModalPill'; const hashIconStyle = (theme: Theme) => css` @@ -26,7 +26,7 @@ const fieldContainerStyle = css` `; export type FieldExamplesProps = { - examples: string | number | boolean | string[] | number[] | DictionaryMeta | undefined; + examples: DictionaryMeta[string]; }; export type FieldNameProps = { From 8b69c4f9c7af7cf61bf9b233e1f731be0accc5d6 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 24 Jun 2025 07:05:59 -0400 Subject: [PATCH 136/217] add borders --- packages/ui/src/viewer-table/DataTable/Table.tsx | 9 ++++++--- packages/ui/src/viewer-table/DataTable/TableHeader.tsx | 1 + packages/ui/src/viewer-table/DataTable/TableRow.tsx | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/Table.tsx b/packages/ui/src/viewer-table/DataTable/Table.tsx index 3ea384b4..0796f18d 100644 --- a/packages/ui/src/viewer-table/DataTable/Table.tsx +++ b/packages/ui/src/viewer-table/DataTable/Table.tsx @@ -43,7 +43,6 @@ const tableContainerStyle = css` -ms-overflow-style: none; scrollbar-width: none; max-width: 100%; - border: 1px solid #eaeaea; border-radius: 4px; &::-webkit-scrollbar { @@ -54,9 +53,13 @@ const tableContainerStyle = css` const tableStyle = css` min-width: 1200px; border-collapse: collapse; + border: 1px solid lightgray; margin-top: 8px; position: relative; `; +const tableHeaderStyle = css` + border: 1px solid #dcdde1; +`; const Table = ({ columns, data }: GenericTableProps) => { const table = useReactTable({ @@ -69,12 +72,12 @@ const Table = ({ columns, data }: GenericTableProps) => {
      - + {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( ))} - + {table.getRowModel().rows.map((row, i: number) => ( ))} diff --git a/packages/ui/src/viewer-table/DataTable/TableHeader.tsx b/packages/ui/src/viewer-table/DataTable/TableHeader.tsx index 18e11a70..ceb4059e 100644 --- a/packages/ui/src/viewer-table/DataTable/TableHeader.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableHeader.tsx @@ -39,6 +39,7 @@ const thStyle = (theme: Theme, index: number) => css` z-index: 20; background-color: #e5edf3; `} + border: 1px solid #DCDDE1; `; type TableHeaderProps = { diff --git a/packages/ui/src/viewer-table/DataTable/TableRow.tsx b/packages/ui/src/viewer-table/DataTable/TableRow.tsx index 1b443089..a9fdb20d 100644 --- a/packages/ui/src/viewer-table/DataTable/TableRow.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableRow.tsx @@ -34,7 +34,6 @@ const rowStyle = (index: number) => css` const tdStyle = (theme: Theme, cellIndex: number, rowIndex: number) => css` ${theme.typography.data} padding: 12px; - border-bottom: 1px solid #eaeaea; max-width: 30vw; white-space: pre-wrap; overflow-wrap: break-word; @@ -47,6 +46,7 @@ const tdStyle = (theme: Theme, cellIndex: number, rowIndex: number) => css` z-index: 10; background-color: ${rowIndex % 2 === 0 ? 'white' : '#F5F7F8'}; `} + border: 1px solid #DCDDE1; `; type TableRowProps = { From 8ae8b699eb9b6f30210147759df63699a558b4a3 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 24 Jun 2025 08:30:58 -0400 Subject: [PATCH 137/217] remove set timeout used for debugging purposes --- .../InteractionPanel/TableOfContentsDropdown.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx index f3e6140c..c779efd2 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/TableOfContentsDropdown.tsx @@ -20,6 +20,7 @@ */ import type { Schema } from '@overture-stack/lectern-dictionary'; +import React from 'react'; import Dropdown from '../../common/Dropdown/Dropdown'; import { useThemeContext } from '../../theme/ThemeContext'; @@ -34,9 +35,7 @@ const TableOfContentsDropdown = ({ schemas, onSelect }: TableOfContentsDropdownP const handleAction = (index: number) => { const anchorId = `#${index}`; onSelect(index); - setTimeout(() => { - window.location.hash = anchorId; - }, 100); + window.location.hash = anchorId; }; const menuItemsFromSchemas = schemas.map((schema, index) => ({ From 92fce9e030add1c6aee4a6153070b654ec55c83a Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 24 Jun 2025 08:32:21 -0400 Subject: [PATCH 138/217] remove classname --- packages/ui/src/common/Dropdown/DropdownItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/common/Dropdown/DropdownItem.tsx b/packages/ui/src/common/Dropdown/DropdownItem.tsx index 3af76d31..49f7e635 100644 --- a/packages/ui/src/common/Dropdown/DropdownItem.tsx +++ b/packages/ui/src/common/Dropdown/DropdownItem.tsx @@ -57,7 +57,7 @@ const DropDownItem = ({ children, action, customStyles }: DropDownItemProps) => const content =
      {children}
      ; if (typeof action === 'function') { return ( -
      +
      {children}
      ); From b7f3225bda9a8645ea002afb4d1a648ff550fe10 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 24 Jun 2025 08:38:58 -0400 Subject: [PATCH 139/217] address pr feedback --- packages/ui/src/common/Button.tsx | 15 ++++-------- packages/ui/src/theme/icons/Spinner.tsx | 2 +- .../InteractionPanel/CollapseAllButton.tsx | 22 +++++++++++++++++- .../InteractionPanel/ExpandAllButton.tsx | 23 ++++++++++++++++++- 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/packages/ui/src/common/Button.tsx b/packages/ui/src/common/Button.tsx index 6eddd363..b60d196e 100644 --- a/packages/ui/src/common/Button.tsx +++ b/packages/ui/src/common/Button.tsx @@ -20,14 +20,13 @@ */ /** @jsxImportSource @emotion/react */ -// This is a slightly refactored version of the stage button -import React, { ReactNode } from 'react'; import { css } from '@emotion/react'; +import React, { ReactNode } from 'react'; import { Theme } from '../theme'; import { useThemeContext } from '../theme/ThemeContext'; -type ButtonProps = { +export interface ButtonProps { children?: ReactNode; disabled?: boolean; onClick?: ( @@ -38,7 +37,7 @@ type ButtonProps = { isLoading?: boolean; leftIcon?: ReactNode; width?: string; -}; +} const getButtonContainerStyles = (theme: any, width?: string) => css` display: flex; @@ -94,10 +93,6 @@ const getIconStyles = () => css` align-items: center; `; -/** - * This is the generic button component used throughout stage, however it has - * the styling that is specific to the current theme that is being used. - */ const Button = React.forwardRef( ( { @@ -113,6 +108,8 @@ const Button = React.forwardRef( ref, ) => { const [internalLoading, setInternalLoading] = React.useState(false); + const theme = useThemeContext(); + const { Spinner } = theme.icons; const shouldShowLoading = !!controlledLoading || (internalLoading && isAsync); const handleClick = async (event: React.SyntheticEvent) => { @@ -120,8 +117,6 @@ const Button = React.forwardRef( await onClick(event); setInternalLoading(false); }; - const theme = useThemeContext(); - const { Spinner } = theme.icons; return (
      css` ${theme.typography.data} `} diff --git a/packages/ui/stories/viewer-table/SchemaTable.stories.tsx b/packages/ui/stories/viewer-table/SchemaTable.stories.tsx index 7e44f5b7..819b2d03 100644 --- a/packages/ui/stories/viewer-table/SchemaTable.stories.tsx +++ b/packages/ui/stories/viewer-table/SchemaTable.stories.tsx @@ -1,13 +1,12 @@ /** @jsxImportSource @emotion/react */ -// import { DictionaryHeader } from '#/viewer-table/DictionaryHeader'; +import { Dictionary, replaceReferences, Schema } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; import SchemaTable from '../../src/viewer-table/DataTable/SchemaTable/SchemaTable'; import Advanced from '../fixtures/advanced.json'; import themeDecorator from '../themeDecorator'; -import { Dictionary, Schema } from '@overture-stack/lectern-dictionary'; -const dictionary: Dictionary = Advanced as Dictionary; +const dictionary: Dictionary = replaceReferences(Advanced as Dictionary); const schema: Schema = dictionary.schemas[0]; const meta = { From 7c40856cccae058305f31ce03ef84a08992a4266 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 24 Jun 2025 11:06:07 -0400 Subject: [PATCH 142/217] some css changes --- packages/ui/src/common/Pill.tsx | 38 +++++++++++++------ packages/ui/src/common/ReadMoreText.tsx | 2 +- .../SchemaTable/Columns/DataType.tsx | 10 +---- .../DataTable/SchemaTable/Columns/Fields.tsx | 10 +++-- .../DataTable/SchemaTable/OpenModalPill.tsx | 8 +++- 5 files changed, 43 insertions(+), 25 deletions(-) diff --git a/packages/ui/src/common/Pill.tsx b/packages/ui/src/common/Pill.tsx index dda461a7..26f65528 100644 --- a/packages/ui/src/common/Pill.tsx +++ b/packages/ui/src/common/Pill.tsx @@ -1,10 +1,10 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; import React, { CSSProperties, MouseEvent, ReactNode } from 'react'; -import colors from '../theme/styles/colors'; - -export type PillVariant = 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info'; -export type PillSize = 'small' | 'medium' | 'large'; +import { Theme } from '../theme'; +import { useThemeContext } from '../theme/ThemeContext'; +export type PillVariant = 'default' | 'button'; +export type PillSize = 'extra-small' | 'small' | 'medium' | 'large'; export interface PillProps { children: ReactNode; @@ -16,15 +16,31 @@ export interface PillProps { style?: CSSProperties; } -const getVariantStyles = (dark: boolean) => { - return { - background: dark ? '#D9D9D9' : '#E5E7EA', - color: dark ? colors.black : colors.black, +const getVariantStyles = (dark: boolean, variant: PillVariant, theme: Theme) => { + const VARIANT_STYLES = { + default: { + background: dark ? '#D9D9D9' : '#E5E7EA', + color: theme.colors.black, + border: 'none', + }, + button: { + background: '#FFFF', + color: theme.colors.black, + border: `1px solid ${theme.colors.black}`, + }, }; + return VARIANT_STYLES[variant]; }; const getSizeStyles = (size: PillSize) => { const sizeStyles = { + 'extra-small': { + padding: '1px 6px', + fontSize: '8px', + lineHeight: '12px', + borderRadius: '6px', + gap: '3px', + }, small: { padding: '2px 8px', fontSize: '10px', @@ -52,7 +68,8 @@ const getSizeStyles = (size: PillSize) => { }; const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, dark = false, style }: PillProps) => { - const variantStyles = getVariantStyles(dark); + const theme = useThemeContext(); + const variantStyles = getVariantStyles(dark, variant, theme); const sizeStyles = getSizeStyles(size); const pillStyles = css` @@ -67,9 +84,9 @@ const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, d border-radius: ${sizeStyles.borderRadius}; background-color: ${variantStyles.background}; color: ${variantStyles.color}; + border: ${variantStyles.border}; transition: all 0.2s ease-in-out; user-select: none; - ${onClick ? css` cursor: pointer; @@ -83,7 +100,6 @@ const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, d } ` : ''} - ${icon ? css` .pill-icon { diff --git a/packages/ui/src/common/ReadMoreText.tsx b/packages/ui/src/common/ReadMoreText.tsx index d918796e..c594d57e 100644 --- a/packages/ui/src/common/ReadMoreText.tsx +++ b/packages/ui/src/common/ReadMoreText.tsx @@ -44,7 +44,7 @@ const defaultWrapperStyle = (theme: Theme) => css` const linkStyle = (theme: Theme) => css` ${theme.typography?.label2}; - color: ${theme.colors.accent_dark}; + color: ${theme.colors.black}; cursor: pointer; display: inline-flex; align-items: center; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx index 195912c4..2a904a9c 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx @@ -1,9 +1,9 @@ /** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; import { SchemaField } from '@overture-stack/lectern-dictionary'; import { CellContext } from '@tanstack/react-table'; import React from 'react'; import Pill from '../../../../common/Pill'; -import { css } from '@emotion/react'; const containerStyle = () => css` display: flex; @@ -13,13 +13,7 @@ const containerStyle = () => css` export const renderDataTypeColumn = (type: CellContext) => { const { valueType, isArray, delimiter, unique } = type.row.original; const renderContent = () => { - return isArray ? - - Array with -
      - Delimiter "{delimiter}" -
      - : valueType.charAt(0).toUpperCase() + valueType.slice(1); + return isArray ? 'Array' : valueType.charAt(0).toUpperCase() + valueType.slice(1); }; return (
      diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx index 125ef608..42da28bf 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx @@ -10,7 +10,6 @@ import OpenModalPill from '../OpenModalPill'; const hashIconStyle = (theme: Theme) => css` opacity: 0; - margin-left: 8px; transition: opacity 0.2s ease; border-bottom: 2px solid ${theme.colors.secondary}; &:hover { @@ -62,11 +61,16 @@ const FieldName = ({ name, onHashClick, uniqueKeys }: FieldNameProps) => { const theme = useThemeContext(); const { Hash } = theme.icons; const displayKeys = uniqueKeys.filter((value) => value !== ''); + const fieldNameStyle = css` + display: flex; + align-items: center; + gap: 2px; + `; return ( -
      +
      {name} - + {displayKeys.length === 1 && {displayKeys}} {displayKeys.length > 1 && } diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx index c47b3dcb..a7e65dca 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx @@ -1,13 +1,17 @@ /** @jsxImportSource @emotion/react */ import React from 'react'; +import Pill, { PillVariant } from '../../../common/Pill'; import Eye from '../../../theme/icons/Eye'; -import Pill from '../../../common/Pill'; export type OpenModalButtonProps = { title: string; }; const OpenModalPill = ({ title }: OpenModalButtonProps) => { return ( - }> + } + > {title} ); From c54ec9a89a9165554b9303388db7abceb6029103 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 24 Jun 2025 11:08:07 -0400 Subject: [PATCH 143/217] more bolding --- .../ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx index 42da28bf..41b6e2fc 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx @@ -62,6 +62,7 @@ const FieldName = ({ name, onHashClick, uniqueKeys }: FieldNameProps) => { const { Hash } = theme.icons; const displayKeys = uniqueKeys.filter((value) => value !== ''); const fieldNameStyle = css` + ${theme.typography.label} display: flex; align-items: center; gap: 2px; From c12142b8aae149084a95a95af5f360169e8e7532 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 24 Jun 2025 13:42:55 -0400 Subject: [PATCH 144/217] fix some logical issues as well as some css --- packages/ui/src/common/Pill.tsx | 8 ++- .../SchemaTable/Columns/AllowedValues.tsx | 55 ++++++++++--------- .../SchemaTable/Columns/Attribute.tsx | 2 +- .../SchemaTable/Columns/DataType.tsx | 2 +- .../DataTable/SchemaTable/OpenModalPill.tsx | 2 +- packages/ui/stories/fixtures/advanced.json | 1 - 6 files changed, 39 insertions(+), 31 deletions(-) diff --git a/packages/ui/src/common/Pill.tsx b/packages/ui/src/common/Pill.tsx index 26f65528..b9b1d996 100644 --- a/packages/ui/src/common/Pill.tsx +++ b/packages/ui/src/common/Pill.tsx @@ -37,13 +37,15 @@ const getSizeStyles = (size: PillSize) => { 'extra-small': { padding: '1px 6px', fontSize: '8px', + fontWeight: '700', lineHeight: '12px', - borderRadius: '6px', + borderRadius: '9px', gap: '3px', }, small: { padding: '2px 8px', fontSize: '10px', + fontWeight: '700', lineHeight: '14px', borderRadius: '9px', gap: '4px', @@ -51,6 +53,7 @@ const getSizeStyles = (size: PillSize) => { medium: { padding: '4px 12px', fontSize: '16px', + fontWeight: '700', lineHeight: '16px', borderRadius: '9px', gap: '6px', @@ -58,6 +61,7 @@ const getSizeStyles = (size: PillSize) => { large: { padding: '6px 16px', fontSize: '14px', + fontWeight: '700', lineHeight: '20px', borderRadius: '20px', gap: '8px', @@ -80,7 +84,7 @@ const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, d padding: ${sizeStyles.padding}; font-size: ${sizeStyles.fontSize}; line-height: ${sizeStyles.lineHeight}; - font-weight: 500; + font-weight: ${sizeStyles.fontWeight}; border-radius: ${sizeStyles.borderRadius}; background-color: ${variantStyles.background}; color: ${variantStyles.color}; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx index 93c7bd32..f2a3c183 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx @@ -3,15 +3,15 @@ import { MatchRuleCount, RestrictionRange, SchemaField, SchemaRestrictions } fro import { CellContext } from '@tanstack/react-table'; const handleRange = (range: RestrictionRange, restrictionItems: string[]) => { - if (range.min && range.max) { - restrictionItems.push('Min: ' + range.min + '\nMax: ' + range.max); - } else if (range.min) { + if (range.min !== undefined && range.max !== undefined) { + restrictionItems.push(`Min: ${range.min}\nMax: ${range.max}`); + } else if (range.min !== undefined) { restrictionItems.push('Min: ' + range.min); - } else if (range.max) { + } else if (range.max !== undefined) { restrictionItems.push('Max: ' + range.max); - } else if (range.exclusiveMin) { + } else if (range.exclusiveMin !== undefined) { restrictionItems.push('Greater than ' + range.exclusiveMin); - } else if (range.exclusiveMax) { + } else if (range.exclusiveMax !== undefined) { restrictionItems.push('Less than ' + range.exclusiveMax); } }; @@ -20,23 +20,26 @@ const handleCodeListsWithCountRestrictions = ( codeList: string | string[] | number[], count: MatchRuleCount, restrictionItems: string[], + isArray: boolean, + delimiter: string = ',', ) => { const codeListDisplay = Array.isArray(codeList) ? codeList.join(', ') : codeList; + const delimiterText = isArray ? `, delimited by "${delimiter}"` : ''; if (typeof count === 'number') { - restrictionItems.push(`Exactly ${count} from: ${codeListDisplay}`); + restrictionItems.push(`Exactly ${count}${delimiterText} from:\n ${codeListDisplay}`); } else { const range = count; - if (range.min && range.max) { - restrictionItems.push(`Select ${range.min} to ${range.max} from: ${codeListDisplay}`); - } else if (range.min) { - restrictionItems.push(`At least ${range.min} from: ${codeListDisplay}`); - } else if (range.max) { - restrictionItems.push(`Up to ${range.max} from: ${codeListDisplay}`); - } else if (range.exclusiveMin) { - restrictionItems.push(`More than ${range.exclusiveMin} from: ${codeListDisplay}`); - } else if (range.exclusiveMax) { - restrictionItems.push(`Fewer than ${range.exclusiveMax} from: ${codeListDisplay}`); + if (range.min !== undefined && range.max !== undefined) { + restrictionItems.push(`Select ${range.min} to ${range.max}${delimiterText} from:\n${codeListDisplay}`); + } else if (range.min !== undefined) { + restrictionItems.push(`At least ${range.min}${delimiterText} from:\n${codeListDisplay}`); + } else if (range.max !== undefined) { + restrictionItems.push(`Up to ${range.max}${delimiterText} from:\n${codeListDisplay}`); + } else if (range.exclusiveMin !== undefined) { + restrictionItems.push(`More than ${range.exclusiveMin}${delimiterText} from:\n${codeListDisplay}`); + } else if (range.exclusiveMax !== undefined) { + restrictionItems.push(`Fewer than ${range.exclusiveMax}${delimiterText} from:\n${codeListDisplay}`); } } }; @@ -57,13 +60,13 @@ export const renderAllowedValuesColumn = (restrictions: CellContext `"${item}"`).join(',\n') + restrictionsValue.codeList.join(',\n') : `"${restrictionsValue.codeList}"`; restrictionItems.push('One of:\n' + `${codeListDisplay}`); } @@ -78,11 +81,13 @@ export const renderAllowedValuesColumn = (restrictions: CellContext 0) { - restrictionItems.push(`delimited by "${schemaField.delimiter ?? ','}"`); + handleCodeListsWithCountRestrictions( + restrictionsValue.codeList, + restrictionsValue.count, + restrictionItems, + schemaField.isArray || false, + schemaField.delimiter ?? ',', + ); } if (schemaField.unique) { diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx index decca421..e3490ed2 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx @@ -13,7 +13,7 @@ export const renderAttributesColumn = (schemaRestrictions: SchemaRestrictions | return handleRequiredWhen(); } return ( - + {schemaRestrictions && 'required' in schemaRestrictions && schemaRestrictions.required ? 'Required' : 'Optional'} ); diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx index 2a904a9c..663f33ee 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx @@ -11,7 +11,7 @@ const containerStyle = () => css` gap: 10px; `; export const renderDataTypeColumn = (type: CellContext) => { - const { valueType, isArray, delimiter, unique } = type.row.original; + const { valueType, isArray, unique } = type.row.original; const renderContent = () => { return isArray ? 'Array' : valueType.charAt(0).toUpperCase() + valueType.slice(1); }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx index a7e65dca..17bb8faa 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx @@ -8,7 +8,7 @@ export type OpenModalButtonProps = { const OpenModalPill = ({ title }: OpenModalButtonProps) => { return ( } > diff --git a/packages/ui/stories/fixtures/advanced.json b/packages/ui/stories/fixtures/advanced.json index 1e24d07c..9a861907 100644 --- a/packages/ui/stories/fixtures/advanced.json +++ b/packages/ui/stories/fixtures/advanced.json @@ -11,7 +11,6 @@ "values": [ "Matthews", "Nylander", - "Marner", "Tavares", "Rielly", "Domi", From bae83b108cec3a7c683916a42e672bc06197c0e0 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 24 Jun 2025 15:11:56 -0400 Subject: [PATCH 145/217] css changes for the table --- packages/ui/src/common/Pill.tsx | 19 ++++- .../SchemaTable/Columns/Attribute.tsx | 18 +++- .../SchemaTable/Columns/DataType.tsx | 3 +- .../ui/src/viewer-table/DataTable/Table.tsx | 84 ++++++++++++++++--- 4 files changed, 108 insertions(+), 16 deletions(-) diff --git a/packages/ui/src/common/Pill.tsx b/packages/ui/src/common/Pill.tsx index b9b1d996..c4a215d6 100644 --- a/packages/ui/src/common/Pill.tsx +++ b/packages/ui/src/common/Pill.tsx @@ -41,6 +41,8 @@ const getSizeStyles = (size: PillSize) => { lineHeight: '12px', borderRadius: '9px', gap: '3px', + width: '65px', + maxWidth: '75px', }, small: { padding: '2px 8px', @@ -49,6 +51,8 @@ const getSizeStyles = (size: PillSize) => { lineHeight: '14px', borderRadius: '9px', gap: '4px', + width: '80px', + maxWidth: '100px', }, medium: { padding: '4px 12px', @@ -57,6 +61,8 @@ const getSizeStyles = (size: PillSize) => { lineHeight: '16px', borderRadius: '9px', gap: '6px', + width: '95px', + maxWidth: '140px', }, large: { padding: '6px 16px', @@ -65,6 +71,8 @@ const getSizeStyles = (size: PillSize) => { lineHeight: '20px', borderRadius: '20px', gap: '8px', + width: '150px', + maxWidth: '180px', }, }; @@ -91,6 +99,12 @@ const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, d border: ${variantStyles.border}; transition: all 0.2s ease-in-out; user-select: none; + width: ${sizeStyles.width}; + max-width: ${sizeStyles.maxWidth}; + text-align: center; + word-wrap: break-word; + overflow-wrap: break-word; + hyphens: auto; ${onClick ? css` cursor: pointer; @@ -110,9 +124,10 @@ const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, d display: flex; align-items: center; font-size: ${parseInt(sizeStyles.fontSize) - 2}px; + flex-shrink: 0; } ` - : ''} + : ''}; `; const handleClick = (event: MouseEvent) => { @@ -131,7 +146,7 @@ const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, d tabIndex={onClick ? 0 : undefined} > {icon && {icon}} - {children} + {children}
      ); }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx index e3490ed2..200159e2 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx @@ -1,9 +1,17 @@ /** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; import { SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import Pill from '../../../../common/Pill'; import OpenModalButton from '../OpenModalPill'; + export type Attributes = 'Required' | 'Optional' | 'Required When'; +const containerStyle = css` + display: flex; + align-items: center; + flex-direction: column; + gap: 10px; +`; export const renderAttributesColumn = (schemaRestrictions: SchemaRestrictions | undefined) => { //TODO: Implement this when specs next week arrive. const handleRequiredWhen = () => { @@ -13,8 +21,12 @@ export const renderAttributesColumn = (schemaRestrictions: SchemaRestrictions | return handleRequiredWhen(); } return ( - - {schemaRestrictions && 'required' in schemaRestrictions && schemaRestrictions.required ? 'Required' : 'Optional'} - +
      + + {schemaRestrictions && 'required' in schemaRestrictions && schemaRestrictions.required ? + 'Required' + : 'Optional'} + +
      ); }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx index 663f33ee..7f17cf46 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx @@ -5,8 +5,9 @@ import { CellContext } from '@tanstack/react-table'; import React from 'react'; import Pill from '../../../../common/Pill'; -const containerStyle = () => css` +const containerStyle = css` display: flex; + align-items: center; flex-direction: column; gap: 10px; `; diff --git a/packages/ui/src/viewer-table/DataTable/Table.tsx b/packages/ui/src/viewer-table/DataTable/Table.tsx index 0796f18d..28f078f0 100644 --- a/packages/ui/src/viewer-table/DataTable/Table.tsx +++ b/packages/ui/src/viewer-table/DataTable/Table.tsx @@ -23,6 +23,7 @@ import { css } from '@emotion/react'; import { ColumnDef, getCoreRowModel, HeaderGroup, useReactTable } from '@tanstack/react-table'; +import { useCallback, useEffect, useRef, useState } from 'react'; import TableHeader from './TableHeader'; import TableRow from './TableRow'; @@ -31,19 +32,37 @@ export type GenericTableProps = { columns: ColumnDef[]; }; -const sectionStyle = css` +const scrollWrapperStyle = css` + position: relative; + overflow: hidden; + border-radius: 4px; margin-bottom: 48px; - max-width: 1200px; `; -// We can keep the scrollbar which would mean it spans the whole table or just have the scrollbar hidden. +// Shadow overlays +const shadowStyle = css` + position: absolute; + top: 0; + z-index: 100; + width: 20px; + height: 100%; + pointer-events: none; + transition: opacity 0.3s ease; +`; + +const leftShadowStyle = (width: number, opacity: number) => css` + ${shadowStyle} + left: ${width + 25}px; + background: linear-gradient(90deg, rgba(0, 0, 0, 0.035), transparent); + opacity: ${opacity}; +`; + const tableContainerStyle = css` overflow-x: auto; -webkit-scrollbar: none; -ms-overflow-style: none; scrollbar-width: none; max-width: 100%; - border-radius: 4px; &::-webkit-scrollbar { display: none; @@ -57,7 +76,7 @@ const tableStyle = css` margin-top: 8px; position: relative; `; -const tableHeaderStyle = css` +const tableBorderStyle = css` border: 1px solid #dcdde1; `; @@ -67,24 +86,69 @@ const Table = ({ columns, data }: GenericTableProps) => { columns, getCoreRowModel: getCoreRowModel(), }); + /***************************** MESSY SCROLLING BEHAVIOR***********************/ + const scrollRef = useRef(null); + const [showLeftShadow, setShowLeftShadow] = useState(false); + const [firstColumnWidth, setFirstColumnWidth] = useState(0); + + // We need to compute the width of the first column in order to make sure that the scrolling occurs after that point + const updateFirstColumnWidth = useCallback(() => { + if (!scrollRef.current) { + return; + } + const firstColumnHeader = scrollRef.current.querySelector('th:first-child'); + if (firstColumnHeader) { + setFirstColumnWidth(parseFloat(window.getComputedStyle(firstColumnHeader).width)); + } + }, []); + + const handleScroll = useCallback(() => { + if (!scrollRef.current) { + return; + } + const { scrollLeft } = scrollRef.current; + setShowLeftShadow(scrollLeft > 0); + }, []); + + // Handle scroll events + useEffect(() => { + const scrollElement = scrollRef.current; + if (!scrollElement) { + return; + } + handleScroll(); + scrollElement.addEventListener('scroll', handleScroll); + return () => scrollElement.removeEventListener('scroll', handleScroll); + }, [handleScroll]); + + // Handle resize events for first column width + useEffect(() => { + // Initial width calculation + updateFirstColumnWidth(); + + window.addEventListener('resize', updateFirstColumnWidth); + return () => window.removeEventListener('resize', updateFirstColumnWidth); + }, [updateFirstColumnWidth]); + /***************************** MESSY SCROLLING BEHAVIOR***********************/ return ( -
      -
      +
      +
      +
      - + {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( ))} - + {table.getRowModel().rows.map((row, i: number) => ( ))}
      -
      +
      ); }; From 85216b204ca3ed05b7fd1fa3c37de818fb1efbd5 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 24 Jun 2025 16:11:07 -0400 Subject: [PATCH 146/217] code style fixes --- .../SchemaTable/Columns/AllowedValues.tsx | 62 +++++++++++++------ .../SchemaTable/Columns/Attribute.tsx | 28 ++++++++- .../SchemaTable/Columns/DataType.tsx | 31 +++++++++- .../DataTable/SchemaTable/Columns/Fields.tsx | 20 ++++-- .../DataTable/SchemaTable/OpenModalPill.tsx | 4 +- .../DataTable/SchemaTable/SchemaTable.tsx | 24 ++++++- .../viewer-table/DataTable/TableHeader.tsx | 3 +- packages/ui/stories/fixtures/advanced.json | 14 ++++- 8 files changed, 147 insertions(+), 39 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx index f2a3c183..71fb103e 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx @@ -1,18 +1,41 @@ +/* + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /** @jsxImportSource @emotion/react */ import { MatchRuleCount, RestrictionRange, SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { CellContext } from '@tanstack/react-table'; -const handleRange = (range: RestrictionRange, restrictionItems: string[]) => { +export type AllowedValuesColumnProps = { + restrictions: CellContext; +}; + +const handleRange = (range: RestrictionRange, restrictionItems: string[]): void => { if (range.min !== undefined && range.max !== undefined) { restrictionItems.push(`Min: ${range.min}\nMax: ${range.max}`); } else if (range.min !== undefined) { - restrictionItems.push('Min: ' + range.min); + restrictionItems.push(`Min: ${range.min}`); } else if (range.max !== undefined) { - restrictionItems.push('Max: ' + range.max); + restrictionItems.push(`Max: ${range.max}`); } else if (range.exclusiveMin !== undefined) { - restrictionItems.push('Greater than ' + range.exclusiveMin); + restrictionItems.push(`Greater than ${range.exclusiveMin}`); } else if (range.exclusiveMax !== undefined) { - restrictionItems.push('Less than ' + range.exclusiveMax); + restrictionItems.push(`Less than ${range.exclusiveMax}`); } }; @@ -22,29 +45,28 @@ const handleCodeListsWithCountRestrictions = ( restrictionItems: string[], isArray: boolean, delimiter: string = ',', -) => { +): void => { const codeListDisplay = Array.isArray(codeList) ? codeList.join(', ') : codeList; const delimiterText = isArray ? `, delimited by "${delimiter}"` : ''; if (typeof count === 'number') { - restrictionItems.push(`Exactly ${count}${delimiterText} from:\n ${codeListDisplay}`); + restrictionItems.push(`Exactly ${count}${delimiterText} from:\n${codeListDisplay}`); } else { - const range = count; - if (range.min !== undefined && range.max !== undefined) { - restrictionItems.push(`Select ${range.min} to ${range.max}${delimiterText} from:\n${codeListDisplay}`); - } else if (range.min !== undefined) { - restrictionItems.push(`At least ${range.min}${delimiterText} from:\n${codeListDisplay}`); - } else if (range.max !== undefined) { - restrictionItems.push(`Up to ${range.max}${delimiterText} from:\n${codeListDisplay}`); - } else if (range.exclusiveMin !== undefined) { - restrictionItems.push(`More than ${range.exclusiveMin}${delimiterText} from:\n${codeListDisplay}`); - } else if (range.exclusiveMax !== undefined) { - restrictionItems.push(`Fewer than ${range.exclusiveMax}${delimiterText} from:\n${codeListDisplay}`); + if (count.min !== undefined && count.max !== undefined) { + restrictionItems.push(`Select ${count.min} to ${count.max}${delimiterText} from:\n${codeListDisplay}`); + } else if (count.min !== undefined) { + restrictionItems.push(`At least ${count.min}${delimiterText} from:\n${codeListDisplay}`); + } else if (count.max !== undefined) { + restrictionItems.push(`Up to ${count.max}${delimiterText} from:\n${codeListDisplay}`); + } else if (count.exclusiveMin !== undefined) { + restrictionItems.push(`More than ${count.exclusiveMin}${delimiterText} from:\n${codeListDisplay}`); + } else if (count.exclusiveMax !== undefined) { + restrictionItems.push(`Fewer than ${count.exclusiveMax}${delimiterText} from:\n${codeListDisplay}`); } } }; -export const renderAllowedValuesColumn = (restrictions: CellContext) => { +export const renderAllowedValuesColumn = (restrictions: CellContext): string => { const schemaField: SchemaField = restrictions.row.original; const restrictionsValue: SchemaRestrictions = restrictions.getValue(); const restrictionItems: string[] = []; @@ -60,7 +82,7 @@ export const renderAllowedValuesColumn = (restrictions: CellContext. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; import { SchemaRestrictions } from '@overture-stack/lectern-dictionary'; +import React from 'react'; import Pill from '../../../../common/Pill'; -import OpenModalButton from '../OpenModalPill'; +import OpenModalPill from '../OpenModalPill'; export type Attributes = 'Required' | 'Optional' | 'Required When'; @@ -12,14 +32,16 @@ const containerStyle = css` flex-direction: column; gap: 10px; `; + export const renderAttributesColumn = (schemaRestrictions: SchemaRestrictions | undefined) => { - //TODO: Implement this when specs next week arrive. const handleRequiredWhen = () => { - return ; + return ; }; + if (schemaRestrictions && 'if' in schemaRestrictions && schemaRestrictions.if) { return handleRequiredWhen(); } + return (
      diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx index 7f17cf46..03d659b0 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx @@ -1,3 +1,22 @@ +/* + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; import { SchemaField } from '@overture-stack/lectern-dictionary'; @@ -5,21 +24,27 @@ import { CellContext } from '@tanstack/react-table'; import React from 'react'; import Pill from '../../../../common/Pill'; +export type DataTypeColumnProps = { + type: CellContext; +}; + const containerStyle = css` display: flex; align-items: center; flex-direction: column; gap: 10px; `; + export const renderDataTypeColumn = (type: CellContext) => { - const { valueType, isArray, unique } = type.row.original; - const renderContent = () => { + const { valueType, isArray } = type.row.original; + + const renderContent = (): string => { return isArray ? 'Array' : valueType.charAt(0).toUpperCase() + valueType.slice(1); }; + return (
      {renderContent()} - {unique === true && Unique}
      ); }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx index 41b6e2fc..deb63eef 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx @@ -33,6 +33,7 @@ export type FieldNameProps = { uniqueKeys: string[]; index: number; onHashClick: () => void; + foreignKey: string; }; export type FieldDescriptionProps = { @@ -57,7 +58,7 @@ const FieldExamples = ({ examples }: FieldExamplesProps) => { ); }; -const FieldName = ({ name, onHashClick, uniqueKeys }: FieldNameProps) => { +const FieldName = ({ name, onHashClick, uniqueKeys, foreignKey }: FieldNameProps) => { const theme = useThemeContext(); const { Hash } = theme.icons; const displayKeys = uniqueKeys.filter((value) => value !== ''); @@ -73,8 +74,9 @@ const FieldName = ({ name, onHashClick, uniqueKeys }: FieldNameProps) => { - {displayKeys.length === 1 && {displayKeys}} - {displayKeys.length > 1 && } + {displayKeys.length === 1 && !foreignKey && {displayKeys}} + {displayKeys.length > 1 && !foreignKey && } + {foreignKey && }
      ); }; @@ -108,7 +110,7 @@ export const FieldsColumn = ({ field }: { field: CellContext(null); const [isCopying, setIsCopying] = useState(false); const [copySuccess, setCopySuccess] = useState(false); @@ -150,9 +152,15 @@ export const FieldsColumn = ({ field }: { field: CellContext - + {fieldDescription && } - {fieldExamples && } + {!foreignKey && fieldExamples && }
      ); }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx index 17bb8faa..bf601e00 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx @@ -1,6 +1,6 @@ /** @jsxImportSource @emotion/react */ import React from 'react'; -import Pill, { PillVariant } from '../../../common/Pill'; +import Pill from '../../../common/Pill'; import Eye from '../../../theme/icons/Eye'; export type OpenModalButtonProps = { title: string; @@ -9,7 +9,7 @@ const OpenModalPill = ({ title }: OpenModalButtonProps) => { return ( } > {title} diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTable.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTable.tsx index 155bf609..3a4fdc96 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTable.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTable.tsx @@ -1,12 +1,32 @@ +/* + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + import { Schema } from '@overture-stack/lectern-dictionary'; import React from 'react'; import Table from '../Table'; import { getSchemaBaseColumns } from './SchemaTableInit'; -type schemaTableProps = { +export type SchemaTableProps = { schema: Schema; }; -const SchemaTable = ({ schema }: schemaTableProps) => { + +const SchemaTable = ({ schema }: SchemaTableProps) => { return ; }; diff --git a/packages/ui/src/viewer-table/DataTable/TableHeader.tsx b/packages/ui/src/viewer-table/DataTable/TableHeader.tsx index ceb4059e..860fff1a 100644 --- a/packages/ui/src/viewer-table/DataTable/TableHeader.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableHeader.tsx @@ -42,9 +42,8 @@ const thStyle = (theme: Theme, index: number) => css` border: 1px solid #DCDDE1; `; -type TableHeaderProps = { +export type TableHeaderProps = { headerGroup: HeaderGroup; - columnSlice?: [number, number] | [number]; }; const TableHeader = ({ headerGroup }: TableHeaderProps) => { diff --git a/packages/ui/stories/fixtures/advanced.json b/packages/ui/stories/fixtures/advanced.json index 9a861907..29660cc5 100644 --- a/packages/ui/stories/fixtures/advanced.json +++ b/packages/ui/stories/fixtures/advanced.json @@ -87,9 +87,21 @@ { "name": "first_star", "valueType": "string", + "description": "The 'ng stars' is a long-standing hockey tradition to recognize the three best individual performances in a game. This honor system dates back to the 1930s and has become one of hockey's most cherished traditions, serving as both a way to acknowledge outstanding play and provide fans with a memorable conclusion to each game. ", "restrictions": { "required": true, - "codeList": "#/maple_leafs_roster/values" + "codeList": "#/maple_leafs_roster/values", + "foreignKey": [ + { + "schema": "player_profiles", + "mappings": [ + { + "local": "first_star", + "foreign": "player_name" + } + ] + } + ] } }, { From b39af9b6bab2c969238033afc50c1485fadea556 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Tue, 24 Jun 2025 17:03:09 -0400 Subject: [PATCH 147/217] remove the side effects inside of the TableOfContentsDropdown --- .../AttributeFilterDropdown.tsx | 26 ++++++++++++++++--- .../DictionaryVersionSwitcher.tsx | 2 +- .../InteractionPanel/InteractionPanel.tsx | 6 ++--- .../TableOfContentsDropdown.tsx | 7 +---- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx index 745a1ed8..23b6a4ff 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/AttributeFilterDropdown.tsx @@ -1,6 +1,26 @@ +/* + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import React from 'react'; import Dropdown from '../../common/Dropdown/Dropdown'; -export type FilterDropdownProps = { +export type AttributeFilterDropdownProps = { filters: FilterOptions[]; setFilters: (filters: FilterOptions[]) => void; disabled?: boolean; @@ -8,7 +28,7 @@ export type FilterDropdownProps = { export type FilterOptions = 'Required' | 'All Fields'; -const FilterDropdown = ({ filters, setFilters, disabled }: FilterDropdownProps) => { +const AttributeFilterDropdown = ({ filters, setFilters, disabled }: AttributeFilterDropdownProps) => { const handleFilterSelect = (selectedFilterName: FilterOptions) => { // If we click the filter again then we want to toggle it off, iff it is the same filter being clicked if (filters?.includes(selectedFilterName)) { @@ -31,4 +51,4 @@ const FilterDropdown = ({ filters, setFilters, disabled }: FilterDropdownProps) return ; }; -export default FilterDropdown; +export default AttributeFilterDropdown; diff --git a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx index 85416449..34c66f5e 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx @@ -24,7 +24,7 @@ import { Dictionary } from '@overture-stack/lectern-dictionary'; import Dropdown from '../../common/Dropdown/Dropdown'; import { useThemeContext } from '../../theme/ThemeContext'; -type VersionSwitcherProps = { +export type VersionSwitcherProps = { dictionaryData: Dictionary[]; dictionaryIndex: number; onVersionChange: (index: number) => void; diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index 77a33ca5..ae84fbf1 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -32,7 +32,7 @@ import DownloadTemplatesButton from './DownloadTemplatesButton'; import ExpandAllButton from './ExpandAllButton'; import TableOfContentsDropdown from './TableOfContentsDropdown'; -type InteractionPanelProps = { +export type InteractionPanelProps = { disabled?: boolean; setIsCollapsed: (isCollapsed: boolean) => void; onSelect: (schemaNameIndex: number) => void; @@ -85,8 +85,8 @@ const InteractionPanel = ({ disabled = false, setIsCollapsed, onSelect, currDict setFilters={currDictionary.setFilters} disabled={disabled} /> - setIsCollapsed(true)} disabled={disabled} /> - setIsCollapsed(false)} disabled={disabled} /> + setIsCollapsed(false)} disabled={disabled} /> + setIsCollapsed(true)} disabled={disabled} />
      { const theme = useThemeContext(); const { List } = theme.icons; - const handleAction = (index: number) => { - const anchorId = `#${index}`; - onSelect(index); - window.location.hash = anchorId; - }; const menuItemsFromSchemas = schemas.map((schema, index) => ({ label: schema.name, action: () => { - handleAction(index); + onSelect(index); }, })); From f76e907ccf78368432e6725f02565c056b57a065 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 25 Jun 2025 10:22:04 -0400 Subject: [PATCH 148/217] naming consistency fixes and fix any css --- .../DictionaryVersionSwitcher.tsx | 8 +++--- .../InteractionPanel/InteractionPanel.tsx | 26 +++++++------------ 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx index 34c66f5e..45c08cf9 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/DictionaryVersionSwitcher.tsx @@ -24,19 +24,19 @@ import { Dictionary } from '@overture-stack/lectern-dictionary'; import Dropdown from '../../common/Dropdown/Dropdown'; import { useThemeContext } from '../../theme/ThemeContext'; -export type VersionSwitcherProps = { +export type DictionaryVersionSwitcherProps = { dictionaryData: Dictionary[]; dictionaryIndex: number; onVersionChange: (index: number) => void; disabled?: boolean; }; -const VersionSwitcher = ({ +const DictionaryVersionSwitcher = ({ dictionaryData, dictionaryIndex, onVersionChange, disabled = false, -}: VersionSwitcherProps) => { +}: DictionaryVersionSwitcherProps) => { const theme = useThemeContext(); const { History } = theme.icons; @@ -66,4 +66,4 @@ const VersionSwitcher = ({ ); }; -export default VersionSwitcher; +export default DictionaryVersionSwitcher; diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index ae84fbf1..1711360b 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -54,20 +54,10 @@ const panelStyles = (theme: Theme) => css` border-bottom: 1px solid ${theme.colors.grey_4}; background-color: ${theme.colors.white}; min-height: 70px; - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: visible; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - position: relative; `; -const leftSectionStyles = css` - display: flex; - align-items: center; - gap: 16px; -`; - -const rightSectionStyles = css` +const sectionStyles = css` display: flex; align-items: center; gap: 16px; @@ -75,11 +65,12 @@ const rightSectionStyles = css` const InteractionPanel = ({ disabled = false, setIsCollapsed, onSelect, currDictionary }: InteractionPanelProps) => { const theme: Theme = useThemeContext(); - const currDict: Dictionary = currDictionary.dictionaryData[currDictionary.dictionaryIndex]; + const currentDictionary: Dictionary = currDictionary.dictionaryData[currDictionary.dictionaryIndex]; + return (
      -
      - +
      + setIsCollapsed(false)} disabled={disabled} /> setIsCollapsed(true)} disabled={disabled} />
      -
      + +
      From d0db70da287666ab3bfa5170751e0b801036cef5 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 25 Jun 2025 10:24:08 -0400 Subject: [PATCH 149/217] revert some cleanup since its messed up some stuff --- .../ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index 1711360b..19505710 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -54,7 +54,11 @@ const panelStyles = (theme: Theme) => css` border-bottom: 1px solid ${theme.colors.grey_4}; background-color: ${theme.colors.white}; min-height: 70px; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: visible; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + position: relative; `; const sectionStyles = css` From a2f69bb5ead034456c4cbbfefb0fbce1928cb111 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 25 Jun 2025 11:00:29 -0400 Subject: [PATCH 150/217] remove the bug accidentally introduced --- packages/ui/src/common/Button.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/ui/src/common/Button.tsx b/packages/ui/src/common/Button.tsx index 50f2f36f..157d0b2c 100644 --- a/packages/ui/src/common/Button.tsx +++ b/packages/ui/src/common/Button.tsx @@ -20,10 +20,8 @@ */ /** @jsxImportSource @emotion/react */ -import { css } from '@emotion/react'; -import React, { ReactNode } from 'react'; import { css, SerializedStyles } from '@emotion/react'; - +import React, { ReactNode } from 'react'; import { Theme } from '../theme'; import { useThemeContext } from '../theme/ThemeContext'; From 9430030e825841746c7fbba63b129d24aef15295 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 25 Jun 2025 11:45:35 -0400 Subject: [PATCH 151/217] seperate the return type into prefix and content to easily render --- .../SchemaTable/Columns/AllowedValues.tsx | 75 +++++++++++++------ 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx index 71fb103e..ad760ec1 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx @@ -21,28 +21,33 @@ import { MatchRuleCount, RestrictionRange, SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { CellContext } from '@tanstack/react-table'; +export type restrictionItem = { + prefix: string | null; + content: string; +}; export type AllowedValuesColumnProps = { restrictions: CellContext; }; -const handleRange = (range: RestrictionRange, restrictionItems: string[]): void => { +const handleRange = (range: RestrictionRange, restrictionItems: restrictionItem[]): void => { if (range.min !== undefined && range.max !== undefined) { - restrictionItems.push(`Min: ${range.min}\nMax: ${range.max}`); + restrictionItems.push({ prefix: 'Min: ', content: `${range.min}` }); + restrictionItems.push({ prefix: 'Max: ', content: `${range.max}` }); } else if (range.min !== undefined) { - restrictionItems.push(`Min: ${range.min}`); + restrictionItems.push({ prefix: 'Min: ', content: `${range.min}` }); } else if (range.max !== undefined) { - restrictionItems.push(`Max: ${range.max}`); + restrictionItems.push({ prefix: 'Max: ', content: `${range.max}` }); } else if (range.exclusiveMin !== undefined) { - restrictionItems.push(`Greater than ${range.exclusiveMin}`); + restrictionItems.push({ prefix: 'Greater than ', content: `${range.exclusiveMin}` }); } else if (range.exclusiveMax !== undefined) { - restrictionItems.push(`Less than ${range.exclusiveMax}`); + restrictionItems.push({ prefix: 'Less than ', content: `${range.exclusiveMin}` }); } }; const handleCodeListsWithCountRestrictions = ( codeList: string | string[] | number[], count: MatchRuleCount, - restrictionItems: string[], + restrictionItems: restrictionItem[], isArray: boolean, delimiter: string = ',', ): void => { @@ -50,39 +55,63 @@ const handleCodeListsWithCountRestrictions = ( const delimiterText = isArray ? `, delimited by "${delimiter}"` : ''; if (typeof count === 'number') { - restrictionItems.push(`Exactly ${count}${delimiterText} from:\n${codeListDisplay}`); + restrictionItems.push({ + prefix: `Exactly ${count}${delimiterText} from:`, + content: `${codeListDisplay}`, + }); } else { if (count.min !== undefined && count.max !== undefined) { - restrictionItems.push(`Select ${count.min} to ${count.max}${delimiterText} from:\n${codeListDisplay}`); + restrictionItems.push({ + prefix: `Select ${count.min} to ${count.max}${delimiterText} from:`, + content: `${codeListDisplay}`, + }); } else if (count.min !== undefined) { - restrictionItems.push(`At least ${count.min}${delimiterText} from:\n${codeListDisplay}`); + restrictionItems.push({ + prefix: `At least ${count.min}${delimiterText} from:`, + content: `${codeListDisplay}`, + }); } else if (count.max !== undefined) { - restrictionItems.push(`Up to ${count.max}${delimiterText} from:\n${codeListDisplay}`); + restrictionItems.push({ + prefix: `Up to ${count.max}${delimiterText} from:`, + content: `${codeListDisplay}`, + }); } else if (count.exclusiveMin !== undefined) { - restrictionItems.push(`More than ${count.exclusiveMin}${delimiterText} from:\n${codeListDisplay}`); + restrictionItems.push({ + prefix: `More than ${count.exclusiveMin}${delimiterText} from:`, + content: `${codeListDisplay}`, + }); } else if (count.exclusiveMax !== undefined) { - restrictionItems.push(`Fewer than ${count.exclusiveMax}${delimiterText} from:\n${codeListDisplay}`); + restrictionItems.push({ + prefix: `Fewer than ${count.exclusiveMax}${delimiterText} from:`, + content: `${codeListDisplay}`, + }); } } }; -export const renderAllowedValuesColumn = (restrictions: CellContext): string => { +export const renderAllowedValuesColumn = ( + restrictions: CellContext, +): restrictionItem[] => { const schemaField: SchemaField = restrictions.row.original; const restrictionsValue: SchemaRestrictions = restrictions.getValue(); - const restrictionItems: string[] = []; + const restrictionItems: restrictionItem[] = []; if (!restrictionsValue || Object.keys(restrictionsValue).length === 0) { - return 'None'; + restrictionItems.push({ prefix: null, content: 'None' }); + return restrictionItems; } if ('if' in restrictionsValue && restrictionsValue.if) { - restrictionItems.push('Depends on'); + restrictionItems.push({ prefix: 'If condition', content: 'See field description for conditional requirements' }); } if ('regex' in restrictionsValue && restrictionsValue.regex) { const regexValue = Array.isArray(restrictionsValue.regex) ? restrictionsValue.regex.join(', ') : restrictionsValue.regex; - restrictionItems.push(`Must match pattern: ${regexValue}\nSee field description for examples.`); + restrictionItems.push({ + prefix: 'Must match pattern: ', + content: `${regexValue}\nSee field description for examples.`, + }); } if ('codeList' in restrictionsValue && restrictionsValue.codeList && !('count' in restrictionsValue)) { @@ -90,7 +119,7 @@ export const renderAllowedValuesColumn = (restrictions: CellContext 0 ? restrictionItems.join('\n') : 'None'; + return restrictionItems; }; From c06e62b1e9f1d1c11439da68618c23101e6821be Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 26 Jun 2025 07:47:26 -0400 Subject: [PATCH 152/217] add the bolding for the prefixes --- packages/ui/src/common/Pill.tsx | 9 +-- packages/ui/src/common/ReadMoreText.tsx | 2 +- .../SchemaTable/Columns/AllowedValues.tsx | 70 ++++++++++++++++--- .../DataTable/SchemaTable/OpenModalPill.tsx | 3 + .../DataTable/SchemaTable/SchemaTableInit.tsx | 5 +- .../src/viewer-table/DataTable/TableRow.tsx | 2 +- 6 files changed, 72 insertions(+), 19 deletions(-) diff --git a/packages/ui/src/common/Pill.tsx b/packages/ui/src/common/Pill.tsx index c4a215d6..1648a917 100644 --- a/packages/ui/src/common/Pill.tsx +++ b/packages/ui/src/common/Pill.tsx @@ -22,11 +22,13 @@ const getVariantStyles = (dark: boolean, variant: PillVariant, theme: Theme) => background: dark ? '#D9D9D9' : '#E5E7EA', color: theme.colors.black, border: 'none', + hoverBackground: dark ? '#CCCCCC' : '#D8DADD', }, button: { background: '#FFFF', color: theme.colors.black, border: `1px solid ${theme.colors.black}`, + hoverBackground: '#F5F5F5', }, }; return VARIANT_STYLES[variant]; @@ -109,12 +111,7 @@ const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, d css` cursor: pointer; &:hover { - transform: translateY(-1px); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - } - &:active { - transform: translateY(0); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + background-color: ${variantStyles.hoverBackground}; } ` : ''} diff --git a/packages/ui/src/common/ReadMoreText.tsx b/packages/ui/src/common/ReadMoreText.tsx index c594d57e..45f877f3 100644 --- a/packages/ui/src/common/ReadMoreText.tsx +++ b/packages/ui/src/common/ReadMoreText.tsx @@ -43,7 +43,7 @@ const defaultWrapperStyle = (theme: Theme) => css` `; const linkStyle = (theme: Theme) => css` - ${theme.typography?.label2}; + ${theme.typography?.label}; color: ${theme.colors.black}; cursor: pointer; display: inline-flex; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx index ad760ec1..9a34e541 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx @@ -20,6 +20,9 @@ /** @jsxImportSource @emotion/react */ import { MatchRuleCount, RestrictionRange, SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { CellContext } from '@tanstack/react-table'; +import { Theme } from '../../../../theme'; +import { css } from '@emotion/react'; +import { useThemeContext } from '../../../../theme/ThemeContext'; export type restrictionItem = { prefix: string | null; @@ -56,40 +59,40 @@ const handleCodeListsWithCountRestrictions = ( if (typeof count === 'number') { restrictionItems.push({ - prefix: `Exactly ${count}${delimiterText} from:`, + prefix: `Exactly ${count}${delimiterText} from: `, content: `${codeListDisplay}`, }); } else { if (count.min !== undefined && count.max !== undefined) { restrictionItems.push({ - prefix: `Select ${count.min} to ${count.max}${delimiterText} from:`, + prefix: `Select ${count.min} to ${count.max}${delimiterText} from: `, content: `${codeListDisplay}`, }); } else if (count.min !== undefined) { restrictionItems.push({ - prefix: `At least ${count.min}${delimiterText} from:`, + prefix: `At least ${count.min}${delimiterText} from: `, content: `${codeListDisplay}`, }); } else if (count.max !== undefined) { restrictionItems.push({ - prefix: `Up to ${count.max}${delimiterText} from:`, + prefix: `Up to ${count.max}${delimiterText} from: `, content: `${codeListDisplay}`, }); } else if (count.exclusiveMin !== undefined) { restrictionItems.push({ - prefix: `More than ${count.exclusiveMin}${delimiterText} from:`, + prefix: `More than ${count.exclusiveMin}${delimiterText} from: `, content: `${codeListDisplay}`, }); } else if (count.exclusiveMax !== undefined) { restrictionItems.push({ - prefix: `Fewer than ${count.exclusiveMax}${delimiterText} from:`, + prefix: `Fewer than ${count.exclusiveMax}${delimiterText} from: `, content: `${codeListDisplay}`, }); } } }; -export const renderAllowedValuesColumn = ( +export const computeAllowedValuesColumn = ( restrictions: CellContext, ): restrictionItem[] => { const schemaField: SchemaField = restrictions.row.original; @@ -102,7 +105,10 @@ export const renderAllowedValuesColumn = ( } if ('if' in restrictionsValue && restrictionsValue.if) { - restrictionItems.push({ prefix: 'If condition', content: 'See field description for conditional requirements' }); + restrictionItems.push({ + prefix: 'PLACEHOLDER\n', + content: 'See field description for conditional requirements', + }); } if ('regex' in restrictionsValue && restrictionsValue.regex) { @@ -119,7 +125,7 @@ export const renderAllowedValuesColumn = ( Array.isArray(restrictionsValue.codeList) ? restrictionsValue.codeList.join(',\n') : `"${restrictionsValue.codeList}"`; - restrictionItems.push({ prefix: 'One of:\n', content: `${codeListDisplay}` }); + restrictionItems.push({ prefix: 'One of: \n', content: `${codeListDisplay}` }); } if ('range' in restrictionsValue && restrictionsValue.range) { @@ -151,3 +157,49 @@ export const renderAllowedValuesColumn = ( return restrictionItems; }; + +export const renderAllowedValuesColumn = ( + restrictionItems: restrictionItem[], + restrictions: CellContext, +) => { + const restrictionsValue: SchemaRestrictions = restrictions.getValue(); + const theme = useThemeContext(); + const linkStyle = (theme: Theme) => css` + ${theme.typography?.label}; + color: ${theme.colors.black}; + cursor: pointer; + display: inline-flex; + align-items: center; + background: none; + border: none; + padding: 0; + margin-top: 4px; + text-decoration: underline; + &:hover { + text-decoration: underline; + } + `; + return ( +
      + {restrictionItems.map((item: restrictionItem) => { + const { prefix, content } = item; + return ( +
      + {prefix && {prefix}} + {content} +
      + ); + })} + {!!(restrictionsValue && 'if' in restrictionsValue && restrictionsValue.if) && ( +
      { + alert('Modal has been opened\n\n\n Hello World'); + }} + css={linkStyle(theme)} + > + View More +
      + )} +
      + ); +}; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx index bf601e00..3238df73 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx @@ -11,6 +11,9 @@ const OpenModalPill = ({ title }: OpenModalButtonProps) => { size={title === 'Required When' ? 'medium' : 'extra-small'} variant={title === 'Required When' || 'Primary Key' || 'Foreign Key' ? 'button' : 'default'} icon={} + onClick={() => { + alert('Modal has been opened\n\n\n Hello World'); + }} > {title} diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTableInit.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTableInit.tsx index 7b046643..963f9079 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTableInit.tsx @@ -23,7 +23,7 @@ import { SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { CellContext, createColumnHelper } from '@tanstack/react-table'; -import { renderAllowedValuesColumn } from './Columns/AllowedValues'; +import { computeAllowedValuesColumn, renderAllowedValuesColumn } from './Columns/AllowedValues'; import { renderAttributesColumn } from './Columns/Attribute'; import { renderDataTypeColumn } from './Columns/DataType'; import { FieldsColumn } from './Columns/Fields'; @@ -55,7 +55,8 @@ export const getSchemaBaseColumns = () => [ id: 'allowedValues', header: 'Allowed Values', cell: (restrictions: CellContext) => { - return renderAllowedValuesColumn(restrictions); + const computedRestrictions = computeAllowedValuesColumn(restrictions); + return renderAllowedValuesColumn(computedRestrictions, restrictions); }, }), ]; diff --git a/packages/ui/src/viewer-table/DataTable/TableRow.tsx b/packages/ui/src/viewer-table/DataTable/TableRow.tsx index 046216f8..f0948dcb 100644 --- a/packages/ui/src/viewer-table/DataTable/TableRow.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableRow.tsx @@ -24,8 +24,8 @@ import { css } from '@emotion/react'; import { Row, flexRender } from '@tanstack/react-table'; import ReadMoreText from '../../common/ReadMoreText'; -import { useThemeContext } from '../../theme/ThemeContext'; import { Theme } from '../../theme'; +import { useThemeContext } from '../../theme/ThemeContext'; const rowStyle = (index: number) => css` background-color: ${index % 2 === 0 ? '' : '#F5F7F8'}; From 36bd606c761c1a60785e6a5eb2ba35b78acc954e Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 26 Jun 2025 09:46:58 -0400 Subject: [PATCH 153/217] fix the required when styling --- .../SchemaTable/Columns/AllowedValues.tsx | 22 +- .../SchemaTable/Columns/Attribute.tsx | 21 +- .../SchemaTable/Columns/DataType.tsx | 3 +- .../DataTable/SchemaTable/SchemaTableInit.tsx | 9 +- packages/ui/stories/fixtures/advanced.json | 606 ++++++++++++++++-- .../viewer-table/SchemaTable.stories.tsx | 11 +- 6 files changed, 592 insertions(+), 80 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx index 9a34e541..e25a0ce7 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx @@ -43,7 +43,7 @@ const handleRange = (range: RestrictionRange, restrictionItems: restrictionItem[ } else if (range.exclusiveMin !== undefined) { restrictionItems.push({ prefix: 'Greater than ', content: `${range.exclusiveMin}` }); } else if (range.exclusiveMax !== undefined) { - restrictionItems.push({ prefix: 'Less than ', content: `${range.exclusiveMin}` }); + restrictionItems.push({ prefix: 'Less than ', content: `${range.exclusiveMax}` }); } }; @@ -158,12 +158,12 @@ export const computeAllowedValuesColumn = ( return restrictionItems; }; -export const renderAllowedValuesColumn = ( - restrictionItems: restrictionItem[], - restrictions: CellContext, -) => { +export const renderAllowedValuesColumn = (restrictions: CellContext) => { + const restrictionItems = computeAllowedValuesColumn(restrictions); const restrictionsValue: SchemaRestrictions = restrictions.getValue(); + const theme = useThemeContext(); + const linkStyle = (theme: Theme) => css` ${theme.typography?.label}; color: ${theme.colors.black}; @@ -179,6 +179,11 @@ export const renderAllowedValuesColumn = ( text-decoration: underline; } `; + + const onClick = () => { + alert('Modal has been opened\n\n\n Hello World'); + }; + return (
      {restrictionItems.map((item: restrictionItem) => { @@ -191,12 +196,7 @@ export const renderAllowedValuesColumn = ( ); })} {!!(restrictionsValue && 'if' in restrictionsValue && restrictionsValue.if) && ( -
      { - alert('Modal has been opened\n\n\n Hello World'); - }} - css={linkStyle(theme)} - > +
      View More
      )} diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx index 5318664a..6ed37ea5 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx @@ -30,6 +30,7 @@ const containerStyle = css` display: flex; align-items: center; flex-direction: column; + justify-content: center; gap: 10px; `; @@ -37,18 +38,16 @@ export const renderAttributesColumn = (schemaRestrictions: SchemaRestrictions | const handleRequiredWhen = () => { return ; }; - - if (schemaRestrictions && 'if' in schemaRestrictions && schemaRestrictions.if) { - return handleRequiredWhen(); - } - return ( -
      - - {schemaRestrictions && 'required' in schemaRestrictions && schemaRestrictions.required ? - 'Required' - : 'Optional'} - +
      + {schemaRestrictions && 'if' in schemaRestrictions && schemaRestrictions.if ? + handleRequiredWhen() + : + {schemaRestrictions && 'required' in schemaRestrictions && schemaRestrictions.required ? + 'Required' + : 'Optional'} + + }
      ); }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx index 03d659b0..56c8cd55 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx @@ -36,7 +36,7 @@ const containerStyle = css` `; export const renderDataTypeColumn = (type: CellContext) => { - const { valueType, isArray } = type.row.original; + const { valueType, isArray, unique } = type.row.original; const renderContent = (): string => { return isArray ? 'Array' : valueType.charAt(0).toUpperCase() + valueType.slice(1); @@ -45,6 +45,7 @@ export const renderDataTypeColumn = (type: CellContext) => return (
      {renderContent()} + {unique && Unique}
      ); }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTableInit.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTableInit.tsx index 963f9079..3019a512 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTableInit.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTableInit.tsx @@ -23,18 +23,18 @@ import { SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { CellContext, createColumnHelper } from '@tanstack/react-table'; -import { computeAllowedValuesColumn, renderAllowedValuesColumn } from './Columns/AllowedValues'; + +import { renderAllowedValuesColumn } from './Columns/AllowedValues'; import { renderAttributesColumn } from './Columns/Attribute'; import { renderDataTypeColumn } from './Columns/DataType'; import { FieldsColumn } from './Columns/Fields'; -// This file is responsible for defining the columns of the schema table, depending on user defined types and schemas. const columnHelper = createColumnHelper(); export const getSchemaBaseColumns = () => [ columnHelper.accessor('name', { header: 'Fields', - cell: (field) => { + cell: (field: CellContext) => { return ; }, }), @@ -55,8 +55,7 @@ export const getSchemaBaseColumns = () => [ id: 'allowedValues', header: 'Allowed Values', cell: (restrictions: CellContext) => { - const computedRestrictions = computeAllowedValuesColumn(restrictions); - return renderAllowedValuesColumn(computedRestrictions, restrictions); + return renderAllowedValuesColumn(restrictions); }, }), ]; diff --git a/packages/ui/stories/fixtures/advanced.json b/packages/ui/stories/fixtures/advanced.json index 29660cc5..3ce35a61 100644 --- a/packages/ui/stories/fixtures/advanced.json +++ b/packages/ui/stories/fixtures/advanced.json @@ -48,23 +48,91 @@ "Missing - Not provided", "Missing - Restricted access" ] + }, + "player_status": { + "description": "Player availability status options.", + "values": ["Active", "Injured", "Suspended", "Traded", "Retired"] + }, + "injury_types": { + "description": "Types of injuries that can occur in hockey.", + "values": ["Concussion", "Broken bone", "Muscle strain", "Ligament tear", "Cut/Laceration", "Bruise/Contusion"] + }, + "position_types": { + "description": "Hockey player positions.", + "values": ["Center", "Left Wing", "Right Wing", "Left Defense", "Right Defense", "Goaltender"] } }, "schemas": [ { "name": "game_statistics", "description": "A schema for recording the statistics of a single hockey game.", - "fields": [ { "name": "game_id", "unique": true, "valueType": "string", - "description": "A unique identifier assigned to any given game played by the Toronto Maple Leafs. ", + "description": "A unique identifier assigned to any given game played by the Toronto Maple Leafs. Examples: TOR_2024_001, MTL_2024_H01", + "restrictions": { + "required": true, + "regex": "^[A-Z]{3}_[0-9]{4}_[A-Z0-9]{1,3}$" + }, + "meta": { + "examples": ["TOR_2024_001", "MTL_2024_H01", "BOS_2024_P01"] + } + }, + { + "name": "season_year", + "valueType": "integer", + "description": "The NHL season year for this game.", + "restrictions": { + "required": true, + "range": { "min": 2000, "max": 2030 } + } + }, + { + "name": "game_date", + "valueType": "string", + "description": "Date the game was played in YYYY-MM-DD format.", + "restrictions": { + "required": true, + "regex": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" + }, + "meta": { + "examples": ["2024-01-15", "2024-03-20"] + } + }, + { + "name": "home_team_score", + "valueType": "integer", + "description": "Final score for the home team.", + "restrictions": { + "required": true, + "range": { "min": 0, "max": 20 } + } + }, + { + "name": "away_team_score", + "valueType": "integer", + "description": "Final score for the away team.", "restrictions": { - "uniqueKey": ["HelloWorld"], "required": true, - "regex": "^[A-Z]{3}_[A-Z0-9]{2,5}_[A-Z0-9]{1,5}$" + "range": { "min": 0, "max": 20 } + } + }, + { + "name": "overtime_duration", + "valueType": "number", + "description": "Duration of overtime in minutes (if applicable).", + "restrictions": { + "range": { "exclusiveMin": 0, "max": 20 } + } + }, + { + "name": "attendance", + "valueType": "integer", + "description": "Number of fans in attendance.", + "restrictions": { + "range": { "exclusiveMin": 0, "exclusiveMax": 25000 } } }, { @@ -72,72 +140,94 @@ "valueType": "string", "isArray": true, "delimiter": "|", - "description": "The 'ng stars' is a long-standing hockey tradition to recognize the three best individual performances in a game. This honor system dates back to the 1930s and has become one of hockey's most cherished traditions, serving as both a way to acknowledge outstanding play and provide fans with a memorable conclusion to each game. ", + "description": "The 'three stars' is a long-standing hockey tradition to recognize the three best individual performances in a game.", "restrictions": { - "uniqueKey": ["HelloWorld", "Bye World"], "required": true, - "codeList": ["A", "B", "C"], - "count": { "min": 2, "max": 3 } + "codeList": "#/maple_leafs_roster/values", + "count": { "min": 3, "max": 3 } }, "meta": { - "selection_rule": "Choose exactly three players from the roster.", - "examples": ["BLF_67_2INF", "BOS_GM7_4_0"] + "examples": ["Matthews|Nylander|Rielly"] } }, { - "name": "first_star", + "name": "game_tags", "valueType": "string", - "description": "The 'ng stars' is a long-standing hockey tradition to recognize the three best individual performances in a game. This honor system dates back to the 1930s and has become one of hockey's most cherished traditions, serving as both a way to acknowledge outstanding play and provide fans with a memorable conclusion to each game. ", + "isArray": true, + "delimiter": ",", + "description": "Tags to categorize the game type or significance.", "restrictions": { - "required": true, - "codeList": "#/maple_leafs_roster/values", - "foreignKey": [ - { - "schema": "player_profiles", - "mappings": [ - { - "local": "first_star", - "foreign": "player_name" - } - ] - } - ] + "codeList": [ + "Regular Season", + "Playoffs", + "Exhibition", + "Rivalry", + "Special Event", + "Home Opener", + "Season Finale" + ], + "count": { "min": 1, "max": 4 } + } + }, + { + "name": "weather_conditions", + "valueType": "string", + "isArray": true, + "delimiter": ";", + "description": "Weather conditions for outdoor games.", + "restrictions": { + "codeList": ["Clear", "Cloudy", "Light Snow", "Heavy Snow", "Rain", "Windy"], + "count": { "exclusiveMax": 3 } } }, { - "name": "second_star", + "name": "promotional_items", "valueType": "string", + "isArray": true, + "delimiter": ",", + "description": "Items given away during the game.", + "restrictions": { + "codeList": ["Bobblehead", "Jersey", "Puck", "Poster", "Magnet", "Keychain"], + "count": { "exclusiveMin": 0 } + } + }, + { + "name": "star_selections", + "valueType": "string", + "isArray": true, + "delimiter": "|", + "description": "Exactly 3 players selected as stars of the game.", "restrictions": { "required": true, - "codeList": ["A", "B", "C"], - "count": { "min": 2 } + "codeList": "#/maple_leafs_roster/values", + "count": 3 } }, { - "name": "third_star", + "name": "penalty_types", "valueType": "string", + "isArray": true, + "delimiter": ",", + "description": "Select at least 1 from available penalty types.", "restrictions": { - "if": { - "conditions": [ - { - "fields": ["player_status"], - "match": { - "value": "Injured" - } - } - ] - }, - "then": { - "required": true - }, - "else": { - "empty": true - } + "codeList": ["Minor", "Major", "Misconduct", "Match", "Penalty Shot"], + "count": { "min": 1 } + } + }, + { + "name": "referee_ratings", + "valueType": "integer", + "isArray": true, + "delimiter": ",", + "description": "Up to 4 referee performance ratings.", + "restrictions": { + "codeList": [1, 2, 3, 4, 5], + "count": { "max": 4 } } } ], "restrictions": { - "uniqueKeys": [["first_star", "second_star", "third_star"]] + "uniqueKeys": [["game_id"], ["season_year", "game_date", "home_team_score", "away_team_score"]] } }, { @@ -148,14 +238,44 @@ "name": "player_name", "valueType": "string", "unique": true, + "description": "Full name of the player as it appears on the roster.", "restrictions": { "required": true, "codeList": "#/maple_leafs_roster/values" } }, + { + "name": "jersey_number", + "valueType": "integer", + "unique": true, + "description": "Player's jersey number (must be unique on team).", + "restrictions": { + "required": true, + "range": { "min": 1, "max": 99 } + } + }, + { + "name": "position", + "valueType": "string", + "description": "Primary playing position.", + "restrictions": { + "required": true, + "codeList": "#/position_types/values" + } + }, + { + "name": "age", + "valueType": "integer", + "description": "Player's current age in years.", + "restrictions": { + "required": true, + "range": { "min": 18, "max": 45 } + } + }, { "name": "games_played", "valueType": "integer", + "description": "Total number of games played this season.", "restrictions": { "required": true, "range": { "min": 0 } @@ -164,9 +284,83 @@ { "name": "is_active", "valueType": "boolean", + "description": "Whether the player is currently active on the roster.", "restrictions": { "required": true } + }, + { + "name": "player_status", + "valueType": "string", + "description": "Current status of the player.", + "restrictions": { + "required": true, + "codeList": "#/player_status/values" + } + }, + { + "name": "injury_details", + "valueType": "string", + "description": "If the player status is 'Injured', specify the injury details.", + "restrictions": { + "if": { + "conditions": [ + { + "fields": ["player_status"], + "match": { + "value": "Injured" + } + } + ] + }, + "then": { + "required": true, + "codeList": "#/injury_types/values" + }, + "else": { + "empty": true + } + } + }, + { + "name": "suspension_games_remaining", + "valueType": "integer", + "description": "Number of games remaining in suspension.", + "restrictions": { + "if": { + "conditions": [ + { + "fields": ["player_status"], + "match": { + "value": "Suspended" + } + } + ] + }, + "then": { + "required": true, + "range": { "min": 1, "max": 82 } + }, + "else": { + "empty": true + } + } + }, + { + "name": "salary_cap_hit", + "valueType": "number", + "description": "Player's salary cap hit in millions of dollars.", + "restrictions": { + "range": { "exclusiveMax": 15.0 } + } + }, + { + "name": "contract_years_remaining", + "valueType": "integer", + "description": "Years remaining on current contract.", + "restrictions": { + "range": { "exclusiveMin": 0, "max": 8 } + } } ] }, @@ -178,24 +372,81 @@ "name": "event_id", "valueType": "string", "unique": true, - "restrictions": { "required": true } + "description": "Unique identifier for each game event.", + "restrictions": { + "required": true, + "regex": "^EVT_[0-9]{8}_[0-9]{3}$" + }, + "meta": { + "examples": ["EVT_20240115_001", "EVT_20240115_002"] + } }, { "name": "game_id", "valueType": "string", - "restrictions": { "required": true } + "description": "Reference to the game where this event occurred.", + "restrictions": { + "required": true, + "foreignKey": [ + { + "schema": "game_statistics", + "mappings": [ + { + "local": "game_id", + "foreign": "game_id" + } + ] + } + ] + } }, { "name": "player_name", "valueType": "string", - "restrictions": { "required": true } + "description": "Name of the player involved in the event.", + "restrictions": { + "required": true, + "foreignKey": [ + { + "schema": "player_profiles", + "mappings": [ + { + "local": "player_name", + "foreign": "player_name" + } + ] + } + ] + } }, { "name": "event_type", "valueType": "string", + "description": "Type of event that occurred.", + "restrictions": { + "required": true, + "codeList": ["goal", "assist", "penalty", "save", "shot", "hit", "block"] + } + }, + { + "name": "period", + "valueType": "integer", + "description": "Game period when the event occurred.", + "restrictions": { + "required": true, + "codeList": [1, 2, 3, 4] + } + }, + { + "name": "time_in_period", + "valueType": "string", + "description": "Time within the period when event occurred (MM:SS format).", "restrictions": { "required": true, - "codeList": ["goal", "assist", "penalty"] + "regex": "^[0-1][0-9]:[0-5][0-9]$" + }, + "meta": { + "examples": ["05:30", "12:45", "19:59"] } } ], @@ -211,6 +462,261 @@ } ] } + }, + { + "name": "advanced_stats", + "description": "Advanced statistical measures for players and games.", + "fields": [ + { + "name": "player_name", + "valueType": "string", + "description": "Name of the player these stats belong to.", + "restrictions": { + "required": true, + "foreignKey": [ + { + "schema": "player_profiles", + "mappings": [ + { + "local": "player_name", + "foreign": "player_name" + } + ] + } + ] + } + }, + { + "name": "game_id", + "valueType": "string", + "description": "Game these statistics are from.", + "restrictions": { + "required": true, + "foreignKey": [ + { + "schema": "game_statistics", + "mappings": [ + { + "local": "game_id", + "foreign": "game_id" + } + ] + } + ] + } + }, + { + "name": "corsi_for_percentage", + "valueType": "number", + "description": "Corsi For percentage (shot attempt differential).", + "restrictions": { + "range": { "min": 0.0, "max": 100.0 } + } + }, + { + "name": "fenwick_for_percentage", + "valueType": "number", + "description": "Fenwick For percentage (unblocked shot attempt differential).", + "restrictions": { + "range": { "exclusiveMin": 0.0, "exclusiveMax": 100.0 } + } + }, + { + "name": "expected_goals", + "valueType": "number", + "description": "Expected goals based on shot quality and location.", + "restrictions": { + "range": { "min": 0.0 } + } + }, + { + "name": "high_danger_chances", + "valueType": "integer", + "description": "Number of high-danger scoring chances.", + "restrictions": { + "range": { "exclusiveMin": 0 } + } + } + ], + "restrictions": { + "uniqueKeys": [["player_name", "game_id"]], + "foreignKeys": [ + { + "schema": "player_profiles", + "mappings": [{ "local": "player_name", "foreign": "player_name" }] + }, + { + "schema": "game_statistics", + "mappings": [{ "local": "game_id", "foreign": "game_id" }] + } + ] + } + }, + { + "name": "test_edge_cases", + "description": "Schema specifically for testing edge cases and comprehensive validation scenarios.", + "fields": [ + { + "name": "record_id", + "valueType": "string", + "unique": true, + "description": "Unique identifier for test records.", + "restrictions": { + "required": true, + "regex": "^TEST_[A-Z]{3}_[0-9]{4}$" + }, + "meta": { + "examples": ["TEST_ABC_1234", "TEST_XYZ_5678"] + } + }, + { + "name": "optional_field", + "valueType": "string", + "description": "This field demonstrates an optional field with no restrictions." + }, + { + "name": "exactly_two_selections", + "valueType": "string", + "isArray": true, + "delimiter": ";", + "description": "Must select exactly 2 items from the list.", + "restrictions": { + "codeList": ["Option A", "Option B", "Option C", "Option D", "Option E"], + "count": 2 + } + }, + { + "name": "select_range_from_list", + "valueType": "string", + "isArray": true, + "delimiter": ",", + "description": "Select between 2 to 4 items from available options.", + "restrictions": { + "codeList": ["Red", "Blue", "Green", "Yellow", "Purple", "Orange"], + "count": { "min": 2, "max": 4 } + } + }, + { + "name": "fewer_than_three", + "valueType": "integer", + "isArray": true, + "delimiter": ",", + "description": "Select fewer than 3 numbers.", + "restrictions": { + "codeList": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + "count": { "exclusiveMax": 3 } + } + }, + { + "name": "more_than_one", + "valueType": "string", + "isArray": true, + "delimiter": "|", + "description": "Must select more than 1 item.", + "restrictions": { + "codeList": ["Alpha", "Beta", "Gamma", "Delta"], + "count": { "exclusiveMin": 1 } + } + }, + { + "name": "score_greater_than_zero", + "valueType": "number", + "description": "Score must be greater than 0.", + "restrictions": { + "range": { "exclusiveMin": 0 } + } + }, + { + "name": "percentage_less_than_hundred", + "valueType": "number", + "description": "Percentage value that must be less than 100.", + "restrictions": { + "range": { "exclusiveMax": 100 } + } + }, + { + "name": "age_range_example", + "valueType": "integer", + "description": "Age must be between 18 and 65 (inclusive).", + "restrictions": { + "range": { "min": 18, "max": 65 } + } + }, + { + "name": "category_type", + "valueType": "string", + "description": "Category that determines other field requirements.", + "restrictions": { + "required": true, + "codeList": ["Premium", "Standard", "Basic"] + } + }, + { + "name": "premium_features", + "valueType": "string", + "isArray": true, + "delimiter": ",", + "description": "Available only for Premium category users.", + "restrictions": { + "if": { + "conditions": [ + { + "fields": ["category_type"], + "match": { + "value": "Premium" + } + } + ] + }, + "then": { + "required": true, + "codeList": ["Advanced Analytics", "Priority Support", "Custom Reporting", "API Access"], + "count": { "min": 1, "max": 3 } + }, + "else": { + "empty": true + } + } + }, + { + "name": "standard_or_premium_feature", + "valueType": "string", + "description": "Feature available for Standard and Premium users.", + "restrictions": { + "if": { + "conditions": [ + { + "fields": ["category_type"], + "match": { + "value": "Basic" + } + } + ] + }, + "then": { + "empty": true + }, + "else": { + "required": true, + "codeList": ["Email Support", "Basic Reporting", "Standard Dashboard"] + } + } + } + ], + "restrictions": { + "uniqueKeys": [["record_id"]], + "foreignKeys": [ + { + "schema": "game_statistics", + "mappings": [ + { + "local": "record_id", + "foreign": "game_id" + } + ] + } + ] + } } ] } diff --git a/packages/ui/stories/viewer-table/SchemaTable.stories.tsx b/packages/ui/stories/viewer-table/SchemaTable.stories.tsx index 819b2d03..4a286837 100644 --- a/packages/ui/stories/viewer-table/SchemaTable.stories.tsx +++ b/packages/ui/stories/viewer-table/SchemaTable.stories.tsx @@ -7,7 +7,6 @@ import Advanced from '../fixtures/advanced.json'; import themeDecorator from '../themeDecorator'; const dictionary: Dictionary = replaceReferences(Advanced as Dictionary); -const schema: Schema = dictionary.schemas[0]; const meta = { component: SchemaTable, @@ -20,7 +19,7 @@ export default meta; type Story = StoryObj; export const Default: Story = { - args: { schema }, + args: { schema: dictionary.schemas[0] }, }; export const PlayerProfiles: Story = { @@ -30,3 +29,11 @@ export const PlayerProfiles: Story = { export const GameEvents: Story = { args: { schema: dictionary.schemas[2] }, }; + +export const AdvancedStats: Story = { + args: { schema: dictionary.schemas[3] }, +}; + +export const EdgeCases: Story = { + args: { schema: dictionary.schemas[4] }, +}; From 91ac1539bbf42a099a60b81487a7d375706e2372 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 26 Jun 2025 09:47:19 -0400 Subject: [PATCH 154/217] remove hello world className --- .../viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx index 6ed37ea5..19f0ca4f 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx @@ -39,7 +39,7 @@ export const renderAttributesColumn = (schemaRestrictions: SchemaRestrictions | return ; }; return ( -
      +
      {schemaRestrictions && 'if' in schemaRestrictions && schemaRestrictions.if ? handleRequiredWhen() : From 487be502c8d9d77d37d74af332f01f53650d7682 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 26 Jun 2025 10:35:25 -0400 Subject: [PATCH 155/217] add new lines --- .../SchemaTable/Columns/AllowedValues.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx index e25a0ce7..29c8f19d 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx @@ -54,38 +54,38 @@ const handleCodeListsWithCountRestrictions = ( isArray: boolean, delimiter: string = ',', ): void => { - const codeListDisplay = Array.isArray(codeList) ? codeList.join(', ') : codeList; + const codeListDisplay = Array.isArray(codeList) ? codeList.join(',\n') : codeList; const delimiterText = isArray ? `, delimited by "${delimiter}"` : ''; if (typeof count === 'number') { restrictionItems.push({ - prefix: `Exactly ${count}${delimiterText} from: `, + prefix: `Exactly ${count}${delimiterText} from:\n`, content: `${codeListDisplay}`, }); } else { if (count.min !== undefined && count.max !== undefined) { restrictionItems.push({ - prefix: `Select ${count.min} to ${count.max}${delimiterText} from: `, + prefix: `Select ${count.min} to ${count.max}${delimiterText} from:\n`, content: `${codeListDisplay}`, }); } else if (count.min !== undefined) { restrictionItems.push({ - prefix: `At least ${count.min}${delimiterText} from: `, + prefix: `At least ${count.min}${delimiterText} from:\n`, content: `${codeListDisplay}`, }); } else if (count.max !== undefined) { restrictionItems.push({ - prefix: `Up to ${count.max}${delimiterText} from: `, + prefix: `Up to ${count.max}${delimiterText} from:\n`, content: `${codeListDisplay}`, }); } else if (count.exclusiveMin !== undefined) { restrictionItems.push({ - prefix: `More than ${count.exclusiveMin}${delimiterText} from: `, + prefix: `More than ${count.exclusiveMin}${delimiterText} from:\n`, content: `${codeListDisplay}`, }); } else if (count.exclusiveMax !== undefined) { restrictionItems.push({ - prefix: `Fewer than ${count.exclusiveMax}${delimiterText} from: `, + prefix: `Fewer than ${count.exclusiveMax}${delimiterText} from:\n`, content: `${codeListDisplay}`, }); } @@ -104,7 +104,7 @@ export const computeAllowedValuesColumn = ( return restrictionItems; } - if ('if' in restrictionsValue && restrictionsValue.if) { + if ('if' in restrictionsValue && (restrictionsValue.if || restrictionsValue.else || restrictionsValue.then)) { restrictionItems.push({ prefix: 'PLACEHOLDER\n', content: 'See field description for conditional requirements', @@ -197,7 +197,7 @@ export const renderAllowedValuesColumn = (restrictions: CellContext - View More + View details
      )}
      From 1dff4954dc7036dc84c503b42fff15eeb2dbb425 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 26 Jun 2025 14:21:42 -0400 Subject: [PATCH 156/217] some naming changes and getting rid of pill dark mode --- packages/ui/src/common/Pill.tsx | 21 +- .../SchemaTable/Columns/AllowedValues.tsx | 67 +- .../SchemaTable/Columns/Attribute.tsx | 2 +- .../SchemaTable/Columns/DataType.tsx | 4 +- .../DataTable/SchemaTable/SchemaTable.tsx | 2 +- .../ui/src/viewer-table/DataTable/Table.tsx | 22 +- .../{advanced.json => TorontoMapleLeafs.json} | 0 packages/ui/stories/fixtures/pcgl.json | 2677 +++++++++++++++++ .../viewer-table/SchemaTable.stories.tsx | 84 +- .../DictionaryVersionSwitcher.stories.tsx | 2 +- .../InteractionPanel.stories.tsx | 2 +- .../TableOfContentDropdown.stories.tsx | 2 +- 12 files changed, 2842 insertions(+), 43 deletions(-) rename packages/ui/stories/fixtures/{advanced.json => TorontoMapleLeafs.json} (100%) create mode 100644 packages/ui/stories/fixtures/pcgl.json diff --git a/packages/ui/src/common/Pill.tsx b/packages/ui/src/common/Pill.tsx index 1648a917..0572fa1c 100644 --- a/packages/ui/src/common/Pill.tsx +++ b/packages/ui/src/common/Pill.tsx @@ -12,17 +12,16 @@ export interface PillProps { size?: PillSize; icon?: ReactNode; onClick?: (event: MouseEvent) => void; - dark?: boolean; style?: CSSProperties; } -const getVariantStyles = (dark: boolean, variant: PillVariant, theme: Theme) => { +const getVariantStyles = (variant: PillVariant, theme: Theme) => { const VARIANT_STYLES = { default: { - background: dark ? '#D9D9D9' : '#E5E7EA', + background: '#E5E7EA', color: theme.colors.black, border: 'none', - hoverBackground: dark ? '#CCCCCC' : '#D8DADD', + hoverBackground: '#D8DADD', }, button: { background: '#FFFF', @@ -41,7 +40,7 @@ const getSizeStyles = (size: PillSize) => { fontSize: '8px', fontWeight: '700', lineHeight: '12px', - borderRadius: '9px', + borderRadius: '3px', gap: '3px', width: '65px', maxWidth: '75px', @@ -51,7 +50,7 @@ const getSizeStyles = (size: PillSize) => { fontSize: '10px', fontWeight: '700', lineHeight: '14px', - borderRadius: '9px', + borderRadius: '4px', gap: '4px', width: '80px', maxWidth: '100px', @@ -61,7 +60,7 @@ const getSizeStyles = (size: PillSize) => { fontSize: '16px', fontWeight: '700', lineHeight: '16px', - borderRadius: '9px', + borderRadius: '5px', gap: '6px', width: '95px', maxWidth: '140px', @@ -71,7 +70,7 @@ const getSizeStyles = (size: PillSize) => { fontSize: '14px', fontWeight: '700', lineHeight: '20px', - borderRadius: '20px', + borderRadius: '6px', gap: '8px', width: '150px', maxWidth: '180px', @@ -81,9 +80,9 @@ const getSizeStyles = (size: PillSize) => { return sizeStyles[size]; }; -const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, dark = false, style }: PillProps) => { +const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, style }: PillProps) => { const theme = useThemeContext(); - const variantStyles = getVariantStyles(dark, variant, theme); + const variantStyles = getVariantStyles(variant, theme); const sizeStyles = getSizeStyles(size); const pillStyles = css` @@ -143,7 +142,7 @@ const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, d tabIndex={onClick ? 0 : undefined} > {icon && {icon}} - {children} + {children}
      ); }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx index 29c8f19d..5f79880d 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx @@ -23,6 +23,7 @@ import { CellContext } from '@tanstack/react-table'; import { Theme } from '../../../../theme'; import { css } from '@emotion/react'; import { useThemeContext } from '../../../../theme/ThemeContext'; +import Pill from '../../../../common/Pill'; export type restrictionItem = { prefix: string | null; @@ -103,13 +104,7 @@ export const computeAllowedValuesColumn = ( restrictionItems.push({ prefix: null, content: 'None' }); return restrictionItems; } - - if ('if' in restrictionsValue && (restrictionsValue.if || restrictionsValue.else || restrictionsValue.then)) { - restrictionItems.push({ - prefix: 'PLACEHOLDER\n', - content: 'See field description for conditional requirements', - }); - } + restrictionItems.push(...handleDependsOn(restrictionsValue)); if ('regex' in restrictionsValue && restrictionsValue.regex) { const regexValue = @@ -158,6 +153,41 @@ export const computeAllowedValuesColumn = ( return restrictionItems; }; +const handleDependsOn = (schemaRestrictions: SchemaRestrictions): restrictionItem[] => { + const restrictionItems: restrictionItem[] = []; + + if (schemaRestrictions && 'compare' in schemaRestrictions && schemaRestrictions.compare) { + } + if ( + schemaRestrictions && + 'if' in schemaRestrictions && + schemaRestrictions.if && + 'conditions' in schemaRestrictions.if && + schemaRestrictions.if.conditions && + Array.isArray(schemaRestrictions.if.conditions) + ) { + const allFields: string[] = []; + schemaRestrictions.if.conditions.forEach((condition) => { + if (condition.fields !== undefined) { + if (Array.isArray(condition.fields)) { + allFields.push(...condition.fields); + } else if (typeof condition.fields === 'string') { + allFields.push(condition.fields); + } + } + }); + // Since there can be multiple fields with multiple conditions, there is a possibility that there are duplicates + const uniqueFields = [...new Set(allFields)]; + if (uniqueFields.length > 0) { + restrictionItems.push({ + prefix: 'Depends on:\n', + content: uniqueFields.join(', '), + }); + } + } + + return restrictionItems; +}; export const renderAllowedValuesColumn = (restrictions: CellContext) => { const restrictionItems = computeAllowedValuesColumn(restrictions); const restrictionsValue: SchemaRestrictions = restrictions.getValue(); @@ -188,6 +218,27 @@ export const renderAllowedValuesColumn = (restrictions: CellContext {restrictionItems.map((item: restrictionItem) => { const { prefix, content } = item; + + //Must render inside of pill + if (prefix === 'Depends on:\n') { + return ( +
      + {prefix && {prefix}} + + {content} + +
      + ); + } return (
      {prefix && {prefix}} @@ -195,7 +246,7 @@ export const renderAllowedValuesColumn = (restrictions: CellContext ); })} - {!!(restrictionsValue && 'if' in restrictionsValue && restrictionsValue.if) && ( + {restrictionsValue && 'if' in restrictionsValue && restrictionsValue.if && (
      View details
      diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx index 19f0ca4f..c3a75b00 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx @@ -42,7 +42,7 @@ export const renderAttributesColumn = (schemaRestrictions: SchemaRestrictions |
      {schemaRestrictions && 'if' in schemaRestrictions && schemaRestrictions.if ? handleRequiredWhen() - : + : {schemaRestrictions && 'required' in schemaRestrictions && schemaRestrictions.required ? 'Required' : 'Optional'} diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx index 56c8cd55..d548c689 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx @@ -44,8 +44,8 @@ export const renderDataTypeColumn = (type: CellContext) => return (
      - {renderContent()} - {unique && Unique} + {renderContent()} + {unique && Unique}
      ); }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTable.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTable.tsx index 3a4fdc96..1b2af498 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTable.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/SchemaTable.tsx @@ -16,9 +16,9 @@ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - import { Schema } from '@overture-stack/lectern-dictionary'; import React from 'react'; + import Table from '../Table'; import { getSchemaBaseColumns } from './SchemaTableInit'; diff --git a/packages/ui/src/viewer-table/DataTable/Table.tsx b/packages/ui/src/viewer-table/DataTable/Table.tsx index 28f078f0..7e320045 100644 --- a/packages/ui/src/viewer-table/DataTable/Table.tsx +++ b/packages/ui/src/viewer-table/DataTable/Table.tsx @@ -39,10 +39,9 @@ const scrollWrapperStyle = css` margin-bottom: 48px; `; -// Shadow overlays const shadowStyle = css` position: absolute; - top: 0; + top: 0.7%; z-index: 100; width: 20px; height: 100%; @@ -57,16 +56,16 @@ const leftShadowStyle = (width: number, opacity: number) => css` opacity: ${opacity}; `; +const rightShadowStyle = (opacity: number) => css` + ${shadowStyle} + right: 0; + background: linear-gradient(270deg, rgba(0, 0, 0, 0.06), transparent); + opacity: ${opacity}; +`; + const tableContainerStyle = css` overflow-x: auto; - -webkit-scrollbar: none; - -ms-overflow-style: none; - scrollbar-width: none; max-width: 100%; - - &::-webkit-scrollbar { - display: none; - } `; const tableStyle = css` @@ -89,6 +88,7 @@ const Table = ({ columns, data }: GenericTableProps) => { /***************************** MESSY SCROLLING BEHAVIOR***********************/ const scrollRef = useRef(null); const [showLeftShadow, setShowLeftShadow] = useState(false); + const [showRightShadow, setShowRightShadow] = useState(false); const [firstColumnWidth, setFirstColumnWidth] = useState(0); // We need to compute the width of the first column in order to make sure that the scrolling occurs after that point @@ -106,8 +106,9 @@ const Table = ({ columns, data }: GenericTableProps) => { if (!scrollRef.current) { return; } - const { scrollLeft } = scrollRef.current; + const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current; setShowLeftShadow(scrollLeft > 0); + setShowRightShadow(scrollWidth - scrollLeft - clientWidth > 0); }, []); // Handle scroll events @@ -135,6 +136,7 @@ const Table = ({ columns, data }: GenericTableProps) => {
      +
      {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( diff --git a/packages/ui/stories/fixtures/advanced.json b/packages/ui/stories/fixtures/TorontoMapleLeafs.json similarity index 100% rename from packages/ui/stories/fixtures/advanced.json rename to packages/ui/stories/fixtures/TorontoMapleLeafs.json diff --git a/packages/ui/stories/fixtures/pcgl.json b/packages/ui/stories/fixtures/pcgl.json new file mode 100644 index 00000000..ceb13cc8 --- /dev/null +++ b/packages/ui/stories/fixtures/pcgl.json @@ -0,0 +1,2677 @@ +{ + "name": "prod_pcgl_schema", + "description": "Base clinical schema that incorporates all PCGL base entities", + "version": "1.1", + "schemas": [ + { + "name": "participant", + "description": "The collection of all data related to a specific individual human in the context of a specific study.", + "fields": [ + { + "meta": { + "displayName": "submitter_participant_id", + "mappings": { + "CQDG": "submitter_participant_id", + "mCODE.STU3": "Patient.Identifier", + "FHIR": "Patient.Identifier", + "Phenopacket": "individual.id", + "BeaconV2": "individual.id", + "ARGO": "submitter_participant_id", + "MOH": "submitter_participant_id" + }, + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_participant_id", + "description": "Unique identifier of the participant within the study, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}" + }, + "valueType": "string", + "unique": true + }, + { + "meta": { + "displayName": "study_id", + "mappings": { + "ARGO": "studyId", + "CQDG": "study_id" + } + }, + "name": "study_id", + "description": "Unique identifier of the study.", + "restrictions": { + "required": true + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "disease_specific_modifier", + "examples": ["MONDO:0000001"] + }, + "name": "disease_specific_modifier", + "description": "If the permission is DUO:0000007 (disease specific research), then MONDO codes must be provided to specify which disease(s) apply.", + "valueType": "string", + "restrictions": { + "if": { + "conditions": [ + { + "fields": ["duo_permission"], + "match": { + "value": "DUO:0000007 (disease specific research)" + }, + "case": "all" + } + ] + }, + "then": { + "required": true, + "regex": "^MONDO:\\d{7}$" + }, + "else": { + "required": false, + "empty": true + } + }, + "isArray": true, + "delimiter": "|" + }, + { + "meta": { + "displayName": "duo_modifier", + "mappings": { + "CQDG": "duo_modifier" + } + }, + "name": "duo_modifier", + "description": "Collection of Data Use Ontology (DUO) codes in Compact URI (CURIE) pattern to add requirements, limitations, or prohibitions within the permitted boundary for the Participant data.", + "valueType": "string", + "restrictions": { + "codeList": [ + "DUO:0000043 (clinical care use)", + "DUO:0000029 (return to database or resource)", + "DUO:0000028 (institution specific restriction)", + "DUO:0000027 (project specific restriction)", + "DUO:0000026 (user specific restriction)", + "DUO:0000025 (time limit on use)", + "DUO:0000024 (publication moratorium)", + "DUO:0000022 (geographical restriction)", + "DUO:0000021 (ethics approval required)", + "DUO:0000020 (collaboration required)", + "DUO:0000019 (publication required)", + "DUO:0000018 (not for profit, non commercial use only)", + "DUO:0000046 (non-commercial use only)", + "DUO:0000045 (not for profit organisation use only)", + "DUO:0000016 (genetic studies only)", + "DUO:0000015 (no general methods research)", + "DUO:0000012 (research specific restrictions)", + "DUO:00000044 (population origins or ancestry research prohibited)" + ] + }, + "isArray": true, + "delimiter": "|" + }, + { + "meta": { + "displayName": "duo_permission", + "mappings": { + "CQDG": "duo_permission" + } + }, + "name": "duo_permission", + "description": "Select one of the Data Use Ontology (DUO) codes in Compact URI (CURIE) pattern to indicate permitted uses or focused areas of research for the Participant data.", + "restrictions": { + "required": true, + "codeList": [ + "DUO:0000042 (general research use)", + "DUO:0000006 (health or medical or biomedical research)", + "DUO:0000007 (disease specific research)", + "DUO:0000011 (population origins or ancestry research only)", + "DUO:0000004 (no restriction)" + ] + }, + "valueType": "string" + } + ] + }, + { + "name": "sociodemographic", + "description": "Captures sociodemographic characteristics of an individual that may vary over time and can influence health status, access to care, or disease outcomes. These factors are often collected longitudinally and reflect broader social determinants of health. The PCGL requires information on eight key sociodemographic variables defined by the Canadian Institute for Health Information (CIHI).", + "fields": [ + { + "meta": { + "displayName": "submitter_sociodem_id", + "examples": ["90234", "BLD_participant_89", "AML-90"] + }, + "name": "submitter_sociodem_id", + "description": "Unique identifier for the sociodemographic record, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}$" + }, + "valueType": "string", + "unique": true + }, + { + "meta": { + "displayName": "submitter_participant_id", + "mappings": { + "CQDG": "submitter_participant_id", + "mCODE.STU3": "Patient.Identifier", + "FHIR": "Patient.Identifier", + "Phenopacket": "individual.id", + "BeaconV2": "individual.id", + "ARGO": "submitter_participant_id", + "MOH": "submitter_participant_id" + }, + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_participant_id", + "description": "Unique identifier of the participant within the study, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "age_at_sociodem_collection", + "mappings": { + "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0": "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0" + } + }, + "name": "age_at_sociodem_collection", + "description": "Age (in days) of the participant at the time of sociodemographic data collection", + "restrictions": { + "required": true + }, + "valueType": "integer" + }, + { + "meta": { + "displayName": "education", + "mappings": { + "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0": "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0" + } + }, + "name": "education", + "description": "The highest level of education attained.", + "restrictions": { + "required": true, + "codeList": [ + "No formal education", + "Elementary school or equivalent", + "High school (secondary school) diploma or equivalency certificate", + "Certificate of Apprenticeship, Certificate of Qualification (Journeyperson's designation), or other trades certificate or diploma", + "College, CEGEP, or other non-university certificate or diploma", + "Bachelor\u2019s degree", + "Degree in medicine, dentistry, veterinary medicine or optometry", + "Master\u2019s degree", + "Doctoral degree", + "Post-doctoral fellowship or training", + "Prefer not to answer", + "Not applicable", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "education_collect_method" + }, + "name": "education_collect_method", + "description": "Studies must report how the education data was collected. This is necessary to enable data aggregation, harmonization, and reproducibility. ", + "restrictions": { + "required": true, + "codeList": [ + "Self-identified", + "Derived", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "ethnicity", + "mappings": { + "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0": "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0" + }, + "required": ["EDI indicator"] + }, + "name": "ethnicity", + "description": "Refers to the ethnic or cultural origins of a person's ancestors. The provided values are based on the list of ethnic or cultural origins 2021 defined by Statistics Canada", + "restrictions": { + "required": true, + "codeList": [ + "Free text input", + "Another Ethnic or Cultural Origin", + "Do not know", + "Prefer not to answer", + "Not applicable", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + }, + "valueType": "string", + "isArray": true, + "delimiter": "|" + }, + { + "meta": { + "displayName": "ethnicity_another_category", + "mappings": { + "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0": "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0" + } + }, + "name": "ethnicity_another_category", + "description": "A response which does not fall into any of the previous categories. For example, a response of \u201cOther\u201d from retrospective studies. Prospective projects should instead allow participants to freely self-identify.", + "valueType": "string", + "restrictions": { + "if": { + "conditions": [ + { + "fields": ["ethnicity"], + "match": { + "codeList": ["Free text input"] + }, + "case": "any" + } + ] + }, + "then": { + "required": true + }, + "else": { + "required": false, + "empty": true + } + } + }, + { + "meta": { + "displayName": "ethnicity_collect_method" + }, + "name": "ethnicity_collect_method", + "description": "Studies must report how the ethnicity data was collected. This is necessary to enable data aggregation, harmonization, and reproducibility. ", + "restrictions": { + "required": true, + "codeList": [ + "Socially assigned", + "Self-identified", + "Derived", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "gender", + "mappings": { + "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0": "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0" + }, + "required": ["EDI indicator"] + }, + "name": "gender", + "description": "Refers to an individual's personal and social identity as a man, woman or non-binary person (a person who is not exclusively a man or a woman). The provided values are based on the categories defined by Statistics Canada", + "restrictions": { + "required": true, + "codeList": [ + "Man", + "Woman", + "Another Gender", + "Prefer not to answer", + "Not applicable", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "gender_another_gender", + "mappings": { + "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0": "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0" + } + }, + "name": "gender_another_gender", + "description": "A response which does not fall into any of the previous gender reporting categories.", + "valueType": "string", + "restrictions": { + "if": { + "conditions": [ + { + "fields": ["gender"], + "match": { + "value": "Another Gender" + }, + "case": "all" + } + ] + }, + "then": { + "required": true + }, + "else": { + "required": false, + "empty": true + } + } + }, + { + "meta": { + "displayName": "gender_collect_method" + }, + "name": "gender_collect_method", + "description": "Studies must report how the gender data was collected. This is necessary to enable data aggregation, harmonization, and reproducibility. ", + "restrictions": { + "required": true, + "codeList": [ + "Self-identified", + "Other", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "geographic_location", + "mappings": { + "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0": "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0" + } + }, + "name": "geographic_location", + "description": "A participant\u2019s postal code at their current address of residence. ", + "restrictions": { + "required": true, + "regex": "^[A-Za-z][0-9][A-Za-z][0-9][A-Za-z][0-9]$|^Missing \\- Unknown$|^Missing \\- Not collected$|^Missing \\- Not provided$|^Missing \\- Restricted access$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "geographic_location_additional" + }, + "name": "geographic_location_additional", + "description": "When postal code is less specific, additional information may be reported.", + "valueType": "string" + }, + { + "meta": { + "displayName": "geographic_location_collect_method" + }, + "name": "geographic_location_collect_method", + "description": "Studies must report how the geographic_location data was collected. This is necessary to enable data aggregation, harmonization, and reproducibility. ", + "restrictions": { + "required": true, + "codeList": [ + "Self-identified", + "Derived", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "personal_income", + "mappings": { + "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0": "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0" + } + }, + "name": "personal_income", + "description": "Personal annual income from all sources after taxes.", + "restrictions": { + "required": true, + "codeList": [ + "Less than $15,000", + "$ 15,000 - $ 19,999", + "$ 20,000 - $ 29,000", + "$ 30,000 - $ 49,999", + "$ 50,000 - $ 69,999", + "$ 70,000 - $ 84,999", + "$ 85,000 - $ 99,999", + "$ 100,000 - $ 124,999", + "$ 125,000 - $ 149,999", + "$ 150,000 or more", + "Prefer not to answer", + "Not applicable", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "personal_income_collect_method" + }, + "name": "personal_income_collect_method", + "description": "Studies must report how the personal_income data was collected. This is necessary to enable data aggregation, harmonization, and reproducibility. ", + "restrictions": { + "required": true, + "codeList": [ + "Self-identified", + "Derived", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "race", + "mappings": { + "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0": "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0" + }, + "required": ["EDI indicator"] + }, + "name": "race", + "description": "A social construct used to judge and categorize people based on perceived differences in physical appearance in ways that create and maintain power differentials within social hierarchies. There is no scientifically supported biological basis for discrete racial groups. The provided values are based on race-based data standard defined by CIHI guidance", + "restrictions": { + "required": true, + "codeList": [ + "Black", + "East Asian", + "Indigenous (First Nations, Inuk/Inuit, M\u00e9tis)", + "Latin American", + "Middle Eastern or North African", + "South Asian", + "Southeast Asian", + "White", + "Another Racial Category", + "Do not know", + "Prefer not to answer", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + }, + "valueType": "string", + "isArray": true, + "delimiter": "|" + }, + { + "meta": { + "displayName": "race_another_racial_category", + "mappings": { + "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0": "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0" + } + }, + "name": "race_another_racial_category", + "description": "A response which does not fall into any of the previous racial reporting categories (e.g., Pacific Islander, Indigenous identities outside of North America, etc.).", + "valueType": "string", + "restrictions": { + "if": { + "conditions": [ + { + "fields": ["race"], + "match": { + "codeList": ["Another Racial Category"] + }, + "case": "all" + } + ] + }, + "then": { + "required": true + }, + "else": { + "required": false, + "empty": true + } + } + }, + { + "meta": { + "displayName": "race_collect_method" + }, + "name": "race_collect_method", + "description": "Studies must report how race data was collected. This is necessary to enable data aggregation, harmonization, and reproducibility. Responses from prospective projects should always be self-identified. To respect participants\u2019 autonomy and right to self-determination, as well as to avoid conflating differences in (genetic) population structure with socially constructed racial categories, responses that were self-identified should not be aggregated with responses that were socially assigned or derived. ", + "restrictions": { + "required": true, + "codeList": [ + "Socially assigned", + "Self-identified", + "Derived", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "sex_another_category", + "mappings": { + "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0": "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0" + } + }, + "name": "sex_another_category", + "description": "A response which does not fall into any of the previous categories. ", + "valueType": "string", + "restrictions": { + "if": { + "conditions": [ + { + "fields": ["sex_at_birth"], + "match": { + "value": "Another Sex" + }, + "case": "any" + } + ] + }, + "then": { + "required": true + }, + "else": { + "required": false, + "empty": true + } + } + }, + { + "meta": { + "displayName": "sex_at_birth", + "mappings": { + "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0": "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0" + }, + "required": ["EDI indicator"] + }, + "name": "sex_at_birth", + "description": "Refers to sex assigned at birth. Sex at birth is typically assigned based on a person's reproductive system and other physical characteristics. The provided values are based on the categoried defined by Statistics Canada", + "restrictions": { + "required": true, + "codeList": [ + "Male", + "Female", + "Intersex", + "Another Sex", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "sex_collect_method" + }, + "name": "sex_collect_method", + "description": "Studies must report how the sex_at_birth data was collected. This is necessary to enable data aggregation, harmonization, and reproducibility. ", + "restrictions": { + "required": true, + "codeList": [ + "Self-identified", + "Clinician-recorded", + "Derived", + "Other", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "sociodem_date_collection", + "mappings": { + "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0": "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0" + } + }, + "name": "sociodem_date_collection", + "description": "Many sociodemographic variables are social constructs, and the way participants self-identify may evolve over time. \nPlease indicate the actual date when the data was collected in format: YYYY-MM-DD, if available. \nIf the exact day is unknown, partial dates are acceptable:\n- Use YYYY-MM if the day is unknown but the month and year are known\n- Use YYYY if only the year is known", + "restrictions": { + "required": true, + "regex": "^(?:\\d{4}|\\d{4}-\\d{2}|\\d{4}-\\d{2}-\\d{2})$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "sociodem_notes", + "mappings": { + "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0": "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0" + }, + "examples": [ + "\n For example, in the Canadian Census, the order of the provided response options for ethnicity were shown to influence number of received responses per reporting option. \n\nOther examples may include inclusion/exclusion criteria based on sociodemographic variables." + ] + }, + "name": "sociodem_notes", + "description": "Studies may choose to report whether potential bias may exist due to the way reporting categories were defined and handled.", + "valueType": "string" + }, + { + "meta": { + "displayName": "sociodem_question" + }, + "name": "sociodem_question", + "description": "Indicate whether the questions asked for the sociodemographic variable follow the PCGL reference question and standard reporting categories.", + "restrictions": { + "required": true, + "codeList": [ + "PCGL reference question", + "Another question", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access", + "Missing - Unknown" + ] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "sociodem_question_detail", + "mappings": { + "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0": "PCGL.Reference.Survey.v1.0.and.PCGL.Guidance.Document.for.Participant.Sociodemographic.Data.v1.0" + } + }, + "name": "sociodem_question_detail", + "description": "Report the questions asked for the sociodemographic variable, including the provided response options/reporting categories if it does not follow the PCGL reference question and standard reporting categories.", + "valueType": "string", + "restrictions": { + "if": { + "conditions": [ + { + "fields": ["sociodem_question"], + "match": { + "codeList": ["PCGL reference question", "Another question"] + }, + "case": "any" + } + ] + }, + "then": { + "required": true + }, + "else": { + "required": false, + "empty": true + } + } + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "participant", + "mappings": [ + { + "local": "submitter_participant_id", + "foreign": "submitter_participant_id" + } + ] + } + ] + } + }, + { + "name": "demographic", + "description": "Data for the characterization of the participant by means of segmenting the population (e.g., characterization by age, sex, or race).", + "fields": [ + { + "meta": { + "displayName": "submitter_participant_id", + "mappings": { + "CQDG": "submitter_participant_id", + "mCODE.STU3": "Patient.Identifier", + "FHIR": "Patient.Identifier", + "Phenopacket": "individual.id", + "BeaconV2": "individual.id", + "ARGO": "submitter_participant_id", + "MOH": "submitter_participant_id" + }, + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_participant_id", + "description": "Unique identifier of the participant within the study, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "age_at_death" + }, + "name": "age_at_death", + "description": "Age of participant (in days) at time of death", + "valueType": "integer" + }, + { + "meta": { + "displayName": "age_at_enrollment" + }, + "name": "age_at_enrollment", + "description": "Age (in days) of participant at time of enrollment into the study", + "valueType": "integer" + }, + { + "meta": { + "displayName": "cause_of_death", + "mappings": { + "ARGO": "cause_of_death", + "MOH": "cause_of_death" + } + }, + "name": "cause_of_death", + "description": "Indicate the cause of a participant's death.", + "valueType": "string", + "restrictions": { + "codeList": [ + "Died of cancer", + "Died of other reasons", + "Not Applicable", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + } + }, + { + "meta": { + "displayName": "vital_status", + "mappings": { + "CQDG": "vital_status", + "Phenopacket": "individual.vital_status", + "BeaconV2": "individual.vital_status" + } + }, + "name": "vital_status", + "description": "Participant's last known state of living or deceased.", + "valueType": "string", + "restrictions": { + "codeList": [ + "Alive", + "Deceased", + "Not Applicable", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access" + ] + } + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "participant", + "mappings": [ + { + "local": "submitter_participant_id", + "foreign": "submitter_participant_id" + } + ] + } + ] + } + }, + { + "name": "diagnosis", + "description": "The disease that is inferred to be present in the individual, family or cohort being analyzed.", + "fields": [ + { + "meta": { + "displayName": "submitter_diagnosis_id", + "mappings": { + "mCODE.STU1": "Condition.identifier", + "mCODE.STU2": "Condition.identifier", + "FHIR": "condition.code", + "ARGO": "submitter_diagnosis_id", + "MOH": "submitter_diagnosis_id" + }, + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_diagnosis_id", + "description": "Unique identifier of the primary diagnosis event, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}$" + }, + "valueType": "string", + "unique": true + }, + { + "meta": { + "displayName": "submitter_participant_id", + "mappings": { + "CQDG": "submitter_participant_id", + "mCODE.STU3": "Patient.Identifier", + "FHIR": "Patient.Identifier", + "Phenopacket": "individual.id", + "BeaconV2": "individual.id", + "ARGO": "submitter_participant_id", + "MOH": "submitter_participant_id" + }, + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_participant_id", + "description": "Unique identifier of the participant within the study, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "age_at_diagnosis", + "mappings": { + "mCODE.STU3": "condition.onset", + "FHIR": "condition.onset", + "Phenopacket": "disease.onset", + "ARGO": "age_at_diagnosis", + "MOH": "age_at_diagnosis" + } + }, + "name": "age_at_diagnosis", + "description": "Age of participant (in days) at time of diagnosis of the condition.", + "valueType": "integer" + }, + { + "meta": { + "displayName": "disease_code", + "mappings": { + "mCODE.STU3": "condition.code", + "FHIR": "condition.code", + "Phenopacket": "disease.term", + "ARGO": "disease_code", + "MOH": "disease_code", + "CQDG": "disease_code" + }, + "required": [ + "Provide code in Compact URI (CURIE) pattern. ICD-10 code: refer to https://icd.who.int/browse10/2019/en MONDO code: refer to https://www.ebi.ac.uk/ols/ontologies/mondo" + ], + "examples": ["MONDO:0000001", "icd10:C34"] + }, + "name": "disease_code", + "description": "Use ICD-10 code or Mondo code to represent the disease diagnosed.", + "restrictions": { + "required": true, + "regex": "^icd10:(([XVI]+)|([A-Z][0-9]+((-[A-Z][0-9]+)|(\\.[0-9]))?))$|^MONDO:\\d{7}$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "disease_term", + "mappings": { + "mCODE.STU3": "condition.code", + "FHIR": "condition.code", + "Phenopacket": "disease.term" + } + }, + "name": "disease_term", + "description": "Provide the standardized and human readable term derived from the coding system associated with the disease_code", + "valueType": "string" + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "participant", + "mappings": [ + { + "local": "submitter_participant_id", + "foreign": "submitter_participant_id" + } + ] + } + ] + } + }, + { + "name": "treatment", + "description": "Medications, procedures, other actions taken for clinical management", + "fields": [ + { + "meta": { + "displayName": "submitter_treatment_id", + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_treatment_id", + "description": "Unique identifier of the treatment, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}$" + }, + "valueType": "string", + "unique": true + }, + { + "meta": { + "displayName": "submitter_diagnosis_id", + "mappings": { + "mCODE.STU1": "Condition.identifier", + "mCODE.STU2": "Condition.identifier", + "FHIR": "condition.code", + "ARGO": "submitter_diagnosis_id", + "MOH": "submitter_diagnosis_id" + }, + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_diagnosis_id", + "description": "Unique identifier of the primary diagnosis event, assigned by the data provider.", + "restrictions": { + "if": { + "conditions": [ + { + "fields": ["submitter_treatment_id"], + "match": { + "exists": false + }, + "case": "all" + } + ] + }, + "then": { + "required": false, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}$" + } + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "submitter_participant_id", + "mappings": { + "CQDG": "submitter_participant_id", + "mCODE.STU3": "Patient.Identifier", + "FHIR": "Patient.Identifier", + "Phenopacket": "individual.id", + "BeaconV2": "individual.id", + "ARGO": "submitter_participant_id", + "MOH": "submitter_participant_id" + }, + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_participant_id", + "description": "Unique identifier of the participant within the study, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "age_at_treatment" + }, + "name": "age_at_treatment", + "description": "Age (in days) of the participant at the time the treatment was administered.", + "valueType": "integer" + }, + { + "meta": { + "displayName": "treatment_duration" + }, + "name": "treatment_duration", + "description": "The length of time (in days) over which the treatment was administered.", + "valueType": "integer" + }, + { + "meta": { + "displayName": "treatment_intent" + }, + "name": "treatment_intent", + "description": "The purpose of the treatment or the desired effect or outcome resulting from the treatment.", + "valueType": "string", + "restrictions": { + "codeList": [ + "Curative", + "Diagnostic", + "Forensic", + "Guidance", + "Palliative", + "Preventative", + "Screening", + "Supportive", + "Other", + "Missing - Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access", + "Not applicable" + ] + } + }, + { + "meta": { + "displayName": "treatment_response", + "mappings": { + "ARGO": "treatment_response", + "MOH": "treatment_response", + "CQDG": "treatment_response" + } + }, + "name": "treatment_response", + "description": "The outcome of the treatment, indicating how the patient responded to the intervention", + "valueType": "string", + "restrictions": { + "codeList": [ + "Clinical remission", + "Disease Progression", + "Improvement of symptoms", + "No improvement of symptoms", + "No sign of disease", + "Partial Response", + "Stable Disease", + "Treatment cessation due to toxicity", + "Worsening of symptoms", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access", + "Missing - Unknown", + "Not applicable" + ] + } + }, + { + "meta": { + "displayName": "treatment_status", + "mappings": { + "ARGO": "treatment_status", + "MOH": "treatment_status" + } + }, + "name": "treatment_status", + "description": "Indicate the donor's status of the prescribed treatment.", + "valueType": "string", + "restrictions": { + "codeList": [ + "Other", + "Patient choice (stopped or interrupted treatment)", + "Physician decision (stopped or interrupted treatment)", + "Treatment completed as prescribed", + "Treatment incomplete because patient died", + "Treatment incomplete due to technical or organizational problems", + "Treatment ongoing", + "Treatment stopped due to acute toxicity", + "Treatment stopped due to lack of efficacy (disease progression)", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access", + "Missing - Unknown", + "Not applicable" + ] + } + }, + { + "meta": { + "displayName": "treatment_type" + }, + "name": "treatment_type", + "description": "The category or method of treatment administered", + "restrictions": { + "required": true, + "codeList": ["Medication", "Pharmacotherapy", "Procedure", "Radiation therapy", "Other"] + }, + "valueType": "string" + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "diagnosis", + "mappings": [ + { + "local": "submitter_diagnosis_id", + "foreign": "submitter_diagnosis_id" + } + ] + }, + { + "schema": "participant", + "mappings": [ + { + "local": "submitter_participant_id", + "foreign": "submitter_participant_id" + } + ] + } + ] + } + }, + { + "name": "follow_up", + "description": "Any point of contact with a patient after primary diagnosis. ", + "fields": [ + { + "meta": { + "displayName": "submitter_participant_id", + "mappings": { + "CQDG": "submitter_participant_id", + "mCODE.STU3": "Patient.Identifier", + "FHIR": "Patient.Identifier", + "Phenopacket": "individual.id", + "BeaconV2": "individual.id", + "ARGO": "submitter_participant_id", + "MOH": "submitter_participant_id" + }, + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_participant_id", + "description": "Unique identifier of the participant within the study, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "age_at_followup" + }, + "name": "age_at_followup", + "description": "Participant's age (in days) at time of the follow up event", + "valueType": "integer" + }, + { + "meta": { + "displayName": "disease_status_at_followup" + }, + "name": "disease_status_at_followup", + "description": "Indicate the participant's disease status at time of follow-up", + "restrictions": { + "required": true, + "codeList": [ + "Complete remission", + "Distant progression", + "Loco-regional progression", + "No evidence of disease", + "Partial remission", + "Progression NOS", + "Relapse or recurrence", + "Stable" + ] + }, + "valueType": "string" + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "participant", + "mappings": [ + { + "local": "submitter_participant_id", + "foreign": "submitter_participant_id" + } + ] + } + ] + } + }, + { + "name": "procedure", + "description": "A clinical procedure performed on a subject. For example a surgical or diagnostic procedure such as a biopsy.", + "fields": [ + { + "meta": { + "displayName": "submitter_treatment_id", + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_treatment_id", + "description": "Unique identifier of the treatment, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "procedure_body_site_code", + "mappings": { + "mCODE.STU3": "Specimen.collection.bodySite", + "FHIR": "Specimen.collection.bodySite", + "ARGO": "procedure_body_site_code", + "MOH": "procedure_body_site_code" + }, + "required": [ + "Refer to the International Classification of Diseases for Oncology, 3rd Edition (WHO ICD-O-3) manual for guidelines at https://apps.who.int/iris/handle/10665/42344" + ], + "examples": ["C50.1", "C18"] + }, + "name": "procedure_body_site_code", + "description": "Indicate the ICD-O-3 topography code to describe the site of the surgery if applicable. Please use C80.9 if the site is Unkown.", + "restrictions": { + "required": true, + "regex": "^ICDO3:[C][0-9]{2}(.[0-9]{1})?$|^UBERON:\\d{7,}$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "procedure_body_site_term" + }, + "name": "procedure_body_site_term", + "description": "Provide the standardized and human readable term derived from the coding system associated with the procedure_body_site_code", + "valueType": "string" + }, + { + "meta": { + "displayName": "procedure_code", + "required": [ + "Provide code in Compact URI (CURIE) pattern. NCIt URL: https://ncit.nci.nih.gov/ncitbrowser/ConceptReport.jsp?dictionary=NCI_Thesaurus&code=C SNOMED-CT URL: http://snomed.info/id/ UMLS URL: https://uts.nlm.nih.gov/uts/umls/concept/ CCI URL: https://www.cihi.ca/en/cci-coding-structure" + ], + "examples": ["NCIT:C51894", "snomedct:284196006", "umls:C2584994", "CCI:1CA14STCDR"] + }, + "name": "procedure_code", + "description": "Use code from NCIt, SNOMED-CT, UMLS or CCI to represent the procedure performed.", + "restrictions": { + "required": true, + "regex": "^NCIT:C\\d+$|^snomedct:(\\w+)?\\d+$|^umls:C\\d+$|^CCI:[A-Z0-9]([0-9]{2}|[A-Z]{2}){3}(([0-9]{2}|[A-Z]{2})[A-Z0-9]?)?$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "procedure_term" + }, + "name": "procedure_term", + "description": "Provide the standardized and human readable term derived from the coding system associated with the procedure_code", + "valueType": "string" + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "treatment", + "mappings": [ + { + "local": "submitter_treatment_id", + "foreign": "submitter_treatment_id" + } + ] + } + ] + } + }, + { + "name": "medication", + "description": "An agent such as a drug (pharmaceutical agent), broadly defined as prescription and over-the-counter medicines, vaccines, and large-molecule biologic therapies.", + "fields": [ + { + "meta": { + "displayName": "submitter_treatment_id", + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_treatment_id", + "description": "Unique identifier of the treatment, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "actual_cumulative_drug_dose" + }, + "name": "actual_cumulative_drug_dose", + "description": "Indicate the total actual cumulative drug dose in the same units specified in drug_dose_units.", + "valueType": "number" + }, + { + "meta": { + "displayName": "drug_code", + "required": [ + "Provide code in Compact URI (CURIE) pattern. RxNorm URL: https://rxnav.nlm.nih.gov/REST/rxcui/{code} KEGG URL: https://www.kegg.jp/entry/{code} PubChem URL: https://pubchem.ncbi.nlm.nih.gov/compound/{code} NCIt URL: https://ncit.nci.nih.gov/ncitbrowser/ConceptReport.jsp?dictionary=NCI_Thesaurus&code=C{code}" + ], + "examples": ["rxnorm:221058", "kegg.drug:D00123", "pubchem.compound:100101", "NCIT:C138986"] + }, + "name": "drug_code", + "description": "Provide the standardized code from RxNorm, KEGG, PubChem or NCIt to represent the drug.", + "restrictions": { + "required": true, + "regex": "^rxnorm:\\d{1,8}$|^kegg\\.drug:D\\d+$|^pubchem\\.chem:\\d+$|^NCIT:C\\d{1,7}$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "drug_dose_units" + }, + "name": "drug_dose_units", + "description": "Indicate units used to record drug dose.", + "valueType": "string", + "restrictions": { + "regex": "^UO:\\d{7}$" + } + }, + { + "meta": { + "displayName": "drug_term" + }, + "name": "drug_term", + "description": "Provide the standardized and human readable term derived from the coding system associated with the drug_code", + "valueType": "string" + }, + { + "meta": { + "displayName": "prescribed_cumulative_drug_dose" + }, + "name": "prescribed_cumulative_drug_dose", + "description": "Indicate the total prescribed cumulative drug dose in the same units specified in drug_dose_units.", + "valueType": "number" + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "treatment", + "mappings": [ + { + "local": "submitter_treatment_id", + "foreign": "submitter_treatment_id" + } + ] + } + ] + } + }, + { + "name": "radiation", + "description": "Uses ionizing radiation, generally as part of cancer treatment to control or kill malignant cells.", + "fields": [ + { + "meta": { + "displayName": "submitter_treatment_id", + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_treatment_id", + "description": "Unique identifier of the treatment, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "anatomical_site_irradiated_code", + "mappings": { + "mCODE.STU3": "Specimen.collection.bodySite", + "FHIR": "Specimen.collection.bodySite", + "MOH": "anatomical_site_irradiated_code", + "ARGO": "anatomical_site_irradiated_code" + }, + "required": [ + "Refer to the International Classification of Diseases for Oncology, 3rd Edition (WHO ICD-O-3) manual for guidelines at https://apps.who.int/iris/handle/10665/42344" + ], + "examples": ["C50.1", "C18"] + }, + "name": "anatomical_site_irradiated_code", + "description": "Indicate the ICD-O-3 topography code to describe the irradiated site of radiation if applicable. Please use C80.9 if the site is Unkown.", + "valueType": "string", + "restrictions": { + "regex": "^ICDO3:[C][0-9]{2}(.[0-9]{1})?$|^UBERON:\\d{7,}$" + } + }, + { + "meta": { + "displayName": "anatomical_site_irradiated_term" + }, + "name": "anatomical_site_irradiated_term", + "description": "Provide the standardized and human readable term derived from the coding system associated with the anatomical_site_irradiated_code", + "valueType": "string" + }, + { + "meta": { + "displayName": "radiation_dosage" + }, + "name": "radiation_dosage", + "description": "Indicate the total dose given in units of Gray (Gy).", + "valueType": "integer" + }, + { + "meta": { + "displayName": "radiation_fractions" + }, + "name": "radiation_fractions", + "description": "Indicate the total number of fractions delivered as part of treatment.", + "valueType": "integer" + }, + { + "meta": { + "displayName": "radiation_modality_code", + "mappings": { + "Phenopacket": "radiation_modality_code" + }, + "examples": ["NCIT:C28039"] + }, + "name": "radiation_modality_code", + "description": "Indicate NCIt code to denote the modality of radiation therapy.\nProvide code in Compact URI (CURIE) pattern.", + "restrictions": { + "required": true, + "regex": "^NCIT:C\\d+$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "radiation_modality_term", + "examples": ["Electron Beam"] + }, + "name": "radiation_modality_term", + "description": "Provide the standardized and human readable term derived from the coding system associated with the radiation modality code.", + "valueType": "string" + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "treatment", + "mappings": [ + { + "local": "submitter_treatment_id", + "foreign": "submitter_treatment_id" + } + ] + } + ] + } + }, + { + "name": "measurement", + "description": "Record individual measurements to capture quantitative, ordinal (e.g., absent/present), or categorical measurements.", + "fields": [ + { + "meta": { + "displayName": "submitter_participant_id", + "mappings": { + "CQDG": "submitter_participant_id", + "mCODE.STU3": "Patient.Identifier", + "FHIR": "Patient.Identifier", + "Phenopacket": "individual.id", + "BeaconV2": "individual.id", + "ARGO": "submitter_participant_id", + "MOH": "submitter_participant_id" + }, + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_participant_id", + "description": "Unique identifier of the participant within the study, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "age_at_measurement" + }, + "name": "age_at_measurement", + "description": "Age (in days) of the participant at the time the lab test or measurement was conducted.", + "valueType": "integer" + }, + { + "meta": { + "displayName": "measurement_code", + "required": ["Provide code in Compact URI (CURIE) pattern. LOINC URL: https://loinc.org/{code}"] + }, + "name": "measurement_code", + "description": "Use standartized LOINC (Logical Observation Identifiers Names and Codes) code to represent quantitative, ordinal, or categorical measurements.", + "restrictions": { + "required": true, + "regex": "^LOINC:[0-9]{1,5}-[0-9]$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "measurement_result_categorical" + }, + "name": "measurement_result_categorical", + "description": "The categorical result of the lab test or measurement.", + "valueType": "string" + }, + { + "meta": { + "displayName": "measurement_result_numeric" + }, + "name": "measurement_result_numeric", + "description": "The numeric result of the lab test or measurement.", + "valueType": "number" + }, + { + "meta": { + "displayName": "measurement_term" + }, + "name": "measurement_term", + "description": "Provide the standardized and human readable term derived from the coding system associated with the measurement_code", + "valueType": "string" + }, + { + "meta": { + "displayName": "measurement_unit" + }, + "name": "measurement_unit", + "description": "Indicate the unit of measurement for the result using LOINC code.", + "valueType": "string", + "restrictions": { + "regex": "^UO:\\d{7}$" + } + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "participant", + "mappings": [ + { + "local": "submitter_participant_id", + "foreign": "submitter_participant_id" + } + ] + } + ] + } + }, + { + "name": "phenotype", + "description": "Individual phenotypic features, observed as either present or absent (excluded), with possible onset, modifiers and frequency", + "fields": [ + { + "meta": { + "displayName": "submitter_participant_id", + "mappings": { + "CQDG": "submitter_participant_id", + "mCODE.STU3": "Patient.Identifier", + "FHIR": "Patient.Identifier", + "Phenopacket": "individual.id", + "BeaconV2": "individual.id", + "ARGO": "submitter_participant_id", + "MOH": "submitter_participant_id" + }, + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_participant_id", + "description": "Unique identifier of the participant within the study, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "age_at_phenotype" + }, + "name": "age_at_phenotype", + "description": "Participant's age (in days) when phenotype was observed", + "valueType": "integer" + }, + { + "meta": { + "displayName": "phenotype_code", + "required": [ + "Provide code in Compact URI (CURIE) pattern. HPO URL: https://hpo.jax.org/app/browse/term/HP:" + ] + }, + "name": "phenotype_code", + "description": "Use standardized HPO (Human Phenotype Ontology) codes to represent the phenotype.", + "restrictions": { + "required": true, + "regex": "^HP:[0-9]{7}$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "phenotype_duration" + }, + "name": "phenotype_duration", + "description": "Indicate the length of time (in days) over which the phenotype was observed in the participant.", + "valueType": "integer" + }, + { + "meta": { + "displayName": "phenotype_observed" + }, + "name": "phenotype_observed", + "description": "Indicate whether the phenotype was observed in the participant.", + "restrictions": { + "required": true, + "codeList": ["No", "Unknown", "Yes"] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "phenotype_severity", + "required": ["Permissible values from https://hpo.jax.org/browse/term/HP:0012824"] + }, + "name": "phenotype_severity", + "description": "The degree or severity of the observed phenotype.", + "valueType": "string", + "restrictions": { + "codeList": ["Borderline", "Mild", "Moderate", "Profound", "Severe"] + } + }, + { + "meta": { + "displayName": "phenotype_term" + }, + "name": "phenotype_term", + "description": "Provide the standardized and human readable term derived from the coding system associated with the phenotype_code.", + "valueType": "string" + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "participant", + "mappings": [ + { + "local": "submitter_participant_id", + "foreign": "submitter_participant_id" + } + ] + } + ] + } + }, + { + "name": "comorbidity", + "description": "Any medical conditions (e.g diabetes, prior cancer malignancies) that have existed or may occur during the clinical course of the participant who has the index disease under study.", + "fields": [ + { + "meta": { + "displayName": "submitter_participant_id", + "mappings": { + "CQDG": "submitter_participant_id", + "mCODE.STU3": "Patient.Identifier", + "FHIR": "Patient.Identifier", + "Phenopacket": "individual.id", + "BeaconV2": "individual.id", + "ARGO": "submitter_participant_id", + "MOH": "submitter_participant_id" + }, + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_participant_id", + "description": "Unique identifier of the participant within the study, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "age_at_comorbidity_diagnosis" + }, + "name": "age_at_comorbidity_diagnosis", + "description": "Indicate the age (in days) of comorbidity diagnosis.", + "valueType": "integer" + }, + { + "meta": { + "displayName": "comorbidity_code", + "required": [ + "Provide code in Compact URI (CURIE) pattern. ICD-10 code: refer to https://icd.who.int/browse10/2019/en MONDO code: refer to https://www.ebi.ac.uk/ols/ontologies/mondo" + ], + "examples": ["MONDO:0000001", "icd10:C34"] + }, + "name": "comorbidity_code", + "description": "Use ICD-10 code or Mondo code to indicate the comorbidity diagnosed. Provide code in Compact URI (CURIE) pattern.", + "restrictions": { + "required": true, + "regex": "^icd10:(([XVI]+)|([A-Z][0-9]+((-[A-Z][0-9]+)|(\\.[0-9]))?))$|^MONDO:\\d{7}$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "comorbidity_status" + }, + "name": "comorbidity_status", + "description": "Indicate the current state or activity of the comorbid condition.", + "valueType": "string", + "restrictions": { + "codeList": [ + "Active", + "In Remission", + "Resolved", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access", + "Missing - Unknown", + "Not applicable" + ] + } + }, + { + "meta": { + "displayName": "comorbidity_term", + "mappings": { + "mCODE.STU3": "condition.code", + "FHIR": "condition.code", + "Phenopacket": "disease.term" + } + }, + "name": "comorbidity_term", + "description": "Provide the standardized and human readable term derived from the coding system associated with the comorbidity_code", + "valueType": "string" + }, + { + "meta": { + "displayName": "comorbidity_treatment_status" + }, + "name": "comorbidity_treatment_status", + "description": "Indicate whether the comorbid condition is currently being treated or not.", + "valueType": "string", + "restrictions": { + "codeList": [ + "Treated and resolved", + "Under treatment", + "Untreated", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access", + "Missing - Unknown", + "Not applicable" + ] + } + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "participant", + "mappings": [ + { + "local": "submitter_participant_id", + "foreign": "submitter_participant_id" + } + ] + } + ] + } + }, + { + "name": "exposure", + "description": "Capture information about external factors, agents, or conditions an individual has encountered that may influence health or disease outcomes.", + "fields": [ + { + "meta": { + "displayName": "submitter_participant_id", + "mappings": { + "CQDG": "submitter_participant_id", + "mCODE.STU3": "Patient.Identifier", + "FHIR": "Patient.Identifier", + "Phenopacket": "individual.id", + "BeaconV2": "individual.id", + "ARGO": "submitter_participant_id", + "MOH": "submitter_participant_id" + }, + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_participant_id", + "description": "Unique identifier of the participant within the study, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "age_at_exposure" + }, + "name": "age_at_exposure", + "description": "Age (in days) when the participant was exposed (or first exposed) ", + "valueType": "integer" + }, + { + "meta": { + "displayName": "exposure_amount" + }, + "name": "exposure_amount", + "description": "Provide intensity, quantity or frequency of the exposure", + "valueType": "integer" + }, + { + "meta": { + "displayName": "exposure_code", + "examples": ["LOINC:43164-0"] + }, + "name": "exposure_code", + "description": "Provide the standardized codes from SNOMED CT, ExO, LOINC to represent the type of exposure.\nProvide code in Compact URI (CURIE) pattern.\n", + "restrictions": { + "required": true, + "regex": "^snomedct:(\\w+)?\\d+$|^ExO:\\d{7}$|^LOINC:[0-9]{1,5}-[0-9]$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "exposure_duration" + }, + "name": "exposure_duration", + "description": "Duration of exposure period", + "valueType": "integer" + }, + { + "meta": { + "displayName": "exposure_status" + }, + "name": "exposure_status", + "description": "Provide the status of the exposure", + "restrictions": { + "required": true, + "codeList": [ + "Current", + "Former", + "Never", + "Exposed - Current Unknown", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access", + "Missing - Unknown", + "Not applicable" + ] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "exposure_term", + "examples": ["Alcohol use"] + }, + "name": "exposure_term", + "description": "Provide the standardized and human readable term derived from the coding system associated with the exposure_code", + "valueType": "string" + }, + { + "meta": { + "displayName": "exposure_unit", + "examples": ["drinks/day"] + }, + "name": "exposure_unit", + "description": "Unit associated with exposure_amount", + "valueType": "string" + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "participant", + "mappings": [ + { + "local": "submitter_participant_id", + "foreign": "submitter_participant_id" + } + ] + } + ] + } + }, + { + "name": "specimen", + "description": "Any material sample taken from a biological entity, living or dead, from physical object or the environment", + "fields": [ + { + "meta": { + "displayName": "submitter_specimen_id", + "mappings": { + "CQDG": "submitter_biospecimen_id", + "mCODE.STU3": "Specimen.Identifier", + "FHIR": "Specimen.Identifier", + "Phenopacket": "biosample.id", + "BeaconV2": "biosample.id", + "ARGO": "submitter_specimen_id", + "MOH": "submitter_specimen_id" + }, + "examples": ["LAML_PO", "00445", "THY_099-tumour"] + }, + "name": "submitter_specimen_id", + "description": "Unique identifier of the specimen within the study, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}$" + }, + "valueType": "string", + "unique": true + }, + { + "meta": { + "displayName": "submitter_participant_id", + "mappings": { + "CQDG": "submitter_participant_id", + "mCODE.STU3": "Patient.Identifier", + "FHIR": "Patient.Identifier", + "Phenopacket": "individual.id", + "BeaconV2": "individual.id", + "ARGO": "submitter_participant_id", + "MOH": "submitter_participant_id" + }, + "examples": ["90234", "BLD_donor_89", "AML-90"] + }, + "name": "submitter_participant_id", + "description": "Unique identifier of the participant within the study, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "age_at_specimen_collection" + }, + "name": "age_at_specimen_collection", + "description": "Indicate participant's age( in days) when specimen was collected.", + "valueType": "integer" + }, + { + "meta": { + "displayName": "specimen_anatomic_location_code", + "mappings": { + "mCODE.STU3": "Specimen.collection.bodySite", + "FHIR": "Specimen.collection.bodySite", + "ARGO": "specimen_anatomic_location_code", + "MOH": "specimen_anatomic_location_code" + }, + "required": [ + "Refer to the International Classification of Diseases for Oncology, 3rd Edition (WHO ICD-O-3) manual for guidelines at https://apps.who.int/iris/handle/10665/42344." + ], + "examples": ["C50.1", "C18"] + }, + "name": "specimen_anatomic_location_code", + "description": "Indicate the ICD-O-3 topography code for the anatomic location of a specimen when it was collected. Please use C80.9 if the primary site of a specimen is Unkown.", + "valueType": "string", + "restrictions": { + "regex": "^ICDO3:[C][0-9]{2}(.[0-9]{1})?$|^UBERON:\\d{7,}$" + } + }, + { + "meta": { + "displayName": "specimen_anatomic_location_term", + "examples": ["placental basal plate"] + }, + "name": "specimen_anatomic_location_term", + "description": "Provide the standardized and human readable term derived from the coding system associated with the specimen anatomic location code.", + "valueType": "string" + }, + { + "meta": { + "displayName": "specimen_laterality", + "mappings": { + "mCODE.STU3": "Specimen.collection.bodySite.extension.lateralityQualifier", + "FHIR": "Specimen.collection.bodySite.extension.lateralityQualifier" + }, + "required": [ + "Reference caDSR CDE ID 2007875 https://cdebrowser.nci.nih.gov/cdebrowserClient/cdeBrowser.html#/search?publicId=2007875&version=2.0" + ] + }, + "name": "specimen_laterality", + "description": "For cancer in a paired organ, indicate the side on which the specimen was obtained.", + "valueType": "string", + "restrictions": { + "codeList": [ + "Left", + "Right", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access", + "Missing - Unknown", + "Not applicable" + ] + } + }, + { + "meta": { + "displayName": "specimen_processing", + "mappings": { + "Phenopacket": "biosample.sample_processing", + "BeaconV2": "biosample.sample_processing", + "ARGO": "specimen_processing", + "MOH": "specimen_processing" + } + }, + "name": "specimen_processing", + "description": "Indicate the technique used to process specimen.", + "valueType": "string", + "restrictions": { + "codeList": [ + "Cryopreservation - other", + "Cryopreservation in dry ice (dead tissue)", + "Cryopreservation in liquid nitrogen (dead tissue)", + "Cryopreservation of live cells in liquid nitrogen", + "Formalin fixed & paraffin embedded", + "Formalin fixed - buffered", + "Formalin fixed - unbuffered", + "Fresh", + "Other", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access", + "Missing - Unknown", + "Not applicable" + ] + } + }, + { + "meta": { + "displayName": "specimen_storage", + "mappings": { + "Phenopacket": "biosample.sample_storage", + "BeaconV2": "biosample.sample_storage", + "ARGO": "specimen_storage", + "MOH": "specimen_storage" + } + }, + "name": "specimen_storage", + "description": "Indicate the method of long term storage for specimen that were not extracted freshly or immediately cultured.", + "valueType": "string", + "restrictions": { + "codeList": [ + "Cut slide", + "Frozen in -70 freezer", + "Frozen in liquid nitrogen", + "Frozen in vapour phase", + "Not Applicable", + "Other", + "Paraffin block", + "RNA later frozen", + "Missing - Not collected", + "Missing - Not provided", + "Missing - Restricted access", + "Missing - Unknown", + "Not applicable" + ] + } + }, + { + "meta": { + "displayName": "specimen_tissue_source_code", + "mappings": { + "CQDG": "biospecimen_tissue_source", + "mCODE.STU3": "Specimen.type", + "FHIR": "Specimen.type", + "Phenopacket": "biosample.sampled_tissue", + "BeaconV2": "biosample.sampled_tissue" + } + }, + "name": "specimen_tissue_source_code", + "description": "Indicate the tissue source of the specimen from which a biopsy or other tissue specimen was obtained. Use codes from NCIt (NCI Thesaurus) or SNOMED-CT (SNOMED Clinical Terms) in Compact URI (CURIE) pattern.", + "restrictions": { + "required": true, + "regex": "^NCIT:C[0-9]+$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "specimen_tissue_source_term" + }, + "name": "specimen_tissue_source_term", + "description": "Indicate the human readable label for the specimen tissue source code.", + "valueType": "string" + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "participant", + "mappings": [ + { + "local": "submitter_participant_id", + "foreign": "submitter_participant_id" + } + ] + } + ] + } + }, + { + "name": "sample", + "description": "Refers to the molecular material (e.g., DNA, RNA, protein) extracted from the specimen and used for experiments or analysis.", + "fields": [ + { + "meta": { + "displayName": "submitter_sample_id", + "mappings": { + "FHIR": "submitter_sample_id", + "mCODE": "submitter_sample_id", + "MOH": "submitter_sample_id", + "ARGO": "submitter_sample_id" + }, + "examples": ["hnc_12", "CCG_34_94583", "BRCA47832-3239"] + }, + "name": "submitter_sample_id", + "description": "Unique identifier of the sample within the study, assigned by the data provider.\n", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}$" + }, + "valueType": "string", + "unique": true + }, + { + "meta": { + "displayName": "submitter_specimen_id", + "mappings": { + "CQDG": "submitter_biospecimen_id", + "mCODE.STU3": "Specimen.Identifier", + "FHIR": "Specimen.Identifier", + "Phenopacket": "biosample.id", + "BeaconV2": "biosample.id", + "ARGO": "submitter_specimen_id", + "MOH": "submitter_specimen_id" + }, + "examples": ["LAML_PO", "00445", "THY_099-tumour"] + }, + "name": "submitter_specimen_id", + "description": "Unique identifier of the specimen within the study, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "molecule_type_code", + "mappings": { + "CQDG": "sample_type", + "Phenopacket": "sample_type", + "BeaconV2": "biosample.sample_type", + "GA4GH.Experiment.Metadata.Checklist": "molecule_type" + }, + "examples": ["NCIT:C449", "NCIT:C812", "NNCIT:C80376", "SO:0000991", "SO:0000234", "SO:0001877"] + }, + "name": "molecule_type_code", + "description": "Indicate NCIT or SO code to denote the type of source material used for sequencing and analysis.\nProvide code in Compact URI (CURIE) pattern.", + "restrictions": { + "required": true, + "regex": "^NCIT:C[0-9]+$|^SO:\\d{7}$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "molecule_type_term", + "examples": ["DNA"] + }, + "name": "molecule_type_term", + "description": "Provide the standardized and human readable term derived from the coding system associated with molecule type code.", + "valueType": "string" + }, + { + "meta": { + "displayName": "sample_status", + "mappings": { + "Phenopacket": "sample_status" + } + }, + "name": "sample_status", + "description": "Indicate the status of the sample for data analysis.", + "restrictions": { + "required": true, + "codeList": ["Case", "Control", "Not applicable"] + }, + "valueType": "string" + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "specimen", + "mappings": [ + { + "local": "submitter_specimen_id", + "foreign": "submitter_specimen_id" + } + ] + } + ] + } + }, + { + "name": "experiment", + "description": "Contains information about the experimental design of the sequencing", + "fields": [ + { + "meta": { + "displayName": "submitter_experiment_id", + "mappings": { + "ARGO": "submitter_experiment_id" + } + }, + "name": "submitter_experiment_id", + "description": "Unique identifier of the experiment within the study, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}$" + }, + "valueType": "string", + "unique": true + }, + { + "meta": { + "displayName": "submitter_sample_id", + "mappings": { + "FHIR": "submitter_sample_id", + "mCODE": "submitter_sample_id", + "MOH": "submitter_sample_id", + "ARGO": "submitter_sample_id" + }, + "examples": ["hnc_12", "CCG_34_94583", "BRCA47832-3239"] + }, + "name": "submitter_sample_id", + "description": "Unique identifier of the sample within the study, assigned by the data provider.\n", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "assay_type_code", + "mappings": { + "GA4GH.Experiment.Metadata.Checklist": "assay_type", + "BeaconV2": "runs.libraryStrategy" + }, + "examples": ["OBI:0002117"] + }, + "name": "assay_type_code", + "description": "Indicate OBI code to provide the sequencing technique intented for the library for sequencing data.\nProvide code in Compact URI (CURIE) pattern.", + "restrictions": { + "required": true, + "regex": "^OBI:\\d{7}$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "assay_type_term", + "examples": ["whole genome sequencing assay"] + }, + "name": "assay_type_term", + "description": "Provide the standardized and human readable term derived from the coding system associated with the assay type code.", + "valueType": "string" + }, + { + "meta": { + "displayName": "experiment_design", + "mappings": { + "GA4GH.Experiment.Metadata.Checklist": "design_description" + } + }, + "name": "experiment_design", + "description": "The high-level experiment design including layout, protocol.", + "valueType": "string" + }, + { + "meta": { + "displayName": "experiment_type", + "mappings": { + "GA4GHExperimentMetadataChecklist": "experiment_type" + } + }, + "name": "experiment_type", + "description": "The broad type of sequencing experiment performed. A mixture of library strategy and source", + "restrictions": { + "required": true, + "codeList": [ + "NCIT:C84343 (Genomics)", + "NCIT:C153189 (Transcriptomics)", + "NCIT:C20085 (Proteomics)", + "NCIT:C153191 (Metagenomics)", + "NCIT:C153190 (Epigenomics)" + ] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "instrument", + "mappings": { + "GA4GH.Experiment.Metadata.Checklist": "instrument" + }, + "examples": [ + "OBI:0002752 (Oxford Nanopore PromethION)", + "OBI:0000759 (Illumina)", + "OBI:0002630 (Illumina NovaSeq 6000)", + "OBI:0002012 (PacBio RS II)" + ] + }, + "name": "instrument", + "description": "Provide detailed information about the sequencing platform or technology used, including the name and/or model number of the specific instrument. Whenever possible, specify the exact model or revision rather than the general manufacturer name. It is recommended to use standardized terminology by including the appropriate OBI (Ontology for Biomedical Investigations) code and term to ensure precision.\n\nUse the format: OBI code in CURIE format (OBI term)\n\nExample values:\nOBI:0002752 (Oxford Nanopore PromethION)\nOBI:0000759 (Illumina)\nOBI:0002630 (Illumina NovaSeq 6000)\nOBI:0002012 (PacBio RS II)", + "restrictions": { + "required": true, + "regex": "^OBI:\\d{7} \\(.*\\)$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "instrument_metadata", + "mappings": { + "GA4GH.Experiment.Metadata.Checklist": "instrument_metadata" + } + }, + "name": "instrument_metadata", + "description": "Captures metadata about sequencing instrument usage (e.g. instruments parameters and usage conditions)", + "valueType": "string" + }, + { + "meta": { + "displayName": "platform", + "mappings": { + "BeaconV2": "runs.platform", + "ARGO": "platform" + } + }, + "name": "platform", + "description": "The sequencing platform type used in data generation.", + "restrictions": { + "required": true, + "codeList": [ + "CAPILLARY", + "DNBSEQ", + "ELEMENT", + "HELICOS", + "ILLUMINA", + "IONTORRENT", + "LS454", + "ONT", + "PACBIO", + "SINGULAR", + "SOLID", + "ULTIMA" + ] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "sequencing_protocol", + "mappings": { + "GA4GH.Experiment.Metadata.Checklist": "sequencing_protocol" + } + }, + "name": "sequencing_protocol", + "description": "Set of rules which guides how the sequencing protocol was followed. Change-tracking services such as Protocol.io or GitHub are encouraged instead of dumping free text in this field.\nE.g, https://www.protocols.io/view/environmental-dna-edna-metabarcoding-protocol-for-rm7vzy3q2lx1/v1", + "valueType": "string" + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "sample", + "mappings": [ + { + "local": "submitter_sample_id", + "foreign": "submitter_sample_id" + } + ] + } + ] + } + }, + { + "name": "read_group", + "description": "Holds the readgroup information about the raw reads generated in a run of sequencing", + "fields": [ + { + "meta": { + "displayName": "submitter_read_group_id", + "mappings": { + "ARGO": "submitter_read_group_id", + "BeaconV2": "runs.id" + } + }, + "name": "submitter_read_group_id", + "description": "The identifier of a read group; must be unique within each payload.", + "restrictions": { + "required": true, + "regex": "^[a-zA-Z0-9\\-_:\\.']+$" + }, + "valueType": "string", + "unique": true + }, + { + "meta": { + "displayName": "submitter_experiment_id", + "mappings": { + "ARGO": "submitter_experiment_id" + } + }, + "name": "submitter_experiment_id", + "description": "Unique identifier of the experiment within the study, assigned by the data provider.", + "restrictions": { + "required": true, + "regex": "^[A-Za-z0-9\\-\\._]{1,64}$" + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "file_r1", + "mappings": { + "ARGO": "file_r1" + } + }, + "name": "file_r1", + "description": "Name of the sequencing file containing reads from the first end of a sequencing run.", + "restrictions": { + "required": true + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "file_r2", + "mappings": { + "ARGO": "file_r2" + } + }, + "name": "file_r2", + "description": "Name of the sequencing file containing reads from the second end of a paired-end sequencing run. Required if and only if paired-end sequencing was done.", + "valueType": "string", + "restrictions": { + "if": { + "conditions": [ + { + "fields": ["library_layout"], + "match": { + "value": "OBI:0000722 (paired-end library)" + }, + "case": "all" + } + ] + }, + "then": { + "required": true + }, + "else": { + "required": false, + "empty": true + } + } + }, + { + "meta": { + "displayName": "insert_size", + "mappings": { + "ARGO": "insert_size", + "GA4GH.Experiment.Metadata.Checklist": "insert_size" + } + }, + "name": "insert_size", + "description": "For paired-end sequencing, the average size of sequences between two sequencing ends. Required only for paired-end sequencing.", + "valueType": "integer", + "restrictions": { + "range": { + "min": 0 + } + } + }, + { + "meta": { + "displayName": "library_description", + "mappings": { + "GA4GH.Experiment.Metadata.Checklist": "library_description" + } + }, + "name": "library_description", + "description": "Description of the nucleotide sequencing library, including targeting information, spot, gap descriptors, and any other information relevant to its construction", + "valueType": "string" + }, + { + "meta": { + "displayName": "library_layout", + "mappings": { + "GA4GH.Experiment.Metadata.Checklist": "library_layout", + "BeaconV2": "runs.libraryLayout" + } + }, + "name": "library_layout", + "description": "Indicate whether the library was built as paired-end or single-end if applicable.", + "restrictions": { + "required": true, + "codeList": ["OBI:0000722 (paired-end library)", "OBI:0000736 (single fragment library)", "Not applicable"] + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "library_name", + "mappings": { + "ARGO.library_name": "ARGO.library_name", + "GA4GH.Experiment.Metadata.Checklist": "library_extra_id" + } + }, + "name": "library_name", + "description": "Name of a sequencing library made from a molecular sample or a sample pool (multiplex sequencing).", + "restrictions": { + "required": true + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "platform_unit", + "mappings": { + "ARGO.platform_unit": "ARGO.platform_unit" + } + }, + "name": "platform_unit", + "description": "Unique identifier for each read group. For example, Illumina has format {FLOWCELL_BARCODE}.{LANE}.{SAMPLE_BARCODE}. The {FLOWCELL_BARCODE} refers to the unique identifier for a particular flow cell. The {LANE} indicates the lane of the flow cell and the {SAMPLE_BARCODE} is a sample/library-specific identifier. For non-multiplex sequencing, platform unit and read group have a one-to-one relationship.", + "restrictions": { + "required": true + }, + "valueType": "string" + }, + { + "meta": { + "displayName": "read_group_id_in_bam", + "mappings": { + "ARGO": "read_group_id_in_bam" + } + }, + "name": "read_group_id_in_bam", + "description": "Optional field indicating the @RD ID in the BAM. If submitted, this will be used to map the @RG ID in the BAM header to the submitter_read_group_id in the payload. After submission, the @RG ID in the payload will be used for all future headers. This cannot be submitted for FASTQ files.", + "valueType": "string", + "restrictions": { + "regex": "^[a-zA-Z0-9\\-_:\\.']+$" + } + }, + { + "meta": { + "displayName": "read_length_r1", + "mappings": { + "ARGO": "read_length_r1" + } + }, + "name": "read_length_r1", + "description": "Length of sequencing reads in file_r1; this corresponds to the number of sequencing cycles of the first end.", + "valueType": "integer", + "restrictions": { + "range": { + "min": 20 + } + } + }, + { + "meta": { + "displayName": "read_length_r2", + "mappings": { + "ARGO": "read_length_r2" + } + }, + "name": "read_length_r2", + "description": "Length of sequencing reads in file_r2; this corresponds to the number of sequencing cycles of the second end.", + "valueType": "integer", + "restrictions": { + "range": { + "min": 20 + } + } + } + ], + "restrictions": { + "foreignKey": [ + { + "schema": "experiment", + "mappings": [ + { + "local": "submitter_experiment_id", + "foreign": "submitter_experiment_id" + } + ] + } + ] + } + } + ] +} diff --git a/packages/ui/stories/viewer-table/SchemaTable.stories.tsx b/packages/ui/stories/viewer-table/SchemaTable.stories.tsx index 4a286837..96aafd6e 100644 --- a/packages/ui/stories/viewer-table/SchemaTable.stories.tsx +++ b/packages/ui/stories/viewer-table/SchemaTable.stories.tsx @@ -3,10 +3,12 @@ import { Dictionary, replaceReferences, Schema } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; import SchemaTable from '../../src/viewer-table/DataTable/SchemaTable/SchemaTable'; -import Advanced from '../fixtures/advanced.json'; +import Advanced from '../fixtures/TorontoMapleLeafs.json'; +import PCGL from '../fixtures/pcgl.json'; import themeDecorator from '../themeDecorator'; -const dictionary: Dictionary = replaceReferences(Advanced as Dictionary); +const torontoMapleLeafsDictionary: Dictionary = replaceReferences(Advanced as Dictionary); +const pcglDictionary: Dictionary = replaceReferences(PCGL as Dictionary); const meta = { component: SchemaTable, @@ -19,21 +21,89 @@ export default meta; type Story = StoryObj; export const Default: Story = { - args: { schema: dictionary.schemas[0] }, + args: { schema: torontoMapleLeafsDictionary.schemas[0] }, }; export const PlayerProfiles: Story = { - args: { schema: dictionary.schemas[1] }, + args: { schema: torontoMapleLeafsDictionary.schemas[1] }, }; export const GameEvents: Story = { - args: { schema: dictionary.schemas[2] }, + args: { schema: torontoMapleLeafsDictionary.schemas[2] }, }; export const AdvancedStats: Story = { - args: { schema: dictionary.schemas[3] }, + args: { schema: torontoMapleLeafsDictionary.schemas[3] }, }; export const EdgeCases: Story = { - args: { schema: dictionary.schemas[4] }, + args: { schema: torontoMapleLeafsDictionary.schemas[4] }, +}; + +export const PCGLParticipant: Story = { + args: { schema: pcglDictionary.schemas[0] }, +}; + +export const PCGLSociodemographic: Story = { + args: { schema: pcglDictionary.schemas[1] }, +}; + +export const PCGLDemographic: Story = { + args: { schema: pcglDictionary.schemas[2] }, +}; + +export const PCGLDiagnosis: Story = { + args: { schema: pcglDictionary.schemas[3] }, +}; + +export const PCGLTreatment: Story = { + args: { schema: pcglDictionary.schemas[4] }, +}; + +export const PCGLFollowUp: Story = { + args: { schema: pcglDictionary.schemas[5] }, +}; + +export const PCGLProcedure: Story = { + args: { schema: pcglDictionary.schemas[6] }, +}; + +export const PCGLMedication: Story = { + args: { schema: pcglDictionary.schemas[7] }, +}; + +export const PCGLRadiation: Story = { + args: { schema: pcglDictionary.schemas[8] }, +}; + +export const PCGLMeasurement: Story = { + args: { schema: pcglDictionary.schemas[9] }, +}; + +export const PCGLPhenotype: Story = { + args: { schema: pcglDictionary.schemas[10] }, +}; + +export const PCGLComorbidity: Story = { + args: { schema: pcglDictionary.schemas[11] }, +}; + +export const PCGLExposure: Story = { + args: { schema: pcglDictionary.schemas[12] }, +}; + +export const PCGLSpecimen: Story = { + args: { schema: pcglDictionary.schemas[13] }, +}; + +export const PCGLSample: Story = { + args: { schema: pcglDictionary.schemas[14] }, +}; + +export const PCGLExperiment: Story = { + args: { schema: pcglDictionary.schemas[15] }, +}; + +export const PCGLReadGroup: Story = { + args: { schema: pcglDictionary.schemas[16] }, }; diff --git a/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx index 83823a86..5d37f65e 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/DictionaryVersionSwitcher.stories.tsx @@ -3,7 +3,7 @@ import { Dictionary } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; import VersionSwitcher from '../../../src/viewer-table/InteractionPanel/DictionaryVersionSwitcher'; -import DictionarySample from '../../fixtures/advanced.json'; +import DictionarySample from '../../fixtures/TorontoMapleLeafs.json'; import themeDecorator from '../../themeDecorator'; const meta = { diff --git a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx index cbe620c6..afb0247f 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/InteractionPanel.stories.tsx @@ -4,7 +4,7 @@ import { Dictionary } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; import type { FilterOptions } from '../../../src/viewer-table/InteractionPanel/AttributeFilterDropdown'; import InteractionPanel from '../../../src/viewer-table/InteractionPanel/InteractionPanel'; -import AdvancedDictionary from '../../fixtures/advanced.json'; +import AdvancedDictionary from '../../fixtures/TorontoMapleLeafs.json'; import themeDecorator from '../../themeDecorator'; const meta = { diff --git a/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx index 79066097..3cba8ff2 100644 --- a/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx +++ b/packages/ui/stories/viewer-table/interaction-panel/TableOfContentDropdown.stories.tsx @@ -3,7 +3,7 @@ import { Schema } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; import TableOfContentsDropdown from '../../../src/viewer-table/InteractionPanel/TableOfContentsDropdown'; -import Dictionary from '../../fixtures/advanced.json'; +import Dictionary from '../../fixtures/TorontoMapleLeafs.json'; import themeDecorator from '../../themeDecorator'; const meta = { From 9606a27e60aab3149a1e210bec5bb7e673a7ba11 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 27 Jun 2025 11:21:33 -0400 Subject: [PATCH 157/217] fix the allowed values --- .../SchemaTable/Columns/AllowedValues.tsx | 72 ++++++++++--------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx index 5f79880d..eaffd4d7 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx @@ -18,12 +18,12 @@ */ /** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; import { MatchRuleCount, RestrictionRange, SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { CellContext } from '@tanstack/react-table'; +import Pill from '../../../../common/Pill'; import { Theme } from '../../../../theme'; -import { css } from '@emotion/react'; import { useThemeContext } from '../../../../theme/ThemeContext'; -import Pill from '../../../../common/Pill'; export type restrictionItem = { prefix: string | null; @@ -210,44 +210,46 @@ export const renderAllowedValuesColumn = (restrictions: CellContext { + const pillStyle = { + fontFamily: 'B612 Mono', + color: theme.colors.accent_dark, + fontWeight: '400', + lineHeight: '20px', + fontSize: '13px', + }; + + const handleViewDetails = () => { alert('Modal has been opened\n\n\n Hello World'); }; + const renderRestrictionItem = (item: restrictionItem) => { + const { prefix, content } = item; + if (prefix === 'Depends on:\n') { + return ( +
      + {prefix && {prefix}} + + {content} + +
      + ); + } + + return ( +
      + {prefix && {prefix}} + {content} +
      + ); + }; + + const hasConditionalRestrictions = restrictionsValue && 'if' in restrictionsValue && restrictionsValue.if; + return (
      - {restrictionItems.map((item: restrictionItem) => { - const { prefix, content } = item; - - //Must render inside of pill - if (prefix === 'Depends on:\n') { - return ( -
      - {prefix && {prefix}} - - {content} - -
      - ); - } - return ( -
      - {prefix && {prefix}} - {content} -
      - ); - })} - {restrictionsValue && 'if' in restrictionsValue && restrictionsValue.if && ( -
      + {restrictionItems.map(renderRestrictionItem)} + {hasConditionalRestrictions && ( +
      View details
      )} From 85c3a76e737f3f8360755ef0f02da8c7f0a8f66c Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 2 Jul 2025 09:51:12 -0400 Subject: [PATCH 158/217] remove comments and add licencing and export type --- .../ui/src/common/Accordion/Accordion.tsx | 45 ++++++++++--------- .../ui/src/common/Dropdown/DropdownItem.tsx | 2 +- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index bc4f5c03..67dfdad3 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -1,10 +1,32 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + /** @jsxImportSource @emotion/react */ + import { css } from '@emotion/react'; -import { useState, useMemo, useCallback } from 'react'; + +import { useMemo, useState } from 'react'; import AccordionItem, { AccordionData } from './AccordionItem'; -import DownloadTemplatesButton from '../../viewer-table/InteractionPanel/DownloadTemplatesButton'; -type AccordionProps = { +export type AccordionProps = { accordionItems: Array; }; @@ -17,24 +39,7 @@ const accordionStyle = css` gap: 24px; `; -/** - * Accordion component to display collapsible items with titles and content. - * @param {AccordionProps} props - The properties for the Accordion component. - * @param {Array} props.accordionItems - An array of accordion items, each containing a title, description and content. - * @returns {JSX.Element} The rendered Accordion component. - * @example - * const accordionItems = [ - * { title: 'Item 1', description: 'Description 1', openOnInit: true, content: 'Content for item 1' }, - * { title: 'Item 2', description: 'Description 2', openOnInit: false, content: 'Content for item 2' }, - * ]; - * - * Essentially pass in an an array of objects that are of type AccordionData, and it will render an accordion with those items. - */ - const Accordion = ({ accordionItems }: AccordionProps) => { - // This state keeps track of the clipboard contents, which can be set by the accordion items. - // Each individual accordion item can set this state when it's tag has been clicked, however only one item can be set at a time. - const [clipboardContents, setClipboardContents] = useState(null); const [isCopying, setIsCopying] = useState(false); const [copySuccess, setCopySuccess] = useState(false); diff --git a/packages/ui/src/common/Dropdown/DropdownItem.tsx b/packages/ui/src/common/Dropdown/DropdownItem.tsx index 49f7e635..d479130d 100644 --- a/packages/ui/src/common/Dropdown/DropdownItem.tsx +++ b/packages/ui/src/common/Dropdown/DropdownItem.tsx @@ -22,7 +22,7 @@ /** @jsxImportSource @emotion/react */ import { css, SerializedStyles } from '@emotion/react'; -import { FC, ReactNode } from 'react'; +import { ReactNode } from 'react'; import type { Theme } from '../../theme'; import { useThemeContext } from '../../theme/ThemeContext'; From 14304b38a838860f31259f9e8a4c99fc21d4e01d Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 2 Jul 2025 09:52:48 -0400 Subject: [PATCH 159/217] remove useless comments --- packages/ui/src/common/Accordion/Accordion.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index 67dfdad3..c156d2a7 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -46,7 +46,7 @@ const Accordion = ({ accordionItems }: AccordionProps) => { const handleCopy = (text: string) => { if (isCopying) { - return; // We don't want to copy if we are already copying + return; } setIsCopying(true); navigator.clipboard @@ -55,7 +55,7 @@ const Accordion = ({ accordionItems }: AccordionProps) => { setCopySuccess(true); setTimeout(() => { setIsCopying(false); - }, 2000); // Reset copy success after 2 seconds as well as the isCopying state + }, 2000); }) .catch((err) => { console.error('Failed to copy text: ', err); @@ -63,7 +63,6 @@ const Accordion = ({ accordionItems }: AccordionProps) => { setIsCopying(false); }); if (copySuccess) { - // Update the clipboard contents const currentURL = window.location.href; setClipboardContents(currentURL); } @@ -76,8 +75,7 @@ const Accordion = ({ accordionItems }: AccordionProps) => { } }, [clipboardContents]); - // This state keeps track of the currently open accordion item index via a boolean array, since each item can be opened or closed independently. - const [openStates, setOpenStates] = useState(accordionItems.map((item) => item.openOnInit)); // Inits the component with the openOnInit prop + const [openStates, setOpenStates] = useState(accordionItems.map((item) => item.openOnInit)); const handleToggle = (index: number) => { setOpenStates((prev) => prev.map((isOpen, i) => (i === index ? !isOpen : isOpen))); From cacce412963a8c2c0832bdab3fc00df212ba5cdf Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 2 Jul 2025 10:41:41 -0400 Subject: [PATCH 160/217] cleanup types and html semantics --- .../ui/src/common/Accordion/Accordion.tsx | 19 +++++++--- .../ui/src/common/Accordion/AccordionItem.tsx | 38 ++++++++++--------- packages/ui/src/theme/styles/icons.ts | 8 ++-- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index c156d2a7..00f8de51 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -30,6 +30,10 @@ export type AccordionProps = { accordionItems: Array; }; +export type AccordionOpenState = { + isOpen: boolean; + toggle: () => void; +}; const accordionStyle = css` list-style: none; padding: 0; @@ -76,22 +80,25 @@ const Accordion = ({ accordionItems }: AccordionProps) => { }, [clipboardContents]); const [openStates, setOpenStates] = useState(accordionItems.map((item) => item.openOnInit)); - const handleToggle = (index: number) => { setOpenStates((prev) => prev.map((isOpen, i) => (i === index ? !isOpen : isOpen))); }; + const setOpenState = (index: number): AccordionOpenState => { + return { + isOpen: openStates[index], + toggle: () => handleToggle(index), + }; + }; + return (
        {accordionItems.map((item, index) => ( handleToggle(index), - }} + accordionData={item} + openState={setOpenState(index)} setClipboardContents={setClipboardContents} /> ))} diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index fe8bca6b..ec0b552f 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -24,43 +24,43 @@ import { css } from '@emotion/react'; import type { ReactNode } from 'react'; import { MouseEvent, useEffect } from 'react'; + import type { Theme } from '../../theme'; import { useThemeContext } from '../../theme/ThemeContext'; import DictionaryDownloadButton, { DictionaryDownloadButtonProps, } from '../../viewer-table/InteractionPanel/DownloadTemplatesButton'; import ReadMoreText from '../ReadMoreText'; +import { AccordionOpenState } from './Accordion'; const MAX_LINES_BEFORE_EXPAND = 2; export type AccordionData = { title: string; openOnInit: boolean; - description: string; - content: ReactNode | string; + description?: string; + content: ReactNode; dictionaryDownloadButtonProps: DictionaryDownloadButtonProps; }; export type AccordionItemProps = { setClipboardContents: (currentSchema: string) => void; - data: AccordionData; + accordionData: AccordionData; index: number; - openState: { - isOpen: boolean; - toggle: () => void; - }; + openState: AccordionOpenState; }; const accordionItemStyle = (theme: Theme) => css` list-style: none; - border: 0.25px solid ${theme.colors.black}; border-radius: 8px; margin-bottom: 1px; overflow: hidden; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + box-shadow: + 0 2px 6px rgba(70, 63, 63, 0.05), + 0 0 0 0.3px ${theme.colors.black}; &:hover { box-shadow: - 0 2px 6px rgba(0, 0, 0, 0.15), + 0 2px 6px rgba(70, 63, 63, 0.15), 0 0 0 0.3px ${theme.colors.black}; } transition: all 0.3s ease; @@ -73,6 +73,7 @@ const accordionItemTitleStyle = css` const accordionItemButtonStyle = (theme: Theme) => css` display: flex; + border: none; align-items: center; justify-content: space-between; padding: 24px 20px; @@ -127,7 +128,7 @@ const descriptionWrapperStyle = (theme: Theme) => css` const accordionCollapseStyle = (isOpen: boolean) => css` overflow: hidden; - max-height: ${isOpen ? '800px' : '0px'}; + max-height: ${isOpen ? 'none' : '0px'}; transition: max-height 0.3s ease; `; @@ -142,21 +143,24 @@ const contentInnerContainerStyle = (theme: Theme) => css` ${theme.typography?.data}; `; -const AccordionItem = ({ index, data, openState, setClipboardContents }: AccordionItemProps) => { +const AccordionItem = ({ index, accordionData, openState, setClipboardContents }: AccordionItemProps) => { const theme = useThemeContext(); - const { description, title, content, dictionaryDownloadButtonProps } = data; + const { description, title, content, dictionaryDownloadButtonProps } = accordionData; const { ChevronDown, Hash } = theme.icons; const indexString = index.toString(); const windowLocationHash = `#${index}`; - useEffect(() => { + const handleInitialHashCheck = () => { if (window.location.hash === windowLocationHash) { - if (!data.openOnInit) { + if (!accordionData.openOnInit) { openState.toggle(); } document.getElementById(indexString)?.scrollIntoView({ behavior: 'smooth' }); } + }; + useEffect(() => { + handleInitialHashCheck(); }, []); const hashOnClick = (event: MouseEvent) => { @@ -168,7 +172,7 @@ const AccordionItem = ({ index, data, openState, setClipboardContents }: Accordi return (
      • -
        +
        +

        diff --git a/packages/ui/src/theme/styles/icons.ts b/packages/ui/src/theme/styles/icons.ts index dee9b5cb..79652033 100644 --- a/packages/ui/src/theme/styles/icons.ts +++ b/packages/ui/src/theme/styles/icons.ts @@ -1,14 +1,12 @@ import ChevronDown from '../icons/ChevronDown'; -import Spinner from '../icons/Spinner'; -import Spinner from '../icons/Spinner'; import Collapse from '../icons/Collapse'; import Eye from '../icons/Eye'; import FileDownload from '../icons/FileDownload'; -import FileDownload from '../icons/FileDownload'; +import Hash from '../icons/Hash'; +import History from '../icons/History'; import List from '../icons/List'; import ListFilter from '../icons/ListFilter'; -import History from '../icons/History'; -import Hash from '../icons/Hash'; +import Spinner from '../icons/Spinner'; export default { ChevronDown, Spinner, From f4c58137f162b207547dde6d688effe1c7bda7d5 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 2 Jul 2025 11:08:47 -0400 Subject: [PATCH 161/217] some functional changes --- .../ui/src/common/Accordion/Accordion.tsx | 13 ++---- .../ui/src/common/Accordion/AccordionItem.tsx | 45 ++++++++++++------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index 00f8de51..b55cfbb6 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -83,14 +83,6 @@ const Accordion = ({ accordionItems }: AccordionProps) => { const handleToggle = (index: number) => { setOpenStates((prev) => prev.map((isOpen, i) => (i === index ? !isOpen : isOpen))); }; - - const setOpenState = (index: number): AccordionOpenState => { - return { - isOpen: openStates[index], - toggle: () => handleToggle(index), - }; - }; - return (
          {accordionItems.map((item, index) => ( @@ -98,7 +90,10 @@ const Accordion = ({ accordionItems }: AccordionProps) => { index={index} key={index} accordionData={item} - openState={setOpenState(index)} + openState={{ + isOpen: openStates[index], + toggle: () => handleToggle(index), + }} setClipboardContents={setClipboardContents} /> ))} diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index ec0b552f..a0f77580 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -143,6 +143,30 @@ const contentInnerContainerStyle = (theme: Theme) => css` ${theme.typography?.data}; `; +const handleInitialHashCheck = ( + windowLocationHash: string, + accordionData: AccordionData, + openState: AccordionOpenState, + indexString: string, +) => { + if (window.location.hash === windowLocationHash) { + if (!accordionData.openOnInit) { + openState.toggle(); + } + document.getElementById(indexString)?.scrollIntoView({ behavior: 'smooth' }); + } +}; + +const hashOnClick = ( + event: MouseEvent, + windowLocationHash: string, + setClipboardContents: (currentSchema: string) => void, +) => { + event.stopPropagation(); + window.location.hash = windowLocationHash; + setClipboardContents(window.location.href); +}; + const AccordionItem = ({ index, accordionData, openState, setClipboardContents }: AccordionItemProps) => { const theme = useThemeContext(); const { description, title, content, dictionaryDownloadButtonProps } = accordionData; @@ -151,24 +175,10 @@ const AccordionItem = ({ index, accordionData, openState, setClipboardContents } const indexString = index.toString(); const windowLocationHash = `#${index}`; - const handleInitialHashCheck = () => { - if (window.location.hash === windowLocationHash) { - if (!accordionData.openOnInit) { - openState.toggle(); - } - document.getElementById(indexString)?.scrollIntoView({ behavior: 'smooth' }); - } - }; useEffect(() => { - handleInitialHashCheck(); + handleInitialHashCheck(windowLocationHash, accordionData, openState, indexString); }, []); - const hashOnClick = (event: MouseEvent) => { - event.stopPropagation(); - window.location.hash = windowLocationHash; - setClipboardContents(window.location.href); - }; - return (
        • @@ -177,7 +187,10 @@ const AccordionItem = ({ index, accordionData, openState, setClipboardContents }
          {title} - + hashOnClick(event, windowLocationHash, setClipboardContents)} + > From d1ac0c12d603ae86a9fbedd4e2d13072cf233f57 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 2 Jul 2025 12:15:14 -0400 Subject: [PATCH 162/217] further code cleanup --- .../ui/src/common/Accordion/AccordionItem.tsx | 70 ++++++++++++------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index a0f77580..a52479b9 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -52,6 +52,7 @@ export type AccordionItemProps = { const accordionItemStyle = (theme: Theme) => css` list-style: none; + width: 100%; border-radius: 8px; margin-bottom: 1px; overflow: hidden; @@ -68,7 +69,12 @@ const accordionItemStyle = (theme: Theme) => css` const accordionItemTitleStyle = css` margin: 0; - width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + padding: 24px 20px; + background-color: #ffffff; + transition: all 0.2s ease; `; const accordionItemButtonStyle = (theme: Theme) => css` @@ -96,7 +102,7 @@ const contentContainerStyle = css` display: flex; flex-direction: row; align-items: center; - gap: 16px; + gap: 0; flex: 1; min-width: 0; flex-wrap: wrap; @@ -112,7 +118,17 @@ const hashIconStyle = (theme: Theme) => css` opacity: 0; margin-left: 8px; transition: opacity 0.2s ease; - border-bottom: 2px solid ${theme.colors.secondary}; + background: transparent; + border: none; + cursor: pointer; + padding: 0; + display: inline-flex; + align-items: center; + + svg { + border-bottom: 2px solid ${theme.colors.secondary}; + } + &:hover { opacity: 1; } @@ -132,7 +148,7 @@ const accordionCollapseStyle = (isOpen: boolean) => css` transition: max-height 0.3s ease; `; -const accordionItemContentStyle = (theme: Theme) => css` +const accordionItemContentStyle = css` padding: 30px; background-color: #ffffff; `; @@ -182,29 +198,33 @@ const AccordionItem = ({ index, accordionData, openState, setClipboardContents } return (
        • - +
          + + + {description && ( + + {description} + + )} +
          +

          -
          +
          {content}
          From c5d99d26267e22d2cb432c450cdbc4382c45d470 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 2 Jul 2025 12:25:05 -0400 Subject: [PATCH 163/217] accidental file change fix --- packages/ui/src/common/Dropdown/Dropdown.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ui/src/common/Dropdown/Dropdown.tsx b/packages/ui/src/common/Dropdown/Dropdown.tsx index ec099536..e200ad9e 100644 --- a/packages/ui/src/common/Dropdown/Dropdown.tsx +++ b/packages/ui/src/common/Dropdown/Dropdown.tsx @@ -49,7 +49,6 @@ const dropdownButtonStyle = ({ theme, width, disabled }: { theme: Theme; width?: border-radius: 9px; height: 42px; box-sizing: border-box; - cursor: ${disabled ? 'not-allowed' : 'pointer'}; transition: all 0.2s ease; z-index: 1000; opacity: 1; From 8ee74156acd26378c3e0868d39d9c2eaa29bb1aa Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 2 Jul 2025 12:25:48 -0400 Subject: [PATCH 164/217] accidental file change fix --- packages/ui/src/common/Dropdown/Dropdown.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/common/Dropdown/Dropdown.tsx b/packages/ui/src/common/Dropdown/Dropdown.tsx index e200ad9e..cf3b2a75 100644 --- a/packages/ui/src/common/Dropdown/Dropdown.tsx +++ b/packages/ui/src/common/Dropdown/Dropdown.tsx @@ -49,6 +49,7 @@ const dropdownButtonStyle = ({ theme, width, disabled }: { theme: Theme; width?: border-radius: 9px; height: 42px; box-sizing: border-box; + cursor: pointer; transition: all 0.2s ease; z-index: 1000; opacity: 1; From 667f3667318966f154705cf5dacf54a7d22c859b Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 2 Jul 2025 12:30:36 -0400 Subject: [PATCH 165/217] format import for pr --- packages/ui/src/common/ReadMoreText.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ui/src/common/ReadMoreText.tsx b/packages/ui/src/common/ReadMoreText.tsx index d918796e..caada584 100644 --- a/packages/ui/src/common/ReadMoreText.tsx +++ b/packages/ui/src/common/ReadMoreText.tsx @@ -20,8 +20,10 @@ */ /** @jsxImportSource @emotion/react */ + import { css } from '@emotion/react'; import { ReactNode, useEffect, useRef, useState } from 'react'; + import type { Theme } from '../theme'; import { useThemeContext } from '../theme/ThemeContext'; From 14e5770492ee6a7d9f614bb65ed7dca894890e1d Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 2 Jul 2025 13:42:21 -0400 Subject: [PATCH 166/217] undo to reduce number of files reviewed --- packages/ui/stories/fixtures/minimalBiosampleModel.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/ui/stories/fixtures/minimalBiosampleModel.ts b/packages/ui/stories/fixtures/minimalBiosampleModel.ts index 6877add2..c03973bc 100644 --- a/packages/ui/stories/fixtures/minimalBiosampleModel.ts +++ b/packages/ui/stories/fixtures/minimalBiosampleModel.ts @@ -19,9 +19,6 @@ const dictionary: Dictionary = { required: true, regex: '#/regex/submitter_id', }, - meta: { - examples: ['DONOR12345'], - }, }, { name: 'gender', From 7e6a70d9fd549472065c23e7fdc2b8ba178d426a Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 2 Jul 2025 13:47:15 -0400 Subject: [PATCH 167/217] reduce diff with removing uneccessary code changes --- packages/ui/src/viewer-table/DataTable/TableRow.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/TableRow.tsx b/packages/ui/src/viewer-table/DataTable/TableRow.tsx index f0948dcb..ca383b75 100644 --- a/packages/ui/src/viewer-table/DataTable/TableRow.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableRow.tsx @@ -49,13 +49,12 @@ const tdStyle = (theme: Theme, cellIndex: number, rowIndex: number) => css` border: 1px solid #DCDDE1; `; -type TableRowProps = { - row: Row; +export type TableRowProps = { + row: Row; index: number; - columnSlice?: [number, number] | [number]; }; -const TableRow = ({ row, index }: TableRowProps) => { +const TableRow = ({ row, index }: TableRowProps) => { const theme = useThemeContext(); return (
      From 92fd01028e40deb51c74afbfc9d715c1594a4b5a Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 2 Jul 2025 14:03:40 -0400 Subject: [PATCH 168/217] add line breaks and fix up formatting if needed --- .../SchemaTable/Columns/AllowedValues.tsx | 4 +++- .../SchemaTable/Columns/Attribute.tsx | 2 ++ .../SchemaTable/Columns/DataType.tsx | 2 ++ .../DataTable/SchemaTable/Columns/Fields.tsx | 20 +++++++++++++++++ .../DataTable/SchemaTable/OpenModalPill.tsx | 22 +++++++++++++++++++ .../DataTable/SchemaTable/SchemaTable.tsx | 1 + .../ui/src/viewer-table/DataTable/Table.tsx | 1 + .../viewer-table/DataTable/TableHeader.tsx | 3 ++- .../src/viewer-table/DataTable/TableRow.tsx | 1 + 9 files changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx index eaffd4d7..f7fe62bb 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx @@ -18,9 +18,11 @@ */ /** @jsxImportSource @emotion/react */ + import { css } from '@emotion/react'; import { MatchRuleCount, RestrictionRange, SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { CellContext } from '@tanstack/react-table'; + import Pill from '../../../../common/Pill'; import { Theme } from '../../../../theme'; import { useThemeContext } from '../../../../theme/ThemeContext'; @@ -217,7 +219,7 @@ export const renderAllowedValuesColumn = (restrictions: CellContext { alert('Modal has been opened\n\n\n Hello World'); }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx index c3a75b00..27068b5e 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx @@ -18,9 +18,11 @@ */ /** @jsxImportSource @emotion/react */ + import { css } from '@emotion/react'; import { SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import React from 'react'; + import Pill from '../../../../common/Pill'; import OpenModalPill from '../OpenModalPill'; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx index d548c689..f0cb5b77 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/DataType.tsx @@ -18,9 +18,11 @@ */ /** @jsxImportSource @emotion/react */ + import { css } from '@emotion/react'; import { SchemaField } from '@overture-stack/lectern-dictionary'; import { CellContext } from '@tanstack/react-table'; + import React from 'react'; import Pill from '../../../../common/Pill'; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx index deb63eef..96c4ab58 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx @@ -1,8 +1,28 @@ +/* + * Copyright (c) 2024 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ /** @jsxImportSource @emotion/react */ + import { css } from '@emotion/react'; import { DictionaryMeta, SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { CellContext } from '@tanstack/react-table'; import React, { useEffect, useMemo, useState } from 'react'; + import Pill from '../../../../common/Pill'; import { Theme } from '../../../../theme'; import { useThemeContext } from '../../../../theme/ThemeContext'; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx index 3238df73..3d9b27b7 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx @@ -1,10 +1,32 @@ +/* + * Copyright (c) 2024 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ /** @jsxImportSource @emotion/react */ + import React from 'react'; + import Pill from '../../../common/Pill'; import Eye from '../../../theme/icons/Eye'; + export type OpenModalButtonProps = { title: string; }; + const OpenModalPill = ({ title }: OpenModalButtonProps) => { return ( css` ${theme.typography.heading}; diff --git a/packages/ui/src/viewer-table/DataTable/TableRow.tsx b/packages/ui/src/viewer-table/DataTable/TableRow.tsx index ca383b75..37e84bbb 100644 --- a/packages/ui/src/viewer-table/DataTable/TableRow.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableRow.tsx @@ -23,6 +23,7 @@ import { css } from '@emotion/react'; import { Row, flexRender } from '@tanstack/react-table'; + import ReadMoreText from '../../common/ReadMoreText'; import { Theme } from '../../theme'; import { useThemeContext } from '../../theme/ThemeContext'; From e3c3eee43ff8e5bfd838b8f3884964d8d2f60d86 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 2 Jul 2025 14:35:21 -0400 Subject: [PATCH 169/217] clean up the header component with the use of the readmore --- .../ui/src/viewer-table/DictionaryHeader.tsx | 68 +++++++------------ 1 file changed, 25 insertions(+), 43 deletions(-) diff --git a/packages/ui/src/viewer-table/DictionaryHeader.tsx b/packages/ui/src/viewer-table/DictionaryHeader.tsx index 39a3aaf2..f820aa18 100644 --- a/packages/ui/src/viewer-table/DictionaryHeader.tsx +++ b/packages/ui/src/viewer-table/DictionaryHeader.tsx @@ -21,9 +21,9 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import { useState } from 'react'; import type { Theme } from '../theme'; import { useThemeContext } from '../theme/ThemeContext'; +import ReadMoreText from '../common/ReadMoreText'; import colours from './styles/colours'; export type DictionaryHeaderProps = { @@ -32,32 +32,28 @@ export type DictionaryHeaderProps = { version?: string; }; -const getChevronStyle = (isExpanded: boolean) => css` - margin-left: 4px; - ${isExpanded && `transform: rotate(180deg);`} -`; - -const linkStyle = (theme: Theme) => css` - ${theme.typography?.subheading} - color: white; - cursor: pointer; - display: inline-flex; - align-items: center; - - &:hover { - text-decoration: underline; - } -`; - // Was unable to find the appropriate font size for the version numbering in the current design system, that matches // Figma mockup so we are using something that is somewhat close with a hard coded font size -const descriptionStyle = (theme: Theme) => css` +const descriptionWrapperStyle = (theme: Theme) => css` ${theme.typography?.data} font-size: 16px; color: white; - margin: 0; - display: inline; + padding: 0; + + button { + ${theme.typography?.subheading} + color: white; + margin-top: 4px; + + &:hover { + text-decoration: underline; + } + + svg { + fill: white; + } + } `; const containerStyle = (theme: Theme) => css` @@ -109,20 +105,8 @@ const descriptionColumnStyle = css` justify-content: center; `; -const DESCRIPTION_LENGTH_THRESHOLD = 140; // Chosen to display ~2-3 lines of text before truncation based on typical container width - const DictionaryHeader = ({ name, description, version }: DictionaryHeaderProps) => { const theme = useThemeContext(); - const { ChevronDown } = theme.icons; - const [isExpanded, setIsExpanded] = useState(false); - - // Determine if the description is long enough to need a toggle, based off of how many characters we want to show by default - // according to the figma styling - const needsToggle = description && description.length > DESCRIPTION_LENGTH_THRESHOLD; - // We want to show all the text if it is not long or if it is already expanded via state variable - const showFull = isExpanded || !needsToggle; - // Based off of showFull, we determine the text to show, either its the full description or a truncated version - const textToShow = showFull ? description : description.slice(0, DESCRIPTION_LENGTH_THRESHOLD) + '... '; return (
      @@ -133,16 +117,14 @@ const DictionaryHeader = ({ name, description, version }: DictionaryHeaderProps)
      {description && (
      -
      - {textToShow} - {needsToggle && ( - setIsExpanded((prev) => !prev)}> - {' '} - {isExpanded ? 'Read less' : 'Show more'} - - - )} -
      + + {description} +
      )} From 072974757674bad497a473d2b0c6f7c23a29d18f Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 2 Jul 2025 14:58:47 -0400 Subject: [PATCH 170/217] generated demo page. --- .../src/viewer-table/DataDictionaryPage.tsx | 115 ++++++++++++++++++ .../DataDictionaryPage.stories.tsx | 80 ++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 packages/ui/src/viewer-table/DataDictionaryPage.tsx create mode 100644 packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx diff --git a/packages/ui/src/viewer-table/DataDictionaryPage.tsx b/packages/ui/src/viewer-table/DataDictionaryPage.tsx new file mode 100644 index 00000000..eaba016c --- /dev/null +++ b/packages/ui/src/viewer-table/DataDictionaryPage.tsx @@ -0,0 +1,115 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** @jsxImportSource @emotion/react */ + +import { css } from '@emotion/react'; +import { Dictionary } from '@overture-stack/lectern-dictionary'; +import React, { useState } from 'react'; +import Accordion from '../common/Accordion/Accordion'; +import { AccordionData } from '../common/Accordion/AccordionItem'; +import SchemaTable from './DataTable/SchemaTable/SchemaTable'; +import DictionaryHeader from './DictionaryHeader'; +import InteractionPanel from './InteractionPanel/InteractionPanel'; +import { FilterOptions } from './InteractionPanel/AttributeFilterDropdown'; + +const interactionPanelSpacing = css` + margin-bottom: 24px; +`; + +export type DataDictionaryPageProps = { + dictionaries: Dictionary[]; + dictionaryIndex?: number; + lecternUrl?: string; + onVersionChange?: (index: number) => void; +}; + +const DataDictionaryPage = ({ + dictionaries, + dictionaryIndex = 0, + lecternUrl = '', + onVersionChange, +}: DataDictionaryPageProps) => { + const [filters, setFilters] = useState([]); + const [isCollapsed, setIsCollapsed] = useState(false); + + const dictionary = dictionaries[dictionaryIndex]; + + const handleSelect = (schemaIndex: number) => { + // Table of contents selection - could implement scroll here + console.log('Selected schema index:', schemaIndex); + }; + + const handleVersionChange = (index: number) => { + if (onVersionChange) { + onVersionChange(index); + } + }; + + // Filter fields based on the selected filters + const getFilteredSchema = (schema: any) => { + if (filters.includes('Required')) { + // Only show required fields + const filteredFields = schema.fields.filter((field: any) => field.restrictions?.required === true); + return { ...schema, fields: filteredFields }; + } + // 'All Fields' or no filter - show all fields + return schema; + }; + + const accordionItems: AccordionData[] = dictionary.schemas.map((schema) => ({ + title: schema.name, + openOnInit: !isCollapsed, + description: schema.description, + content: , + dictionaryDownloadButtonProps: { + version: dictionary.version || '1.0', + name: dictionary.name, + lecternUrl, + fileType: 'tsv', + iconOnly: true, + disabled: false, + }, + })); + + return ( +
      + +
      + +
      + +
      + ); +}; + +export default DataDictionaryPage; diff --git a/packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx b/packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx new file mode 100644 index 00000000..c65cd003 --- /dev/null +++ b/packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx @@ -0,0 +1,80 @@ +/** @jsxImportSource @emotion/react */ + +import { Dictionary, replaceReferences } from '@overture-stack/lectern-dictionary'; +import type { Meta, StoryObj } from '@storybook/react'; +import DataDictionaryPage from '../../src/viewer-table/DataDictionaryPage'; +import PCGL from '../fixtures/pcgl.json'; +import Advanced from '../fixtures/TorontoMapleLeafs.json'; +import themeDecorator from '../themeDecorator'; + +const pcglDictionary: Dictionary = replaceReferences(PCGL as Dictionary); +const advancedDictionary: Dictionary = replaceReferences(Advanced as Dictionary); + +const meta = { + component: DataDictionaryPage, + title: 'Viewer - Table/Data Dictionary Page', + tags: ['autodocs'], + decorators: [themeDecorator()], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const PCGLDictionary: Story = { + args: { + dictionaries: [pcglDictionary], + dictionaryIndex: 0, + lecternUrl: 'localhost:3031', + onVersionChange: (index: number) => { + console.log('Version changed to index:', index); + }, + }, +}; + +export const MultipleVersions: Story = { + args: { + dictionaries: [ + { ...pcglDictionary, version: '1.0', name: `${pcglDictionary.name} v1.0` }, + { ...pcglDictionary, version: '1.1', name: `${pcglDictionary.name} v1.1` }, + { ...pcglDictionary, version: '2.0', name: `${pcglDictionary.name} v2.0` }, + { ...pcglDictionary, version: '2.1', name: `${pcglDictionary.name} v2.1` }, + { ...pcglDictionary, version: '3.0', name: `${pcglDictionary.name} v3.0` }, + ], + dictionaryIndex: 2, + lecternUrl: 'localhost:3031', + onVersionChange: (index: number) => { + console.log('Version changed to index:', index); + }, + }, +}; + +export const AdvancedDictionary: Story = { + args: { + dictionaries: [ + { ...advancedDictionary, version: '1.0', name: `${advancedDictionary.name} v1.0` }, + { ...advancedDictionary, version: '1.5', name: `${advancedDictionary.name} v1.5` }, + { ...advancedDictionary, version: '2.0', name: `${advancedDictionary.name} v2.0` }, + ], + dictionaryIndex: 1, + lecternUrl: 'localhost:3031', + onVersionChange: (index: number) => { + console.log('Version changed to index:', index); + }, + }, +}; + +export const MixedDictionaries: Story = { + args: { + dictionaries: [ + { ...pcglDictionary, version: '1.0' }, + { ...advancedDictionary, version: '1.0' }, + { ...pcglDictionary, version: '2.0' }, + { ...advancedDictionary, version: '2.0' }, + ], + dictionaryIndex: 0, + lecternUrl: 'localhost:3031', + onVersionChange: (index: number) => { + console.log('Version changed to index:', index); + }, + }, +}; From 59cdf22c6d01b89443fdb03c669e38e757e0c617 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 2 Jul 2025 15:14:54 -0400 Subject: [PATCH 171/217] table width fix --- packages/ui/src/viewer-table/DataTable/Table.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/viewer-table/DataTable/Table.tsx b/packages/ui/src/viewer-table/DataTable/Table.tsx index 986ad910..678789f7 100644 --- a/packages/ui/src/viewer-table/DataTable/Table.tsx +++ b/packages/ui/src/viewer-table/DataTable/Table.tsx @@ -70,6 +70,7 @@ const tableContainerStyle = css` `; const tableStyle = css` + width: 100%; min-width: 1200px; border-collapse: collapse; border: 1px solid lightgray; From 23d78208b79fcdff3bdd29462d66b828e4aa9a74 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 2 Jul 2025 15:16:16 -0400 Subject: [PATCH 172/217] cleanup imports --- packages/ui/src/viewer-table/DataDictionaryPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/viewer-table/DataDictionaryPage.tsx b/packages/ui/src/viewer-table/DataDictionaryPage.tsx index eaba016c..0b853948 100644 --- a/packages/ui/src/viewer-table/DataDictionaryPage.tsx +++ b/packages/ui/src/viewer-table/DataDictionaryPage.tsx @@ -28,8 +28,8 @@ import Accordion from '../common/Accordion/Accordion'; import { AccordionData } from '../common/Accordion/AccordionItem'; import SchemaTable from './DataTable/SchemaTable/SchemaTable'; import DictionaryHeader from './DictionaryHeader'; -import InteractionPanel from './InteractionPanel/InteractionPanel'; import { FilterOptions } from './InteractionPanel/AttributeFilterDropdown'; +import InteractionPanel from './InteractionPanel/InteractionPanel'; const interactionPanelSpacing = css` margin-bottom: 24px; From d2ed3f1a23fecdda29e9a1e2a9c4216b563dbc39 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 3 Jul 2025 09:57:55 -0400 Subject: [PATCH 173/217] fix overlaying css issue --- packages/ui/src/common/Dropdown/Dropdown.tsx | 1 + .../ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/ui/src/common/Dropdown/Dropdown.tsx b/packages/ui/src/common/Dropdown/Dropdown.tsx index cf3b2a75..46227a82 100644 --- a/packages/ui/src/common/Dropdown/Dropdown.tsx +++ b/packages/ui/src/common/Dropdown/Dropdown.tsx @@ -82,6 +82,7 @@ const dropdownMenuStyle = (theme: Theme) => css` padding-top: 5px; border-radius: 9px; padding-bottom: 5px; + z-index: 100; `; type MenuItem = { diff --git a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx index 6d7a4cb7..41dece85 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/InteractionPanel.tsx @@ -57,10 +57,7 @@ const panelStyles = (theme: Theme, styles?: SerializedStyles) => css` background-color: ${theme.colors.white}; min-height: 70px; flex-wrap: nowrap; - overflow-x: auto; - overflow-y: visible; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - position: relative; ${styles} `; From faa4ba5528a444b667ce7cd6ed074dba5b4f0011 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 3 Jul 2025 10:07:08 -0400 Subject: [PATCH 174/217] fix vertical alignment --- packages/ui/src/viewer-table/DataTable/TableRow.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ui/src/viewer-table/DataTable/TableRow.tsx b/packages/ui/src/viewer-table/DataTable/TableRow.tsx index 37e84bbb..a60201c8 100644 --- a/packages/ui/src/viewer-table/DataTable/TableRow.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableRow.tsx @@ -39,7 +39,6 @@ const tdStyle = (theme: Theme, cellIndex: number, rowIndex: number) => css` white-space: pre-wrap; overflow-wrap: break-word; word-break: break-word; - vertical-align: top; ${cellIndex === 0 && ` position: sticky; From e111d4c3342b836d9c364d963af59b81591906a6 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 3 Jul 2025 10:18:14 -0400 Subject: [PATCH 175/217] add mock data --- .../stories/fixtures/TorontoMapleLeafs.json | 90 ++++++++++++++++++- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/packages/ui/stories/fixtures/TorontoMapleLeafs.json b/packages/ui/stories/fixtures/TorontoMapleLeafs.json index 3ce35a61..b56a78d1 100644 --- a/packages/ui/stories/fixtures/TorontoMapleLeafs.json +++ b/packages/ui/stories/fixtures/TorontoMapleLeafs.json @@ -7,7 +7,7 @@ }, "references": { "maple_leafs_roster": { - "description": "The official roster for the Toronto Maple Leafs.", + "description": "The official roster for the Toronto Maple Leafs + NHL all-stars + NBA all-stars + Soccer World 11.", "values": [ "Matthews", "Nylander", @@ -32,7 +32,93 @@ "Hakanpaa", "Woll", "Stolarz", - "Murray" + "Murray", + "McDavid", + "MacKinnon", + "Pastrnak", + "Kucherov", + "Panarin", + "Barkov", + "Kopitar", + "Point", + "Marner", + "Stamkos", + "Ovechkin", + "Crosby", + "Malkin", + "Letang", + "Karlsson", + "Fox", + "Josi", + "Hedman", + "Vasilevskiy", + "Shesterkin", + "Hellebuyck", + "Demko", + "LeBron James", + "Stephen Curry", + "Kevin Durant", + "Giannis Antetokounmpo", + "Luka Doncic", + "Joel Embiid", + "Nikola Jokic", + "Shai Gilgeous-Alexander", + "Tyrese Haliburton", + "Damian Lillard", + "Donovan Mitchell", + "Bam Adebayo", + "Jaylen Brown", + "Jayson Tatum", + "Anthony Edwards", + "Kawhi Leonard", + "Paul George", + "Devin Booker", + "Anthony Davis", + "Zion Williamson", + "Ja Morant", + "Trae Young", + "DeMar DeRozan", + "Jimmy Butler", + "Julius Randle", + "Pascal Siakam", + "Scottie Barnes", + "RJ Barrett", + "OG Anunoby", + "Lionel Messi", + "Cristiano Ronaldo", + "Erling Haaland", + "Kylian Mbappe", + "Kevin De Bruyne", + "Jude Bellingham", + "Vinícius Jr", + "Mohamed Salah", + "Harry Kane", + "Victor Osimhen", + "Robert Lewandowski", + "Thibaut Courtois", + "Alisson", + "Ederson", + "Kyle Walker", + "Ruben Dias", + "Virgil van Dijk", + "Antonio Rüdiger", + "Frenkie de Jong", + "Rodri", + "Declan Rice", + "Bernardo Silva", + "Phil Foden", + "Bukayo Saka", + "Pedri", + "Gavi", + "Federico Valverde", + "Luka Modric", + "Toni Kroos", + "Casemiro", + "Bruno Fernandes", + "Rafael Leão", + "Khvicha Kvaratskhelia", + "Lautaro Martínez", + "Julián Álvarez" ] }, "gender_options": { From 2cd120265385ef38720e8fc0c3e3b874b92bebdb Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 3 Jul 2025 10:28:49 -0400 Subject: [PATCH 176/217] add version switching --- .../DataDictionaryPage.stories.tsx | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx b/packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx index c65cd003..a1e61663 100644 --- a/packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx +++ b/packages/ui/stories/viewer-table/DataDictionaryPage.stories.tsx @@ -2,6 +2,7 @@ import { Dictionary, replaceReferences } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; +import React, { useState } from 'react'; import DataDictionaryPage from '../../src/viewer-table/DataDictionaryPage'; import PCGL from '../fixtures/pcgl.json'; import Advanced from '../fixtures/TorontoMapleLeafs.json'; @@ -10,12 +11,39 @@ import themeDecorator from '../themeDecorator'; const pcglDictionary: Dictionary = replaceReferences(PCGL as Dictionary); const advancedDictionary: Dictionary = replaceReferences(Advanced as Dictionary); +// Wrapper component to manage state for version switching +const DataDictionaryPageWrapper = ({ + dictionaries, + initialDictionaryIndex = 0, + lecternUrl = 'localhost:3031', +}: { + dictionaries: Dictionary[]; + initialDictionaryIndex?: number; + lecternUrl?: string; +}) => { + const [dictionaryIndex, setDictionaryIndex] = useState(initialDictionaryIndex); + + const handleVersionChange = (index: number) => { + console.log('Version changed to index:', index); + setDictionaryIndex(index); + }; + + return ( + + ); +}; + const meta = { - component: DataDictionaryPage, + component: DataDictionaryPageWrapper, title: 'Viewer - Table/Data Dictionary Page', tags: ['autodocs'], decorators: [themeDecorator()], -} satisfies Meta; +} satisfies Meta; export default meta; type Story = StoryObj; @@ -23,11 +51,8 @@ type Story = StoryObj; export const PCGLDictionary: Story = { args: { dictionaries: [pcglDictionary], - dictionaryIndex: 0, + initialDictionaryIndex: 0, lecternUrl: 'localhost:3031', - onVersionChange: (index: number) => { - console.log('Version changed to index:', index); - }, }, }; @@ -40,11 +65,8 @@ export const MultipleVersions: Story = { { ...pcglDictionary, version: '2.1', name: `${pcglDictionary.name} v2.1` }, { ...pcglDictionary, version: '3.0', name: `${pcglDictionary.name} v3.0` }, ], - dictionaryIndex: 2, + initialDictionaryIndex: 2, lecternUrl: 'localhost:3031', - onVersionChange: (index: number) => { - console.log('Version changed to index:', index); - }, }, }; @@ -55,11 +77,8 @@ export const AdvancedDictionary: Story = { { ...advancedDictionary, version: '1.5', name: `${advancedDictionary.name} v1.5` }, { ...advancedDictionary, version: '2.0', name: `${advancedDictionary.name} v2.0` }, ], - dictionaryIndex: 1, + initialDictionaryIndex: 1, lecternUrl: 'localhost:3031', - onVersionChange: (index: number) => { - console.log('Version changed to index:', index); - }, }, }; @@ -71,10 +90,7 @@ export const MixedDictionaries: Story = { { ...pcglDictionary, version: '2.0' }, { ...advancedDictionary, version: '2.0' }, ], - dictionaryIndex: 0, + initialDictionaryIndex: 0, lecternUrl: 'localhost:3031', - onVersionChange: (index: number) => { - console.log('Version changed to index:', index); - }, }, }; From ee63fc3663aabe1756244fc3cbc170eee6926203 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf <121772695+SaqAsh@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:09:04 -0400 Subject: [PATCH 177/217] Update packages/ui/src/common/ReadMoreText.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ciarán Schütte --- packages/ui/src/common/ReadMoreText.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ui/src/common/ReadMoreText.tsx b/packages/ui/src/common/ReadMoreText.tsx index caada584..64f565b2 100644 --- a/packages/ui/src/common/ReadMoreText.tsx +++ b/packages/ui/src/common/ReadMoreText.tsx @@ -86,8 +86,10 @@ const ReadMoreText = ({ const contentRef = useRef(null); const [isExpanded, setIsExpanded] = useState(false); const [needsToggle, setNeedsToggle] = useState(false); + const theme = useThemeContext(); const { ChevronDown } = theme.icons; + ``` useEffect(() => { if (contentRef.current) { From eb126466c804e0d9f414a70f5d489aa097a7da39 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 3 Jul 2025 14:33:04 -0400 Subject: [PATCH 178/217] fix typo as well as fix the import spacing --- packages/ui/src/common/Accordion/Accordion.tsx | 2 +- packages/ui/src/common/ReadMoreText.tsx | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index b55cfbb6..04df6690 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -22,8 +22,8 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; - import { useMemo, useState } from 'react'; + import AccordionItem, { AccordionData } from './AccordionItem'; export type AccordionProps = { diff --git a/packages/ui/src/common/ReadMoreText.tsx b/packages/ui/src/common/ReadMoreText.tsx index 64f565b2..7ef0f905 100644 --- a/packages/ui/src/common/ReadMoreText.tsx +++ b/packages/ui/src/common/ReadMoreText.tsx @@ -86,10 +86,9 @@ const ReadMoreText = ({ const contentRef = useRef(null); const [isExpanded, setIsExpanded] = useState(false); const [needsToggle, setNeedsToggle] = useState(false); - + const theme = useThemeContext(); const { ChevronDown } = theme.icons; - ``` useEffect(() => { if (contentRef.current) { From 2ef89ff0d4c19033d7f01b2172c363afd1caeaf2 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 3 Jul 2025 14:50:13 -0400 Subject: [PATCH 179/217] move copying logic into a seperate hook --- .../ui/src/common/Accordion/Accordion.tsx | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index 04df6690..cb3d94b7 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -34,16 +34,8 @@ export type AccordionOpenState = { isOpen: boolean; toggle: () => void; }; -const accordionStyle = css` - list-style: none; - padding: 0; - margin: 0; - display: flex; - flex-direction: column; - gap: 24px; -`; -const Accordion = ({ accordionItems }: AccordionProps) => { +const useClipboard = () => { const [clipboardContents, setClipboardContents] = useState(null); const [isCopying, setIsCopying] = useState(false); const [copySuccess, setCopySuccess] = useState(false); @@ -79,6 +71,25 @@ const Accordion = ({ accordionItems }: AccordionProps) => { } }, [clipboardContents]); + return { + clipboardContents, + setClipboardContents, + isCopying, + copySuccess, + handleCopy, + }; +}; +const accordionStyle = css` + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 24px; +`; + +const Accordion = ({ accordionItems }: AccordionProps) => { + const { setClipboardContents } = useClipboard(); const [openStates, setOpenStates] = useState(accordionItems.map((item) => item.openOnInit)); const handleToggle = (index: number) => { setOpenStates((prev) => prev.map((isOpen, i) => (i === index ? !isOpen : isOpen))); From f4280ba6fa20ecfe362c98494ebac227e746f953 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 3 Jul 2025 15:06:33 -0400 Subject: [PATCH 180/217] make whole area clickable --- packages/ui/src/common/Accordion/AccordionItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index a52479b9..9af9d600 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -196,10 +196,10 @@ const AccordionItem = ({ index, accordionData, openState, setClipboardContents } }, []); return ( -
    • +
    • -
      - {needsToggle && ( + {isTruncated && (

      From 553daec8cd44413f4b5b0e4b372af63ef4549755 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 4 Jul 2025 10:19:32 -0400 Subject: [PATCH 188/217] add comment for why we have mock props --- packages/ui/src/common/Accordion/AccordionItem.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index 741fab51..08dcf9e9 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -213,7 +213,15 @@ const AccordionItem = ({ index, accordionData, openState }: AccordionItemProps) )}
      - + {/* Mock props for the dictionary since we haven't implemented the download per schema yet */} +
      From 6fb2b31905db1567b9c52b46431f4a25e3699073 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 4 Jul 2025 10:49:25 -0400 Subject: [PATCH 189/217] fix the semantic html issues --- .../ui/src/common/Accordion/AccordionItem.tsx | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index 08dcf9e9..02981f6a 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -91,14 +91,10 @@ const contentContainerStyle = css` display: flex; flex-direction: row; align-items: center; - gap: 0; - flex: 1; - min-width: 0; - flex-wrap: wrap; - max-width: calc(100% - 100px); `; const titleStyle = css` + width: 100%; display: flex; align-items: center; `; @@ -126,7 +122,9 @@ const hashIconStyle = (theme: Theme) => css` const descriptionWrapperStyle = (theme: Theme) => css` ${theme.typography?.label2}; color: ${theme.colors.grey_5}; - padding: 4px 8px; + padding-right: 24px; + padding-left: 24px; + padding-bottom: 20px; word-wrap: break-word; overflow-wrap: break-word; `; @@ -189,7 +187,7 @@ const AccordionItem = ({ index, accordionData, openState }: AccordionItemProps) return (
    • -

      +
      - {description && ( - - {description} - - )}
      {/* Mock props for the dictionary since we haven't implemented the download per schema yet */} -

      + + + {description} +
      {content}
      From 3e5dfee3a0af7a2c2440cb2624aaea52daa631ed Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 4 Jul 2025 11:16:00 -0400 Subject: [PATCH 190/217] finish fixing up css --- .../ui/src/common/Accordion/AccordionItem.tsx | 95 ++++++++++--------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index 02981f6a..d9fac3d4 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -57,27 +57,27 @@ const accordionItemStyle = (theme: Theme) => css` `; const accordionItemTitleStyle = css` - margin: 0; display: flex; - align-items: center; + align-items: flex-start; justify-content: space-between; padding: 24px 20px; background-color: #ffffff; transition: all 0.2s ease; + width: 100%; + box-sizing: border-box; `; const accordionItemButtonStyle = (theme: Theme) => css` display: flex; border: none; align-items: center; - justify-content: space-between; - padding: 24px 20px; - background-color: #ffffff; color: ${theme.colors.accent_dark}; cursor: pointer; - transition: all 0.2s ease; ${theme.typography?.button}; text-align: left; + background: transparent; + padding: 8px 0; + flex: 1; `; const chevronStyle = (isOpen: boolean) => css` @@ -89,26 +89,25 @@ const chevronStyle = (isOpen: boolean) => css` const contentContainerStyle = css` display: flex; - flex-direction: row; - align-items: center; + flex-direction: column; + align-items: flex-start; + flex: 1; `; -const titleStyle = css` - width: 100%; +const titleRowStyle = css` display: flex; align-items: center; + width: 100%; + margin-bottom: 10px; `; const hashIconStyle = (theme: Theme) => css` opacity: 0; - margin-left: 8px; transition: opacity 0.2s ease; background: transparent; border: none; cursor: pointer; padding: 0; - display: inline-flex; - align-items: center; svg { border-bottom: 2px solid ${theme.colors.secondary}; @@ -122,11 +121,13 @@ const hashIconStyle = (theme: Theme) => css` const descriptionWrapperStyle = (theme: Theme) => css` ${theme.typography?.label2}; color: ${theme.colors.grey_5}; - padding-right: 24px; - padding-left: 24px; - padding-bottom: 20px; - word-wrap: break-word; overflow-wrap: break-word; + width: 100%; +`; + +const downloadButtonContainerStyle = css` + flex-shrink: 0; + margin-right: 8px; `; const accordionCollapseStyle = (isOpen: boolean) => css` @@ -189,36 +190,40 @@ const AccordionItem = ({ index, accordionData, openState }: AccordionItemProps)
    • - - +
      + + +
      + {/* Mock props for the dictionary since we haven't implemented the download per schema yet */} + + {description} + +
      +
      +
      - {/* Mock props for the dictionary since we haven't implemented the download per schema yet */} -
      - - {description} -
      {content}
      From 13f32d7cc7414d870c1af9303a38f6e6f3f81d85 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 4 Jul 2025 11:17:46 -0400 Subject: [PATCH 191/217] fix any other redundant css and move comment --- packages/ui/src/common/Accordion/AccordionItem.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index d9fac3d4..bdb5d7f3 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -45,6 +45,7 @@ const accordionItemStyle = (theme: Theme) => css` border-radius: 8px; margin-bottom: 1px; overflow: hidden; + background-color: #ffffff; box-shadow: 0 2px 6px rgba(70, 63, 63, 0.05), 0 0 0 0.3px ${theme.colors.black}; @@ -61,7 +62,6 @@ const accordionItemTitleStyle = css` align-items: flex-start; justify-content: space-between; padding: 24px 20px; - background-color: #ffffff; transition: all 0.2s ease; width: 100%; box-sizing: border-box; @@ -138,7 +138,6 @@ const accordionCollapseStyle = (isOpen: boolean) => css` const accordionItemContentStyle = css` padding: 30px; - background-color: #ffffff; `; const contentInnerContainerStyle = (theme: Theme) => css` @@ -208,12 +207,12 @@ const AccordionItem = ({ index, accordionData, openState }: AccordionItemProps)
      - {/* Mock props for the dictionary since we haven't implemented the download per schema yet */} {description}
      + {/* Mock props for the dictionary since we haven't implemented the download per schema yet */} Date: Fri, 4 Jul 2025 12:08:44 -0400 Subject: [PATCH 192/217] fix dropdown css --- packages/ui/src/common/Dropdown/Dropdown.tsx | 15 +++++++-------- packages/ui/src/common/Dropdown/DropdownItem.tsx | 9 +++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/common/Dropdown/Dropdown.tsx b/packages/ui/src/common/Dropdown/Dropdown.tsx index 46227a82..dd99fe42 100644 --- a/packages/ui/src/common/Dropdown/Dropdown.tsx +++ b/packages/ui/src/common/Dropdown/Dropdown.tsx @@ -83,6 +83,8 @@ const dropdownMenuStyle = (theme: Theme) => css` border-radius: 9px; padding-bottom: 5px; z-index: 100; + max-height: 150px; + overflow-y: auto; `; type MenuItem = { @@ -137,15 +139,12 @@ const Dropdown = ({ menuItems = [], title, leftIcon, disabled = false }: DropDow return (
      -
      -
      - {leftIcon} - {title} - -
      - - {open && !disabled &&
      {renderMenuItems()}
      } +
      + {leftIcon} + {title} +
      + {open && !disabled &&
      {renderMenuItems()}
      }
      ); }; diff --git a/packages/ui/src/common/Dropdown/DropdownItem.tsx b/packages/ui/src/common/Dropdown/DropdownItem.tsx index d479130d..ebd40530 100644 --- a/packages/ui/src/common/Dropdown/DropdownItem.tsx +++ b/packages/ui/src/common/Dropdown/DropdownItem.tsx @@ -21,8 +21,10 @@ /** @jsxImportSource @emotion/react */ +import React from 'react'; import { css, SerializedStyles } from '@emotion/react'; import { ReactNode } from 'react'; + import type { Theme } from '../../theme'; import { useThemeContext } from '../../theme/ThemeContext'; @@ -38,8 +40,7 @@ type DropDownItemProps = { const styledListItemStyle = (theme: Theme, customStyles?: any) => css` display: flex; min-height: 100%; - padding-bottom: 5px; - height: 100%; + padding: 10px; align-items: center; border-radius: 9px; justify-content: center; @@ -57,9 +58,9 @@ const DropDownItem = ({ children, action, customStyles }: DropDownItemProps) => const content =
      {children}
      ; if (typeof action === 'function') { return ( -
      +
    • {children} - +
    • ); } From 5dc9b7557acedd9bc4fd381b8a5369c53c384362 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 4 Jul 2025 12:16:02 -0400 Subject: [PATCH 193/217] make the header sticky --- .../src/viewer-table/DataDictionaryPage.tsx | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/packages/ui/src/viewer-table/DataDictionaryPage.tsx b/packages/ui/src/viewer-table/DataDictionaryPage.tsx index 0b853948..b9fc2e71 100644 --- a/packages/ui/src/viewer-table/DataDictionaryPage.tsx +++ b/packages/ui/src/viewer-table/DataDictionaryPage.tsx @@ -24,8 +24,7 @@ import { css } from '@emotion/react'; import { Dictionary } from '@overture-stack/lectern-dictionary'; import React, { useState } from 'react'; -import Accordion from '../common/Accordion/Accordion'; -import { AccordionData } from '../common/Accordion/AccordionItem'; +import Accordion, { AccordionData } from '../common/Accordion/Accordion'; import SchemaTable from './DataTable/SchemaTable/SchemaTable'; import DictionaryHeader from './DictionaryHeader'; import { FilterOptions } from './InteractionPanel/AttributeFilterDropdown'; @@ -88,26 +87,36 @@ const DataDictionaryPage = ({ iconOnly: true, disabled: false, }, + schemaName: 'schemaName', })); return (
      - -
      - +
      + +
      + +
      - +
      ); }; From c9f20b506fe15f225412bcfb864ab6446b0fc5f5 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 4 Jul 2025 12:23:18 -0400 Subject: [PATCH 194/217] remove uneccessary styling and like z-index --- packages/ui/src/common/Dropdown/Dropdown.tsx | 2 +- .../ui/src/common/Dropdown/DropdownItem.tsx | 21 +++++++++++++------ .../src/viewer-table/DataDictionaryPage.tsx | 2 -- .../ui/src/viewer-table/DataTable/Table.tsx | 1 - .../viewer-table/DataTable/TableHeader.tsx | 1 - .../src/viewer-table/DataTable/TableRow.tsx | 1 - 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/common/Dropdown/Dropdown.tsx b/packages/ui/src/common/Dropdown/Dropdown.tsx index dd99fe42..f06adfa6 100644 --- a/packages/ui/src/common/Dropdown/Dropdown.tsx +++ b/packages/ui/src/common/Dropdown/Dropdown.tsx @@ -131,7 +131,7 @@ const Dropdown = ({ menuItems = [], title, leftIcon, disabled = false }: DropDow const renderMenuItems = () => { return menuItems.map(({ label, action }) => ( - + setOpen(false)}> {label} )); diff --git a/packages/ui/src/common/Dropdown/DropdownItem.tsx b/packages/ui/src/common/Dropdown/DropdownItem.tsx index ebd40530..8beeecac 100644 --- a/packages/ui/src/common/Dropdown/DropdownItem.tsx +++ b/packages/ui/src/common/Dropdown/DropdownItem.tsx @@ -21,9 +21,8 @@ /** @jsxImportSource @emotion/react */ -import React from 'react'; import { css, SerializedStyles } from '@emotion/react'; -import { ReactNode } from 'react'; +import React, { ReactNode } from 'react'; import type { Theme } from '../../theme'; import { useThemeContext } from '../../theme/ThemeContext'; @@ -31,6 +30,7 @@ import { useThemeContext } from '../../theme/ThemeContext'; type DropDownItemProps = { action?: string | (() => void); children: ReactNode; + onItemClick?: () => void; customStyles?: { hover?: SerializedStyles; base?: SerializedStyles; @@ -53,18 +53,27 @@ const styledListItemStyle = (theme: Theme, customStyles?: any) => css` ${customStyles?.base} `; -const DropDownItem = ({ children, action, customStyles }: DropDownItemProps) => { +const DropDownItem = ({ children, action, onItemClick, customStyles }: DropDownItemProps) => { const theme = useThemeContext(); - const content =
      {children}
      ; + + const handleClick = () => { + if (typeof action === 'function') { + action(); + } + if (onItemClick) { + onItemClick(); + } + }; + if (typeof action === 'function') { return ( -
    • +
    • {children}
    • ); } - return content; + return
      {children}
      ; }; export default DropDownItem; diff --git a/packages/ui/src/viewer-table/DataDictionaryPage.tsx b/packages/ui/src/viewer-table/DataDictionaryPage.tsx index b9fc2e71..d03fd29e 100644 --- a/packages/ui/src/viewer-table/DataDictionaryPage.tsx +++ b/packages/ui/src/viewer-table/DataDictionaryPage.tsx @@ -66,11 +66,9 @@ const DataDictionaryPage = ({ // Filter fields based on the selected filters const getFilteredSchema = (schema: any) => { if (filters.includes('Required')) { - // Only show required fields const filteredFields = schema.fields.filter((field: any) => field.restrictions?.required === true); return { ...schema, fields: filteredFields }; } - // 'All Fields' or no filter - show all fields return schema; }; diff --git a/packages/ui/src/viewer-table/DataTable/Table.tsx b/packages/ui/src/viewer-table/DataTable/Table.tsx index 678789f7..b4e5acd8 100644 --- a/packages/ui/src/viewer-table/DataTable/Table.tsx +++ b/packages/ui/src/viewer-table/DataTable/Table.tsx @@ -43,7 +43,6 @@ const scrollWrapperStyle = css` const shadowStyle = css` position: absolute; top: 0.7%; - z-index: 100; width: 20px; height: 100%; pointer-events: none; diff --git a/packages/ui/src/viewer-table/DataTable/TableHeader.tsx b/packages/ui/src/viewer-table/DataTable/TableHeader.tsx index 6c1f13f8..c24e2701 100644 --- a/packages/ui/src/viewer-table/DataTable/TableHeader.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableHeader.tsx @@ -37,7 +37,6 @@ const thStyle = (theme: Theme, index: number) => css` ` position: sticky; left: 0; - z-index: 20; background-color: #e5edf3; `} border: 1px solid #DCDDE1; diff --git a/packages/ui/src/viewer-table/DataTable/TableRow.tsx b/packages/ui/src/viewer-table/DataTable/TableRow.tsx index a60201c8..c6426627 100644 --- a/packages/ui/src/viewer-table/DataTable/TableRow.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableRow.tsx @@ -43,7 +43,6 @@ const tdStyle = (theme: Theme, cellIndex: number, rowIndex: number) => css` ` position: sticky; left: 0; - z-index: 10; background-color: ${rowIndex % 2 === 0 ? 'white' : '#F5F7F8'}; `} border: 1px solid #DCDDE1; From 83a503da69d75f8b1f2dbadfba1e9171631b939e Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 4 Jul 2025 12:33:42 -0400 Subject: [PATCH 195/217] fix filtering logic --- .../src/viewer-table/DataDictionaryPage.tsx | 58 +++++++++++-------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/packages/ui/src/viewer-table/DataDictionaryPage.tsx b/packages/ui/src/viewer-table/DataDictionaryPage.tsx index d03fd29e..f2339df2 100644 --- a/packages/ui/src/viewer-table/DataDictionaryPage.tsx +++ b/packages/ui/src/viewer-table/DataDictionaryPage.tsx @@ -22,7 +22,7 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import { Dictionary } from '@overture-stack/lectern-dictionary'; +import { Dictionary, Schema, SchemaField } from '@overture-stack/lectern-dictionary'; import React, { useState } from 'react'; import Accordion, { AccordionData } from '../common/Accordion/Accordion'; import SchemaTable from './DataTable/SchemaTable/SchemaTable'; @@ -53,8 +53,7 @@ const DataDictionaryPage = ({ const dictionary = dictionaries[dictionaryIndex]; const handleSelect = (schemaIndex: number) => { - // Table of contents selection - could implement scroll here - console.log('Selected schema index:', schemaIndex); + // All we need to do is hook up to the accordion's scroll into view, since that is already implemented }; const handleVersionChange = (index: number) => { @@ -63,30 +62,41 @@ const DataDictionaryPage = ({ } }; - // Filter fields based on the selected filters - const getFilteredSchema = (schema: any) => { - if (filters.includes('Required')) { - const filteredFields = schema.fields.filter((field: any) => field.restrictions?.required === true); - return { ...schema, fields: filteredFields }; + const displayData = (data: Dictionary[], filters: FilterOptions[], dictionaryIndex: number) => { + const currentDictionary = data?.[dictionaryIndex]; + // If the filter is not active or we just have nothing to filter, return the original data + if (!filters?.length) { + return currentDictionary; } - return schema; + return { + ...currentDictionary, + schemas: currentDictionary?.schemas?.map((schema: Schema) => ({ + ...schema, + fields: schema.fields.filter((field: SchemaField) => { + // we are going to filter via the constraints that are given + if (filters?.includes('Required')) { + const restrictions = field?.restrictions ?? []; + if ('required' in restrictions && typeof restrictions !== 'function') { + return restrictions.required === true; + } + return false; + } + return filters?.includes('All Fields'); + }), + })), + }; }; - const accordionItems: AccordionData[] = dictionary.schemas.map((schema) => ({ - title: schema.name, - openOnInit: !isCollapsed, - description: schema.description, - content: , - dictionaryDownloadButtonProps: { - version: dictionary.version || '1.0', - name: dictionary.name, - lecternUrl, - fileType: 'tsv', - iconOnly: true, - disabled: false, - }, - schemaName: 'schemaName', - })); + const filteredDictionary = displayData(dictionaries, filters, dictionaryIndex); + + const accordionItems: AccordionData[] = + filteredDictionary?.schemas?.map((schema) => ({ + title: schema.name, + openOnInit: !isCollapsed, + description: schema.description, + content: , + schemaName: 'schemaName', + })) || []; return (
      From ba7057e687a8b1476063a5ce05f8a8f3de58732d Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 4 Jul 2025 15:45:59 -0400 Subject: [PATCH 196/217] fix up a lot of the logic and make it such that we actually have proper clickability as well as make it visually appealing --- .../ui/src/common/Accordion/Accordion.tsx | 14 +++++-- .../ui/src/common/Accordion/AccordionItem.tsx | 40 ++++++++++--------- .../src/viewer-table/DataDictionaryPage.tsx | 5 +-- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/packages/ui/src/common/Accordion/Accordion.tsx b/packages/ui/src/common/Accordion/Accordion.tsx index 8dd91877..06bd6277 100644 --- a/packages/ui/src/common/Accordion/Accordion.tsx +++ b/packages/ui/src/common/Accordion/Accordion.tsx @@ -22,19 +22,19 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import { ReactNode, useMemo, useState } from 'react'; +import { ReactNode, useEffect, useMemo, useState } from 'react'; import AccordionItem from './AccordionItem'; export type AccordionData = { title: string; - openOnInit: boolean; description?: string; content: ReactNode; schemaName: string; }; export type AccordionProps = { accordionItems: Array; + collapseAll: boolean; }; export type AccordionOpenState = { @@ -93,10 +93,16 @@ const accordionStyle = css` display: flex; flex-direction: column; gap: 24px; + cursor: pointer; `; -const Accordion = ({ accordionItems }: AccordionProps) => { - const [openStates, setOpenStates] = useState(accordionItems.map((item) => item.openOnInit)); +const Accordion = ({ accordionItems, collapseAll }: AccordionProps) => { + const [openStates, setOpenStates] = useState(accordionItems.map(() => collapseAll)); + + useEffect(() => { + setOpenStates(accordionItems.map(() => collapseAll)); + }, [collapseAll]); + const handleToggle = (index: number) => { setOpenStates((prev) => prev.map((isOpen, i) => (i === index ? !isOpen : isOpen))); }; diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index bdb5d7f3..b8113e88 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -51,7 +51,7 @@ const accordionItemStyle = (theme: Theme) => css` 0 0 0 0.3px ${theme.colors.black}; &:hover { box-shadow: - 0 2px 6px rgba(70, 63, 63, 0.15), + 0 2px 6px rgba(70, 63, 63, 0.2), 0 0 0 0.3px ${theme.colors.black}; } transition: all 0.3s ease; @@ -71,13 +71,14 @@ const accordionItemButtonStyle = (theme: Theme) => css` display: flex; border: none; align-items: center; - color: ${theme.colors.accent_dark}; cursor: pointer; + background: transparent; + padding: 8px 0px; +`; +const titleStyle = (theme: Theme) => css` ${theme.typography?.button}; + color: ${theme.colors.accent_dark}; text-align: left; - background: transparent; - padding: 8px 0; - flex: 1; `; const chevronStyle = (isOpen: boolean) => css` @@ -91,14 +92,15 @@ const contentContainerStyle = css` display: flex; flex-direction: column; align-items: flex-start; - flex: 1; `; const titleRowStyle = css` display: flex; + gap: 2px; align-items: center; - width: 100%; margin-bottom: 10px; + flex-direction: auto; + flex-wrap: wrap; `; const hashIconStyle = (theme: Theme) => css` @@ -119,10 +121,10 @@ const hashIconStyle = (theme: Theme) => css` `; const descriptionWrapperStyle = (theme: Theme) => css` - ${theme.typography?.label2}; - color: ${theme.colors.grey_5}; + ${theme.typography?.data}; + color: ${theme.colors.black}; overflow-wrap: break-word; - width: 100%; + margin-left: 16px; `; const downloadButtonContainerStyle = css` @@ -137,7 +139,7 @@ const accordionCollapseStyle = (isOpen: boolean) => css` `; const accordionItemContentStyle = css` - padding: 30px; + padding: 0px 30px 30px 30px; `; const contentInnerContainerStyle = (theme: Theme) => css` @@ -154,9 +156,7 @@ const handleInitialHashCheck = ( accordionRef: RefObject, ) => { if (window.location.hash === windowLocationHash) { - if (!accordionData.openOnInit) { - openState.toggle(); - } + openState.toggle(); accordionRef.current?.id === indexString ? accordionRef.current.scrollIntoView({ behavior: 'smooth' }) : null; } }; @@ -182,7 +182,9 @@ const AccordionItem = ({ index, accordionData, openState }: AccordionItemProps) const windowLocationHash = `#${index}`; useEffect(() => { - handleInitialHashCheck(windowLocationHash, accordionData, openState, indexString, accordionRef); + setTimeout(() => { + handleInitialHashCheck(windowLocationHash, accordionData, openState, indexString, accordionRef); + }, 100); }, []); return ( @@ -197,8 +199,8 @@ const AccordionItem = ({ index, accordionData, openState }: AccordionItemProps) height={16} style={chevronStyle(openState.isOpen)} /> - {title} + {title} + + {description} +
      - - {description} -
      {/* Mock props for the dictionary since we haven't implemented the download per schema yet */} diff --git a/packages/ui/src/viewer-table/DataDictionaryPage.tsx b/packages/ui/src/viewer-table/DataDictionaryPage.tsx index f2339df2..d684baac 100644 --- a/packages/ui/src/viewer-table/DataDictionaryPage.tsx +++ b/packages/ui/src/viewer-table/DataDictionaryPage.tsx @@ -48,7 +48,7 @@ const DataDictionaryPage = ({ onVersionChange, }: DataDictionaryPageProps) => { const [filters, setFilters] = useState([]); - const [isCollapsed, setIsCollapsed] = useState(false); + const [isCollapsed, setIsCollapsed] = useState(true); const dictionary = dictionaries[dictionaryIndex]; @@ -92,7 +92,6 @@ const DataDictionaryPage = ({ const accordionItems: AccordionData[] = filteredDictionary?.schemas?.map((schema) => ({ title: schema.name, - openOnInit: !isCollapsed, description: schema.description, content: , schemaName: 'schemaName', @@ -124,7 +123,7 @@ const DataDictionaryPage = ({ />
      - + ); }; From c1879bb26f60f240d93074376da49f1d9e9752ef Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Fri, 4 Jul 2025 15:52:34 -0400 Subject: [PATCH 197/217] remove all redundant css --- packages/ui/src/common/Accordion/AccordionItem.tsx | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index b8113e88..a9d9c96d 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -40,7 +40,6 @@ export type AccordionItemProps = { }; const accordionItemStyle = (theme: Theme) => css` - list-style: none; width: 100%; border-radius: 8px; margin-bottom: 1px; @@ -85,7 +84,6 @@ const chevronStyle = (isOpen: boolean) => css` transform: ${isOpen ? 'rotate(0deg)' : 'rotate(-90deg)'}; transition: transform 0.2s ease; margin-right: 12px; - flex-shrink: 0; `; const contentContainerStyle = css` @@ -109,12 +107,9 @@ const hashIconStyle = (theme: Theme) => css` background: transparent; border: none; cursor: pointer; - padding: 0; - svg { border-bottom: 2px solid ${theme.colors.secondary}; } - &:hover { opacity: 1; } @@ -127,11 +122,6 @@ const descriptionWrapperStyle = (theme: Theme) => css` margin-left: 16px; `; -const downloadButtonContainerStyle = css` - flex-shrink: 0; - margin-right: 8px; -`; - const accordionCollapseStyle = (isOpen: boolean) => css` overflow: hidden; max-height: ${isOpen ? 'none' : '0px'}; @@ -213,7 +203,7 @@ const AccordionItem = ({ index, accordionData, openState }: AccordionItemProps) -
      +
      {/* Mock props for the dictionary since we haven't implemented the download per schema yet */} Date: Fri, 4 Jul 2025 16:05:05 -0400 Subject: [PATCH 198/217] fix html semantic issues --- packages/ui/src/common/Dropdown/Dropdown.tsx | 17 ++++++++++++++--- .../ui/src/common/Dropdown/DropdownItem.tsx | 8 ++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/common/Dropdown/Dropdown.tsx b/packages/ui/src/common/Dropdown/Dropdown.tsx index f06adfa6..06ee435a 100644 --- a/packages/ui/src/common/Dropdown/Dropdown.tsx +++ b/packages/ui/src/common/Dropdown/Dropdown.tsx @@ -73,6 +73,7 @@ const dropDownTitleStyle = (theme: Theme) => css` `; const dropdownMenuStyle = (theme: Theme) => css` + all: unset; ${theme.typography?.button}; position: absolute; top: calc(100% + 5px); @@ -139,12 +140,22 @@ const Dropdown = ({ menuItems = [], title, leftIcon, disabled = false }: DropDow return (
      -
      +
      - {open && !disabled &&
      {renderMenuItems()}
      } + + {open && !disabled && ( + + {renderMenuItems()} + + )}
      ); }; diff --git a/packages/ui/src/common/Dropdown/DropdownItem.tsx b/packages/ui/src/common/Dropdown/DropdownItem.tsx index 8beeecac..047b2d87 100644 --- a/packages/ui/src/common/Dropdown/DropdownItem.tsx +++ b/packages/ui/src/common/Dropdown/DropdownItem.tsx @@ -67,13 +67,17 @@ const DropDownItem = ({ children, action, onItemClick, customStyles }: DropDownI if (typeof action === 'function') { return ( -
    • +
    • {children}
    • ); } - return
      {children}
      ; + return ( +
    • + {children} +
    • + ); }; export default DropDownItem; From d20b93e5b09e5a1556004ea306491feec5b46259 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 7 Jul 2025 09:02:26 -0400 Subject: [PATCH 199/217] fix some accessibility --- packages/ui/src/common/Accordion/AccordionItem.tsx | 4 +++- .../DataTable/SchemaTable/Columns/AllowedValues.tsx | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index a9d9c96d..e0fc4402 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -40,6 +40,7 @@ export type AccordionItemProps = { }; const accordionItemStyle = (theme: Theme) => css` + list-style: none; width: 100%; border-radius: 8px; margin-bottom: 1px; @@ -157,6 +158,7 @@ const hashOnClick = ( setClipboardContents: (currentSchema: string) => void, ) => { event.stopPropagation(); + event.preventDefault(); window.location.hash = windowLocationHash; setClipboardContents(window.location.href); }; @@ -182,7 +184,7 @@ const AccordionItem = ({ index, accordionData, openState }: AccordionItemProps)
      - ); diff --git a/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx index e64da34a..0d69a7bf 100644 --- a/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx +++ b/packages/ui/src/viewer-table/InteractionPanel/ExpandAllButton.tsx @@ -33,7 +33,7 @@ const ExpandAllButton = ({ onClick, disabled }: ExpandAllButtonProps) => { const { Eye } = theme.icons; return ( - ); From 620bc2480481b047f76800cf53008aa014e6df82 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 7 Jul 2025 13:43:35 -0400 Subject: [PATCH 211/217] fix the scroll into view --- packages/ui/src/common/Accordion/AccordionItem.tsx | 4 +++- .../DataTable/SchemaTable/Columns/Fields.tsx | 12 ++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index 08411380..5a7871f7 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -147,7 +147,9 @@ const handleInitialHashCheck = ( ) => { if (window.location.hash === windowLocationHash) { openState.toggle(); - accordionRef.current?.id === indexString ? accordionRef.current.scrollIntoView({ behavior: 'smooth' }) : null; + accordionRef.current?.id === indexString ? + accordionRef.current.scrollIntoView({ block: 'center', behavior: 'smooth' }) + : null; } }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx index 4012d9c5..de0603fd 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx @@ -44,6 +44,9 @@ const fieldContainerStyle = css` scroll-margin: 40%; `; +export type FieldColumnProps = { + field: CellContext; +}; export type FieldExamplesProps = { examples: DictionaryMeta[string]; }; @@ -127,7 +130,11 @@ const useHashClickHandler = (fieldIndex: number, setClipboardContents: (clipboar }; }; -export const FieldsColumn = ({ field }: { field: CellContext }) => { +export const FieldsColumn = ({ field }: FieldColumnProps) => { + const [clipboardContents, setClipboardContents] = useState(null); + const [isCopying, setIsCopying] = useState(false); + const [copySuccess, setCopySuccess] = useState(false); + const fieldName = field.row.original.name; const fieldIndex = field.row.index; const fieldDescription = field.row.original.description; @@ -135,9 +142,6 @@ export const FieldsColumn = ({ field }: { field: CellContext(null); - const [isCopying, setIsCopying] = useState(false); - const [copySuccess, setCopySuccess] = useState(false); const handleCopy = (text: string) => { if (isCopying) { From 8823df1604413fd56369256f44b6c0f3778e8634 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 7 Jul 2025 16:07:30 -0400 Subject: [PATCH 212/217] full width to pill --- packages/ui/src/common/Accordion/AccordionItem.tsx | 4 ++-- packages/ui/src/common/Pill.tsx | 5 +++-- .../DataTable/SchemaTable/Columns/AllowedValues.tsx | 6 ++++-- .../viewer-table/DataTable/SchemaTable/Columns/Fields.tsx | 6 +++++- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/common/Accordion/AccordionItem.tsx b/packages/ui/src/common/Accordion/AccordionItem.tsx index 5a7871f7..c0f49bdf 100644 --- a/packages/ui/src/common/Accordion/AccordionItem.tsx +++ b/packages/ui/src/common/Accordion/AccordionItem.tsx @@ -47,11 +47,11 @@ const accordionItemStyle = (theme: Theme) => css` overflow: hidden; background-color: #ffffff; box-shadow: - 0 2px 6px rgba(70, 63, 63, 0.05), + 0 2px 6px rgba(70, 63, 63, 0.3), 0 0 0 0.3px ${theme.colors.black}; &:hover { box-shadow: - 0 2px 6px rgba(70, 63, 63, 0.2), + 0 2px 6px rgba(70, 63, 63, 0.5), 0 0 0 0.3px ${theme.colors.black}; } transition: all 0.3s ease; diff --git a/packages/ui/src/common/Pill.tsx b/packages/ui/src/common/Pill.tsx index ab8e09aa..29e729b7 100644 --- a/packages/ui/src/common/Pill.tsx +++ b/packages/ui/src/common/Pill.tsx @@ -13,6 +13,7 @@ export interface PillProps { icon?: ReactNode; onClick?: (event: MouseEvent) => void; style?: CSSProperties; + fullWidth?: boolean; } const getVariantStyles = (variant: PillVariant, theme: Theme) => { @@ -72,7 +73,7 @@ const getSizeStyles = (size: PillSize, theme: Theme) => { return sizeStyles[size]; }; -const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, style }: PillProps) => { +const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, style, fullWidth }: PillProps) => { const theme = useThemeContext(); const variantStyles = getVariantStyles(variant, theme); const sizeStyles = getSizeStyles(size, theme); @@ -90,7 +91,7 @@ const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, s border: ${variantStyles.border}; transition: all 0.2s ease-in-out; user-select: none; - width: ${sizeStyles.width}; + width: ${fullWidth ? 'fit-content' : sizeStyles.width}; max-width: ${sizeStyles.maxWidth}; text-align: center; word-wrap: break-word; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx index 2cf38c15..786482eb 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx @@ -197,7 +197,7 @@ export const renderAllowedValuesColumn = (restrictions: CellContext css` - ${theme.typography.paragraphSmallBold}; + ${theme.typography.dataBold}; color: ${theme.colors.black}; cursor: pointer; display: inline-flex; @@ -224,7 +224,9 @@ export const renderAllowedValuesColumn = (restrictions: CellContext {prefix && {prefix}} - {content} + + {content} +
      ); } diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx index de0603fd..3c76229e 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx @@ -97,7 +97,11 @@ const FieldName = ({ name, onHashClick, uniqueKeys, foreignKey }: FieldNameProps onHashClick(event)}> - {displayKeys.length === 1 && !foreignKey && {displayKeys}} + {displayKeys.length === 1 && !foreignKey && ( + + {displayKeys} + + )} {displayKeys.length > 1 && !foreignKey && } {foreignKey && }
      From 497a3997f6d24d8a85d256f302eda45084c5b6fc Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 14 Jul 2025 11:15:33 -0400 Subject: [PATCH 213/217] merge fixes, branch now building --- .../SchemaTable/Columns/Attribute.tsx | 102 ++++++------------ .../SchemaTable/Columns/CustomColumns.tsx | 0 .../DataTable/SchemaTable/OpenModalPill.tsx | 44 -------- .../viewer-table/DataTable/TableHeader.tsx | 7 -- .../src/viewer-table/DataTable/TableRow.tsx | 7 -- .../viewer-table/SchemaTable.stories.tsx | 92 +--------------- 6 files changed, 32 insertions(+), 220 deletions(-) delete mode 100644 packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/CustomColumns.tsx delete mode 100644 packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx index fa0dcc06..6dc45373 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Attribute.tsx @@ -1,5 +1,4 @@ /* - * * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved * * This program and the accompanying materials are made available under the terms of @@ -16,76 +15,37 @@ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * */ -const primary = { - primary1: '#00A88F', - primary2: '#00C4A7', - primary3: '#00DDBE', - primary4: '#40E6CF', - primary5: '#99F1E5', - primary6: '#CCF8F2', - primary7: '#E5FBF8', -}; - -const secondary = { - secondary1: '#0B75A2', - secondary2: '#109ED9', - secondary3: '#4BC6F0', - secondary4: '#66CEF2', - secondary5: '#AEE5F8', - secondary6: '#D2F1FB', - secondary7: '#EDF9FD', -}; - -const greyscale = { - grey1: '#282A35', - grey2: '#5E6068', - grey3: '#AEAFB3', - grey4: '#DFDFE1', - grey5: '#F2F3F5', - grey6: '#F2F5F8', -}; - -const accent = { - accent1_1: '#003055', - accent1_2: '#04518C', - accent1_3: '#4F85AE', - accent1_4: '#9BB9D1', - accent1_5: '#C0D3E2', - accent1_6: '#E5EDF3', -}; - -const accent2 = { - accent2_1: '#9E005D', - accent2_2: '#B74A89', - accent2_3: '#C772A3', - accent2_4: '#E2B7D0', - accent2_5: '#EDD2E1', - accent2_6: '#F7ECF3', -}; - -const accent3 = { - accent3_1: '#CFD509', - accent3_2: '#D9DE3A', - accent3_3: '#E4E775', - accent3_4: '#F0F2B0', - accent3_5: '#F5F7CE', - accent3_6: '#FBFBEB', -}; - -const gradient = { - gradientStart: '#45A0D4', - gradientEnd: '#6EC9D0', -}; - -export default { - ...primary, - ...secondary, - ...greyscale, - ...accent, - ...accent2, - ...accent3, - ...gradient, +/** @jsxImportSource @emotion/react */ + +import { css } from '@emotion/react'; +import { SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; + +import { Row } from '@tanstack/react-table'; +import Pill from '../../../../common/Pill'; +import OpenModalButton from '../../../OpenModalButton'; + +export type Attributes = 'Required' | 'Optional' | 'Required When'; + +const containerStyle = css` + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; + gap: 10px; +`; + +export const renderAttributesColumn = (tableRow: Row) => { + const schemaRestrictions: SchemaRestrictions = tableRow.original.restrictions; + const schemaField: SchemaField = tableRow.original; + const { unique } = schemaField; + return ( +
      + {schemaRestrictions && 'if' in schemaRestrictions ? + alert('Hello World')}>Required When + : {schemaRestrictions && 'required' in schemaRestrictions ? 'Required' : 'Optional'}} + {unique && Unique} +
      + ); }; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/CustomColumns.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/CustomColumns.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx deleted file mode 100644 index 3d9b27b7..00000000 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/OpenModalPill.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2024 The Ontario Institute for Cancer Research. All rights reserved - * - * This program and the accompanying materials are made available under the terms of - * the GNU Affero General Public License v3.0. You should have received a copy of the - * GNU Affero General Public License along with this program. - * If not, see . - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** @jsxImportSource @emotion/react */ - -import React from 'react'; - -import Pill from '../../../common/Pill'; -import Eye from '../../../theme/icons/Eye'; - -export type OpenModalButtonProps = { - title: string; -}; - -const OpenModalPill = ({ title }: OpenModalButtonProps) => { - return ( - } - onClick={() => { - alert('Modal has been opened\n\n\n Hello World'); - }} - > - {title} - - ); -}; -export default OpenModalPill; diff --git a/packages/ui/src/viewer-table/DataTable/TableHeader.tsx b/packages/ui/src/viewer-table/DataTable/TableHeader.tsx index 516ba0d2..53fec68e 100644 --- a/packages/ui/src/viewer-table/DataTable/TableHeader.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableHeader.tsx @@ -27,9 +27,6 @@ import { flexRender, HeaderGroup } from '@tanstack/react-table'; import { Theme } from '../../theme'; import { useThemeContext } from '../../theme/ThemeContext'; -import { Theme } from '../../theme'; -import { useThemeContext } from '../../theme/ThemeContext'; - const thStyle = (theme: Theme, index: number) => css` ${theme.typography.subtitleSecondary}; background: #e5edf3; @@ -52,18 +49,14 @@ const thStyle = (theme: Theme, index: number) => css` border: 1px solid #DCDDE1; `; -export type TableHeaderProps = { export type TableHeaderProps = { headerGroup: HeaderGroup; }; const TableHeader = ({ headerGroup }: TableHeaderProps) => { - const theme = useThemeContext(); const theme = useThemeContext(); return (
      - {headerGroup.headers.map((header, index) => ( - - {row.getVisibleCells().map((cell, cellIndex) => { {row.getVisibleCells().map((cell, cellIndex) => { return ( diff --git a/packages/ui/stories/fixtures/TorontoMapleLeafs.json b/packages/ui/stories/fixtures/TorontoMapleLeafs.json index b56a78d1..e9a2be62 100644 --- a/packages/ui/stories/fixtures/TorontoMapleLeafs.json +++ b/packages/ui/stories/fixtures/TorontoMapleLeafs.json @@ -7,7 +7,7 @@ }, "references": { "maple_leafs_roster": { - "description": "The official roster for the Toronto Maple Leafs + NHL all-stars + NBA all-stars + Soccer World 11.", + "description": "The official roster for the Toronto Maple Leafs.", "values": [ "Matthews", "Nylander", @@ -32,93 +32,7 @@ "Hakanpaa", "Woll", "Stolarz", - "Murray", - "McDavid", - "MacKinnon", - "Pastrnak", - "Kucherov", - "Panarin", - "Barkov", - "Kopitar", - "Point", - "Marner", - "Stamkos", - "Ovechkin", - "Crosby", - "Malkin", - "Letang", - "Karlsson", - "Fox", - "Josi", - "Hedman", - "Vasilevskiy", - "Shesterkin", - "Hellebuyck", - "Demko", - "LeBron James", - "Stephen Curry", - "Kevin Durant", - "Giannis Antetokounmpo", - "Luka Doncic", - "Joel Embiid", - "Nikola Jokic", - "Shai Gilgeous-Alexander", - "Tyrese Haliburton", - "Damian Lillard", - "Donovan Mitchell", - "Bam Adebayo", - "Jaylen Brown", - "Jayson Tatum", - "Anthony Edwards", - "Kawhi Leonard", - "Paul George", - "Devin Booker", - "Anthony Davis", - "Zion Williamson", - "Ja Morant", - "Trae Young", - "DeMar DeRozan", - "Jimmy Butler", - "Julius Randle", - "Pascal Siakam", - "Scottie Barnes", - "RJ Barrett", - "OG Anunoby", - "Lionel Messi", - "Cristiano Ronaldo", - "Erling Haaland", - "Kylian Mbappe", - "Kevin De Bruyne", - "Jude Bellingham", - "Vinícius Jr", - "Mohamed Salah", - "Harry Kane", - "Victor Osimhen", - "Robert Lewandowski", - "Thibaut Courtois", - "Alisson", - "Ederson", - "Kyle Walker", - "Ruben Dias", - "Virgil van Dijk", - "Antonio Rüdiger", - "Frenkie de Jong", - "Rodri", - "Declan Rice", - "Bernardo Silva", - "Phil Foden", - "Bukayo Saka", - "Pedri", - "Gavi", - "Federico Valverde", - "Luka Modric", - "Toni Kroos", - "Casemiro", - "Bruno Fernandes", - "Rafael Leão", - "Khvicha Kvaratskhelia", - "Lautaro Martínez", - "Julián Álvarez" + "Murray" ] }, "gender_options": { @@ -307,14 +221,10 @@ "delimiter": ",", "description": "Up to 4 referee performance ratings.", "restrictions": { - "codeList": [1, 2, 3, 4, 5], - "count": { "max": 4 } + "uniqueKey": ["game_id", "season_year", "game_date", "home_team_score", "away_team_score"] } } - ], - "restrictions": { - "uniqueKeys": [["game_id"], ["season_year", "game_date", "home_team_score", "away_team_score"]] - } + ] }, { "name": "player_profiles", From a94efd2bfa0fff91ea8f901b9cff8eb5bbebb3d7 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 14 Jul 2025 11:42:26 -0400 Subject: [PATCH 217/217] accessiblity fixes --- packages/ui/src/common/ReadMoreText.tsx | 2 +- packages/ui/src/theme/styles/typography.ts | 9 +++++++++ .../DataTable/SchemaTable/Columns/Fields.tsx | 15 +++++---------- .../ui/src/viewer-table/DataTable/TableRow.tsx | 2 +- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/common/ReadMoreText.tsx b/packages/ui/src/common/ReadMoreText.tsx index cae84002..16a3131e 100644 --- a/packages/ui/src/common/ReadMoreText.tsx +++ b/packages/ui/src/common/ReadMoreText.tsx @@ -37,7 +37,7 @@ export type ReadMoreTextProps = { }; const defaultWrapperStyle = (theme: Theme) => css` - ${theme.typography.data}; + ${theme.typography.paragraphSmall}; color: ${theme.colors.black}; padding: 4px 8px; word-wrap: break-word; diff --git a/packages/ui/src/theme/styles/typography.ts b/packages/ui/src/theme/styles/typography.ts index af1809cc..63f8332f 100644 --- a/packages/ui/src/theme/styles/typography.ts +++ b/packages/ui/src/theme/styles/typography.ts @@ -68,6 +68,15 @@ const subtitleSecondary = css` `; const introText = css` + ${baseFontSecondary} + font-size: 18px; + font-weight: 400; + font-style: normal; + font-stretch: normal; + line-height: 100%; + letter-spacing: 0%; +`; +const introTextBold = css` ${baseFontSecondary} font-size: 18px; font-weight: 700; diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx index 3276327c..73d3e4d5 100644 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx +++ b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/Fields.tsx @@ -20,12 +20,7 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import { - DictionaryMeta, - type ForeignKeyRestriction, - SchemaField, - SchemaRestrictions, -} from '@overture-stack/lectern-dictionary'; +import { DictionaryMeta, SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; import { Row } from '@tanstack/react-table'; import { useMemo, useState } from 'react'; @@ -36,14 +31,13 @@ import { useThemeContext } from '../../../../theme/ThemeContext'; const fieldContainerStyle = css` display: flex; flex-direction: column; - gap: 3px; + gap: 10px; `; const fieldNameStyle = (theme: Theme) => css` - ${theme.typography.label} + ${theme.typography.introText} display: flex; align-items: center; - gap: 2px; `; export type FieldExamplesProps = { @@ -137,7 +131,8 @@ export const FieldsColumn = ({ fieldRow }: FieldColumnProps) => { return (
      - {fieldName} {Array.isArray(uniqueKey) && uniqueKey.length === 1 && Primary Key} + {fieldName}{' '} + {Array.isArray(uniqueKey) && uniqueKey.length === 1 && Primary Key} {Array.isArray(uniqueKey) && Compound Key} {foreignKey && Foreign Key} diff --git a/packages/ui/src/viewer-table/DataTable/TableRow.tsx b/packages/ui/src/viewer-table/DataTable/TableRow.tsx index 99398754..1f97e5e0 100644 --- a/packages/ui/src/viewer-table/DataTable/TableRow.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableRow.tsx @@ -34,7 +34,7 @@ const rowStyle = (index: number) => css` `; const tdStyle = (theme: Theme, cellIndex: number, rowIndex: number) => css` - ${theme.typography.data} + ${theme.typography.paragraphSmall} padding: 12px; max-width: 30vw; white-space: pre-wrap;
      {headerGroup.headers.map((header, index) => ( {flexRender(header.column.columnDef.header, header.getContext())} diff --git a/packages/ui/src/viewer-table/DataTable/TableRow.tsx b/packages/ui/src/viewer-table/DataTable/TableRow.tsx index 71bfe243..5a13b5e8 100644 --- a/packages/ui/src/viewer-table/DataTable/TableRow.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableRow.tsx @@ -22,10 +22,7 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import { Row, flexRender } from '@tanstack/react-table'; -import ReadMoreText from '../../common/ReadMoreText'; -import { Theme } from '../../theme'; import { Row, flexRender } from '@tanstack/react-table'; import ReadMoreText from '../../common/ReadMoreText'; @@ -36,8 +33,6 @@ const rowStyle = (index: number) => css` background-color: ${index % 2 === 0 ? '' : '#F5F7F8'}; `; -const tdStyle = (theme: Theme, cellIndex: number, rowIndex: number) => css` - ${theme.typography.data} const tdStyle = (theme: Theme, cellIndex: number, rowIndex: number) => css` ${theme.typography.data} padding: 12px; @@ -54,7 +49,6 @@ const tdStyle = (theme: Theme, cellIndex: number, rowIndex: number) => css` border: 1px solid #DCDDE1; `; -export type TableRowProps = { export type TableRowProps = { row: Row; index: number; @@ -64,7 +58,6 @@ const TableRow = ({ row, index }: TableRowProps) => { const theme = useThemeContext(); return (
      diff --git a/packages/ui/stories/viewer-table/SchemaTable.stories.tsx b/packages/ui/stories/viewer-table/SchemaTable.stories.tsx index 7c735208..319dd3e2 100644 --- a/packages/ui/stories/viewer-table/SchemaTable.stories.tsx +++ b/packages/ui/stories/viewer-table/SchemaTable.stories.tsx @@ -1,17 +1,12 @@ /** @jsxImportSource @emotion/react */ -import { Dictionary, replaceReferences, Schema } from '@overture-stack/lectern-dictionary'; +import { Dictionary, replaceReferences } from '@overture-stack/lectern-dictionary'; import type { Meta, StoryObj } from '@storybook/react'; import SchemaTable from '../../src/viewer-table/DataTable/SchemaTable/SchemaTable'; import Advanced from '../fixtures/TorontoMapleLeafs.json'; import PCGL from '../fixtures/pcgl.json'; -import SchemaTable from '../../src/viewer-table/DataTable/SchemaTable/SchemaTable'; -import Advanced from '../fixtures/TorontoMapleLeafs.json'; -import PCGL from '../fixtures/pcgl.json'; import themeDecorator from '../themeDecorator'; -const torontoMapleLeafsDictionary: Dictionary = replaceReferences(Advanced as Dictionary); -const pcglDictionary: Dictionary = replaceReferences(PCGL as Dictionary); const torontoMapleLeafsDictionary: Dictionary = replaceReferences(Advanced as Dictionary); const pcglDictionary: Dictionary = replaceReferences(PCGL as Dictionary); @@ -109,91 +104,6 @@ export const PCGLExperiment: Story = { args: { schema: pcglDictionary.schemas[15] }, }; -export const PCGLReadGroup: Story = { - args: { schema: pcglDictionary.schemas[16] }, - args: { schema: torontoMapleLeafsDictionary.schemas[0] }, -}; - -export const PlayerProfiles: Story = { - args: { schema: torontoMapleLeafsDictionary.schemas[1] }, -}; - -export const GameEvents: Story = { - args: { schema: torontoMapleLeafsDictionary.schemas[2] }, -}; - -export const AdvancedStats: Story = { - args: { schema: torontoMapleLeafsDictionary.schemas[3] }, -}; - -export const EdgeCases: Story = { - args: { schema: torontoMapleLeafsDictionary.schemas[4] }, -}; - -export const PCGLParticipant: Story = { - args: { schema: pcglDictionary.schemas[0] }, -}; - -export const PCGLSociodemographic: Story = { - args: { schema: pcglDictionary.schemas[1] }, -}; - -export const PCGLDemographic: Story = { - args: { schema: pcglDictionary.schemas[2] }, -}; - -export const PCGLDiagnosis: Story = { - args: { schema: pcglDictionary.schemas[3] }, -}; - -export const PCGLTreatment: Story = { - args: { schema: pcglDictionary.schemas[4] }, -}; - -export const PCGLFollowUp: Story = { - args: { schema: pcglDictionary.schemas[5] }, -}; - -export const PCGLProcedure: Story = { - args: { schema: pcglDictionary.schemas[6] }, -}; - -export const PCGLMedication: Story = { - args: { schema: pcglDictionary.schemas[7] }, -}; - -export const PCGLRadiation: Story = { - args: { schema: pcglDictionary.schemas[8] }, -}; - -export const PCGLMeasurement: Story = { - args: { schema: pcglDictionary.schemas[9] }, -}; - -export const PCGLPhenotype: Story = { - args: { schema: pcglDictionary.schemas[10] }, -}; - -export const PCGLComorbidity: Story = { - args: { schema: pcglDictionary.schemas[11] }, -}; - -export const PCGLExposure: Story = { - args: { schema: pcglDictionary.schemas[12] }, -}; - -export const PCGLSpecimen: Story = { - args: { schema: pcglDictionary.schemas[13] }, -}; - -export const PCGLSample: Story = { - args: { schema: pcglDictionary.schemas[14] }, -}; - -export const PCGLExperiment: Story = { - args: { schema: pcglDictionary.schemas[15] }, -}; - export const PCGLReadGroup: Story = { args: { schema: pcglDictionary.schemas[16] }, }; From 858b3be8061f9eb9ca650ca5174b45ffe7987685 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 14 Jul 2025 11:18:46 -0400 Subject: [PATCH 214/217] port over the pill from main --- packages/ui/src/common/Pill.tsx | 143 ++++++++++++++++---------------- 1 file changed, 73 insertions(+), 70 deletions(-) diff --git a/packages/ui/src/common/Pill.tsx b/packages/ui/src/common/Pill.tsx index 29e729b7..acad0dd5 100644 --- a/packages/ui/src/common/Pill.tsx +++ b/packages/ui/src/common/Pill.tsx @@ -1,19 +1,38 @@ +/* + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /** @jsxImportSource @emotion/react */ + import { css } from '@emotion/react'; -import React, { CSSProperties, MouseEvent, ReactNode } from 'react'; +import { CSSProperties, ReactNode } from 'react'; + import { Theme } from '../theme'; import { useThemeContext } from '../theme/ThemeContext'; -export type PillVariant = 'default' | 'button'; -export type PillSize = 'extra-small' | 'small' | 'medium' | 'large'; +export type PillVariant = 'default'; +export type PillSize = 'extra-small' | 'small' | 'medium' | 'large'; export interface PillProps { children: ReactNode; variant?: PillVariant; size?: PillSize; icon?: ReactNode; - onClick?: (event: MouseEvent) => void; style?: CSSProperties; - fullWidth?: boolean; } const getVariantStyles = (variant: PillVariant, theme: Theme) => { @@ -24,21 +43,17 @@ const getVariantStyles = (variant: PillVariant, theme: Theme) => { border: 'none', hoverBackground: '#D8DADD', }, - button: { - background: '#FFFF', - color: theme.colors.black, - border: `1px solid ${theme.colors.black}`, - hoverBackground: '#F5F5F5', - }, }; return VARIANT_STYLES[variant]; }; -const getSizeStyles = (size: PillSize, theme: Theme) => { +const getSizeStyles = (size: PillSize) => { const sizeStyles = { 'extra-small': { padding: '1px 6px', - font: { ...theme.typography.captionBold }, + fontSize: '8px', + fontWeight: '700', + lineHeight: '12px', borderRadius: '3px', gap: '3px', width: '65px', @@ -46,7 +61,9 @@ const getSizeStyles = (size: PillSize, theme: Theme) => { }, small: { padding: '2px 8px', - font: { ...theme.typography.captionBold }, + fontSize: '10px', + fontWeight: '700', + lineHeight: '14px', borderRadius: '4px', gap: '4px', width: '80px', @@ -54,7 +71,9 @@ const getSizeStyles = (size: PillSize, theme: Theme) => { }, medium: { padding: '4px 12px', - font: { ...theme.typography.paragraphSmallBold }, + fontSize: '16px', + fontWeight: '700', + lineHeight: '16px', borderRadius: '5px', gap: '6px', width: '95px', @@ -62,7 +81,9 @@ const getSizeStyles = (size: PillSize, theme: Theme) => { }, large: { padding: '6px 16px', - font: { ...theme.typography.dataBold }, + fontSize: '14px', + fontWeight: '700', + lineHeight: '20px', borderRadius: '6px', gap: '8px', width: '150px', @@ -73,64 +94,46 @@ const getSizeStyles = (size: PillSize, theme: Theme) => { return sizeStyles[size]; }; -const Pill = ({ children, variant = 'default', size = 'medium', icon, onClick, style, fullWidth }: PillProps) => { +const pillStyles = (sizeStyles, variantStyles, icon) => css` + display: inline-flex; + align-items: center; + justify-content: center; + gap: ${sizeStyles.gap}; + padding: ${sizeStyles.padding}; + font-size: ${sizeStyles.fontSize}; + line-height: ${sizeStyles.lineHeight}; + font-weight: ${sizeStyles.fontWeight}; + border-radius: ${sizeStyles.borderRadius}; + background-color: ${variantStyles.background}; + color: ${variantStyles.color}; + border: ${variantStyles.border}; + transition: all 0.2s ease-in-out; + user-select: none; + width: ${sizeStyles.width}; + max-width: ${sizeStyles.maxWidth}; + text-align: center; + word-wrap: break-word; + overflow-wrap: break-word; + hyphens: auto; + ${icon ? + css` + .pill-icon { + display: flex; + align-items: center; + font-size: ${parseInt(sizeStyles.fontSize) - 2}px; + flex-shrink: 0; + } + ` + : ''}; +`; + +const Pill = ({ children, variant = 'default', size = 'medium', icon, style }: PillProps) => { const theme = useThemeContext(); const variantStyles = getVariantStyles(variant, theme); - const sizeStyles = getSizeStyles(size, theme); - - const pillStyles = css` - ${sizeStyles.font} - display: inline-flex; - align-items: center; - justify-content: center; - gap: ${sizeStyles.gap}; - padding: ${sizeStyles.padding}; - border-radius: ${sizeStyles.borderRadius}; - background-color: ${variantStyles.background}; - color: ${variantStyles.color}; - border: ${variantStyles.border}; - transition: all 0.2s ease-in-out; - user-select: none; - width: ${fullWidth ? 'fit-content' : sizeStyles.width}; - max-width: ${sizeStyles.maxWidth}; - text-align: center; - word-wrap: break-word; - overflow-wrap: break-word; - hyphens: auto; - ${onClick ? - css` - cursor: pointer; - &:hover { - background-color: ${variantStyles.hoverBackground}; - } - ` - : ''} - ${icon ? - css` - .pill-icon { - display: flex; - align-items: center; - flex-shrink: 0; - } - ` - : ''}; - `; - - const handleClick = (event: MouseEvent) => { - if (onClick) { - event.stopPropagation; - onClick(event); - } - }; + const sizeStyles = getSizeStyles(size); return ( -
      +
      {icon && {icon}} {children}
      From 9ff7aaaed4ed8e5b69c1fb98fbe2b03c0ed244b5 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 14 Jul 2025 11:24:28 -0400 Subject: [PATCH 215/217] remove allowed values column --- .../SchemaTable/Columns/AllowedValues.tsx | 254 ------------------ 1 file changed, 254 deletions(-) delete mode 100644 packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx diff --git a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx b/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx deleted file mode 100644 index 786482eb..00000000 --- a/packages/ui/src/viewer-table/DataTable/SchemaTable/Columns/AllowedValues.tsx +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved - * - * This program and the accompanying materials are made available under the terms of - * the GNU Affero General Public License v3.0. You should have received a copy of the - * GNU Affero General Public License along with this program. - * If not, see . - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** @jsxImportSource @emotion/react */ - -import { css } from '@emotion/react'; -import { MatchRuleCount, RestrictionRange, SchemaField, SchemaRestrictions } from '@overture-stack/lectern-dictionary'; -import { CellContext } from '@tanstack/react-table'; - -import Pill from '../../../../common/Pill'; -import { Theme } from '../../../../theme'; -import { useThemeContext } from '../../../../theme/ThemeContext'; - -export type restrictionItem = { - prefix: string | null; - content: string; -}; -export type AllowedValuesColumnProps = { - restrictions: CellContext; -}; - -const handleRange = (range: RestrictionRange, restrictionItems: restrictionItem[]): void => { - if (range.min !== undefined && range.max !== undefined) { - restrictionItems.push({ prefix: 'Min: ', content: `${range.min}` }); - restrictionItems.push({ prefix: 'Max: ', content: `${range.max}` }); - } else if (range.min !== undefined) { - restrictionItems.push({ prefix: 'Min: ', content: `${range.min}` }); - } else if (range.max !== undefined) { - restrictionItems.push({ prefix: 'Max: ', content: `${range.max}` }); - } else if (range.exclusiveMin !== undefined) { - restrictionItems.push({ prefix: 'Greater than ', content: `${range.exclusiveMin}` }); - } else if (range.exclusiveMax !== undefined) { - restrictionItems.push({ prefix: 'Less than ', content: `${range.exclusiveMax}` }); - } -}; - -const handleCodeListsWithCountRestrictions = ( - codeList: string | string[] | number[], - count: MatchRuleCount, - restrictionItems: restrictionItem[], - isArray: boolean, - delimiter: string = ',', -): void => { - const codeListDisplay = Array.isArray(codeList) ? codeList.join(',\n') : codeList; - const delimiterText = isArray ? `, delimited by "${delimiter}"` : ''; - - if (typeof count === 'number') { - restrictionItems.push({ - prefix: `Exactly ${count}${delimiterText} from:\n`, - content: `${codeListDisplay}`, - }); - } else { - if (count.min !== undefined && count.max !== undefined) { - restrictionItems.push({ - prefix: `Select ${count.min} to ${count.max}${delimiterText} from:\n`, - content: `${codeListDisplay}`, - }); - } else if (count.min !== undefined) { - restrictionItems.push({ - prefix: `At least ${count.min}${delimiterText} from:\n`, - content: `${codeListDisplay}`, - }); - } else if (count.max !== undefined) { - restrictionItems.push({ - prefix: `Up to ${count.max}${delimiterText} from:\n`, - content: `${codeListDisplay}`, - }); - } else if (count.exclusiveMin !== undefined) { - restrictionItems.push({ - prefix: `More than ${count.exclusiveMin}${delimiterText} from:\n`, - content: `${codeListDisplay}`, - }); - } else if (count.exclusiveMax !== undefined) { - restrictionItems.push({ - prefix: `Fewer than ${count.exclusiveMax}${delimiterText} from:\n`, - content: `${codeListDisplay}`, - }); - } - } -}; - -export const computeAllowedValuesColumn = ( - restrictions: CellContext, -): restrictionItem[] => { - const schemaField: SchemaField = restrictions.row.original; - const restrictionsValue: SchemaRestrictions = restrictions.getValue(); - const restrictionItems: restrictionItem[] = []; - - if (!restrictionsValue || Object.keys(restrictionsValue).length === 0) { - restrictionItems.push({ prefix: null, content: 'None' }); - return restrictionItems; - } - restrictionItems.push(...handleDependsOn(restrictionsValue)); - - if ('regex' in restrictionsValue && restrictionsValue.regex) { - const regexValue = - Array.isArray(restrictionsValue.regex) ? restrictionsValue.regex.join(', ') : restrictionsValue.regex; - restrictionItems.push({ - prefix: 'Must match pattern: ', - content: `${regexValue}\nSee field description for examples.`, - }); - } - - if ('codeList' in restrictionsValue && restrictionsValue.codeList && !('count' in restrictionsValue)) { - const codeListDisplay = - Array.isArray(restrictionsValue.codeList) ? - restrictionsValue.codeList.join(',\n') - : `"${restrictionsValue.codeList}"`; - restrictionItems.push({ prefix: 'One of: \n', content: `${codeListDisplay}` }); - } - - if ('range' in restrictionsValue && restrictionsValue.range) { - handleRange(restrictionsValue.range, restrictionItems); - } - - if ( - 'codeList' in restrictionsValue && - restrictionsValue.codeList && - 'count' in restrictionsValue && - restrictionsValue.count - ) { - handleCodeListsWithCountRestrictions( - restrictionsValue.codeList, - restrictionsValue.count, - restrictionItems, - schemaField.isArray || false, - schemaField.delimiter ?? ',', - ); - } - - if (schemaField.unique) { - restrictionItems.push({ prefix: null, content: 'Must be unique' }); - } - - if (restrictionItems.length === 0) { - restrictionItems.push({ prefix: null, content: 'None' }); - } - - return restrictionItems; -}; - -const handleDependsOn = (schemaRestrictions: SchemaRestrictions): restrictionItem[] => { - const restrictionItems: restrictionItem[] = []; - - if (schemaRestrictions && 'compare' in schemaRestrictions && schemaRestrictions.compare) { - } - if ( - schemaRestrictions && - 'if' in schemaRestrictions && - schemaRestrictions.if && - 'conditions' in schemaRestrictions.if && - schemaRestrictions.if.conditions && - Array.isArray(schemaRestrictions.if.conditions) - ) { - const allFields: string[] = []; - schemaRestrictions.if.conditions.forEach((condition) => { - if (condition.fields !== undefined) { - if (Array.isArray(condition.fields)) { - allFields.push(...condition.fields); - } else if (typeof condition.fields === 'string') { - allFields.push(condition.fields); - } - } - }); - // Since there can be multiple fields with multiple conditions, there is a possibility that there are duplicates - const uniqueFields = [...new Set(allFields)]; - if (uniqueFields.length > 0) { - restrictionItems.push({ - prefix: 'Depends on:\n', - content: uniqueFields.join(', '), - }); - } - } - - return restrictionItems; -}; -export const renderAllowedValuesColumn = (restrictions: CellContext) => { - const restrictionItems = computeAllowedValuesColumn(restrictions); - const restrictionsValue: SchemaRestrictions = restrictions.getValue(); - - const theme = useThemeContext(); - - const linkStyle = (theme: Theme) => css` - ${theme.typography.dataBold}; - color: ${theme.colors.black}; - cursor: pointer; - display: inline-flex; - align-items: center; - background: none; - border: none; - padding: 0; - margin-top: 4px; - text-decoration: underline; - &:hover { - text-decoration: underline; - } - `; - - //TODO: implement the modal - const handleViewDetails = () => { - alert('Modal has been opened\n\n\n Hello World'); - }; - - //TODO: Fix the theming issues here with the - const renderRestrictionItem = (item: restrictionItem) => { - const { prefix, content } = item; - if (prefix === 'Depends on:\n') { - return ( -
      - {prefix && {prefix}} - - {content} - -
      - ); - } - - return ( -
      - {prefix && {prefix}} - {content} -
      - ); - }; - - const hasConditionalRestrictions = restrictionsValue && 'if' in restrictionsValue && restrictionsValue.if; - - return ( -
      - {restrictionItems.map(renderRestrictionItem)} - {hasConditionalRestrictions && ( -
      - View details -
      - )} -
      - ); -}; From 015f2cc040e88f740c373dd70e2c244c2b9af74e Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Mon, 14 Jul 2025 11:30:03 -0400 Subject: [PATCH 216/217] reduce diff... --- packages/ui/src/common/ReadMoreText.tsx | 4 +- .../src/viewer-table/DataTable/TableRow.tsx | 9 +- .../stories/fixtures/TorontoMapleLeafs.json | 98 +------------------ 3 files changed, 7 insertions(+), 104 deletions(-) diff --git a/packages/ui/src/common/ReadMoreText.tsx b/packages/ui/src/common/ReadMoreText.tsx index d783adda..cae84002 100644 --- a/packages/ui/src/common/ReadMoreText.tsx +++ b/packages/ui/src/common/ReadMoreText.tsx @@ -37,8 +37,8 @@ export type ReadMoreTextProps = { }; const defaultWrapperStyle = (theme: Theme) => css` - ${theme.typography.caption}; - color: ${theme.colors.grey_5}; + ${theme.typography.data}; + color: ${theme.colors.black}; padding: 4px 8px; word-wrap: break-word; overflow-wrap: break-word; diff --git a/packages/ui/src/viewer-table/DataTable/TableRow.tsx b/packages/ui/src/viewer-table/DataTable/TableRow.tsx index 5a13b5e8..99398754 100644 --- a/packages/ui/src/viewer-table/DataTable/TableRow.tsx +++ b/packages/ui/src/viewer-table/DataTable/TableRow.tsx @@ -61,14 +61,7 @@ const TableRow = ({ row, index }: TableRowProps) => { {row.getVisibleCells().map((cell, cellIndex) => { return (
      - css` - ${theme.typography.data} - `} - maxLines={4} - > + {flexRender(cell.column.columnDef.cell, cell.getContext())}