Skip to content

Commit 1c04f75

Browse files
Extract LinkList styles and expose LinkListContainer (#1521)
Extracting styles for the `<LinkList>` components into reusable classes. This change enhances the readability of the implementation. And allows the classes to be used outisde React. Exposing `<LinkListContainer>` as part of the `<LinkList>` API. This allows for easier customization and flexibility. Since it is now possible to style the container and the list individually. This means you can still just render shorter lists (less than 6 LinkListItems) like before. This also paves way for supporting `<Heading>` inside the `<LinkListContainer>`, above the `<LinkListContainer>`. This will be done in the next PR! https://github.com/user-attachments/assets/a6d8bb58-2ba5-41e7-82cf-35320554d068
1 parent 7810d71 commit 1c04f75

File tree

6 files changed

+260
-56
lines changed

6 files changed

+260
-56
lines changed

.changeset/dull-lies-shout.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@obosbbl/grunnmuren-tailwind": patch
3+
---
4+
5+
Extract styles for the `<LinkList>` components to component classes. This makes them reusable outside React, and makes the implementation and CSS for the component more readable.

.changeset/giant-parrots-travel.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
"@obosbbl/grunnmuren-react": patch
3+
---
4+
5+
# Breaking Beta Change
6+
Exposing `<LinkListContainer>` as part of the `<LinkList>` API. This allows for easier customization and flexibility. Since it is now possible to style the container and the list individually. This means you can still just render shorter lists (less than 6 LinkListItems) like before:
7+
8+
``` tsx
9+
<LinkList>
10+
<LinkListItem href="/bolig">Bolig</LinkListItem>
11+
<LinkListItem href="/bank">Bank</LinkListItem>
12+
<LinkListItem href="/medlem">Medlem</LinkListItem>
13+
</LinkList>
14+
```
15+
16+
But the `<LinkList>` itself will no longer divide larger list (more than 5 LinkListItems) into multiple columns like before. For that you will now need to wrap it in the `<LinkListContainer>`:
17+
18+
``` tsx
19+
<LinkListContainer>
20+
<LinkList>
21+
<LinkListItem href="/konsernledelsen">Konsernledelsen</LinkListItem>
22+
<LinkListItem href="/styret">Styret</LinkListItem>
23+
<LinkListItem href="/representantskapet">
24+
Representantskapet
25+
</LinkListItem>
26+
<LinkListItem href="/boligpriser-og-statistikk">
27+
Boligpriser og statistikk
28+
</LinkListItem>
29+
<LinkListItem href="/investor-relations">
30+
Investor Relations
31+
</LinkListItem>
32+
<LinkListItem href="/digital-arsrapport">
33+
Digital årsrapport
34+
</LinkListItem>
35+
</LinkList>
36+
</LinkListContainer>
37+
```
38+
39+
This also paves way for supporting `<Heading>` inside the `<LinkListContainer>`, above the `<LinkListContainer>`. Stay tuned!

apps/docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"build": "pnpm build:props && pnpm build:colors && pnpm build:tsc && pnpm build:assets && pnpm build:app",
1010
"build:app": "vite build",
1111
"build:assets": "mkdir -p public/resources/icons && cp -r node_modules/@obosbbl/grunnmuren-icons-svg/src/* public/resources/icons/",
12-
"build:props": "node extract-component-props.js && biome lint --write component-props.ts --files-max-size=7000000",
12+
"build:props": "node extract-component-props.js && biome lint --write component-props.ts --files-max-size=8000000",
1313
"build:colors": "node extract-colors.js && biome format --write colors.ts",
1414
"build:tsc": "tsc",
1515
"dev": "vite dev --port 3000",

packages/react/src/link-list/link-list.stories.tsx

Lines changed: 158 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Meta } from '@storybook/react-vite';
22
import {
33
UNSAFE_LinkList as LinkList,
4+
UNSAFE_LinkListContainer as LinkListContainer,
45
UNSAFE_LinkListItem as LinkListItem,
56
} from './link-list';
67

@@ -45,29 +46,161 @@ export const ExternalLinkListItems = () => (
4546
);
4647

