Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c0b3dc4
initialize the table of contents component
SaqAsh Jun 5, 2025
8d9f78d
handle logic for the actual scrolling into view for scheams
SaqAsh Jun 5, 2025
ee3a2c9
clean up the table of contents, add the relevant functions
SaqAsh Jun 5, 2025
9f2bd1d
cleanup css styling such that it is uniform and has no unessesary att…
SaqAsh Jun 5, 2025
5414669
fix subpar styling for dropdown
SaqAsh Jun 5, 2025
c10619c
code cleanup
SaqAsh Jun 5, 2025
ffa06e0
add an advanced.json to better facilitate visualation of table of con…
SaqAsh Jun 5, 2025
ce91494
fix naming issue with list
SaqAsh Jun 5, 2025
edc0902
moving dropdown into the interaction panel
SaqAsh Jun 6, 2025
41baefa
use theme context instead of individually importing the icon
SaqAsh Jun 6, 2025
0c97a59
finalizing the styling according to the figma
SaqAsh Jun 6, 2025
50a8e5c
Merge branch 'main' into feature/Table-of-contents
SaqAsh Jun 10, 2025
fe1dd0e
move advanced.json and fix import
SaqAsh Jun 10, 2025
9f29bf9
we want user inputted styles to be in highest priority
SaqAsh Jun 10, 2025
35556bb
export props
SaqAsh Jun 10, 2025
902eccc
change from console.log to alert
SaqAsh Jun 10, 2025
7db7058
implement pr review changes
SaqAsh Jun 12, 2025
34445e9
accidentally removed export
SaqAsh Jun 12, 2025
e9a068e
fix naming inside of stories to make it consistent with props
SaqAsh Jun 12, 2025
2e8400a
Merge branch 'main' into feature/Table-of-contents
SaqAsh Jun 16, 2025
8ae8b69
remove set timeout used for debugging purposes
SaqAsh Jun 24, 2025
92fce9e
remove classname
SaqAsh Jun 24, 2025
288782b
address final pr review
SaqAsh Jun 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 19 additions & 32 deletions packages/ui/src/common/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,19 @@ 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};
color: ${theme.colors.accent_dark};
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;

&:hover {
background-color: ${theme.colors.grey_1};
}
transition: all 0.2s ease;
`;

const parentStyle = css`
Expand All @@ -60,29 +55,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?.button};
color: ${theme.colors.accent_dark};
`;

const dropdownMenuStyle = (theme: Theme) => css`
${theme.typography?.button};
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;
padding-top: 5px;
border-radius: 9px;
padding-bottom: 5px;
`;

type MenuItem = {
Expand All @@ -103,8 +91,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)) {
Expand Down Expand Up @@ -138,10 +124,11 @@ const Dropdown = ({ menuItems = [], title, leftIcon }: DropDownProps) => {
<div css={dropdownButtonStyle(theme)} onClick={handleToggle}>
{leftIcon}
<span css={dropDownTitleStyle(theme)}>{title}</span>
<ChevronDown fill={theme.colors?.black} width={18} height={18} style={chevronStyle(open)} />
<ChevronDown fill={theme.colors?.accent_dark} width={18} height={18} style={chevronStyle(open)} />
</div>

{open && <ul css={dropdownMenuStyle(theme)}>{renderMenuItems()}</ul>}
{/* {open && <>{renderMenuItems()}</>} */}
{open && <div css={dropdownMenuStyle(theme)}>{renderMenuItems()}</div>}
</div>
</div>
);
Expand Down
17 changes: 7 additions & 10 deletions packages/ui/src/common/Dropdown/DropdownItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,18 @@ type DropDownItemProps = {

const styledListItemStyle = (theme: Theme, customStyles?: any) => css`
display: flex;
max-height: 42px;
min-height: 100%;
padding-bottom: 5px;
height: 100%;
align-items: center;
padding: 8px;
border-radius: 9px;
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};
background-color: ${theme.colors.grey_1};
}

${customStyles?.base}
`;

Expand All @@ -60,9 +57,9 @@ const DropDownItem = ({ children, action, customStyles }: DropDownItemProps) =>
const content = <div css={styledListItemStyle(theme, customStyles)}>{children}</div>;
if (typeof action === 'function') {
return (
<a onClick={action} css={styledListItemStyle(theme, customStyles)}>
<div onClick={action} css={styledListItemStyle(theme, customStyles)}>
{children}
</a>
</div>
);
}

Expand Down
54 changes: 54 additions & 0 deletions packages/ui/src/theme/icons/List.tsx
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
* 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 List = ({ fill, width, height, style }: IconProps) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width || 24}
height={height || 24}
viewBox="0 0 24 24"
fill={fill || 'none'}
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
css={css`
${style}
`}
>
<path d="M3 12h.01" />
<path d="M3 18h.01" />
<path d="M3 6h.01" />
<path d="M8 12h13" />
<path d="M8 18h13" />
<path d="M8 6h13" />
</svg>
);
};

export default List;
2 changes: 2 additions & 0 deletions packages/ui/src/theme/styles/icons.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import ChevronDown from '../icons/ChevronDown';
import List from '../icons/List';
export default {
ChevronDown,
List,
};
6 changes: 3 additions & 3 deletions packages/ui/src/theme/styles/typography.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
`;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
*
* 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 <http://www.gnu.org/licenses/>.
*
* 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 React from 'react';
import Dropdown from '../../common/Dropdown/Dropdown';
import { useThemeContext } from '../../theme/ThemeContext';

export type TableOfContentsDropdownProps = {
schemas: Schema[];
onSelect: (schemaIndex: number) => void;
};

const TableOfContentsDropdown = ({ schemas, onSelect }: TableOfContentsDropdownProps) => {
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);
},
}));

Comment on lines +35 to +47
Copy link
Contributor

@ciaranschutte ciaranschutte Jun 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re slack convo, yes this is a side effect, but it works ok here. TableOfContentsDropdown is specialized enough that having scrolling to a hash behaviour built in is fine.

return schemas.length > 0 ?
<Dropdown leftIcon={<List />} title="Table of Contents" menuItems={menuItemsFromSchemas} />
: null;
};

export default TableOfContentsDropdown;
35 changes: 35 additions & 0 deletions packages/ui/stories/fixtures/advanced.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "Advanced",
"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" }
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/** @jsxImportSource @emotion/react */

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 themeDecorator from '../../themeDecorator';

const meta = {
component: TableOfContentsDropdown,
title: 'Viewer - Table/Interaction - Panel/Table of Contents Dropdown',
tags: ['autodocs'],
decorators: [themeDecorator()],
} satisfies Meta<typeof TableOfContentsDropdown>;

export default meta;
type Story = StoryObj<typeof meta>;

// Using the primitiveJson as a mock schema for demonstration purposes
const schemas: Schema[] = Dictionary.schemas as Schema[];

// Mock functions for the story just to demonstrate interaction

const onSelect = (schemaIndex: number) => {
alert(`Accordion has been toggled for the following schema: ${schemas[schemaIndex].name}`);
};

export const Default: Story = {
args: {
schemas: schemas,
onSelect,
},
};

export const Empty: Story = {
args: { schemas: [], onSelect: () => {} },
};