-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
298 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { Story, Canvas, Controls, Meta } from "@storybook/addon-docs"; | ||
import FloatingPanels from "."; | ||
import * as stories from "./FloatingPanels.stories"; | ||
|
||
<Meta of={stories} /> | ||
|
||
# Floating Panels | ||
|
||
The Floating Panels Component is a flexible React component that creates a set of collapsible panels floating on the screen. It's ideal for displaying information or controls that need to be easily accessible but not always visible. | ||
|
||
## Features | ||
|
||
- Customizable position on the screen | ||
- Expandable/collapsible panels | ||
- Configurable icons and titles for each panel | ||
- Ability to include any content within panels | ||
|
||
## Usage | ||
|
||
```js run | ||
const panels = [ | ||
{ | ||
id: "legend", | ||
iconName: "map", | ||
title: "Legend", | ||
defaultExpanded: true, | ||
children: <div>Legend content goes here</div> | ||
}, | ||
{ | ||
id: "view-options", | ||
iconName: "eye", | ||
title: "View Options", | ||
defaultExpanded: true, | ||
children: <div>View options content goes here</div> | ||
}, | ||
{ | ||
id: "person-details", | ||
iconName: "user", | ||
title: "Person Details", | ||
children: <div>Person details content goes here</div> | ||
}, | ||
{ | ||
id: "properties", | ||
iconName: "gear", | ||
title: "Properties", | ||
children: <div>Properties content goes here</div> | ||
} | ||
// Add more panels as needed... | ||
]; | ||
``` | ||
|
||
<Canvas of={stories.defaultFloatingPanels} /> | ||
|
||
## Properties | ||
|
||
<Controls component={FloatingPanels} /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import React from "react"; | ||
import FloatingPanels from "."; | ||
import Box from "../Box"; | ||
import { far } from "@fortawesome/free-regular-svg-icons"; | ||
import { library } from "@fortawesome/fontawesome-svg-core"; | ||
import TextInput from "../TextInput"; | ||
import Toggle from "../Toggle"; | ||
import Spacer from "../Spacer"; | ||
|
||
library.add(far); | ||
|
||
export default { | ||
title: "Components/FloatingPanels", | ||
decorators: [(storyFn) => <Box minHeight="450px">{storyFn()}</Box>], | ||
component: FloatingPanels | ||
}; | ||
const Properties = () => { | ||
return ( | ||
<Spacer mb="r"> | ||
<Toggle id="toggle1" label="Group items" small /> | ||
<Toggle id="toggle2" label="Show teams" small /> | ||
<TextInput | ||
id="textInput1" | ||
key="textInput1" | ||
type="text" | ||
label="Full name" | ||
placeholder="E.g. John Smith" | ||
my="20px" | ||
/> | ||
</Spacer> | ||
); | ||
}; | ||
const panels = [ | ||
{ | ||
id: "view-options", | ||
iconName: "eye", | ||
title: "View Options", | ||
defaultExpanded: true, | ||
children: <div>View options content goes here</div> | ||
}, | ||
{ | ||
id: "properties", | ||
iconName: "sun", | ||
title: "Properties", | ||
children: <Properties /> | ||
}, | ||
{ | ||
id: "person-details", | ||
iconName: "user", | ||
title: "Person Details", | ||
children: <div>Person details content goes here</div> | ||
} | ||
]; | ||
|
||
export const defaultFloatingPanels = () => { | ||
return <FloatingPanels panels={panels} position={{ right: 20, top: 20 }} />; | ||
}; | ||
|
||
defaultFloatingPanels.storyName = "Default Floating Panels"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import styled from "styled-components"; | ||
import { themeGet } from "@styled-system/theme-get"; | ||
|
||
export const Container = styled.div` | ||
position: fixed; | ||
display: flex; | ||
flex-direction: column; | ||
gap: 8px; | ||
width: 300px; | ||
${({ position }) => | ||
Object.entries(position) | ||
.filter(([, value]) => value !== undefined) | ||
.map( | ||
([key, value]) => | ||
`${key}: ${typeof value === "number" ? `${value}px` : value};` | ||
) | ||
.join("\n")} | ||
`; | ||
|
||
export const PanelWrapper = styled.div` | ||
background: white; | ||
border: 1px solid #e5e7eb; | ||
border-radius: 8px; | ||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); | ||
overflow: hidden; | ||
padding: 0 12px; | ||
`; | ||
|
||
export const PanelHeader = styled.button` | ||
font-family: ${themeGet("fonts.main")}; | ||
color: ${themeGet("colors.greyDarkest")}; | ||
width: 100%; | ||
border: none; | ||
appearance: none; | ||
background-color: white; | ||
display: flex; | ||
align-items: center; | ||
justify-content: space-between; | ||
padding: 12px 0; | ||
padding: ${({ $isExpanded }) => ($isExpanded ? "12px 0 0 0" : "12px 0")}; | ||
cursor: pointer; | ||
user-select: none; | ||
transition: padding 0.3s ease-in-out; | ||
&:focus { | ||
outline: none; | ||
} | ||
`; | ||
|
||
export const HeaderContent = styled.div` | ||
display: flex; | ||
align-items: center; | ||
gap: 12px; | ||
`; | ||
|
||
export const Title = styled.span` | ||
font-size: 14px; | ||
font-weight: 500; | ||
`; | ||
|
||
export const IconWrapper = styled.div` | ||
background-color: #3b82f6; | ||
width: 22px; | ||
height: 22px; | ||
border-radius: 50%; | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
`; | ||
|
||
export const ChevronWrapper = styled(IconWrapper)` | ||
transition: background-color 0.3s ease-in-out; | ||
background-color: ${({ isHovered }) => | ||
isHovered ? themeGet("colors.greyLighter") : "white"}; | ||
`; | ||
|
||
export const PanelContent = styled.div` | ||
padding: ${({ $isExpanded }) => ($isExpanded ? "16px 0 12px 0" : "0")}; | ||
height: ${({ $isExpanded }) => ($isExpanded ? "auto" : "0")}; | ||
overflow-y: ${({ $isExpanded }) => | ||
$isExpanded | ||
? "auto" | ||
: "hidden"}; // note: this is showing briefly on transition, how do I hide this? Animation delay on this one maybe? | ||
max-height: ${({ $isExpanded }) => | ||
$isExpanded | ||
? "500px" | ||
: "0"}; // Make max width a split of a set height passed in props divided by amount of panels? | ||
opacity: ${({ $isExpanded }) => ($isExpanded ? "1" : "0")}; | ||
transition: maxHeight 0.3s ease-in-out, opacity 0.3s ease-in-out, | ||
padding 0.3s ease-in-out; | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import React, { useState } from "react"; | ||
import PropTypes from "prop-types"; | ||
|
||
import Icon from "../Icon"; | ||
import { | ||
PanelWrapper, | ||
PanelHeader, | ||
HeaderContent, | ||
Title, | ||
IconWrapper, | ||
ChevronWrapper, | ||
PanelContent | ||
} from "./FloatingPanels.styles"; | ||
|
||
const Panel = ({ iconName, title, children, defaultExpanded = false }) => { | ||
const [isExpanded, setIsExpanded] = useState(defaultExpanded); | ||
const arrowIcon = isExpanded ? "chevron-up" : "chevron-down"; | ||
const [isHovered, setIsHovered] = useState(false); | ||
return ( | ||
<PanelWrapper> | ||
<PanelHeader | ||
onClick={() => setIsExpanded(!isExpanded)} | ||
$isExpanded={isExpanded} | ||
onMouseEnter={() => setIsHovered(true)} | ||
onMouseLeave={() => setIsHovered(false)} | ||
onFocus={() => setIsHovered(true)} | ||
onBlur={() => setIsHovered(false)} | ||
> | ||
<HeaderContent> | ||
<IconWrapper> | ||
<Icon size="xs" color="white" icon={["far", iconName]} /> | ||
</IconWrapper> | ||
<Title>{title}</Title> | ||
</HeaderContent> | ||
<ChevronWrapper isHovered={isHovered}> | ||
<Icon size="sm" icon={["fas", arrowIcon]} color="greyDarker" /> | ||
</ChevronWrapper> | ||
</PanelHeader> | ||
<PanelContent $isExpanded={isExpanded}>{children}</PanelContent> | ||
</PanelWrapper> | ||
); | ||
}; | ||
|
||
Panel.propTypes = { | ||
iconName: PropTypes.string.isRequired, | ||
title: PropTypes.string.isRequired, | ||
children: PropTypes.node.isRequired, | ||
defaultExpanded: PropTypes.bool | ||
}; | ||
|
||
Panel.defaultProps = { | ||
defaultExpanded: false | ||
}; | ||
|
||
export default Panel; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import React from "react"; | ||
import { Container } from "./FloatingPanels.styles"; | ||
import Panel from "./Panel"; | ||
import PropTypes from "prop-types"; | ||
|
||
const FloatingPanels = ({ panels, position = { right: 20, top: 20 } }) => { | ||
return ( | ||
<Container position={position}> | ||
{panels.map((panel) => ( | ||
<Panel key={panel.id} {...panel} /> | ||
))} | ||
</Container> | ||
); | ||
}; | ||
|
||
FloatingPanels.propTypes = { | ||
panels: PropTypes.arrayOf( | ||
PropTypes.shape({ | ||
id: PropTypes.string.isRequired, | ||
iconName: PropTypes.string.isRequired, | ||
title: PropTypes.string.isRequired, | ||
children: PropTypes.node.isRequired, | ||
defaultExpanded: PropTypes.bool | ||
}) | ||
).isRequired, | ||
position: PropTypes.shape({ | ||
top: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), | ||
right: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), | ||
bottom: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), | ||
left: PropTypes.oneOfType([PropTypes.number, PropTypes.string]) | ||
}) | ||
}; | ||
|
||
FloatingPanels.defaultProps = { | ||
position: { right: 20, top: 20 } | ||
}; | ||
|
||
export default FloatingPanels; |