Skip to content
This repository was archived by the owner on Feb 10, 2025. It is now read-only.

Commit f2732c4

Browse files
committed
add: application reservations tab
- add: new Accordion component - add: all application section reservatations
1 parent 05c0ca4 commit f2732c4

27 files changed

Lines changed: 1656 additions & 56 deletions

File tree

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,15 @@ const Content = styled.div<{ $open: boolean }>`
8080

8181
const Heading = styled(H4).attrs({ as: "div" })``;
8282

83-
const Accordion = ({
83+
export function Accordion({
8484
heading,
8585
open = false,
8686
children,
8787
onToggle,
8888
id,
8989
theme = "default",
9090
...rest
91-
}: Props): JSX.Element => {
91+
}: Props): JSX.Element {
9292
const { isOpen, openAccordion, closeAccordion } = useAccordion({
9393
initiallyOpen: open,
9494
});
@@ -102,11 +102,13 @@ const Accordion = ({
102102
}
103103
}
104104
}, [closeAccordion, isOpen, open, openAccordion]);
105+
105106
const icon = isOpen ? (
106107
<IconAngleUp aria-hidden />
107108
) : (
108109
<IconAngleDown aria-hidden />
109110
);
111+
110112
return (
111113
<AccordionElement id={id} {...rest}>
112114
<HeadingButton
@@ -120,15 +122,13 @@ const Accordion = ({
120122
<Content $open={isOpen}>{children}</Content>
121123
</AccordionElement>
122124
);
123-
};
125+
}
124126

125-
export default Accordion;
126-
127-
export const AccordionWithState = ({
127+
export function AccordionWithState({
128128
open: initiallyOpen,
129129
...rest
130-
}: Props): JSX.Element => {
130+
}: Props): JSX.Element {
131131
const [open, setOpen] = useState(initiallyOpen);
132132

133133
return <Accordion onToggle={() => setOpen(!open)} {...rest} open={open} />;
134-
};
134+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { breakpoints } from "common";
2+
import { Button, IconAngleDown, IconAngleUp, useAccordion } from "hds-react";
3+
import { useTranslation } from "next-i18next";
4+
import styled from "styled-components";
5+
6+
// There is only one use case for this so not testing other cases (or making it flexible)
7+
const N_ICONS = 3;
8+
9+
type Props = {
10+
heading: string;
11+
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
12+
initiallyOpen?: boolean;
13+
children: React.ReactNode;
14+
icons?: Array<{
15+
text: string;
16+
textPostfix?: string;
17+
icon: React.ReactNode;
18+
}>;
19+
};
20+
21+
const Heading = styled.h2<{ as: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" }>`
22+
padding: 0;
23+
margin: 0;
24+
`;
25+
26+
const ClosedAccordionWrapper = styled.div`
27+
display: grid;
28+
grid-template-columns: 1fr auto auto;
29+
align-items: center;
30+
gap: var(--spacing-s);
31+
32+
background-color: var(--color-black-10);
33+
padding: var(--spacing-s);
34+
`;
35+
36+
const IconListWrapper = styled.div`
37+
display: grid;
38+
gap: var(--spacing-s);
39+
40+
grid-row: subgrid;
41+
grid-column: 1 / -1;
42+
grid-template-columns: auto;
43+
@media (width > ${breakpoints.m}) {
44+
grid-column: unset;
45+
grid-template-columns: repeat(${N_ICONS}, auto);
46+
}
47+
`;
48+
49+
const ButtonListWrapper = styled.div`
50+
display: block;
51+
gap: var(--spacing-s);
52+
align-self: center;
53+
align-items: end;
54+
55+
@media (width > ${breakpoints.s}) {
56+
grid-column-start: -1;
57+
grid-row: 1 / span 2;
58+
}
59+
& > button:last-child > span {
60+
display: none;
61+
}
62+
@media (width > ${breakpoints.s}) {
63+
& > button:last-child > span {
64+
display: inline;
65+
}
66+
}
67+
`;
68+
69+
const IconLabel = styled.div`
70+
display: flex;
71+
align-items: center;
72+
gap: var(--spacing-xs);
73+
74+
/* truncate the first child span while not touch the postfix */
75+
min-width: 0;
76+
max-width: 100%;
77+
span {
78+
white-space: nowrap;
79+
overflow: hidden;
80+
text-overflow: ellipsis;
81+
}
82+
span:last-child {
83+
flex-shrink: 0;
84+
}
85+
86+
> svg {
87+
flex-shrink: 0;
88+
}
89+
`;
90+
91+
const Content = styled.div<{ $open: boolean }>`
92+
display: ${({ $open }) => ($open ? "block" : "none")};
93+
`;
94+
95+
/// Stylistically different from regular Accordion
96+
/// Regular accordion uses the card title as a button to open/close the card
97+
/// and has no options for other content inside the accordion.
98+
/// Includes internal state
99+
export function AccordionWithIcons({
100+
heading,
101+
headingLevel = 2,
102+
initiallyOpen = false,
103+
icons = [],
104+
children,
105+
...rest
106+
}: Props): JSX.Element {
107+
const { isOpen, openAccordion, closeAccordion } = useAccordion({
108+
initiallyOpen,
109+
});
110+
const { t } = useTranslation();
111+
112+
const handleToggle = () => {
113+
if (isOpen) {
114+
closeAccordion();
115+
} else {
116+
openAccordion();
117+
}
118+
};
119+
120+
return (
121+
<div {...rest}>
122+
<ClosedAccordionWrapper>
123+
<Heading as={`h${headingLevel}`}>{heading}</Heading>
124+
<IconListWrapper>
125+
{icons.map(({ text, icon, textPostfix }) => (
126+
<IconLabel key={text}>
127+
{icon}
128+
<span>{text}</span>
129+
<span>{textPostfix}</span>
130+
</IconLabel>
131+
))}
132+
</IconListWrapper>
133+
<ButtonListWrapper>
134+
<Button
135+
key="toggle"
136+
onClick={handleToggle}
137+
variant="supplementary"
138+
theme="black"
139+
// we are hiding the text on mobile
140+
aria-label={isOpen ? t("common:close") : t("common:show")}
141+
iconRight={
142+
isOpen ? (
143+
<IconAngleUp aria-hidden />
144+
) : (
145+
<IconAngleDown aria-hidden />
146+
)
147+
}
148+
>
149+
{isOpen ? t("common:close") : t("common:show")}
150+
</Button>
151+
</ButtonListWrapper>
152+
</ClosedAccordionWrapper>
153+
<Content $open={isOpen}>{children}</Content>
154+
</div>
155+
);
156+
}

apps/ui/components/application/ApplicationEvent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
uiDateToApiDate,
2020
} from "@/modules/util";
2121
import { ApplicationEventSummary } from "./ApplicationEventSummary";
22-
import Accordion from "../common/Accordion";
22+
import { Accordion } from "@/components/Accordion";
2323
import { getDurationOptions } from "@/modules/const";
2424
import { ConfirmationDialog } from "common/src/components/ConfirmationDialog";
2525
import { MediumButton } from "@/styles/util";

0 commit comments

Comments
 (0)