@ponti-studios/ui 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/data-display/accordion.tsx +10 -1
- package/src/components/feedback/empty-state.tsx +1 -1
- package/src/components/feedback/file-upload-status.tsx +14 -6
- package/src/components/feedback/progress-bar.tsx +3 -2
- package/src/components/feedback/spinner.tsx +3 -4
- package/src/components/feedback/update-guard.stories.tsx +1 -1
- package/src/components/feedback/update-guard.tsx +7 -3
- package/src/components/filters/active-filters-bar.stories.tsx +36 -36
- package/src/components/filters/active-filters-bar.tsx +21 -23
- package/src/components/filters/filter-select.stories.tsx +26 -26
- package/src/components/filters/filter-select.tsx +5 -8
- package/src/components/filters/index.ts +0 -4
- package/src/components/forms/date-month-select.tsx +2 -2
- package/src/components/forms/date-picker.tsx +2 -2
- package/src/components/forms/drop-zone.stories.tsx +112 -28
- package/src/components/forms/drop-zone.tsx +265 -112
- package/src/components/forms/input.tsx +5 -1
- package/src/components/forms/radio-group.tsx +3 -2
- package/src/components/forms/select.tsx +14 -4
- package/src/components/forms/slider.stories.tsx +2 -2
- package/src/components/forms/slider.tsx +12 -9
- package/src/components/forms/switch.tsx +5 -2
- package/src/components/forms/textarea.tsx +5 -1
- package/src/components/layout/index.ts +0 -2
- package/src/components/layout/section-intro.tsx +1 -1
- package/src/components/layout/surface-panel.tsx +10 -2
- package/src/components/navigation/app-navigation.stories.tsx +5 -1
- package/src/components/navigation/app-navigation.tsx +19 -4
- package/src/components/navigation/tabs.tsx +4 -1
- package/src/components/overlays/command.tsx +39 -14
- package/src/components/overlays/popover.stories.tsx +16 -4
- package/src/components/primitives/badge.tsx +1 -1
- package/src/components/primitives/button.tsx +6 -1
- package/src/hooks/use-derived-filter-state.ts +1 -3
- package/src/hooks/use-filter-state.ts +1 -2
- package/src/native/card.tsx +3 -1
- package/src/native/index.ts +21 -11
- package/src/native/list-row.tsx +67 -0
- package/src/styles/tokens/generated/native.generated.ts +34 -6
- package/src/styles/tokens/index.ts +2 -0
- package/src/styles/tokens/normalize-native.ts +5 -3
- package/src/styles/tokens/source/native.tokens.json +42 -7
- package/src/styles/tokens/style-dictionary.native.mjs +4 -1
- package/src/components/filters/filters.stories.tsx +0 -72
- package/src/components/filters/filters.tsx +0 -78
- package/src/components/filters/search-filter-bar.stories.tsx +0 -41
- package/src/components/filters/search-filter-bar.tsx +0 -48
|
@@ -27,18 +27,21 @@ const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
|
|
|
27
27
|
) => (
|
|
28
28
|
<SliderPrimitive.Root
|
|
29
29
|
ref={ref}
|
|
30
|
-
className={cn("relative flex w-full touch-none items-center select-none", className)}
|
|
31
30
|
{...props}
|
|
32
31
|
onValueChange={(value) => onValueChange?.(Array.isArray(value) ? [...value] : [value])}
|
|
33
32
|
>
|
|
34
|
-
<SliderPrimitive.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
<SliderPrimitive.Control
|
|
34
|
+
className={cn("relative flex w-full touch-none items-center py-2 select-none", className)}
|
|
35
|
+
>
|
|
36
|
+
<SliderPrimitive.Track className="bg-muted relative h-2 w-full grow rounded-full">
|
|
37
|
+
<SliderPrimitive.Indicator className="bg-primary absolute h-full rounded-full" />
|
|
38
|
+
<SliderPrimitive.Thumb
|
|
39
|
+
aria-label={ariaLabel ?? "Slider"}
|
|
40
|
+
aria-labelledby={ariaLabelledby}
|
|
41
|
+
className="border-border focus-visible:border-ring focus-visible:bg-primary focus-visible:ring-ring bg-background z-10 block h-6 w-6 rounded-full border shadow-sm ring-1 ring-border/5 transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50"
|
|
42
|
+
/>
|
|
43
|
+
</SliderPrimitive.Track>
|
|
44
|
+
</SliderPrimitive.Control>
|
|
42
45
|
</SliderPrimitive.Root>
|
|
43
46
|
),
|
|
44
47
|
);
|
|
@@ -15,7 +15,10 @@ function Switch({
|
|
|
15
15
|
data-slot="switch"
|
|
16
16
|
data-size={size}
|
|
17
17
|
className={cn(
|
|
18
|
-
"peer
|
|
18
|
+
"peer inline-flex shrink-0 items-center rounded-full border border-transparent p-px",
|
|
19
|
+
"data-checked:bg-primary data-unchecked:bg-input data-checked:justify-end data-unchecked:justify-start",
|
|
20
|
+
"focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none",
|
|
21
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
19
22
|
size === "default" ? "h-[1.15rem] w-8" : "h-3.5 w-6",
|
|
20
23
|
className,
|
|
21
24
|
)}
|
|
@@ -25,7 +28,7 @@ function Switch({
|
|
|
25
28
|
data-slot="switch-thumb"
|
|
26
29
|
data-size={size}
|
|
27
30
|
className={cn(
|
|
28
|
-
"pointer-events-none block rounded-full bg-
|
|
31
|
+
"pointer-events-none block rounded-full bg-background shadow-sm ring-1 ring-border/5 transition-all",
|
|
29
32
|
size === "default" ? "size-4" : "size-3",
|
|
30
33
|
)}
|
|
31
34
|
/>
|
|
@@ -7,7 +7,11 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
|
7
7
|
<textarea
|
|
8
8
|
data-slot="textarea"
|
|
9
9
|
className={cn(
|
|
10
|
-
"
|
|
10
|
+
"border-input flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base transition-colors md:text-sm",
|
|
11
|
+
"placeholder:text-muted-foreground",
|
|
12
|
+
"focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none",
|
|
13
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
14
|
+
"aria-invalid:ring-destructive/20 aria-invalid:border-destructive",
|
|
11
15
|
className,
|
|
12
16
|
)}
|
|
13
17
|
{...props}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
export { ScrollArea } from "./scroll-area";
|
|
2
2
|
export type { ScrollAreaProps } from "./scroll-area";
|
|
3
|
-
export { SearchFilterBar } from "../filters/search-filter-bar";
|
|
4
|
-
export type { SearchFilterBarProps } from "../filters/search-filter-bar";
|
|
5
3
|
export { SectionIntro } from "./section-intro";
|
|
6
4
|
export type { SectionIntroProps } from "./section-intro";
|
|
7
5
|
export { SurfacePanel } from "./surface-panel";
|
|
@@ -21,7 +21,7 @@ export function SectionIntro({ eyebrow, title, description, actions }: SectionIn
|
|
|
21
21
|
</div>
|
|
22
22
|
) : null}
|
|
23
23
|
<div className="space-y-3">
|
|
24
|
-
<h1 className="text-
|
|
24
|
+
<h1 className="text-2xl text-foreground font-semibold tracking-tight">{title}</h1>
|
|
25
25
|
{description ? <p className="text-muted-foreground text-sm">{description}</p> : null}
|
|
26
26
|
</div>
|
|
27
27
|
</div>
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import type { ComponentPropsWithoutRef, ElementType } from "react";
|
|
2
2
|
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
|
|
3
5
|
type SurfacePanelProps<T extends ElementType = "div"> = { as?: T } & Omit<
|
|
4
6
|
ComponentPropsWithoutRef<T>,
|
|
5
|
-
"as"
|
|
7
|
+
"as"
|
|
6
8
|
>;
|
|
7
9
|
|
|
8
10
|
/** Rounded, bordered surface panel — a generic content container. */
|
|
9
11
|
export function SurfacePanel<T extends ElementType = "div">({
|
|
10
12
|
as,
|
|
13
|
+
className,
|
|
11
14
|
...props
|
|
12
15
|
}: SurfacePanelProps<T>) {
|
|
13
16
|
const Component = as ?? "div";
|
|
14
|
-
return
|
|
17
|
+
return (
|
|
18
|
+
<Component
|
|
19
|
+
className={cn("border-border bg-card rounded-3xl border p-5", className)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
15
23
|
}
|
|
@@ -121,7 +121,11 @@ export const WithCustomRouterLink: Story = {
|
|
|
121
121
|
|
|
122
122
|
<Navigation.Action>
|
|
123
123
|
<Button size="md" asChild>
|
|
124
|
-
<a
|
|
124
|
+
<a
|
|
125
|
+
href="/get-started"
|
|
126
|
+
data-router-link="stand-in"
|
|
127
|
+
onClick={(e) => e.preventDefault()}
|
|
128
|
+
>
|
|
125
129
|
Get started
|
|
126
130
|
</a>
|
|
127
131
|
</Button>
|
|
@@ -26,17 +26,26 @@ export interface NavigationRootProps {
|
|
|
26
26
|
* shape) so `Navigation.Item`'s active state and `asChild` composition
|
|
27
27
|
* behave identically in both places.
|
|
28
28
|
*/
|
|
29
|
-
function NavigationRoot({
|
|
29
|
+
function NavigationRoot({
|
|
30
|
+
children,
|
|
31
|
+
ariaLabel = "Primary navigation",
|
|
32
|
+
className,
|
|
33
|
+
}: NavigationRootProps) {
|
|
30
34
|
const [mobileOpen, setMobileOpen] = React.useState(false);
|
|
31
35
|
|
|
32
36
|
const items = React.Children.toArray(children);
|
|
33
|
-
const brand = items.find(
|
|
37
|
+
const brand = items.find(
|
|
38
|
+
(child) => React.isValidElement(child) && child.type === NavigationBrand,
|
|
39
|
+
);
|
|
34
40
|
const rest = items.filter((child) => child !== brand);
|
|
35
41
|
|
|
36
42
|
return (
|
|
37
43
|
<header className="bg-background/95 supports-backdrop-filter:bg-background/60 sticky top-0 z-50 flex w-full justify-center border-b px-4 backdrop-blur">
|
|
38
44
|
<nav
|
|
39
|
-
className={cn(
|
|
45
|
+
className={cn(
|
|
46
|
+
"flex min-h-14 w-full max-w-7xl items-center justify-between gap-6",
|
|
47
|
+
className,
|
|
48
|
+
)}
|
|
40
49
|
aria-label={ariaLabel}
|
|
41
50
|
>
|
|
42
51
|
<div className="flex min-w-0 items-center">{brand}</div>
|
|
@@ -108,7 +117,13 @@ export type NavigationItemProps = NavigationItemOwnProps &
|
|
|
108
117
|
Omit<ComponentPropsWithoutRef<"a">, keyof NavigationItemOwnProps>;
|
|
109
118
|
|
|
110
119
|
/** A single nav link, wrapped in `<li>`. Auto-closes the mobile Sheet on click when rendered there. */
|
|
111
|
-
function NavigationItem({
|
|
120
|
+
function NavigationItem({
|
|
121
|
+
active = false,
|
|
122
|
+
asChild = false,
|
|
123
|
+
className,
|
|
124
|
+
children,
|
|
125
|
+
...props
|
|
126
|
+
}: NavigationItemProps) {
|
|
112
127
|
const isMobile = React.useContext(MobileMenuContext);
|
|
113
128
|
const itemClassName = cn(
|
|
114
129
|
"inline-flex h-9 max-h-9 shrink-0 items-center px-3 py-2 text-sm font-medium hover:bg-muted hover:text-muted-foreground data-[active]:font-semibold data-[active]:text-foreground",
|
|
@@ -25,7 +25,10 @@ function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPr
|
|
|
25
25
|
<TabsPrimitive.Tab
|
|
26
26
|
data-slot="tabs-trigger"
|
|
27
27
|
className={cn(
|
|
28
|
-
"text-foreground
|
|
28
|
+
"text-foreground flex flex-1 items-center justify-center gap-2 rounded-full px-3 py-2 text-sm font-medium whitespace-nowrap transition-all",
|
|
29
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
30
|
+
"focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none",
|
|
31
|
+
"data-active:ring-1 data-active:ring-inset data-active:bg-background data-active:text-foreground data-active:ring-border",
|
|
29
32
|
className,
|
|
30
33
|
)}
|
|
31
34
|
{...props}
|
|
@@ -2,6 +2,7 @@ import { Combobox as CommandPrimitive } from "@base-ui/react/combobox";
|
|
|
2
2
|
import { SearchIcon } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
5
6
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./dialog";
|
|
6
7
|
|
|
7
8
|
function Command({
|
|
@@ -14,7 +15,10 @@ function Command({
|
|
|
14
15
|
return (
|
|
15
16
|
<div
|
|
16
17
|
data-slot="command"
|
|
17
|
-
className={
|
|
18
|
+
className={cn(
|
|
19
|
+
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
|
|
20
|
+
className,
|
|
21
|
+
)}
|
|
18
22
|
>
|
|
19
23
|
<CommandPrimitive.Root {...props}>{children}</CommandPrimitive.Root>
|
|
20
24
|
</div>
|
|
@@ -63,7 +67,11 @@ function CommandInput({
|
|
|
63
67
|
<CommandPrimitive.Input
|
|
64
68
|
data-slot="command-input"
|
|
65
69
|
aria-label={ariaLabel}
|
|
66
|
-
className={
|
|
70
|
+
className={cn(
|
|
71
|
+
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden",
|
|
72
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
73
|
+
className,
|
|
74
|
+
)}
|
|
67
75
|
onChange={(event) => {
|
|
68
76
|
onChange?.(event);
|
|
69
77
|
onValueChange?.(event.currentTarget.value);
|
|
@@ -74,14 +82,18 @@ function CommandInput({
|
|
|
74
82
|
);
|
|
75
83
|
}
|
|
76
84
|
|
|
77
|
-
function CommandList({
|
|
85
|
+
function CommandList({
|
|
86
|
+
children,
|
|
87
|
+
className,
|
|
88
|
+
...props
|
|
89
|
+
}: React.ComponentProps<typeof CommandPrimitive.List>) {
|
|
78
90
|
if (typeof children === "function") {
|
|
79
91
|
return (
|
|
80
92
|
<CommandPrimitive.List
|
|
81
93
|
data-slot="command-list"
|
|
82
94
|
aria-label="Command results"
|
|
83
95
|
tabIndex={0}
|
|
84
|
-
className="max-h-75 scroll-py-1 overflow-x-hidden overflow-y-auto"
|
|
96
|
+
className={cn("max-h-75 scroll-py-1 overflow-x-hidden overflow-y-auto", className)}
|
|
85
97
|
{...props}
|
|
86
98
|
>
|
|
87
99
|
{children}
|
|
@@ -103,7 +115,7 @@ function CommandList({ children, ...props }: React.ComponentProps<typeof Command
|
|
|
103
115
|
data-slot="command-list"
|
|
104
116
|
aria-label="Command results"
|
|
105
117
|
tabIndex={0}
|
|
106
|
-
className="max-h-75 scroll-py-1 overflow-x-hidden overflow-y-auto"
|
|
118
|
+
className={cn("max-h-75 scroll-py-1 overflow-x-hidden overflow-y-auto", className)}
|
|
107
119
|
{...props}
|
|
108
120
|
>
|
|
109
121
|
{listChildren}
|
|
@@ -113,11 +125,14 @@ function CommandList({ children, ...props }: React.ComponentProps<typeof Command
|
|
|
113
125
|
);
|
|
114
126
|
}
|
|
115
127
|
|
|
116
|
-
function CommandEmpty({
|
|
128
|
+
function CommandEmpty({
|
|
129
|
+
className,
|
|
130
|
+
...props
|
|
131
|
+
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
|
117
132
|
return (
|
|
118
133
|
<CommandPrimitive.Empty
|
|
119
134
|
data-slot="command-empty"
|
|
120
|
-
className="py-6 text-center text-sm"
|
|
135
|
+
className={cn("py-6 text-center text-sm", className)}
|
|
121
136
|
{...props}
|
|
122
137
|
/>
|
|
123
138
|
);
|
|
@@ -132,7 +147,7 @@ function CommandGroup({
|
|
|
132
147
|
return (
|
|
133
148
|
<CommandPrimitive.Group
|
|
134
149
|
data-slot="command-group"
|
|
135
|
-
className={
|
|
150
|
+
className={cn("text-foreground overflow-hidden p-1", className)}
|
|
136
151
|
{...props}
|
|
137
152
|
>
|
|
138
153
|
{heading ? (
|
|
@@ -153,7 +168,11 @@ function CommandItem({ className, onSelect, onClick, ...props }: CommandItemProp
|
|
|
153
168
|
return (
|
|
154
169
|
<CommandPrimitive.Item
|
|
155
170
|
data-slot="command-item"
|
|
156
|
-
className={
|
|
171
|
+
className={cn(
|
|
172
|
+
"data-highlighted:bg-muted data-highlighted:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-hidden select-none",
|
|
173
|
+
"data-disabled:pointer-events-none data-disabled:opacity-50",
|
|
174
|
+
className,
|
|
175
|
+
)}
|
|
157
176
|
onClick={(event) => {
|
|
158
177
|
onClick?.(event);
|
|
159
178
|
onSelect?.();
|
|
@@ -168,24 +187,30 @@ function CommandSeparator({ className, ...props }: React.ComponentProps<"div">)
|
|
|
168
187
|
<div
|
|
169
188
|
data-slot="command-separator"
|
|
170
189
|
aria-hidden="true"
|
|
171
|
-
className={
|
|
190
|
+
className={cn("bg-border -mx-1 h-px", className)}
|
|
172
191
|
{...props}
|
|
173
192
|
/>
|
|
174
193
|
);
|
|
175
194
|
}
|
|
176
195
|
|
|
177
|
-
function CommandShortcut(props: React.ComponentProps<"span">) {
|
|
196
|
+
function CommandShortcut({ className, ...props }: React.ComponentProps<"span">) {
|
|
178
197
|
return (
|
|
179
198
|
<span
|
|
180
199
|
data-slot="command-shortcut"
|
|
181
|
-
className="text-muted-foreground ml-auto text-xs tracking-widest"
|
|
200
|
+
className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
|
|
182
201
|
{...props}
|
|
183
202
|
/>
|
|
184
203
|
);
|
|
185
204
|
}
|
|
186
205
|
|
|
187
|
-
function CommandListLoading(props: React.ComponentProps<"div">) {
|
|
188
|
-
return
|
|
206
|
+
function CommandListLoading({ className, ...props }: React.ComponentProps<"div">) {
|
|
207
|
+
return (
|
|
208
|
+
<div
|
|
209
|
+
data-slot="command-list-loading"
|
|
210
|
+
className={cn("py-6 text-center text-sm", className)}
|
|
211
|
+
{...props}
|
|
212
|
+
/>
|
|
213
|
+
);
|
|
189
214
|
}
|
|
190
215
|
|
|
191
216
|
export {
|
|
@@ -25,12 +25,24 @@ export const Default: Story = {
|
|
|
25
25
|
</div>
|
|
26
26
|
<div className="grid gap-4 pt-4">
|
|
27
27
|
<div className="grid grid-cols-3 items-center gap-4">
|
|
28
|
-
<label className="text-sm">
|
|
29
|
-
|
|
28
|
+
<label htmlFor="popover-width" className="text-sm">
|
|
29
|
+
Width
|
|
30
|
+
</label>
|
|
31
|
+
<input
|
|
32
|
+
id="popover-width"
|
|
33
|
+
className="col-span-2 h-8 rounded border px-2 text-sm"
|
|
34
|
+
defaultValue="100%"
|
|
35
|
+
/>
|
|
30
36
|
</div>
|
|
31
37
|
<div className="grid grid-cols-3 items-center gap-4">
|
|
32
|
-
<label className="text-sm">
|
|
33
|
-
|
|
38
|
+
<label htmlFor="popover-height" className="text-sm">
|
|
39
|
+
Height
|
|
40
|
+
</label>
|
|
41
|
+
<input
|
|
42
|
+
id="popover-height"
|
|
43
|
+
className="col-span-2 h-8 rounded border px-2 text-sm"
|
|
44
|
+
defaultValue="25px"
|
|
45
|
+
/>
|
|
34
46
|
</div>
|
|
35
47
|
</div>
|
|
36
48
|
</PopoverContent>
|
|
@@ -12,7 +12,7 @@ const badgeVariants = cva(
|
|
|
12
12
|
secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/80",
|
|
13
13
|
destructive: "bg-destructive text-destructive-foreground [a&]:hover:bg-destructive/90",
|
|
14
14
|
outline: "border-border text-foreground [a&]:hover:bg-muted",
|
|
15
|
-
ghost: "
|
|
15
|
+
ghost: "bg-transparent text-foreground [a&]:hover:bg-muted",
|
|
16
16
|
link: "text-foreground underline-offset-4 [a&]:hover:underline",
|
|
17
17
|
/** Quiet metadata marker for real reference IDs (case/appointment/feed
|
|
18
18
|
* numbers) — never for decorative sequence markers. */
|
|
@@ -6,7 +6,12 @@ import { cn } from "../../lib/utils";
|
|
|
6
6
|
import { Spinner } from "../feedback/spinner";
|
|
7
7
|
|
|
8
8
|
const buttonVariants = cva(
|
|
9
|
-
|
|
9
|
+
[
|
|
10
|
+
"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md border border-transparent font-medium transition-colors outline-none",
|
|
11
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
12
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
13
|
+
"[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
14
|
+
],
|
|
10
15
|
{
|
|
11
16
|
variants: {
|
|
12
17
|
variant: {
|
|
@@ -53,9 +53,7 @@ export function useDerivedFilterState<T extends Record<string, FilterConfig>>({
|
|
|
53
53
|
.filter(([, v]) => v !== "")
|
|
54
54
|
.map(([key, v]) => ({
|
|
55
55
|
id: key,
|
|
56
|
-
label: fields[key]?.label
|
|
57
|
-
? fields[key].label!(v)
|
|
58
|
-
: `${key}: ${v}`,
|
|
56
|
+
label: fields[key]?.label ? fields[key].label!(v) : `${key}: ${v}`,
|
|
59
57
|
onRemove: () => setValue(key, ""),
|
|
60
58
|
}));
|
|
61
59
|
}, [values, fields, setValue]);
|
|
@@ -18,8 +18,7 @@ export function useFilterState<T extends Record<string, unknown>>(
|
|
|
18
18
|
const setFilters = useCallback(
|
|
19
19
|
(newFilters: T | ((prev: T) => T)) => {
|
|
20
20
|
setFiltersState((prev) => {
|
|
21
|
-
const updatedFilters =
|
|
22
|
-
typeof newFilters === "function" ? newFilters(prev) : newFilters;
|
|
21
|
+
const updatedFilters = typeof newFilters === "function" ? newFilters(prev) : newFilters;
|
|
23
22
|
|
|
24
23
|
if (debounceMs && debounceMs > 0) {
|
|
25
24
|
if (debounceTimerRef.current) {
|
package/src/native/card.tsx
CHANGED
|
@@ -4,7 +4,9 @@ import { Text, View, type TextProps, type ViewProps } from "react-native";
|
|
|
4
4
|
import { cn } from "../lib/utils";
|
|
5
5
|
|
|
6
6
|
function Card({ className, ...props }: ViewProps) {
|
|
7
|
-
return
|
|
7
|
+
return (
|
|
8
|
+
<View className={cn("bg-card gap-4 rounded border border-border p-4", className)} {...props} />
|
|
9
|
+
);
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
function CardHeader({ className, ...props }: ViewProps) {
|
package/src/native/index.ts
CHANGED
|
@@ -6,23 +6,33 @@
|
|
|
6
6
|
* code, so web consumers never pull React Native into their graph and native
|
|
7
7
|
* consumers never pull in react-dom.
|
|
8
8
|
*/
|
|
9
|
+
export {
|
|
10
|
+
Card,
|
|
11
|
+
CardAction,
|
|
12
|
+
CardContent,
|
|
13
|
+
CardDescription,
|
|
14
|
+
CardFooter,
|
|
15
|
+
CardHeader,
|
|
16
|
+
CardTitle,
|
|
17
|
+
} from "./card";
|
|
18
|
+
export { IconButton } from "./icon-button";
|
|
19
|
+
export { ListRow } from "./list-row";
|
|
9
20
|
export { createMakeStyles, type NamedStyles } from "./make-styles";
|
|
10
21
|
export { nativeShadows, type NativeShadowLayer } from "./shadows";
|
|
11
|
-
export { useColorMode, useThemeColors } from "./use-theme-colors";
|
|
12
|
-
export { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./card";
|
|
13
22
|
export { TextField } from "./text-field";
|
|
14
|
-
export {
|
|
23
|
+
export { useColorMode, useThemeColors } from "./use-theme-colors";
|
|
15
24
|
|
|
16
25
|
export {
|
|
17
|
-
typography as nativeTypography,
|
|
18
|
-
spacing as nativeSpacing,
|
|
19
|
-
screenMargin as nativeScreenMargin,
|
|
20
|
-
componentSize as nativeComponentSize,
|
|
21
26
|
borderRadius as nativeBorderRadius,
|
|
22
|
-
|
|
27
|
+
componentSize as nativeComponentSize,
|
|
23
28
|
fontFamily as nativeFontFamily,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
screenMargin as nativeScreenMargin,
|
|
30
|
+
spacing as nativeSpacing,
|
|
31
|
+
streamItem as nativeStreamItem,
|
|
32
|
+
typography as nativeTypography,
|
|
27
33
|
type BorderRadiusToken,
|
|
34
|
+
type ComponentSizeToken,
|
|
35
|
+
type SpacingToken,
|
|
36
|
+
type TypographyVariant,
|
|
28
37
|
} from "../styles/tokens/normalize-native";
|
|
38
|
+
export { motion as nativeMotion } from "../styles/tokens";
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { Pressable, type StyleProp, Text, type TextStyle, View } from "react-native";
|
|
3
|
+
import { useCSSVariable } from "uniwind";
|
|
4
|
+
|
|
5
|
+
interface ListRowProps {
|
|
6
|
+
accessibilityLabel: string;
|
|
7
|
+
actionTestID?: string;
|
|
8
|
+
eyebrow?: string;
|
|
9
|
+
leading?: ReactNode;
|
|
10
|
+
onLongPress?: () => void;
|
|
11
|
+
onPress: () => void;
|
|
12
|
+
subtitle?: string | null;
|
|
13
|
+
testID?: string;
|
|
14
|
+
title: string;
|
|
15
|
+
titleStyle?: StyleProp<TextStyle>;
|
|
16
|
+
trailing?: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function ListRow({
|
|
20
|
+
accessibilityLabel,
|
|
21
|
+
actionTestID,
|
|
22
|
+
eyebrow,
|
|
23
|
+
leading,
|
|
24
|
+
onLongPress,
|
|
25
|
+
onPress,
|
|
26
|
+
subtitle,
|
|
27
|
+
testID,
|
|
28
|
+
title,
|
|
29
|
+
titleStyle,
|
|
30
|
+
trailing,
|
|
31
|
+
}: ListRowProps) {
|
|
32
|
+
const [muted] = useCSSVariable(["--color-muted"]) as string[];
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Pressable
|
|
36
|
+
accessibilityLabel={accessibilityLabel}
|
|
37
|
+
accessibilityRole="button"
|
|
38
|
+
onLongPress={onLongPress}
|
|
39
|
+
onPress={onPress}
|
|
40
|
+
className="flex-row items-center gap-3 min-h-14 border-b border-border px-4 py-2"
|
|
41
|
+
style={({ pressed }) => pressed && { backgroundColor: muted }}
|
|
42
|
+
testID={testID ?? actionTestID}
|
|
43
|
+
>
|
|
44
|
+
{leading ? <View className="items-center justify-center w-6">{leading}</View> : null}
|
|
45
|
+
<View className="flex-1 gap-0.5 min-w-0">
|
|
46
|
+
{eyebrow ? (
|
|
47
|
+
<Text className="text-caption2 text-muted-foreground tracking-[0.2]">{eyebrow}</Text>
|
|
48
|
+
) : null}
|
|
49
|
+
<Text numberOfLines={2} className="text-body text-foreground" style={titleStyle}>
|
|
50
|
+
{title}
|
|
51
|
+
</Text>
|
|
52
|
+
{subtitle ? (
|
|
53
|
+
<Text
|
|
54
|
+
ellipsizeMode="tail"
|
|
55
|
+
numberOfLines={1}
|
|
56
|
+
className="text-caption1 text-muted-foreground"
|
|
57
|
+
>
|
|
58
|
+
{subtitle}
|
|
59
|
+
</Text>
|
|
60
|
+
) : null}
|
|
61
|
+
</View>
|
|
62
|
+
{trailing ? <View className="items-center justify-center">{trailing}</View> : null}
|
|
63
|
+
</Pressable>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { ListRow };
|
|
@@ -223,16 +223,44 @@ export default {
|
|
|
223
223
|
icon: "0.375rem",
|
|
224
224
|
},
|
|
225
225
|
"stream-item": {
|
|
226
|
-
"border-radius": "
|
|
226
|
+
"border-radius": "0rem",
|
|
227
227
|
"border-width": "1px",
|
|
228
|
-
gap: "0.
|
|
229
|
-
"icon-size": "1.
|
|
230
|
-
"min-height": "
|
|
231
|
-
"padding-horizontal": "
|
|
232
|
-
"padding-vertical": "0.
|
|
228
|
+
gap: "0.75rem",
|
|
229
|
+
"icon-size": "1.25rem",
|
|
230
|
+
"min-height": "3.5rem",
|
|
231
|
+
"padding-horizontal": "1rem",
|
|
232
|
+
"padding-vertical": "0.5rem",
|
|
233
233
|
},
|
|
234
234
|
"font-family": {
|
|
235
235
|
primary: "System",
|
|
236
236
|
mono: "Menlo",
|
|
237
237
|
},
|
|
238
|
+
motion: {
|
|
239
|
+
duration: {
|
|
240
|
+
instant: "0ms",
|
|
241
|
+
quick: "150ms",
|
|
242
|
+
standard: "300ms",
|
|
243
|
+
deliberate: "500ms",
|
|
244
|
+
},
|
|
245
|
+
easing: {
|
|
246
|
+
enter: [0, 0, 0.2, 1],
|
|
247
|
+
exit: [0.4, 0, 1, 1],
|
|
248
|
+
inOut: [0.4, 0, 0.2, 1],
|
|
249
|
+
},
|
|
250
|
+
distance: {
|
|
251
|
+
rowEnter: "0.5rem",
|
|
252
|
+
sceneReveal: "2rem",
|
|
253
|
+
},
|
|
254
|
+
opacity: {
|
|
255
|
+
hidden: 0,
|
|
256
|
+
visible: 1,
|
|
257
|
+
disabled: 0.5,
|
|
258
|
+
},
|
|
259
|
+
interruption: {
|
|
260
|
+
policy: "cancel-and-settle",
|
|
261
|
+
},
|
|
262
|
+
reducedMotion: {
|
|
263
|
+
policy: "replace-transform-with-opacity",
|
|
264
|
+
},
|
|
265
|
+
},
|
|
238
266
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import darkTokens from "./generated/dark.colors.generated";
|
|
2
2
|
import foundations from "./generated/foundations.generated";
|
|
3
3
|
import lightTokens from "./generated/light.colors.generated";
|
|
4
|
+
import nativeTokens from "./generated/native.generated";
|
|
4
5
|
|
|
5
6
|
const toPixels = (value: string) => {
|
|
6
7
|
if (value.endsWith("rem")) return Number.parseFloat(value) * 16;
|
|
@@ -62,6 +63,7 @@ export const transitionDurations = Object.fromEntries(
|
|
|
62
63
|
Object.entries(foundations.duration).map(([key, value]) => [key, toMilliseconds(value)]),
|
|
63
64
|
) as Record<keyof typeof foundations.duration, number>;
|
|
64
65
|
export const animations = foundations.animate;
|
|
66
|
+
export const motion = nativeTokens.motion;
|
|
65
67
|
|
|
66
68
|
export type RadiusToken = keyof typeof radii;
|
|
67
69
|
export type TextSizeToken = keyof typeof textSizes;
|
|
@@ -17,7 +17,9 @@ const makeFlat = (raw: Record<string, unknown>) => ({
|
|
|
17
17
|
fontWeight: raw.fontWeight as number,
|
|
18
18
|
lineHeight: unwrap(raw.lineHeight as DimensionValue),
|
|
19
19
|
letterSpacing: unwrap(raw.letterSpacing as DimensionValue),
|
|
20
|
-
...(raw.textTransform
|
|
20
|
+
...(raw.textTransform
|
|
21
|
+
? { textTransform: raw.textTransform as "uppercase" | "lowercase" | "capitalize" | "none" }
|
|
22
|
+
: {}),
|
|
21
23
|
});
|
|
22
24
|
|
|
23
25
|
const generated = nativeGenerated as unknown as {
|
|
@@ -62,13 +64,13 @@ export const screenMargin = toPixels(generated["screen-margin"]);
|
|
|
62
64
|
|
|
63
65
|
export const componentSize = Object.fromEntries(
|
|
64
66
|
Object.entries(generated["component-size"]).map(([key, value]) => [key, toPixels(value)]),
|
|
65
|
-
) as Record<keyof typeof generated["component-size"], number>;
|
|
67
|
+
) as Record<keyof (typeof generated)["component-size"], number>;
|
|
66
68
|
|
|
67
69
|
export type ComponentSizeToken = keyof typeof componentSize;
|
|
68
70
|
|
|
69
71
|
export const borderRadius = Object.fromEntries(
|
|
70
72
|
Object.entries(generated["border-radius"]).map(([key, value]) => [key, toPixels(value)]),
|
|
71
|
-
) as Record<keyof typeof generated["border-radius"], number>;
|
|
73
|
+
) as Record<keyof (typeof generated)["border-radius"], number>;
|
|
72
74
|
|
|
73
75
|
export type BorderRadiusToken = keyof typeof borderRadius;
|
|
74
76
|
|