4748
export const AutoResponsive = () => (
48-
<LinkList>
49-
<LinkListItem href="/konsernledelsen">Konsernledelsen</LinkListItem>
50-
<LinkListItem href="/styret">Styret</LinkListItem>
51-
<LinkListItem href="/representantskapet">Representantskapet</LinkListItem>
52-
<LinkListItem href="/boligpriser-og-statistikk">
53-
Boligpriser og statistikk
54-
</LinkListItem>
55-
<LinkListItem href="/investor-relations">Investor Relations</LinkListItem>
56-
<LinkListItem href="/digital-arsrapport">Digital årsrapport</LinkListItem>
57-
<LinkListItem href="/jobb-i-obos">Jobb i OBOS</LinkListItem>
58-
<LinkListItem href="/presse">Presse</LinkListItem>
59-
<LinkListItem href="/logoer">Logoer</LinkListItem>
60-
<LinkListItem href="/obos-boligkonferanse">
61-
OBOS Boligkonferanse
62-
</LinkListItem>
63-
<LinkListItem href="/obos-ligaen">OBOS-ligaen</LinkListItem>
64-
<LinkListItem href="/datterselskaper">Datterselskaper</LinkListItem>
65-
<LinkListItem href="/vedtekter">Vedtekter</LinkListItem>
66-
<LinkListItem href="/generalforsamlingen-i-obos">
67-
Generalforsamlingen i OBOS
68-
</LinkListItem>
69-
<LinkListItem href="/strategi-og-styrende-dokumenter">
70-
Strategi og styrende dokumenter
71-
</LinkListItem>
72-
</LinkList>
49+
<div className="grid gap-y-8">
50+
<h2 className="heading-l">2 items</h2>
51+
<LinkListContainer>
52+
<LinkList>
53+
<LinkListItem download href="/">
54+
Medlemsvilkår
55+
</LinkListItem>
56+
<LinkListItem download href="/about">
57+
Samtykke
58+
</LinkListItem>
59+
</LinkList>
60+
</LinkListContainer>
61+
<h2 className="heading-l">3 items</h2>
62+
<LinkListContainer>
63+
<LinkList>
64+
<LinkListItem href="/bolig">Bolig</LinkListItem>
65+
<LinkListItem href="/bank">Bank</LinkListItem>
66+
<LinkListItem href="/medlem">Medlem</LinkListItem>
67+
</LinkList>
68+
</LinkListContainer>
69+
<h2 className="heading-l">5 items</h2>
70+
<LinkListContainer>
71+
<LinkList>
72+
<LinkListItem href="/konsernledelsen">Konsernledelsen</LinkListItem>
73+
<LinkListItem href="/styret">Styret</LinkListItem>
74+
<LinkListItem href="/representantskapet">
75+
Representantskapet
76+
</LinkListItem>
77+
<LinkListItem href="/boligpriser-og-statistikk">
78+
Boligpriser og statistikk
79+
</LinkListItem>
80+
<LinkListItem href="/investor-relations">
81+
Investor Relations
82+
</LinkListItem>
83+
</LinkList>
84+
</LinkListContainer>
85+
<h2 className="heading-l">6 items</h2>
86+
<LinkListContainer>
87+
<LinkList>
88+
<LinkListItem href="/konsernledelsen">Konsernledelsen</LinkListItem>
89+
<LinkListItem href="/styret">Styret</LinkListItem>
90+
<LinkListItem href="/representantskapet">
91+
Representantskapet
92+
</LinkListItem>
93+
<LinkListItem href="/boligpriser-og-statistikk">
94+
Boligpriser og statistikk
95+
</LinkListItem>
96+
<LinkListItem href="/investor-relations">
97+
Investor Relations
98+
</LinkListItem>
99+
<LinkListItem href="/digital-arsrapport">
100+
Digital årsrapport
101+
</LinkListItem>
102+
</LinkList>
103+
</LinkListContainer>
104+
<h2 className="heading-l">7 items</h2>
105+
<LinkListContainer>
106+
<LinkList>
107+
<LinkListItem href="/konsernledelsen">Konsernledelsen</LinkListItem>
108+
<LinkListItem href="/styret">Styret</LinkListItem>
109+
<LinkListItem href="/representantskapet">
110+
Representantskapet
111+
</LinkListItem>
112+
<LinkListItem href="/boligpriser-og-statistikk">
113+
Boligpriser og statistikk
114+
</LinkListItem>
115+
<LinkListItem href="/investor-relations">
116+
Investor Relations
117+
</LinkListItem>
118+
<LinkListItem href="/digital-arsrapport">
119+
Digital årsrapport
120+
</LinkListItem>
121+
<LinkListItem href="/jobb-i-obos">Jobb i OBOS</LinkListItem>
122+
</LinkList>
123+
</LinkListContainer>
124+
<h2 className="heading-l">9 items</h2>
125+
<LinkListContainer>
126+
<LinkList>
127+
<LinkListItem href="/konsernledelsen">Konsernledelsen</LinkListItem>
128+
<LinkListItem href="/styret">Styret</LinkListItem>
129+
<LinkListItem href="/representantskapet">
130+
Representantskapet
131+
</LinkListItem>
132+
<LinkListItem href="/boligpriser-og-statistikk">
133+
Boligpriser og statistikk
134+
</LinkListItem>
135+
<LinkListItem href="/investor-relations">
136+
Investor Relations
137+
</LinkListItem>
138+
<LinkListItem href="/digital-arsrapport">
139+
Digital årsrapport
140+
</LinkListItem>
141+
<LinkListItem href="/jobb-i-obos">Jobb i OBOS</LinkListItem>
142+
<LinkListItem href="/presse">Presse</LinkListItem>
143+
<LinkListItem href="/logoer">Logoer</LinkListItem>
144+
</LinkList>
145+
</LinkListContainer>
146+
<h2 className="heading-l">10 items</h2>
147+
<LinkListContainer>
148+
<LinkList>
149+
<LinkListItem href="/konsernledelsen">Konsernledelsen</LinkListItem>
150+
<LinkListItem href="/styret">Styret</LinkListItem>
151+
<LinkListItem href="/representantskapet">
152+
Representantskapet
153+
</LinkListItem>
154+
<LinkListItem href="/boligpriser-og-statistikk">
155+
Boligpriser og statistikk
156+
</LinkListItem>
157+
<LinkListItem href="/investor-relations">
158+
Investor Relations
159+
</LinkListItem>
160+
<LinkListItem href="/digital-arsrapport">
161+
Digital årsrapport
162+
</LinkListItem>
163+
<LinkListItem href="/jobb-i-obos">Jobb i OBOS</LinkListItem>
164+
<LinkListItem href="/presse">Presse</LinkListItem>
165+
<LinkListItem href="/logoer">Logoer</LinkListItem>
166+
<LinkListItem href="/obos-boligkonferanse">
167+
OBOS Boligkonferanse
168+
</LinkListItem>
169+
</LinkList>
170+
</LinkListContainer>
171+
<h2 className="heading-l">15 items</h2>
172+
<LinkListContainer>
173+
<LinkList>
174+
<LinkListItem href="/konsernledelsen">Konsernledelsen</LinkListItem>
175+
<LinkListItem href="/styret">Styret</LinkListItem>
176+
<LinkListItem href="/representantskapet">
177+
Representantskapet
178+
</LinkListItem>
179+
<LinkListItem href="/boligpriser-og-statistikk">
180+
Boligpriser og statistikk
181+
</LinkListItem>
182+
<LinkListItem href="/investor-relations">
183+
Investor Relations
184+
</LinkListItem>
185+
<LinkListItem href="/digital-arsrapport">
186+
Digital årsrapport
187+
</LinkListItem>
188+
<LinkListItem href="/jobb-i-obos">Jobb i OBOS</LinkListItem>
189+
<LinkListItem href="/presse">Presse</LinkListItem>
190+
<LinkListItem href="/logoer">Logoer</LinkListItem>
191+
<LinkListItem href="/obos-boligkonferanse">
192+
OBOS Boligkonferanse
193+
</LinkListItem>
194+
<LinkListItem href="/obos-ligaen">OBOS-ligaen</LinkListItem>
195+
<LinkListItem href="/datterselskaper">Datterselskaper</LinkListItem>
196+
<LinkListItem href="/vedtekter">Vedtekter</LinkListItem>
197+
<LinkListItem href="/generalforsamlingen-i-obos">
198+
Generalforsamlingen i OBOS
199+
</LinkListItem>
200+
<LinkListItem href="/strategi-og-styrende-dokumenter">
201+
Strategi og styrende dokumenter
202+
</LinkListItem>
203+
</LinkList>
204+
</LinkListContainer>
205+
</div>
73206
);

