Skip to content

Commit

Permalink
vertical tab widget layout
Browse files Browse the repository at this point in the history
  • Loading branch information
magland committed Apr 26, 2024
1 parent ad751f7 commit 4d9496f
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 12 deletions.
2 changes: 1 addition & 1 deletion gui/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@fi-sci/figurl-sortingview",
"private": false,
"version": "12.0.4",
"version": "12.0.6",
"scripts": {
"dev": "vite --port 3000",
"build": "tsc && vite build && node devel/generate_file_manifest.js",
Expand Down
99 changes: 97 additions & 2 deletions gui/src/libraries/core-views/component-tab-widget/TabWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,46 @@ type Props = {
tabs: {
label: string
}[]
tabBarLayout?: 'horizontal' | 'vertical'
width: number
height: number
}

// needs to correspond to css (not best system) - see mountainview.css
const tabBarHeight = 30 + 5

const TabWidget: FunctionComponent<PropsWithChildren<Props>> = ({children, tabs, width, height}) => {
const TabWidget: FunctionComponent<PropsWithChildren<Props>> = ({children, tabs, tabBarLayout, width, height}) => {
const tbl = tabBarLayout || 'horizontal'
if (tbl === 'horizontal') {
return (
<TabWidgetHorizontal
tabs={tabs}
tabBarLayout={tabBarLayout}
width={width}
height={height}
>
{children}
</TabWidgetHorizontal>
)
}
else if (tbl === 'vertical') {
return (
<TabWidgetVertical
tabs={tabs}
tabBarLayout={tabBarLayout}
width={width}
height={height}
>
{children}
</TabWidgetVertical>
)
}
else {
return <div>TabWidget: unknown tabBarLayout {tbl}</div>
}
}

const TabWidgetHorizontal: FunctionComponent<PropsWithChildren<Props>> = ({children, tabs, tabBarLayout, width, height}) => {
const [currentTabIndex, setCurrentTabIndex] = useState<number | undefined>(undefined)
const children2 = children as React.ReactElement[]
if ((children2 || []).length !== tabs.length) {
Expand All @@ -32,7 +64,7 @@ const TabWidget: FunctionComponent<PropsWithChildren<Props>> = ({children, tabs,
tabs={tabs}
currentTabIndex={currentTabIndex}
onCurrentTabIndexChanged={setCurrentTabIndex}
onTabClosed={() => {}}
onTabClosed={undefined}
/>
</div>
{
Expand All @@ -49,4 +81,67 @@ const TabWidget: FunctionComponent<PropsWithChildren<Props>> = ({children, tabs,
)
}

const TabWidgetVertical: FunctionComponent<PropsWithChildren<Props>> = ({children, tabs, tabBarLayout, width, height}) => {
const [currentTabIndex, setCurrentTabIndex] = useState<number | undefined>(0)
const children2 = children as React.ReactElement[]
if ((children2 || []).length !== tabs.length) {
throw Error('TabWidget: incorrect number of tabs')
}
const leftPanelWidth = Math.min(250, width / 3)
const W = width - leftPanelWidth
const H = height
return (
<div
style={{position: 'absolute', left: 0, top: 0, width: W, height: H}}
className="TabWidgetVertical"
>
<div style={{position: 'absolute', left: 0, top: 0, width: leftPanelWidth, height: H}}>
<VerticalTabSelectionPanel
width={leftPanelWidth}
height={H}
tabs={tabs}
currentTabIndex={currentTabIndex}
onCurrentTabIndexChanged={setCurrentTabIndex}
/>
</div>
<div style={{position: 'absolute', left: leftPanelWidth, top: 0, width: W - leftPanelWidth, height: H}}>
{
children2.map((c, i) => {
const visible = i === currentTabIndex
return (
<div key={`child-${i}`} style={{visibility: visible ? undefined : 'hidden', overflowY: 'hidden', overflowX: 'hidden', position: 'absolute', left: 0, top: 0, width: W - leftPanelWidth, height: H}}>
<c.type {...c.props} width={W - leftPanelWidth} height={H}/>
</div>
)
})
}
</div>
</div>
)
}

type VerticalTabSelectionPanelProps = {
tabs: {
label: string
}[]
currentTabIndex?: number
onCurrentTabIndexChanged: (i: number) => void
width: number
height: number
}

const VerticalTabSelectionPanel: FunctionComponent<VerticalTabSelectionPanelProps> = ({tabs, currentTabIndex, onCurrentTabIndexChanged, width, height}) => {
return (
<div style={{position: 'absolute', left: 0, top: 0, width: width, height: height, overflowY: 'auto'}}>
{
tabs.map((tab, i) => (
<div key={i} style={{padding: 4, cursor: 'pointer', backgroundColor: i === currentTabIndex ? 'lightgray' : undefined}} onClick={() => onCurrentTabIndexChanged(i)}>
{tab.label}
</div>
))
}
</div>
)
}

export default TabWidget
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type Props = {
}[]
currentTabIndex: number | undefined
onCurrentTabIndexChanged: (i: number) => void
onTabClosed: (i: number) => void
onTabClosed?: (i: number) => void
}

const TabWidgetTabBar: FunctionComponent<Props> = ({ tabs, currentTabIndex, onCurrentTabIndexChanged, onTabClosed }) => {
Expand Down Expand Up @@ -53,7 +53,7 @@ const TabWidgetTabBar: FunctionComponent<Props> = ({ tabs, currentTabIndex, onCu
type TabProps = {
tab: {label: string}
tabIndex: number
onClose: (i: number) => void
onClose?: (i: number) => void
opts: {selected?: boolean}
onClick: (i: number) => void
}
Expand All @@ -75,7 +75,7 @@ const TabWidgetTab: FunctionComponent<TabProps> = ({tab, onClose, opts, onClick,
{<icon.type {...icon.props} style={{paddingRight: 5, paddingLeft: 3, paddingTop: 0, width: 20, height: 20, display: 'inline', verticalAlign: 'middle'}} />}
<span style={{display: 'inline', verticalAlign: 'middle'}}>{tab.label}</span>
<span>&nbsp;</span>
<IconButton
{onClose ? <IconButton
component="div"
onClick={() => onClose(tabIndex)}
className="CloseButton"
Expand All @@ -88,12 +88,12 @@ const TabWidgetTab: FunctionComponent<TabProps> = ({tab, onClose, opts, onClick,
fontSize: 20
}}
/>
</IconButton>
</IconButton> : <span />}
</div>
)
const style: React.CSSProperties = useMemo(() => (opts.selected ? {color: 'black', fontWeight: 'bold'} : {color: 'gray'}), [opts.selected])
return (
<Tab key={tabIndex} label={label} className="Tab" style={style} />
<Tab key={tabIndex} label={label} className="Tab" style={style} wrapped={true} />
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export type LayoutItem = {
} | {
type: 'TabLayout'
items: LayoutItem[]
tabBarLayout?: 'horizontal' | 'vertical'
itemProperties: {
label: string
}[]
Expand Down Expand Up @@ -86,7 +87,8 @@ export const isLayoutItem = (x: any): x is LayoutItem => {
items: isArrayOf(isLayoutItem),
itemProperties: isArrayOf(z => (validateObject(z, {
label: isString
})))
}))),
tabBarLayout: optional(isOneOf(['horizontal', 'vertical'].map(s => (isEqualTo(s)))))
})),
(y: any) => (validateObject(y, {
type: isEqualTo('View'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const TabLayoutItemView: FunctionComponent<Props> = ({layoutItem, ViewComponent,
tabs={tabs}
width={width}
height={height}
tabBarLayout={layoutItem.tabBarLayout}
>
{
items.map((item, ii) => (
Expand Down
9 changes: 6 additions & 3 deletions sortingview/views/TabLayout.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List
from typing import List, Literal
from .View import View


Expand All @@ -13,16 +13,19 @@ class TabLayout(View):
Tab layout
"""

def __init__(self, items: List[TabLayoutItem], **kwargs) -> None:
def __init__(self, items: List[TabLayoutItem], tab_bar_layout: Literal['horizontal', 'vertical'] = 'horizontal', **kwargs) -> None:
super().__init__("TabLayout", is_layout=True, **kwargs)
self._items = items
self._tab_bar_layout = tab_bar_layout

def to_dict(self) -> dict:
ret = {
"type": self.type,
"items": [item.view.to_dict() if item.view.is_layout else {"type": "View", "viewId": item.view.id} for item in self._items],
"itemProperties": [{"label": item.label} for item in self._items],
"itemProperties": [{"label": item.label} for item in self._items]
}
if self._tab_bar_layout != "horizontal":
ret["tabBarLayout"] = self._tab_bar_layout
return ret

def child_views(self) -> List[View]:
Expand Down

0 comments on commit 4d9496f

Please sign in to comment.