Skip to content

Commit 2e6cb05

Browse files
committed
Add tabs implementation
1 parent fe48df2 commit 2e6cb05

31 files changed

Lines changed: 1432 additions & 716 deletions

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
# `<regular-layout>`
22

3-
A library for resizable panel layouts using CSS `grid`.
3+
[![Build Status](https://img.shields.io/github/actions/workflow/status/texodus/regular-layout/build.yaml?event=push&style=for-the-badge)](https://github.com/texodus/regular-layout/actions/workflows/build.yaml)
4+
[![npm](https://img.shields.io/npm/v/regular-layout.svg?style=for-the-badge)](https://www.npmjs.com/package/regular-layout)
45

5-
- 7kb, zero dependencies
6-
- Web Component
7-
-
6+
A library for resizable & repositionable panel layouts, using
7+
[CSS `grid`](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout).
8+
9+
- Zero depedencies, pure TypeScript, tiny.
10+
- Implemented as a [Web Component](https://developer.mozilla.org/en-US/docs/Web/API/Web_components),
11+
interoperable with any framework and fully customizable.
12+
- Covered in bees.
813

914
## Installation
1015

examples/index.css

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
body {
2+
font-family: "ui-monospace", "SFMono-Regular", "SF Mono", "Menlo", "Consolas", "Liberation Mono", monospace;
3+
}
4+
5+
/* Layout */
6+
regular-layout {
7+
overflow: hidden;
8+
position: absolute;
9+
top: 36px;
10+
left: 0;
11+
right: 0;
12+
bottom: 0;
13+
}
14+
15+
/* Frame */
16+
regular-layout-frame {
17+
--titlebar-height: 24px;
18+
position: relative;
19+
box-sizing: border-box;
20+
margin: 3px;
21+
border-radius: 0 0 6px 6px;
22+
border: 1px solid #666;
23+
box-shadow: 0px 6px 6px -4px rgba(150, 150, 180);
24+
}
25+
26+
regular-layout-frame::part(titlebar) {
27+
display: flex;
28+
align-items: stretch;
29+
margin-left: -1px;
30+
margin-right: -1px;
31+
margin-bottom: 0px;
32+
}
33+
34+
regular-layout-frame::part(tab) {
35+
display: flex;
36+
flex: 1 1 150px;
37+
align-items: center;
38+
padding: 0 12px;
39+
cursor: pointer;
40+
max-width: 150px;
41+
text-overflow: ellipsis;
42+
border: 1px solid #666;
43+
border-radius: 6px 6px 0 0;
44+
opacity: 0.5;
45+
}
46+
47+
regular-layout-frame::part(active-tab) {
48+
opacity: 1;
49+
}
50+
51+
/* Frame in Overlay Mode */
52+
regular-layout-frame:not([slot]) {
53+
background-color: rgba(0, 0, 0, 0.2) !important;
54+
border: 1px dashed rgb(0, 0, 0);
55+
border-radius: 6px;
56+
margin: 0;
57+
box-shadow: none;
58+
transition:
59+
top 0.1s ease-in-out,
60+
height 0.1s ease-in-out,
61+
width 0.1s ease-in-out,
62+
left 0.1s ease-in-out;
63+
}
64+
65+
regular-layout-frame::part(container) {
66+
display: none;
67+
}
68+
69+
regular-layout-frame[slot]::part(container) {
70+
display: revert;
71+
}
72+
73+
/* Colors */
74+
regular-layout :nth-child(8n+1),
75+
regular-layout :nth-child(8n+1)::part(tab) {
76+
background-color: #ffadadff;
77+
}
78+
79+
regular-layout :nth-child(8n+2),
80+
regular-layout :nth-child(8n+2)::part(tab) {
81+
background-color: #ffd6a5ff;
82+
}
83+
84+
regular-layout :nth-child(8n+3),
85+
regular-layout :nth-child(8n+3)::part(tab) {
86+
background-color: #fdffb6ff;
87+
}
88+
89+
regular-layout :nth-child(8n+4),
90+
regular-layout :nth-child(8n+4)::part(tab) {
91+
background-color: #caffbfff;
92+
}
93+
94+
regular-layout :nth-child(8n+5),
95+
regular-layout :nth-child(8n+5)::part(tab) {
96+
background-color: #9bf6ffff;
97+
}
98+
99+
regular-layout :nth-child(8n+6),
100+
regular-layout :nth-child(8n+6)::part(tab) {
101+
background-color: #a0c4ffff;
102+
}
103+
104+
regular-layout :nth-child(8n+7),
105+
regular-layout :nth-child(8n+7)::part(tab) {
106+
background-color: #bdb2ffff;
107+
}
108+
109+
regular-layout :nth-child(8n+8),
110+
regular-layout :nth-child(8n+8)::part(tab) {
111+
background-color: #ffc6ffff;
112+
}

examples/index.html

Lines changed: 26 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,28 @@
11
<!DOCTYPE html>
22
<html lang="en">
3-
4-
<head>
5-
<meta charset="UTF-8">
6-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7-
<title>Regular Layout</title>
8-
<style>
9-
regular-layout {
10-
overflow: hidden;
11-
position: absolute;
12-
top: 36px;
13-
left: 0;
14-
right: 0;
15-
bottom: 0;
16-
}
17-
18-
regular-layout-frame {
19-
position: relative;
20-
box-sizing: border-box;
21-
margin: 3px;
22-
border-radius: 0 0 6px 6px;
23-
border: 1px solid #666;
24-
box-shadow: 0px 6px 6px -4px rgba(150, 150, 180);
25-
}
26-
27-
regular-layout-frame[slot=AAA] {
28-
background-color: #ffb3b0;
29-
}
30-
31-
regular-layout-frame[slot=BBB] {
32-
background-color: #b0e0a3;
33-
}
34-
35-
regular-layout-frame[slot=CCC] {
36-
background-color: #99c9ff;
37-
}
38-
39-
regular-layout-frame[slot=DDD] {
40-
background-color: #ffe699;
41-
}
42-
43-
regular-layout-frame[slot=EEE] {
44-
background-color: #ffcf99;
45-
}
46-
47-
regular-layout-frame[slot=FFF] {
48-
background-color: #ff99fd;
49-
}
50-
51-
regular-layout-frame:not([slot]) {
52-
background-color: rgba(0, 0, 0, 0.2);
53-
border: 1px dashed rgb(0,0,0);
54-
border-radius: 6px;
55-
margin: 0;
56-
box-shadow: none;
57-
transition:
58-
top 0.1s ease-in-out,
59-
height 0.1s ease-in-out,
60-
width 0.1s ease-in-out,
61-
left 0.1s ease-in-out;
62-
}
63-
64-
regular-layout-frame::part(container) {
65-
display: none;
66-
}
67-
68-
regular-layout-frame[slot]::part(container) {
69-
display:revert;
70-
}
71-
72-
73-
regular-layout-frame {
74-
--titlebar-height: 24px;
75-
}
76-
77-
regular-layout-frame::part(titlebar) {
78-
display: flex;
79-
align-items: center;
80-
padding: 0 12px;
81-
cursor: pointer;
82-
background-color: inherit;
83-
border: 1px solid #666;
84-
border-radius: 6px 6px 0 0;
85-
margin-left: -1px;
86-
margin-right: -1px;
87-
margin-bottom: 0px;
88-
max-width: 200px;
89-
}
90-
</style>
91-
</head>
92-
93-
<body>
94-
<header>
95-
<button id="add">Add Panel</button>
96-
</header>
97-
<regular-layout>
98-
<regular-layout-frame slot="AAA"></regular-layout-frame>
99-
<regular-layout-frame slot="BBB"></regular-layout-frame>
100-
<regular-layout-frame slot="CCC"></regular-layout-frame>
101-
<regular-layout-frame slot="DDD"></regular-layout-frame>
102-
<regular-layout-frame slot="EEE"></regular-layout-frame>
103-
<regular-layout-frame slot="FFF"></regular-layout-frame>
104-
</regular-layout>
105-
</body>
106-
107-
<script type="module">
108-
import "/dist/index.js";
109-
110-
const add = document.querySelector("#add")
111-
add.addEventListener("click", () => {
112-
const chars = 'abcdefghijklmnopqrstuvwxyz';
113-
let name = '';
114-
for (let i = 0; i < 8; i++) {
115-
name += chars.charAt(Math.floor(Math.random() * chars.length));
116-
}
117-
118-
const elem = document.createElement("regular-layout-frame");
119-
elem.setAttribute("slot", name);
120-
layout.appendChild(elem);
121-
layout.insertPanel(name, []);
122-
})
123-
124-
const layout = document.querySelector("regular-layout");
125-
fetch("./layout.json").then(req => req.json()).then(x => layout.restore(x));
126-
</script>
127-
128-
</html>
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Regular Layout</title>
7+
<link rel="stylesheet" href="./index.css">
8+
<script type="module" src="./index.js"></script>
9+
</head>
10+
<body>
11+
<header>
12+
<button id="add">Add Panel</button>
13+
<button id="save">Save State</button>
14+
<button id="restore">Restore State</button>
15+
<button id="clear">Clear</button>
16+
</header>
17+
<regular-layout>
18+
<regular-layout-frame slot="AAA"></regular-layout-frame>
19+
<regular-layout-frame slot="BBB"></regular-layout-frame>
20+
<regular-layout-frame slot="CCC"></regular-layout-frame>
21+
<regular-layout-frame slot="DDD"></regular-layout-frame>
22+
<regular-layout-frame slot="EEE"></regular-layout-frame>
23+
<regular-layout-frame slot="FFF"></regular-layout-frame>
24+
<regular-layout-frame slot="GGG"></regular-layout-frame>
25+
<regular-layout-frame slot="HHH"></regular-layout-frame>
26+
</regular-layout>
27+
</body>
28+
</html>

examples/index.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
2+
// ░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░█░░░█▀█░█░█░█▀█░█░█░▀█▀░▀▄░░░░░░░░
3+
// ░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░█░░░█▀█░░█░░█░█░█░█░░█░░░▄▀░░░░░░░
4+
// ░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░▀▀▀░▀░▀░░▀░░▀▀▀░▀▀▀░░▀░░▀░░░░░░░░░
5+
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
6+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
7+
// ┃ * Copyright (c) 2026, the Regular Layout Authors. This file is part * ┃
8+
// ┃ * of the Regular Layout library, distributed under the terms of the * ┃
9+
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11+
12+
import "/dist/index.js";
13+
14+
const add = document.querySelector("#add");
15+
const save = document.querySelector("#save");
16+
const restore = document.querySelector("#restore");
17+
const clear = document.querySelector("#clear");
18+
add.addEventListener("click", () => {
19+
// Note: this *demo* implementation leaks `div` elements, because they
20+
// are not removed from the light DOM by `clear` or `restore`. You must
21+
// handle the lifecycle of the light DOM objects yourself!
22+
const chars = "abcdefghijklmnopqrstuvwxyz";
23+
let name = "";
24+
for (let i = 0; i < 8; i++) {
25+
name += chars.charAt(Math.floor(Math.random() * chars.length));
26+
}
27+
28+
const COLORS = ["AAA", "BBB", "CCC", "DDD", "EEE", "FFF"];
29+
const elem = document.createElement("regular-layout-frame");
30+
elem.setAttribute("slot", name);
31+
elem.classList.add(COLORS[Math.floor(COLORS.length * Math.random())]);
32+
layout.appendChild(elem);
33+
layout.insertPanel(name, []);
34+
});
35+
36+
const req = await fetch("./layout.json");
37+
let state = await req.json();
38+
39+
const layout = document.querySelector("regular-layout");
40+
layout.restore(state);
41+
save.addEventListener("click", () => {
42+
state = layout.save();
43+
});
44+
45+
restore.addEventListener("click", () => {
46+
if (state) {
47+
layout.restore(state);
48+
}
49+
});
50+
51+
clear.addEventListener("click", () => {
52+
layout.clear();
53+
});

examples/layout.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@
1212
"children": [
1313
{
1414
"type": "child-panel",
15-
"child": "AAA"
15+
"child": ["AAA"]
1616
},
1717
{
1818
"type": "split-panel",
1919
"orientation": "vertical",
2020
"children": [
2121
{
2222
"type": "child-panel",
23-
"child": "BBB"
23+
"child": ["BBB"]
2424
},
2525
{
2626
"type": "child-panel",
27-
"child": "CCC"
27+
"child": ["CCC"]
2828
}
2929
],
3030
"sizes": [
@@ -44,11 +44,11 @@
4444
"children": [
4545
{
4646
"type": "child-panel",
47-
"child": "DDD"
47+
"child": ["DDD"]
4848
},
4949
{
5050
"type": "child-panel",
51-
"child": "EEE"
51+
"child": ["EEE"]
5252
}
5353
],
5454
"sizes": [
@@ -64,7 +64,7 @@
6464
},
6565
{
6666
"type": "child-panel",
67-
"child": "FFF"
67+
"child": ["FFF", "GGG", "HHH"]
6868
}
6969
],
7070
"sizes": [

0 commit comments

Comments
 (0)