packages/react/src/link-list/link-list.tsx

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,33 @@ import {
44
LinkExternal,
55
} from '@obosbbl/grunnmuren-icons-react';
66
import { cx } from 'cva';
7-
import { Children, type JSX, type ReactNode } from 'react';
7+
import type { JSX, ReactNode } from 'react';
88
import {
99
UNSAFE_Link as Link,
1010
type UNSAFE_LinkProps as LinkProps,
1111
} from '../link';
1212

13-
type LinkListProps = React.HTMLProps<HTMLDivElement> & {
13+
type LinkListContainerProps = React.HTMLProps<HTMLDivElement> & {
1414
children: JSX.Element | JSX.Element[];
1515
};
1616

17-
const LinkList = ({ className, children, ...restProps }: LinkListProps) => {
18-
const numberofLinks = Children.count(children);
19-
return (
20-
<div className={cx(className, '@container')} {...restProps}>
21-
<ul
22-
className={cx(
23-
'min-w-fit',
24-
// Hide dividers at the top of the list (overflow-y) and prevents arrow icon from overflowing container when animated to the right (overflow-x)
25-
'overflow-hidden',
26-
// Add a small gap between items that fits the divider lines (this way the divider line don't take up any space in each item)
27-
'grid auto-rows-max gap-y-px',
28-
// Gaps for when the list is displayed in multiple columns
29-
'@lg:gap-x-12 @md:gap-x-9 @sm:gap-x-4 @xl:gap-x-16',
30-
numberofLinks > 5 && [
31-
'@xl:grid-cols-2',
32-
(numberofLinks === 9 || numberofLinks > 10) && '@4xl:grid-cols-3',
33-
],
34-
)}
35-
>
36-
{children}
37-
</ul>
38-
</div>
39-
);
17+
const LinkListContainer = ({
18+
className,
19+
...restProps
20+
}: LinkListContainerProps) => (
21+
<div className={cx(className, 'link-list-container')} {...restProps} />
22+
);
23+
24+
type LinkListProps = React.HTMLProps<HTMLDivElement> & {
25+
children: JSX.Element | JSX.Element[];
4026
};
4127

28+
const LinkList = ({ className, children, ...restProps }: LinkListProps) => (
29+
<LinkListContainer className={className} {...restProps}>
30+
<ul data-slot="link-list">{children}</ul>
31+
</LinkListContainer>
32+
);
33+
4234
type LinkListItemProps = LinkProps & {
4335
children: ReactNode;
4436
isExternal?: boolean;
@@ -64,10 +56,7 @@ const LinkListItem = ({
6456
}
6557

6658
return (
67-
<li
68-
// Creates divider lines that works in any grid layout and with the focus ring
69-
className="after:-top-px relative p-0.75 after:absolute after:right-0 after:left-0 after:h-px after:w-full after:bg-gray-light"
70-
>
59+
<li data-slot="link-list-item">
7160
<Link
7261
{...restProps}
7362
className={cx(
@@ -84,7 +73,9 @@ const LinkListItem = ({
8473

8574
export {
8675
LinkList as UNSAFE_LinkList,
87-
type LinkListProps as UNSAFE_LinkListProps,
76+
LinkListContainer as UNSAFE_LinkListContainer,
8877
LinkListItem as UNSAFE_LinkListItem,
78+
type LinkListContainerProps as UNSAFE_LinkListContainerProps,
8979
type LinkListItemProps as UNSAFE_LinkListItemProps,
80+
type LinkListProps as UNSAFE_LinkListProps,
9081
};

packages/tailwind/tailwind-base.css

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,39 @@
194194
display: none;
195195
}
196196
}
197+
198+
@layer components {
199+
/*** Link List component styles ***/
200+
.link-list-container {
201+
@apply @container;
202+
203+
[data-slot="link-list"] {
204+
@apply @sm:gap-x-4 @md:gap-x-9 @lg:gap-x-12 @xl:gap-x-16;
205+
206+
&:has([data-slot="link-list-item"]:nth-child(6)):not(
207+
:has([data-slot="link-list-item"]:nth-child(9):last-child)
208+
):not(:has([data-slot="link-list-item"]:nth-child(11))) {
209+
@apply @4xl:grid-cols-2;
210+
}
211+
&:has([data-slot="link-list-item"]:nth-child(9):last-child),
212+
&:has([data-slot="link-list-item"]:nth-child(11)) {
213+
@apply @4xl:grid-cols-3;
214+
}
215+
}
216+
}
217+
218+
.link-list,
219+
[data-slot="link-list"] {
220+
/**
221+
* Hides dividers at the top of the list (overflow-y)
222+
* while preventing arrow icons from overflowing container when animated to the right (overflow-x)
223+
*/
224+
@apply min-w-fit overflow-hidden grid auto-rows-max gap-y-px;
225+
}
226+
227+
.link-list-item,
228+
[data-slot="link-list-item"] {
229+
/** Creates divider lines that works in any grid layout and with the focus ring */
230+
@apply after:-top-px relative p-0.75 after:absolute after:right-0 after:left-0 after:h-px after:w-full after:bg-gray-light;
231+
}
232+
}

0 commit comments

Comments
 (0)