Skip to content

Commit 77fba09

Browse files
authored
Few minor improvements (#351)
Signed-off-by: Nik Nasr <[email protected]>
1 parent d6b8c6e commit 77fba09

File tree

4 files changed

+112
-74
lines changed

4 files changed

+112
-74
lines changed

libs/ui/icons/src/lib/Icons.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ import {
4747
Radio,
4848
ClockAlert,
4949
Play,
50+
Code,
51+
ChartNoAxesColumn,
5052
} from 'lucide-react';
5153
import type { LucideIcon } from 'lucide-react';
5254
import { tv } from '@restate/util/styles';
@@ -136,6 +138,8 @@ export const enum IconName {
136138
Eye = 'Eye',
137139
ClockAlert = 'ClockAlert',
138140
Play = 'Play',
141+
Code = 'Code',
142+
Usage = 'Usage',
139143
}
140144
export interface IconsProps {
141145
name: IconName;
@@ -210,6 +214,8 @@ const ICONS: Record<IconName, LucideIcon> = {
210214
[IconName.ClockAlert]: ClockAlert,
211215
[IconName.Eye]: Eye,
212216
[IconName.Play]: Play,
217+
[IconName.Code]: Code,
218+
[IconName.Usage]: ChartNoAxesColumn,
213219
};
214220

215221
const styles = tv({

libs/ui/nav/src/lib/Nav.tsx

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,46 @@ import {
1515
DropdownPopover,
1616
DropdownTrigger,
1717
} from '@restate/ui/dropdown';
18-
import { NavItem } from './NavItem';
18+
import { NavItem, NavSearchItem, useGetHrefFromSearch } from './NavItem';
1919
import { Button } from '@restate/ui/button';
2020
import { useLocation } from 'react-router';
2121
import { Icon, IconName } from '@restate/ui/icons';
2222

2323
interface NavProps {
2424
className?: string;
2525
ariaCurrentValue?: AriaAttributes['aria-current'];
26-
children: ReactElement<ComponentProps<typeof NavItem>>[];
26+
children: ReactElement<
27+
ComponentProps<typeof NavItem | typeof NavSearchItem>
28+
>[];
29+
layout?: 'vertical' | 'horizontal';
2730
}
2831

2932
const styles = tv({
3033
base: 'flex items-center gap-0',
34+
slots: {
35+
indicator:
36+
'absolute rounded-xl border border-black/10 bg-white shadow-xs transition-all duration-300 ease-in-out [&:not(:has(+ul>li>*[data-active=true]))]:hidden',
37+
},
38+
variants: {
39+
layout: {
40+
vertical: { base: 'flex-col [&>*]:w-full', indicator: 'left-0 w-full' },
41+
horizontal: {
42+
base: '',
43+
indicator: 'top-0 h-full',
44+
},
45+
},
46+
},
3147
});
3248

33-
export function Nav({ children, className, ariaCurrentValue }: NavProps) {
49+
export function Nav({
50+
children,
51+
className,
52+
ariaCurrentValue,
53+
layout = 'horizontal',
54+
}: NavProps) {
3455
const containerElementRef = useRef<HTMLDivElement | null>(null);
3556
const activeIndicatorElement = useRef<HTMLDivElement | null>(null);
57+
const { base, indicator } = styles({ layout });
3658

3759
useEffect(() => {
3860
const updateStyle = (activeElement: Node | null) => {
@@ -41,8 +63,13 @@ export function Nav({ children, className, ariaCurrentValue }: NavProps) {
4163
activeElement.dataset.active === 'true' &&
4264
activeIndicatorElement.current
4365
) {
44-
activeIndicatorElement.current.style.width = `${activeElement.clientWidth}px`;
45-
activeIndicatorElement.current.style.left = `${activeElement.offsetLeft}px`;
66+
if (layout === 'vertical') {
67+
activeIndicatorElement.current.style.height = `${activeElement.clientHeight}px`;
68+
activeIndicatorElement.current.style.top = `${activeElement.offsetTop}px`;
69+
} else {
70+
activeIndicatorElement.current.style.width = `${activeElement.clientWidth}px`;
71+
activeIndicatorElement.current.style.left = `${activeElement.offsetLeft}px`;
72+
}
4673
}
4774
};
4875
const callback: MutationCallback = (mutationList) => {
@@ -86,21 +113,19 @@ export function Nav({ children, className, ariaCurrentValue }: NavProps) {
86113
detailsElement?.removeEventListener('toggle', updateStyleWithDetails);
87114
window.removeEventListener('resize', updateStyleWithDetails);
88115
};
89-
}, []);
116+
}, [layout]);
90117

91118
const location = useLocation();
119+
const getHref = useGetHrefFromSearch();
92120

93121
return (
94122
<NavContext.Provider value={{ value: ariaCurrentValue }}>
95123
<div
96124
className="relative hidden rounded-xl border-[0.5px] border-transparent lg:block [&:has(a:focus)]:border-[0.5px] [&:has(a:focus)]:border-zinc-800/5 [&:has(a:focus)]:bg-black/3 [&:has(a:focus)]:shadow-[inset_0_1px_0px_0px_rgba(0,0,0,0.03)] [&:has(a:hover)]:border-[0.5px] [&:has(a:hover)]:border-zinc-800/5 [&:has(a:hover)]:bg-black/3 [&:has(a:hover)]:shadow-[inset_0_1px_0px_0px_rgba(0,0,0,0.03)]"
97125
ref={containerElementRef}
98126
>
99-
<div
100-
className="absolute top-0 h-full rounded-xl border border-black/10 bg-white shadow-xs transition-all duration-300 ease-in-out [&:not(:has(+ul>li>a[data-active=true]))]:hidden"
101-
ref={activeIndicatorElement}
102-
/>
103-
<ul className={styles({ className })}>{children}</ul>
127+
<div className={indicator()} ref={activeIndicatorElement} />
128+
<ul className={base({ className })}>{children}</ul>
104129
</div>
105130
<div className="lg:hidden">
106131
<Dropdown>
@@ -112,9 +137,13 @@ export function Nav({ children, className, ariaCurrentValue }: NavProps) {
112137
{Children.map(children, (child) => (
113138
<span
114139
className={
115-
location.pathname.startsWith(child.props.href)
116-
? ''
117-
: 'hidden'
140+
'href' in child.props
141+
? location.pathname.startsWith(child.props.href)
142+
? ''
143+
: 'hidden'
144+
: location.search.includes(child.props.search)
145+
? ''
146+
: 'hidden'
118147
}
119148
>
120149
{child.props.children}
@@ -138,7 +167,18 @@ export function Nav({ children, className, ariaCurrentValue }: NavProps) {
138167
.filter((href) => location.pathname.startsWith(href))}
139168
>
140169
{Children.map(children, (child) => (
141-
<DropdownItem href={child.props.href} value={child.props.href}>
170+
<DropdownItem
171+
href={
172+
'href' in child.props
173+
? child.props.href
174+
: getHref(child.props.search).href
175+
}
176+
value={
177+
'href' in child.props
178+
? child.props.href
179+
: child.props.search
180+
}
181+
>
142182
{child.props.children}
143183
</DropdownItem>
144184
))}

libs/ui/nav/src/lib/NavItem.tsx

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useLocation, useNavigation } from 'react-router';
22
import { focusRing } from '@restate/ui/focus';
33
import { Link } from '@restate/ui/link';
4-
import { PropsWithChildren, useContext, useMemo } from 'react';
4+
import { PropsWithChildren, useCallback, useContext, useMemo } from 'react';
55
import { tv } from '@restate/util/styles';
66
import { NavContext } from './NavContext';
77
import { Button } from '@restate/ui/button';
@@ -16,7 +16,7 @@ const styles = tv({
1616
base: 'group isolate flex cursor-default rounded-xl px-3 py-1.5 text-center text-sm no-underline transition hover:bg-black/3 pressed:bg-gray-200',
1717
variants: {
1818
isCurrent: {
19-
true: 'text-gray-800',
19+
true: 'font-medium text-gray-700',
2020
false: 'text-gray-500',
2121
},
2222
},
@@ -51,7 +51,7 @@ export function NavItem({
5151
return (
5252
<li>
5353
<Link
54-
className={styles()}
54+
className={styles({ isCurrent: isActive })}
5555
href={preserveSearchParams ? `${href}${search}${location.hash}` : href}
5656
data-active={isActive}
5757
{...(isActive && { 'aria-current': value })}
@@ -65,40 +65,56 @@ export function NavItem({
6565
interface NavSearchItemProps {
6666
search: string;
6767
}
68-
export function NavSearchItem({
69-
children,
70-
search,
71-
}: PropsWithChildren<NavSearchItemProps>) {
68+
69+
export function useGetHrefFromSearch() {
7270
const currentLocation = useLocation();
7371
const { location: nextLocation, state } = useNavigation();
7472
const location = state === 'loading' ? nextLocation : currentLocation;
75-
const currentSearchParams = new URLSearchParams(location.search);
76-
currentSearchParams.sort();
77-
const targetSearchParams = new URLSearchParams(search);
78-
const keys = Array.from(targetSearchParams.keys());
79-
const excludingNewParams = keys.reduce((search, key) => {
80-
search.delete(key);
81-
return search;
82-
}, new URLSearchParams(location.search));
83-
const withNewParams = new URLSearchParams([
84-
...excludingNewParams,
85-
...new URLSearchParams(search),
86-
]);
87-
const withNewParamsSorted = new URLSearchParams(withNewParams);
88-
withNewParamsSorted.sort();
8973

90-
const isActive =
91-
currentSearchParams.toString() === withNewParamsSorted.toString();
92-
const targetSearch =
93-
targetSearchParams.size > 0 ? `?${withNewParams.toString()}` : '';
74+
const getHref = useCallback(
75+
(search: string) => {
76+
const currentSearchParams = new URLSearchParams(location.search);
77+
currentSearchParams.sort();
78+
const targetSearchParams = new URLSearchParams(search);
79+
const keys = Array.from(targetSearchParams.keys());
80+
const excludingNewParams = keys.reduce((search, key) => {
81+
search.delete(key);
82+
return search;
83+
}, new URLSearchParams(location.search));
84+
const withNewParams = new URLSearchParams([
85+
...excludingNewParams,
86+
...new URLSearchParams(search),
87+
]);
88+
const withNewParamsSorted = new URLSearchParams(withNewParams);
89+
withNewParamsSorted.sort();
90+
91+
const targetSearch =
92+
targetSearchParams.size > 0 ? `?${withNewParams.toString()}` : '';
93+
return {
94+
href: `${location.pathname}${targetSearch}${location.hash}`,
95+
isActive:
96+
currentSearchParams.toString() === withNewParamsSorted.toString(),
97+
};
98+
},
99+
[location.hash, location.pathname, location.search],
100+
);
101+
102+
return getHref;
103+
}
94104

105+
export function NavSearchItem({
106+
children,
107+
search,
108+
}: PropsWithChildren<NavSearchItemProps>) {
109+
const getHref = useGetHrefFromSearch();
110+
const { href, isActive } = getHref(search);
95111
const { value } = useContext(NavContext);
96112

97113
return (
98114
<li>
99115
<Link
100116
className={styles()}
101-
href={`${location.pathname}${targetSearch}${location.hash}`}
117+
href={href}
102118
data-active={isActive}
103119
{...(isActive && { 'aria-current': value })}
104120
>

libs/ui/section/src/lib/Section.tsx

Lines changed: 9 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,52 @@
1-
import { createContext, PropsWithChildren, use } from 'react';
1+
import { PropsWithChildren } from 'react';
22
import { tv } from '@restate/util/styles';
33

44
interface SectionProps {
55
className?: string;
6-
variant?: 'standard' | 'two-cols';
76
}
87

9-
const SectionContext = createContext<{ variant: 'standard' | 'two-cols' }>({
10-
variant: 'standard',
11-
});
12-
138
const styles = tv({
14-
base: 'section',
15-
variants: {
16-
variant: {
17-
standard: 'flex flex-col rounded-xl bg-gray-100 p-0.5',
18-
'two-cols': 'grid grid-cols-1 gap-x-10 gap-y-4 sm:grid-cols-[20ch_1fr]',
19-
},
20-
},
21-
defaultVariants: {
22-
variant: 'standard',
23-
},
9+
base: 'section flex flex-col rounded-xl bg-gray-100 p-0.5',
2410
});
2511
export function Section({
2612
children,
2713
className,
28-
variant = 'standard',
2914
}: PropsWithChildren<SectionProps>) {
30-
return (
31-
<SectionContext.Provider value={{ variant }}>
32-
<section className={styles({ className, variant })}>{children}</section>
33-
</SectionContext.Provider>
34-
);
15+
return <section className={styles({ className })}>{children}</section>;
3516
}
3617

3718
interface SectionTitleProps {
3819
className?: string;
20+
variant?: 'standard' | 'settings';
3921
}
4022

4123
const stylesSectionTitle = tv({
4224
base: '',
4325
variants: {
4426
variant: {
4527
standard: 'px-2 pt-2 pb-1 text-xs font-semibold text-gray-400 uppercase',
46-
'two-cols':
47-
'col-start-1 flex flex-col gap-1 text-base leading-7 font-semibold text-gray-900 sm:pt-3 [&_p]:text-sm [&_p]:leading-6 [&_p]:font-normal [&_p]:text-gray-600',
28+
settings:
29+
'mb-2 flex flex-col gap-0.5 px-2 text-base leading-7 font-semibold text-gray-900 sm:pt-3 [&_a]:text-gray-500 [&_p]:text-sm [&_p]:leading-6 [&_p]:font-normal [&_p]:text-gray-500',
4830
},
4931
},
5032
});
5133
export function SectionTitle({
5234
children,
5335
className,
36+
variant,
5437
}: PropsWithChildren<SectionTitleProps>) {
55-
const { variant } = use(SectionContext);
5638
return (
5739
<h3 className={stylesSectionTitle({ className, variant })}>{children}</h3>
5840
);
5941
}
6042

6143
const stylesSectionContent = tv({
62-
base: '',
44+
base: 'rounded-[calc(0.75rem-0.125rem)] p-3 text-sm',
6345
variants: {
6446
raised: {
6547
true: 'border bg-white shadow-xs',
6648
false: '',
6749
},
68-
variant: {
69-
standard: 'rounded-[calc(0.75rem-0.125rem)] p-3 text-sm',
70-
'two-cols': 'col-start-1 min-w-0 sm:col-start-2',
71-
},
7250
},
7351
defaultVariants: {
7452
raised: true,
@@ -82,10 +60,8 @@ export function SectionContent({
8260
className?: string;
8361
raised?: boolean;
8462
}>) {
85-
const { variant } = use(SectionContext);
86-
8763
return (
88-
<div className={stylesSectionContent({ className, raised, variant })}>
64+
<div className={stylesSectionContent({ className, raised })}>
8965
{children}
9066
</div>
9167
);

0 commit comments

Comments
 (0)