@noya-app/noya-designsystem 0.1.66 → 0.1.68
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/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +14 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +81 -5
- package/dist/index.d.ts +81 -5
- package/dist/index.js +1205 -857
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1047 -698
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
- package/src/components/Anchor.tsx +30 -0
- package/src/components/BaseToolbar.tsx +11 -8
- package/src/components/Chip.tsx +16 -2
- package/src/components/Collection.tsx +10 -0
- package/src/components/Combobox.tsx +30 -21
- package/src/components/ComboboxMenu.tsx +49 -18
- package/src/components/Dialog.tsx +7 -1
- package/src/components/EditableText.tsx +7 -3
- package/src/components/Grid.tsx +2 -0
- package/src/components/GridView.tsx +52 -9
- package/src/components/List.tsx +54 -9
- package/src/components/ListView.tsx +44 -0
- package/src/components/ScrollArea2.tsx +56 -0
- package/src/components/SearchCompletionMenu.tsx +6 -2
- package/src/components/TreeView.tsx +4 -0
- package/src/components/UserPointer.tsx +34 -7
- package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +1 -3
- package/src/components/listView/ListViewEditableRowTitle.tsx +23 -1
- package/src/components/listView/ListViewRoot.tsx +45 -13
- package/src/components/sorting/Sortable.tsx +22 -10
- package/src/components/workspace/WorkspaceLayout.tsx +62 -4
- package/src/components/workspace/WorkspaceSideContext.tsx +18 -0
- package/src/components/workspace/renderPanelChildren.tsx +2 -2
- package/src/components/workspace/types.ts +8 -1
- package/src/contexts/DialogContext.tsx +22 -10
- package/src/index.css +143 -1
- package/src/index.tsx +4 -0
- package/src/theme/proseTheme.ts +2 -0
- package/src/utils/combobox.ts +17 -2
- package/src/utils/detailLineClamp.ts +33 -0
- package/tailwind.config.ts +3 -1
|
@@ -7,7 +7,13 @@ import {
|
|
|
7
7
|
useSize,
|
|
8
8
|
useWindowSize,
|
|
9
9
|
} from "@noya-app/react-utils";
|
|
10
|
-
import React, {
|
|
10
|
+
import React, {
|
|
11
|
+
memo,
|
|
12
|
+
useCallback,
|
|
13
|
+
useImperativeHandle,
|
|
14
|
+
useMemo,
|
|
15
|
+
useRef,
|
|
16
|
+
} from "react";
|
|
11
17
|
import { cx } from "../../utils/classNames";
|
|
12
18
|
import { MenuItem } from "../internal/Menu";
|
|
13
19
|
import { DrawerWorkspaceLayout } from "./DrawerWorkspaceLayout";
|
|
@@ -19,6 +25,7 @@ import {
|
|
|
19
25
|
RenderPanel,
|
|
20
26
|
SidebarRef,
|
|
21
27
|
} from "./types";
|
|
28
|
+
import { WorkspaceSideProvider } from "./WorkspaceSideContext";
|
|
22
29
|
|
|
23
30
|
export type SideType = "auto" | "panel" | "drawer";
|
|
24
31
|
|
|
@@ -280,13 +287,57 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
|
|
|
280
287
|
(left ? leftSidebarPercentage : 0) -
|
|
281
288
|
(right ? rightSidebarPercentage : 0);
|
|
282
289
|
|
|
290
|
+
const isDrawerActive =
|
|
291
|
+
sideType === "drawer" ||
|
|
292
|
+
(sideType === "auto" && parentSize.width <= sideTypeBreakpoint);
|
|
293
|
+
|
|
294
|
+
const closeLeftSidebar = useCallback(() => {
|
|
295
|
+
if (isDrawerActive) {
|
|
296
|
+
leftSidebarRef.current?.collapse();
|
|
297
|
+
}
|
|
298
|
+
}, [isDrawerActive]);
|
|
299
|
+
|
|
300
|
+
const closeRightSidebar = useCallback(() => {
|
|
301
|
+
if (isDrawerActive) {
|
|
302
|
+
rightSidebarRef.current?.collapse();
|
|
303
|
+
}
|
|
304
|
+
}, [isDrawerActive]);
|
|
305
|
+
|
|
306
|
+
const leftChildrenProps = useMemo(() => {
|
|
307
|
+
return {
|
|
308
|
+
activeTabValue: leftTabValue,
|
|
309
|
+
closeSidebar: closeLeftSidebar,
|
|
310
|
+
};
|
|
311
|
+
}, [leftTabValue, closeLeftSidebar]);
|
|
312
|
+
|
|
313
|
+
const rightChildrenProps = useMemo(() => {
|
|
314
|
+
return {
|
|
315
|
+
activeTabValue: rightTabValue,
|
|
316
|
+
closeSidebar: closeRightSidebar,
|
|
317
|
+
};
|
|
318
|
+
}, [rightTabValue, closeRightSidebar]);
|
|
319
|
+
|
|
320
|
+
const leftSidebarProviderValue = useMemo(() => {
|
|
321
|
+
return {
|
|
322
|
+
closeSidebar: closeLeftSidebar,
|
|
323
|
+
};
|
|
324
|
+
}, [closeLeftSidebar]);
|
|
325
|
+
|
|
326
|
+
const rightSidebarProviderValue = useMemo(() => {
|
|
327
|
+
return {
|
|
328
|
+
closeSidebar: closeRightSidebar,
|
|
329
|
+
};
|
|
330
|
+
}, [closeRightSidebar]);
|
|
331
|
+
|
|
283
332
|
const leftPanel =
|
|
284
333
|
left && leftVisible !== false ? (
|
|
285
334
|
<PanelInner
|
|
286
335
|
style={leftStyle}
|
|
287
336
|
className={cx("n-bg-sidebar-background n-flex-col", leftClassName)}
|
|
288
337
|
>
|
|
289
|
-
{
|
|
338
|
+
<WorkspaceSideProvider value={leftSidebarProviderValue}>
|
|
339
|
+
{leftTabValue ? renderPanelChildren(left, leftChildrenProps) : null}
|
|
340
|
+
</WorkspaceSideProvider>
|
|
290
341
|
</PanelInner>
|
|
291
342
|
) : null;
|
|
292
343
|
|
|
@@ -296,7 +347,11 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
|
|
|
296
347
|
style={rightStyle}
|
|
297
348
|
className={cx("n-bg-sidebar-background n-flex-col", rightClassName)}
|
|
298
349
|
>
|
|
299
|
-
{
|
|
350
|
+
<WorkspaceSideProvider value={rightSidebarProviderValue}>
|
|
351
|
+
{rightTabValue
|
|
352
|
+
? renderPanelChildren(right, rightChildrenProps)
|
|
353
|
+
: null}
|
|
354
|
+
</WorkspaceSideProvider>
|
|
300
355
|
</PanelInner>
|
|
301
356
|
) : null;
|
|
302
357
|
|
|
@@ -365,7 +420,10 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
|
|
|
365
420
|
<div
|
|
366
421
|
ref={containerRef}
|
|
367
422
|
id={id}
|
|
368
|
-
className={cx(
|
|
423
|
+
className={cx(
|
|
424
|
+
"n-flex n-flex-col n-bg-canvas-background n-relative",
|
|
425
|
+
className
|
|
426
|
+
)}
|
|
369
427
|
style={style}
|
|
370
428
|
data-theme={theme}
|
|
371
429
|
>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
4
|
+
|
|
5
|
+
export type WorkspaceSideContextValue = {
|
|
6
|
+
/**
|
|
7
|
+
* Collapses the current sidebar when available (e.g., in drawer/mobile layout).
|
|
8
|
+
*/
|
|
9
|
+
closeSidebar?: () => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const WorkspaceSideContext = createContext<WorkspaceSideContextValue>({});
|
|
13
|
+
|
|
14
|
+
export const WorkspaceSideProvider = WorkspaceSideContext.Provider;
|
|
15
|
+
|
|
16
|
+
export function useWorkspaceSide(): WorkspaceSideContextValue {
|
|
17
|
+
return useContext(WorkspaceSideContext);
|
|
18
|
+
}
|
|
@@ -2,10 +2,10 @@ import { RenderPanel } from "./types";
|
|
|
2
2
|
|
|
3
3
|
export function renderPanelChildren<TabValue extends string>(
|
|
4
4
|
fn: RenderPanel<TabValue>,
|
|
5
|
-
activeTabValue: TabValue
|
|
5
|
+
props: { activeTabValue: TabValue; closeSidebar?: () => void }
|
|
6
6
|
) {
|
|
7
7
|
if (typeof fn === "function") {
|
|
8
|
-
return fn(
|
|
8
|
+
return fn(props);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
return fn;
|
|
@@ -23,7 +23,14 @@ export type PanelLayoutState = {
|
|
|
23
23
|
|
|
24
24
|
export type RenderPanel<TabValue extends string> =
|
|
25
25
|
| ReactNode
|
|
26
|
-
| ((props: {
|
|
26
|
+
| ((props: {
|
|
27
|
+
activeTabValue: TabValue;
|
|
28
|
+
/**
|
|
29
|
+
* When the layout is using a drawer (mobile), this will collapse
|
|
30
|
+
* the current sidebar. It may be undefined for panel layouts.
|
|
31
|
+
*/
|
|
32
|
+
closeSidebar?: () => void;
|
|
33
|
+
}) => ReactNode);
|
|
27
34
|
|
|
28
35
|
export type LayoutProps<
|
|
29
36
|
LeftTab extends string = string,
|
|
@@ -65,7 +65,7 @@ const DialogContext = createContext<DialogContextValue | undefined>(undefined);
|
|
|
65
65
|
type BaseDialogContents = {
|
|
66
66
|
style?: React.CSSProperties;
|
|
67
67
|
className?: string;
|
|
68
|
-
title:
|
|
68
|
+
title: ReactNode;
|
|
69
69
|
description?: ReactNode;
|
|
70
70
|
};
|
|
71
71
|
|
|
@@ -228,6 +228,7 @@ export const DialogProvider = function DialogProvider({
|
|
|
228
228
|
);
|
|
229
229
|
|
|
230
230
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
231
|
+
const confirmButtonRef = useRef<HTMLButtonElement>(null);
|
|
231
232
|
const dialogRef = useRef<IDialog>(null);
|
|
232
233
|
|
|
233
234
|
const containsElement = useCallback((element: HTMLElement) => {
|
|
@@ -268,16 +269,25 @@ export const DialogProvider = function DialogProvider({
|
|
|
268
269
|
)}
|
|
269
270
|
onOpenAutoFocus={useCallback(
|
|
270
271
|
(event: Event) => {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
272
|
+
if (!contents) return;
|
|
273
|
+
|
|
274
|
+
if (contents.type === "input") {
|
|
275
|
+
event.stopPropagation();
|
|
276
|
+
event.preventDefault();
|
|
277
|
+
inputRef.current?.focus();
|
|
278
|
+
inputRef.current?.setSelectionRange(
|
|
279
|
+
0,
|
|
280
|
+
inputRef.current.value.length
|
|
281
|
+
);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
275
284
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
285
|
+
if (contents.type === "confirmation") {
|
|
286
|
+
event.stopPropagation();
|
|
287
|
+
event.preventDefault();
|
|
288
|
+
confirmButtonRef.current?.focus();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
281
291
|
},
|
|
282
292
|
[contents]
|
|
283
293
|
)}
|
|
@@ -295,6 +305,8 @@ export const DialogProvider = function DialogProvider({
|
|
|
295
305
|
</Button>
|
|
296
306
|
<Spacer.Horizontal size={16} />
|
|
297
307
|
<Button
|
|
308
|
+
colorScheme="primary"
|
|
309
|
+
ref={confirmButtonRef}
|
|
298
310
|
onClick={() => {
|
|
299
311
|
contents.resolve(true);
|
|
300
312
|
setContents(undefined);
|
package/src/index.css
CHANGED
|
@@ -128,7 +128,28 @@
|
|
|
128
128
|
--n-chip-primary-shadow: rgb(238, 229, 255);
|
|
129
129
|
--n-chip-secondary-shadow: rgb(205, 238, 231);
|
|
130
130
|
--n-chip-error-shadow: rgb(255, 219, 219);
|
|
131
|
-
--n-chip-default-shadow:
|
|
131
|
+
--n-chip-default-shadow: rgba(0, 0, 0, 0.1);
|
|
132
|
+
/* Hover shadows for outlined Chip */
|
|
133
|
+
--n-chip-primary-shadow-hover: color-mix(
|
|
134
|
+
in srgb,
|
|
135
|
+
var(--n-chip-primary-shadow) 85%,
|
|
136
|
+
var(--n-text) 15%
|
|
137
|
+
);
|
|
138
|
+
--n-chip-secondary-shadow-hover: color-mix(
|
|
139
|
+
in srgb,
|
|
140
|
+
var(--n-chip-secondary-shadow) 85%,
|
|
141
|
+
var(--n-text) 15%
|
|
142
|
+
);
|
|
143
|
+
--n-chip-error-shadow-hover: color-mix(
|
|
144
|
+
in srgb,
|
|
145
|
+
var(--n-chip-error-shadow) 85%,
|
|
146
|
+
var(--n-text) 15%
|
|
147
|
+
);
|
|
148
|
+
--n-chip-default-shadow-hover: color-mix(
|
|
149
|
+
in srgb,
|
|
150
|
+
var(--n-chip-default-shadow) 80%,
|
|
151
|
+
var(--n-text) 20%
|
|
152
|
+
);
|
|
132
153
|
--n-floating-button: rgb(248, 248, 250);
|
|
133
154
|
--n-block-border: rgb(184, 201, 218);
|
|
134
155
|
--n-block-highlight: rgb(255, 165, 0);
|
|
@@ -249,3 +270,124 @@
|
|
|
249
270
|
color: var(--n-text-disabled);
|
|
250
271
|
pointer-events: none;
|
|
251
272
|
}
|
|
273
|
+
|
|
274
|
+
/* Direct child pre blocks */
|
|
275
|
+
.n-prose pre {
|
|
276
|
+
background-color: var(--n-indigo-25);
|
|
277
|
+
border-width: 1px;
|
|
278
|
+
border-style: solid;
|
|
279
|
+
border-color: var(--n-divider);
|
|
280
|
+
border-radius: 0.375rem; /* rounded-md */
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* Code inside pre */
|
|
284
|
+
.n-prose pre > code {
|
|
285
|
+
font-size: 12.8px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* Inline code (not inside pre): hide before/after content */
|
|
289
|
+
.n-prose code:not(:where(pre *))::before,
|
|
290
|
+
.n-prose code:not(:where(pre *))::after {
|
|
291
|
+
content: none !important;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/* Blockquote paragraphs: remove decorative quotes, reset style/weight */
|
|
295
|
+
.n-prose blockquote p::before,
|
|
296
|
+
.n-prose blockquote p::after {
|
|
297
|
+
content: none !important;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* Inline code (not inside pre): background, border, padding, radius, weight */
|
|
301
|
+
.n-prose code:not(:where(pre *)) {
|
|
302
|
+
background-color: var(--n-indigo-25);
|
|
303
|
+
border-width: 1px;
|
|
304
|
+
border-style: solid;
|
|
305
|
+
border-color: var(--n-divider);
|
|
306
|
+
padding-left: 0.25rem; /* px-1 */
|
|
307
|
+
padding-right: 0.25rem; /* px-1 */
|
|
308
|
+
padding-top: 0.125rem; /* py-0.5 */
|
|
309
|
+
padding-bottom: 0.125rem; /* py-0.5 */
|
|
310
|
+
border-radius: 0.25rem; /* rounded */
|
|
311
|
+
font-weight: 400; /* font-normal */
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.n-prose blockquote p {
|
|
315
|
+
font-weight: 400; /* font-normal */
|
|
316
|
+
font-style: normal; /* not-italic */
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* Task list container adjustments */
|
|
320
|
+
.n-prose .contains-task-list {
|
|
321
|
+
padding-left: 0; /* pl-0 */
|
|
322
|
+
margin-left: 0; /* ml-0 */
|
|
323
|
+
list-style-position: inside; /* list-inside */
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/* Task list items: layout + spacing */
|
|
327
|
+
.n-prose .task-list-item {
|
|
328
|
+
list-style-type: none; /* list-none */
|
|
329
|
+
margin-left: 0; /* ml-0 */
|
|
330
|
+
padding-left: 1px; /* pl-px */
|
|
331
|
+
display: grid; /* grid */
|
|
332
|
+
grid-template-columns: auto 1fr; /* grid-cols-[auto,1fr] */
|
|
333
|
+
align-items: start; /* items-start */
|
|
334
|
+
column-gap: 0.5rem; /* gap-x-2 */
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* Checkbox column placement + visual tweaks */
|
|
338
|
+
.n-prose .task-list-item > .task-list-item-checkbox {
|
|
339
|
+
grid-column: 1 / 2; /* col-start-1 col-end-2 */
|
|
340
|
+
margin-top: 2px; /* mt-[2px] */
|
|
341
|
+
margin-right: 0; /* mr-0 */
|
|
342
|
+
accent-color: var(--n-primary); /* accent-[var(--n-primary)] */
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* Non-checkbox content spans the second column */
|
|
346
|
+
.n-prose .task-list-item > :not(.task-list-item-checkbox) {
|
|
347
|
+
grid-column: 2 / 3; /* col-start-2 col-end-3 */
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* Inline paragraphs/spans inside task items */
|
|
351
|
+
.n-prose .task-list-item > p,
|
|
352
|
+
.n-prose .task-list-item > span {
|
|
353
|
+
margin: 0; /* m-0 */
|
|
354
|
+
display: inline; /* inline */
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* Nested lists inside task items */
|
|
358
|
+
.n-prose .task-list-item > ul,
|
|
359
|
+
.n-prose .task-list-item > ol {
|
|
360
|
+
margin: 0; /* m-0 */
|
|
361
|
+
list-style-position: inside; /* list-inside */
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* When a list item is marked as a task item but has no checkbox,
|
|
365
|
+
collapse to a single-column grid so all children stack vertically. */
|
|
366
|
+
.n-prose .task-list-item:not(:has(> .task-list-item-checkbox)) {
|
|
367
|
+
grid-template-columns: 1fr;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.n-prose .task-list-item:not(:has(> .task-list-item-checkbox)) > * {
|
|
371
|
+
grid-column: 1 / -1;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/* Non-task list items inside a list that contains task items
|
|
375
|
+
should keep their native bullets; ensure nested lists break
|
|
376
|
+
onto the next line similar to GitHub's markdown rendering. */
|
|
377
|
+
.n-prose .contains-task-list > li:not(.task-list-item) > ul,
|
|
378
|
+
.n-prose .contains-task-list > li:not(.task-list-item) > ol {
|
|
379
|
+
display: block;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/* Ensure the first paragraph sits inline with the bullet */
|
|
383
|
+
.n-prose .contains-task-list > li:not(.task-list-item) > p {
|
|
384
|
+
display: inline;
|
|
385
|
+
margin: 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/* Spacing for nested unordered lists within mixed task/non-task lists */
|
|
389
|
+
.n-prose .contains-task-list > li:not(.task-list-item) > ul {
|
|
390
|
+
margin-left: 16px;
|
|
391
|
+
margin-top: 4px;
|
|
392
|
+
margin-bottom: 4px;
|
|
393
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./components/ActionMenu";
|
|
|
3
3
|
export * from "./components/ActivityIndicator";
|
|
4
4
|
export * from "./components/ActivityLog";
|
|
5
5
|
export * from "./components/ai-assistant/AIAssistantLayout";
|
|
6
|
+
export * from "./components/Anchor";
|
|
6
7
|
export * from "./components/AnimatePresence";
|
|
7
8
|
export * from "./components/Avatar";
|
|
8
9
|
export * from "./components/Banner";
|
|
@@ -61,6 +62,7 @@ export * from "./components/ResizableContainer";
|
|
|
61
62
|
export * from "./components/RingProgress";
|
|
62
63
|
export * from "./components/ScrollableSidebar";
|
|
63
64
|
export * from "./components/ScrollArea";
|
|
65
|
+
export * from "./components/ScrollArea2";
|
|
64
66
|
export * from "./components/SearchCompletionMenu";
|
|
65
67
|
export * from "./components/Section";
|
|
66
68
|
export * from "./components/SegmentedControl";
|
|
@@ -84,6 +86,7 @@ export * from "./components/TreeView";
|
|
|
84
86
|
export * from "./components/UserPointer";
|
|
85
87
|
export * from "./components/workspace/types";
|
|
86
88
|
export * from "./components/workspace/WorkspaceLayout";
|
|
89
|
+
export * from "./components/workspace/WorkspaceSideContext";
|
|
87
90
|
// Contexts
|
|
88
91
|
export * from "./contexts/DesignSystemConfiguration";
|
|
89
92
|
export * from "./contexts/DialogContext";
|
|
@@ -100,6 +103,7 @@ export * from "./hooks/useLabel";
|
|
|
100
103
|
export * from "./hooks/usePlatform";
|
|
101
104
|
export * from "./hooks/useTheme";
|
|
102
105
|
export * from "./theme";
|
|
106
|
+
export * from "./theme/proseTheme";
|
|
103
107
|
export * from "./utils/classNames";
|
|
104
108
|
export * from "./utils/createSectionedMenu";
|
|
105
109
|
export * from "./utils/formatByteSize";
|
package/src/utils/combobox.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { chunkBy, partition } from "@noya-app/noya-utils";
|
|
4
|
-
import { useSyncExternalStore } from "react";
|
|
4
|
+
import React, { useSyncExternalStore } from "react";
|
|
5
5
|
import {
|
|
6
6
|
isMenuItemSectionHeader,
|
|
7
7
|
isNonSelectableMenuItem,
|
|
@@ -306,7 +306,11 @@ export function filterWithGroupedSections<T extends string>(
|
|
|
306
306
|
);
|
|
307
307
|
} else {
|
|
308
308
|
const scoredItems = fuzzyFilter({
|
|
309
|
-
items: selectableItems.map((item) =>
|
|
309
|
+
items: selectableItems.map((item) => {
|
|
310
|
+
const title = item.title?.toString?.() ?? "";
|
|
311
|
+
const tooltipText = getNodeText(item.tooltip);
|
|
312
|
+
return tooltipText ? `${title} ${tooltipText}` : title;
|
|
313
|
+
}),
|
|
310
314
|
query,
|
|
311
315
|
});
|
|
312
316
|
|
|
@@ -389,3 +393,14 @@ export function useComboboxState<T extends string>(state: ComboboxState<T>) {
|
|
|
389
393
|
state.getSnapshot
|
|
390
394
|
);
|
|
391
395
|
}
|
|
396
|
+
|
|
397
|
+
function getNodeText(node: React.ReactNode): string {
|
|
398
|
+
if (node === null || node === undefined || typeof node === "boolean")
|
|
399
|
+
return "";
|
|
400
|
+
if (typeof node === "string" || typeof node === "number") return String(node);
|
|
401
|
+
if (Array.isArray(node)) return node.map(getNodeText).join("");
|
|
402
|
+
if (React.isValidElement(node)) {
|
|
403
|
+
return getNodeText((node as any).props?.children);
|
|
404
|
+
}
|
|
405
|
+
return "";
|
|
406
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const DETAIL_LINE_CLAMP_CLASS_MAP: Record<number, string> = {
|
|
2
|
+
1: "n-truncate",
|
|
3
|
+
2: "n-text-wrap n-line-clamp-2",
|
|
4
|
+
3: "n-text-wrap n-line-clamp-3",
|
|
5
|
+
4: "n-text-wrap n-line-clamp-4",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function normalizeDetailLineClamp(
|
|
9
|
+
lineClamp?: number
|
|
10
|
+
): number | undefined {
|
|
11
|
+
if (lineClamp == null) return undefined;
|
|
12
|
+
|
|
13
|
+
const normalized = Math.floor(lineClamp);
|
|
14
|
+
|
|
15
|
+
if (!Number.isFinite(normalized)) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (normalized < 1) return 1;
|
|
20
|
+
if (normalized > 4) return 4;
|
|
21
|
+
|
|
22
|
+
return normalized as 1 | 2 | 3 | 4;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getDetailLineClampClass(
|
|
26
|
+
lineClamp?: number
|
|
27
|
+
): string | undefined {
|
|
28
|
+
const normalized = normalizeDetailLineClamp(lineClamp);
|
|
29
|
+
|
|
30
|
+
return normalized ? DETAIL_LINE_CLAMP_CLASS_MAP[normalized] : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const DETAIL_LINE_CLAMP_CLASSES = DETAIL_LINE_CLAMP_CLASS_MAP;
|
package/tailwind.config.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import noyaConfig from "@noya-app/noya-tailwind-config";
|
|
2
2
|
import containerQueries from "@tailwindcss/container-queries";
|
|
3
|
+
import typography from "@tailwindcss/typography";
|
|
3
4
|
import type { Config } from "tailwindcss";
|
|
4
5
|
|
|
5
6
|
const config = {
|
|
6
7
|
...noyaConfig,
|
|
7
8
|
prefix: "n-",
|
|
8
|
-
plugins: [containerQueries],
|
|
9
|
+
plugins: [containerQueries, typography],
|
|
10
|
+
safelist: [...noyaConfig.safelist, "dark:n-prose-invert"],
|
|
9
11
|
} satisfies Config;
|
|
10
12
|
|
|
11
13
|
export default config;
|