Skip to content

Commit

Permalink
Miawong/sc 59049/auto expand and collapse the the config nav (#3555)
Browse files Browse the repository at this point in the history
  • Loading branch information
miaawong authored Jan 19, 2023
1 parent cecb860 commit 1ada015
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 96 deletions.
183 changes: 90 additions & 93 deletions web/src/components/config_render/ConfigGroup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,28 @@ import { setOrder } from "./ConfigUtil";
import { ConfigWrapper, ConfigItems } from "./ConfigComponents";
import Icon from "../Icon";

export default class ConfigGroup extends React.Component {
constructor() {
super();
this.markdownNode = React.createRef();
}
const ConfigGroup = (props) => {
const markdownNode = React.createRef();

handleItemChange = (itemName, value, data) => {
if (this.props.handleChange) {
this.props.handleChange(itemName, value, data);
const handleItemChange = (itemName, value, data) => {
if (props.handleChange) {
props.handleChange(itemName, value, data);
}
};

handleAddItem = (itemName) => {
if (this.props.handleAddItem) {
this.props.handleAddItem(itemName);
const handleAddItem = (itemName) => {
if (props.handleAddItem) {
props.handleAddItem(itemName);
}
};

handleRemoveItem = (itemName, itemToRemove) => {
if (this.props.handleRemoveItem) {
this.props.handleRemoveItem(itemName, itemToRemove);
const handleRemoveItem = (itemName, itemToRemove) => {
if (props.handleRemoveItem) {
props.handleRemoveItem(itemName, itemToRemove);
}
};

renderConfigItems = (items, readonly) => {
const renderConfigItems = (items, readonly) => {
if (!items) {
return null;
}
Expand All @@ -51,11 +48,11 @@ export default class ConfigGroup extends React.Component {
return (
<ConfigInput
key={`${i}-${item.name}`}
handleOnChange={this.handleItemChange}
handleAddItem={this.handleAddItem}
handleRemoveItem={this.handleRemoveItem}
handleOnChange={handleItemChange}
handleAddItem={handleAddItem}
handleRemoveItem={handleRemoveItem}
inputType="text"
groupName={this.props.item.name}
groupName={props.item.name}
hidden={item.hidden}
when={item.when}
{...item}
Expand All @@ -67,11 +64,11 @@ export default class ConfigGroup extends React.Component {
return (
<ConfigTextarea
key={`${i}-${item.name}`}
handleOnChange={this.handleItemChange}
handleAddItem={this.handleAddItem}
handleRemoveItem={this.handleRemoveItem}
handleOnChange={handleItemChange}
handleAddItem={handleAddItem}
handleRemoveItem={handleRemoveItem}
hidden={item.hidden}
groupName={this.props.item.name}
groupName={props.item.name}
when={item.when}
{...item}
readonly={isReadOnly}
Expand All @@ -82,11 +79,11 @@ export default class ConfigGroup extends React.Component {
return (
<ConfigCheckbox
key={`${i}-${item.name}`}
handleOnChange={this.handleItemChange}
handleAddItem={this.handleAddItem}
handleRemoveItem={this.handleRemoveItem}
handleOnChange={handleItemChange}
handleAddItem={handleAddItem}
handleRemoveItem={handleRemoveItem}
hidden={item.hidden}
groupName={this.props.item.name}
groupName={props.item.name}
when={item.when}
{...item}
readonly={isReadOnly}
Expand All @@ -99,7 +96,7 @@ export default class ConfigGroup extends React.Component {
key={`${i}-${item.name}`}
className="field field-type-label"
style={{
margin: this.props.affix ? "0" : "15px",
margin: props.affix ? "0" : "15px",
order: setOrder(i + 1, item.affix),
}}
>
Expand All @@ -108,7 +105,7 @@ export default class ConfigGroup extends React.Component {
recommended={item.recommended}
required={item.required}
hidden={item.hidden}
groupName={this.props.item.name}
groupName={props.item.name}
when={item.when}
name={item.name}
error={item.error}
Expand All @@ -120,22 +117,22 @@ export default class ConfigGroup extends React.Component {
return (
<ConfigWrapper
key={`${i}-${item.name}`}
className={"field-type-label"}
className={"field-type-label "}
marginTop={item.affix ? "0" : "15px"}
order={setOrder(i + 1, item.affix)}
>
<ConfigFileInput
{...item}
title={item.title}
recommended={item.recommended}
groupName={this.props.item.name}
groupName={props.item.name}
required={item.required}
handleChange={this.handleItemChange}
handleRemoveItem={this.handleRemoveItem}
handleChange={handleItemChange}
handleRemoveItem={handleRemoveItem}
hidden={item.hidden}
when={item.when}
configSequence={this.props.configSequence}
appSlug={this.props.appSlug}
configSequence={props.configSequence}
appSlug={props.appSlug}
readonly={isReadOnly}
index={i + 1}
/>
Expand All @@ -145,9 +142,9 @@ export default class ConfigGroup extends React.Component {
return (
<ConfigSelectOne
key={`${i}-${item.name}`}
handleOnChange={this.handleItemChange}
handleOnChange={handleItemChange}
hidden={item.hidden}
groupName={this.props.item.name}
groupName={props.item.name}
when={item.when}
{...item}
readonly={isReadOnly}
Expand All @@ -158,7 +155,7 @@ export default class ConfigGroup extends React.Component {
return (
<div
key={`${i}-${item.name}`}
className={`u-marginTop--15 u-marginBottom--15 ${
className={`u-marginTop--15 u-marginBottom--15 ${
item.hidden || item.when === "false" ? "hidden" : ""
}`}
style={{ order: setOrder(i + 1, item.affix) }}
Expand All @@ -172,11 +169,11 @@ export default class ConfigGroup extends React.Component {
return (
<ConfigInput
key={`${i}-${item.name}`}
handleOnChange={this.handleItemChange}
handleAddItem={this.handleAddItem}
handleRemoveItem={this.handleRemoveItem}
handleOnChange={handleItemChange}
handleAddItem={handleAddItem}
handleRemoveItem={handleRemoveItem}
hidden={item.hidden}
groupName={this.props.item.name}
groupName={props.item.name}
when={item.when}
inputType="password"
{...item}
Expand All @@ -202,65 +199,65 @@ export default class ConfigGroup extends React.Component {
});
};

isAtLeastOneItemVisible = () => {
const { item } = this.props;
const isAtLeastOneItemVisible = () => {
const { item } = props;
if (!item) {
return false;
}
return some(this.props.item.items, (item) => {
return some(props.item.items, (item) => {
if (!isEmpty(item)) {
return ConfigService.isVisible(this.props.items, item);
return ConfigService.isVisible(props.items, item);
}
});
};

render() {
const { item, readonly } = this.props;
const hidden = item && item.when === "false";
if (hidden || !this.isAtLeastOneItemVisible()) {
return null;
}
const hasAffix = item.items.every((option) => option.affix);
return (
<div className="flex-column flex-auto">
{item && (
const { item, readonly } = props;
const hidden = item && item.when === "false";
if (hidden || !isAtLeastOneItemVisible()) {
return null;
}
const hasAffix = item.items.every((option) => option.affix);
return (
<div className="flex-column flex-auto">
{item && (
<div
id={`${item.name}`}
className={`flex-auto config-item-wrapper card-item u-padding--15 observe-elements ${
isAtLeastOneItemVisible() ? "u-marginBottom--20" : ""
} config-groups`}
>
<h3 className="card-item-title">{item.title}</h3>
{item.description !== "" ? (
<div className="field-section-help-text help-text-color u-marginTop--10">
<Markdown
ref={markdownNode}
options={{
linkTarget: "_blank",
linkify: true,
}}
>
{item.description}
</Markdown>
</div>
) : null}
<div
id={item.name}
className={`flex-auto config-item-wrapper card-item u-padding--15 ${
this.isAtLeastOneItemVisible() ? "u-marginBottom--20" : ""
}`}
className="config-items"
style={{ display: hasAffix ? "grid" : "block" }}
>
<h3 className="card-item-title">{item.title}</h3>
{item.description !== "" ? (
<div className="field-section-help-text help-text-color u-marginTop--10">
<Markdown
ref={this.markdownNode}
options={{
linkTarget: "_blank",
linkify: true,
}}
>
{item.description}
</Markdown>
</div>
) : null}
<div
className="config-items"
style={{ display: hasAffix ? "grid" : "block" }}
>
{this.renderConfigItems(item.items, readonly)}
</div>
{item.repeatable && (
<div className="u-marginTop--15">
<button className="btn secondary blue rounded add-btn">
<Icon icon="plus" size={14} />
Add another {item.title}
</button>
</div>
)}
{renderConfigItems(item.items, readonly)}
</div>
)}
</div>
);
}
}
{item.repeatable && (
<div className="u-marginTop--15">
<button className="btn secondary blue rounded add-btn">
<Icon icon="plus" size={14} />
Add another {item.title}
</button>
</div>
)}
</div>
)}
</div>
);
};

export default ConfigGroup;
44 changes: 44 additions & 0 deletions web/src/features/AppConfig/components/AppConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,49 @@ class AppConfig extends Component<Props, State> {
saveButtonText = "Generate Upgrade Command";
}

const sections = document.querySelectorAll(".observe-elements");

const callback = (entries: IntersectionObserverEntry[]) => {
entries.forEach(({ isIntersecting, target }) => {
// find the group nav link that matches the current section in view
const groupNav = document.querySelector(
`#config-group-nav-${target.id}`
);
// find the active link in the group nav
const activeLink = document.querySelector(".active-item");
const hash = this.props.location.hash.slice(1);
const activeLinkByHash = document.querySelector(`a[href='#${hash}']`);
if (isIntersecting) {
groupNav?.classList.add("is-active");
// if your group is active, item will be active
if (activeLinkByHash && groupNav?.contains(activeLinkByHash)) {
activeLinkByHash.classList.add("active-item");
}
} else {
// if the section is not in view, remove the highlight from the active link
if (groupNav?.contains(activeLink) && activeLink) {
activeLink.classList.remove("active-item");
}
// remove the highlight from the group nav link
groupNav?.classList.remove("is-active");
}
});
};

const options = {
root: document,
// rootMargin is the amount of space around the root element that the intersection observer will look for intersections
rootMargin: "20% 0% -75% 0%",
// threshold: the proportion of the element that must be within the root bounds for it to be considered intersecting
threshold: 0.15,
};

const observer = new IntersectionObserver(callback, options);

sections.forEach((section) => {
observer.observe(section);
});

return (
<div className="flex flex-column u-paddingLeft--20 u-paddingBottom--20 u-paddingRight--20 alignItems--center">
<KotsPageTitle pageName="Config" showAppSlug />
Expand Down Expand Up @@ -608,6 +651,7 @@ class AppConfig extends Component<Props, State> {
? "group-open"
: ""
}`}
id={`config-group-nav-${group.name}`}
>
<div
className="flex alignItems--center"
Expand Down
9 changes: 6 additions & 3 deletions web/src/scss/components/watches/WatchConfig.scss
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,6 @@ a:visited {
}

&.group-open {
& .group-title {
font-weight: 700;
}
.icon.u-darkDropdownArrow,
.arrow-down {
transform: rotate(180deg);
Expand All @@ -83,6 +80,12 @@ a:visited {
display: block;
}
}
&.is-active {
& .group-title {
font-weight: 700;
color: $text-color-primary;
}
}
}
}
.side-nav-items {
Expand Down

0 comments on commit 1ada015

Please sign in to comment.