Skip to content

Commit ff367f5

Browse files
authored
Merge branch 'main' into progress
2 parents 63db509 + a05560b commit ff367f5

File tree

16 files changed

+12041
-9570
lines changed

16 files changed

+12041
-9570
lines changed

.changeset/little-llamas-brake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik-ui/headless': patch
3+
---
4+
5+
Enable select item indicator styling by passing down properties

.changeset/rich-windows-admire.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik-ui/headless': patch
3+
---
4+
5+
fix: Only run single modal close step

.github/actions/setup/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ runs:
2525
- name: Setup pnpm
2626
uses: pnpm/[email protected]
2727
with:
28-
version: 8
28+
version: 9.3.0
2929

3030
- name: Use Node
3131
uses: actions/setup-node@v4

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@ jobs:
4040
with:
4141
node_version: 20
4242
- run: pnpm build.headless
43-
- run: pnpx pkg-pr-new publish ./dist/packages/kit-headless
43+
- run: pnpx pkg-pr-new publish --compact ./dist/packages/kit-headless
4444
env:
4545
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GITHUB_TOKEN is provided automatically in any repository

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ node_modules
1313
.rollup.cache
1414
tsconfig.tsbuildinfo
1515
.nx/cache
16+
.nx/workspace-data
1617

1718
# IDE - VSCode
1819
.vscode/*

.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
.nx/cache
3+
.nx/workspace-data
34
coverage
Lines changed: 127 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,117 @@
1-
import {
2-
$,
3-
QwikIntrinsicElements,
4-
component$,
5-
useSignal,
6-
useVisibleTask$,
7-
} from '@builder.io/qwik';
81
import { ContentHeading } from '@builder.io/qwik-city';
92
import { cn } from '@qwik-ui/utils';
3+
import { component$, useSignal, useVisibleTask$, $ } from '@builder.io/qwik';
104

115
export const DashboardTableOfContents = component$(
126
({ headings }: { headings: ContentHeading[] }) => {
13-
const itemIds = headings.map((item) => item.id);
14-
const activeHeading = useActiveItem(itemIds);
15-
167
if (headings.length === 0) {
178
return null;
189
}
19-
2010
return (
2111
<div class="space-y-2">
2212
<div class="font-medium">On This Page</div>
23-
<Tree headings={headings} activeItem={activeHeading.value} />
13+
<TableOfContent headings={headings} />
2414
</div>
2515
);
2616
},
2717
);
2818

19+
type TableOfContentProps = { headings: ContentHeading[] };
20+
interface Node extends ContentHeading {
21+
level: number;
22+
children: Array<Node>;
23+
activeItem: string;
24+
}
25+
type Tree = Array<Node>;
26+
export const TableOfContent = component$<TableOfContentProps>((props) => {
27+
const inifiniteStopper = props.headings.map((heading) => {
28+
return { text: heading.text, id: heading.id, level: heading.level };
29+
});
30+
const itemIds = props.headings.map((item) => item.id);
31+
const activeHeading = useActiveItem(itemIds);
32+
const tree = getTree(inifiniteStopper);
33+
return <RecursiveJSX tree={tree[0]} activeItem={activeHeading.value} />;
34+
});
35+
36+
function deltaToStrg(
37+
currNode: Node,
38+
nextNode: Node,
39+
): 'same level' | 'down one level' | 'up one level' {
40+
const delta = currNode.level - nextNode.level;
41+
if (delta === 1) {
42+
return 'up one level';
43+
}
44+
if (delta === -1) {
45+
return 'down one level';
46+
}
47+
48+
if (delta === 0) {
49+
return 'same level';
50+
}
51+
throw new Error(
52+
`bad headings: are not continous from: #${currNode.id} to #${nextNode.id}`,
53+
);
54+
}
55+
function getTree(nodes: ContentHeading[]) {
56+
let currNode = nodes[0] as Node;
57+
currNode.children = [];
58+
const tree = [currNode];
59+
const childrenMap = new Map<number, Tree>();
60+
childrenMap.set(currNode.level, currNode.children);
61+
for (let index = 1; index < nodes.length; index++) {
62+
const nextNode = nodes[index] as Node;
63+
nextNode.children = [];
64+
childrenMap.set(nextNode.level, nextNode.children);
65+
const deltaStrg = deltaToStrg(currNode, nextNode);
66+
switch (deltaStrg) {
67+
case 'up one level': {
68+
const grandParent = childrenMap.get(currNode.level - 2);
69+
grandParent?.push(nextNode);
70+
break;
71+
}
72+
case 'same level': {
73+
const parent = childrenMap.get(currNode.level - 1);
74+
parent?.push(nextNode);
75+
break;
76+
}
77+
case 'down one level': {
78+
currNode.children.push(nextNode);
79+
break;
80+
}
81+
default:
82+
break;
83+
}
84+
currNode = nextNode;
85+
}
86+
return tree;
87+
}
88+
type RecursiveJSXProps = {
89+
tree: Node;
90+
activeItem: string;
91+
limit?: number;
92+
};
93+
const RecursiveJSX = component$<RecursiveJSXProps>(({ tree, activeItem, limit = 3 }) => {
94+
const currNode: Node = tree;
95+
return currNode?.children?.length && currNode.level < limit ? (
96+
<ul class={cn('m-0 list-none', { 'pl-4': currNode.level !== 1 })}>
97+
{currNode.children.map((childNode) => {
98+
return (
99+
<li key={currNode.id} class={cn('mt-0 list-none pt-2')}>
100+
<Anchor node={childNode} activeItem={activeItem} />
101+
{childNode.children.length ? (
102+
<>
103+
<RecursiveJSX tree={childNode} activeItem={activeItem} />
104+
</>
105+
) : null}
106+
</li>
107+
);
108+
})}
109+
</ul>
110+
) : null;
111+
});
112+
29113
const useActiveItem = (itemIds: string[]) => {
30-
const activeId = useSignal<string>();
114+
const activeId = useSignal<string>('');
31115

32116
useVisibleTask$(({ cleanup }) => {
33117
const observer = new IntersectionObserver(
@@ -60,45 +144,35 @@ const useActiveItem = (itemIds: string[]) => {
60144

61145
return activeId;
62146
};
63-
64-
type TreeProps = QwikIntrinsicElements['ul'] & {
65-
headings: ContentHeading[];
66-
level?: number;
67-
activeItem?: string;
147+
type AnchorThingProps = {
148+
node: Node;
149+
activeItem: string;
68150
};
69-
70-
const Tree = component$<TreeProps>(({ headings, level = 1, activeItem }) => {
71-
return headings.length > 0 && level < 3 ? (
72-
<ul class={cn('m-0 list-none', { 'pl-4': level !== 1 })}>
73-
{headings.map((heading) => {
74-
return (
75-
<li key={heading.id} class={cn('mt-0 pt-2')}>
76-
<a
77-
href={`#${heading.id}`}
78-
onClick$={[
79-
$(() => {
80-
const element = document.getElementById(heading.id);
81-
if (element) {
82-
const navbarHeight = 90;
83-
const elementPosition =
84-
element.getBoundingClientRect().top + window.scrollY - navbarHeight;
85-
window.scrollTo({ top: elementPosition, behavior: 'auto' });
86-
}
87-
}),
88-
]}
89-
class={cn(
90-
heading.level > 2 ? 'ml-4' : null,
91-
'inline-block no-underline transition-colors hover:text-foreground',
92-
heading.id === `${activeItem}`
93-
? 'font-medium text-foreground'
94-
: 'text-muted-foreground',
95-
)}
96-
>
97-
{heading.text}
98-
</a>
99-
</li>
100-
);
101-
})}
102-
</ul>
103-
) : null;
151+
export const Anchor = component$<AnchorThingProps>((props) => {
152+
const currNode = props.node;
153+
const activeItem = props.activeItem;
154+
const isActiveItem = currNode.id === `${activeItem}`;
155+
return (
156+
<a
157+
href={`#${currNode.id}`}
158+
onClick$={[
159+
$(() => {
160+
const element = document.getElementById(currNode.id);
161+
if (element) {
162+
const navbarHeight = 90;
163+
const elementPosition =
164+
element.getBoundingClientRect().top + window.scrollY - navbarHeight;
165+
window.scrollTo({ top: elementPosition, behavior: 'auto' });
166+
}
167+
}),
168+
]}
169+
class={cn(
170+
currNode.level > 2 ? 'ml-4' : null,
171+
'inline-block no-underline transition-colors hover:text-foreground',
172+
isActiveItem ? 'font-medium text-foreground' : 'text-muted-foreground',
173+
)}
174+
>
175+
{currNode.text}
176+
</a>
177+
);
104178
});

apps/website/src/routes/docs/headless/separator/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ A separator separates and distinguishes sections of content or groups of menuite
1414

1515
Qwik UI's Separator implementation follows [WAI-Aria separator role requirements](https://www.w3.org/TR/wai-aria-1.2/#separator). This separator is static and should not be used for interactivity.
1616

17-
##### ✨ Features
17+
## ✨ Features
1818

1919
- Supports both horizontal and vertical orientation
2020

apps/website/src/routes/docs/styled/alert/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ import { Alert } from '~/components/ui';
8787

8888
```tsx
8989
<Alert.Root>
90-
<Terminal className="h-4 w-4" />
90+
<Terminal class="h-4 w-4" />
9191
<Alert.Title>Heads up!</Alert.Title>
9292
<Alert.Description>
9393
You can add components and dependencies to your app using the cli.

apps/website/src/routes/docs/styled/skeleton/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import { Skeleton } from '~/components/ui';
3838
```
3939

4040
```tsx
41-
<Skeleton className="h-6 w-24 rounded-full" />
41+
<Skeleton class="h-6 w-24 rounded-full" />
4242
```
4343

4444
## Examples

0 commit comments

Comments
 (0)