@bug-on/md3-react 2.0.2 → 2.0.3
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/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useClickOutside.d.ts +8 -0
- package/dist/index.css +23 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +834 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +825 -4
- package/dist/index.mjs.map +1 -1
- package/dist/ui/search/animated-placeholder.d.ts +54 -0
- package/dist/ui/search/hooks/use-search-keyboard.d.ts +32 -0
- package/dist/ui/search/hooks/use-search-view-focus.d.ts +6 -0
- package/dist/ui/search/index.d.ts +27 -0
- package/dist/ui/search/search-bar.d.ts +32 -0
- package/dist/ui/search/search-context.d.ts +24 -0
- package/dist/ui/search/search-view-docked.d.ts +25 -0
- package/dist/ui/search/search-view-fullscreen.d.ts +36 -0
- package/dist/ui/search/search.d.ts +50 -0
- package/dist/ui/search/search.tokens.d.ts +112 -0
- package/dist/ui/search/search.types.d.ts +131 -0
- package/dist/ui/search/trailing-action.d.ts +9 -0
- package/package.json +2 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file animated-placeholder.tsx
|
|
3
|
+
* MD3 Expressive Search — Animated placeholder overlay.
|
|
4
|
+
*
|
|
5
|
+
* Replaces the native `::placeholder` with a GPU-accelerated `translateX`
|
|
6
|
+
* animation so the placeholder text can smoothly slide from its alignment
|
|
7
|
+
* position (center / right) to left when the search input is focused.
|
|
8
|
+
*
|
|
9
|
+
* Implementation notes:
|
|
10
|
+
* - Only `transform: translateX` is animated → no layout triggers, no paint.
|
|
11
|
+
* - Container width is measured once via `useLayoutEffect` (before paint) to
|
|
12
|
+
* avoid a first-render flash, then kept fresh via `ResizeObserver`.
|
|
13
|
+
* - xOffset is stored in `useState` so Framer Motion picks up changes and
|
|
14
|
+
* re-animates smoothly on container resize.
|
|
15
|
+
* - The span is never unmounted — only opacity-toggled — to preserve the
|
|
16
|
+
* measurement ref between renders.
|
|
17
|
+
*/
|
|
18
|
+
import * as React from "react";
|
|
19
|
+
interface AnimatedPlaceholderProps {
|
|
20
|
+
/** Placeholder text to display. */
|
|
21
|
+
text: string;
|
|
22
|
+
/** Alignment of the placeholder when idle (not focused). @default "left" */
|
|
23
|
+
textAlign: "left" | "center" | "right";
|
|
24
|
+
/**
|
|
25
|
+
* Whether the placeholder should be visible.
|
|
26
|
+
* Pass `!query` — hide when the user has typed something.
|
|
27
|
+
*/
|
|
28
|
+
visible: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the search is in an active/focused state.
|
|
31
|
+
* When `true`, the placeholder snaps to `left` regardless of `textAlign`.
|
|
32
|
+
*/
|
|
33
|
+
focused: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The `<input>` element that this component wraps.
|
|
36
|
+
* It should have `w-full` instead of `flex-1` since this wrapper
|
|
37
|
+
* takes over the `flex-1` role in the parent flex layout.
|
|
38
|
+
*/
|
|
39
|
+
children: React.ReactNode;
|
|
40
|
+
/** Extra className forwarded to the wrapper div. */
|
|
41
|
+
className?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Wraps a search `<input>` with an animated placeholder overlay.
|
|
45
|
+
*
|
|
46
|
+
* The wrapper div occupies `flex-1` so it fits seamlessly in the
|
|
47
|
+
* horizontal flex layout used by search bar headers. The children
|
|
48
|
+
* (the `<input>`) should use `w-full` to fill the wrapper.
|
|
49
|
+
*
|
|
50
|
+
* Accessibility: `aria-label` on the `<input>` carries the placeholder
|
|
51
|
+
* text for screen readers; this span is `aria-hidden="true"`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function AnimatedPlaceholder({ text, textAlign, visible, focused, children, className, }: AnimatedPlaceholderProps): import("react/jsx-runtime").JSX.Element;
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file use-search-keyboard.ts
|
|
3
|
+
* Keyboard navigation hook for the MD3 Search component.
|
|
4
|
+
*
|
|
5
|
+
* Handles:
|
|
6
|
+
* - ArrowDown / ArrowUp → navigate through suggestions (role="option")
|
|
7
|
+
* - Enter → submit search or select active suggestion
|
|
8
|
+
* - Escape → close the SearchView
|
|
9
|
+
*/
|
|
10
|
+
import type { UseSearchKeyboardReturn } from "../search.types";
|
|
11
|
+
interface UseSearchKeyboardOptions {
|
|
12
|
+
/** Whether the SearchView is currently open. */
|
|
13
|
+
active: boolean;
|
|
14
|
+
/** Callback to close the SearchView. */
|
|
15
|
+
onActiveChange: (active: boolean) => void;
|
|
16
|
+
/** Callback for search submission. */
|
|
17
|
+
onSearch: (query: string) => void;
|
|
18
|
+
/** Current search query. */
|
|
19
|
+
query: string;
|
|
20
|
+
/** Total number of suggestion items in the listbox. */
|
|
21
|
+
itemCount: number;
|
|
22
|
+
/** Called when user selects a specific suggestion by index. */
|
|
23
|
+
onSelectSuggestion?: (index: number) => void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Manages keyboard navigation for the Search component.
|
|
27
|
+
*
|
|
28
|
+
* Complies with WAI-ARIA Combobox pattern:
|
|
29
|
+
* @see https://www.w3.org/WAI/ARIA/apg/patterns/combobox/
|
|
30
|
+
*/
|
|
31
|
+
export declare function useSearchKeyboard({ active, onActiveChange, onSearch, query, itemCount, onSelectSuggestion, }: UseSearchKeyboardOptions): UseSearchKeyboardReturn;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Focuses `inputRef` when `active` becomes true, using a double-rAF
|
|
4
|
+
* to wait for Framer Motion's layout animation to finish painting.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useSearchViewFocus(inputRef: React.RefObject<HTMLInputElement | null>, active: boolean): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file index.ts
|
|
3
|
+
* MD3 Expressive Search — Public API exports.
|
|
4
|
+
*
|
|
5
|
+
* Components:
|
|
6
|
+
* - Search: Orchestrator (SearchBar + SearchView)
|
|
7
|
+
* - SearchBar: Collapsed pill state (standalone use)
|
|
8
|
+
* - SearchViewDocked: Expanded docked popup (standalone use)
|
|
9
|
+
* - SearchViewFullScreen: Expanded full-screen overlay (standalone use)
|
|
10
|
+
*
|
|
11
|
+
* Hook:
|
|
12
|
+
* - useSearchKeyboard: WAI-ARIA combobox keyboard navigation
|
|
13
|
+
*
|
|
14
|
+
* Tokens:
|
|
15
|
+
* - SearchTokens: Dimensional tokens (heights, sizes)
|
|
16
|
+
* - SEARCH_COLORS: CSS custom property color references
|
|
17
|
+
* - SEARCH_TYPOGRAPHY: Typography class strings
|
|
18
|
+
* - Animation constants
|
|
19
|
+
*/
|
|
20
|
+
export { useSearchKeyboard } from "./hooks/use-search-keyboard";
|
|
21
|
+
export { Search } from "./search";
|
|
22
|
+
export { SEARCH_BAR_EXIT_SPRING, SEARCH_BAR_EXPAND_SPRING, SEARCH_COLORS, SEARCH_DOCKED_REVEAL_SPRING, SEARCH_FULLSCREEN_SPRING, SEARCH_TYPOGRAPHY, SearchTokens, } from "./search.tokens";
|
|
23
|
+
export type { SearchProps, SearchStyleType, SearchVariant, } from "./search.types";
|
|
24
|
+
export { SearchBar } from "./search-bar";
|
|
25
|
+
export { useSearch } from "./search-context";
|
|
26
|
+
export { SearchViewDocked } from "./search-view-docked";
|
|
27
|
+
export { SearchViewFullScreen } from "./search-view-fullscreen";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file search-bar.tsx
|
|
3
|
+
* MD3 Expressive SearchBar — Collapsed state.
|
|
4
|
+
*
|
|
5
|
+
* Renders a pill-shaped search bar (CornerFull, h-56px).
|
|
6
|
+
* When focused/clicked → calls onActiveChange(true) to open SearchView.
|
|
7
|
+
*
|
|
8
|
+
* Option B (MD3 morphing): SearchBar is wrapped in its own AnimatePresence
|
|
9
|
+
* with mode="popLayout". When SearchView opens, SearchBar plays an exit
|
|
10
|
+
* animation (opacity → 0, scale → 0.95) before unmounting, releasing the
|
|
11
|
+
* shared layoutId so SearchView can claim it and morph from the same origin.
|
|
12
|
+
*
|
|
13
|
+
* Role: combobox (WAI-ARIA Search Combobox pattern).
|
|
14
|
+
* @see https://www.w3.org/WAI/ARIA/apg/patterns/combobox/
|
|
15
|
+
*/
|
|
16
|
+
import * as React from "react";
|
|
17
|
+
import type { SearchInternalProps, SearchProps } from "./search.types";
|
|
18
|
+
type SearchBarProps = Pick<SearchProps, "query" | "onQueryChange" | "onSearch" | "active" | "onActiveChange" | "leadingIcon" | "trailingIcon" | "placeholder" | "textAlign" | "className" | "aria-label"> & SearchInternalProps & {
|
|
19
|
+
/** KeyDown handler from useSearchKeyboard. */
|
|
20
|
+
onKeyDown: (e: React.KeyboardEvent) => void;
|
|
21
|
+
/** Currently highlighted suggestion index (-1 = none). */
|
|
22
|
+
activeIndex: number;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* SearchBar — collapsed state of the MD3 Search component.
|
|
26
|
+
*
|
|
27
|
+
* Uses Framer Motion `layout` + shared `layoutId` to morph into
|
|
28
|
+
* SearchView when active. Wrapped in AnimatePresence with mode="popLayout"
|
|
29
|
+
* so it exits (fades/scales out) before SearchView claims the layoutId.
|
|
30
|
+
*/
|
|
31
|
+
export declare function SearchBar({ query, onQueryChange, onSearch, active, onActiveChange, leadingIcon, trailingIcon, placeholder, textAlign, className, "aria-label": ariaLabel, searchId, listboxId, onKeyDown, activeIndex, }: SearchBarProps): import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* MD3 Expressive Search Context
|
|
4
|
+
* Shared state for the Search orchestrator and its children.
|
|
5
|
+
*/
|
|
6
|
+
interface SearchContextValue {
|
|
7
|
+
/** Unique ID for the results listbox, used for aria-controls. */
|
|
8
|
+
listboxId: string;
|
|
9
|
+
/** Currently highlighted suggestion index. -1 = none. */
|
|
10
|
+
activeIndex: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Provider for Search component state.
|
|
14
|
+
* Internal use only within the library.
|
|
15
|
+
*/
|
|
16
|
+
export declare function SearchProvider({ children, value, }: {
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
value: SearchContextValue;
|
|
19
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
/**
|
|
21
|
+
* Hook to access Search state from children (e.g., search items).
|
|
22
|
+
*/
|
|
23
|
+
export declare function useSearch(): SearchContextValue;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file search-view-docked.tsx
|
|
3
|
+
* MD3 Expressive SearchView — Docked variant.
|
|
4
|
+
*
|
|
5
|
+
* Displays as a popup dropdown below the SearchBar.
|
|
6
|
+
* Recommended for medium and large screens (tablets, desktops).
|
|
7
|
+
*
|
|
8
|
+
* Shape: CornerExtraLarge (rounded-[28px]) per SearchViewTokens.DockedContainerShape.
|
|
9
|
+
* Header height: 56dp per SearchViewTokens.DockedHeaderContainerHeight.
|
|
10
|
+
*
|
|
11
|
+
* Animation (Option B — MD3 morphing):
|
|
12
|
+
* - Shares `layoutId` with SearchBar. After SearchBar exits via its own
|
|
13
|
+
* AnimatePresence (mode="popLayout"), this view claims the layoutId and
|
|
14
|
+
* Framer Motion morphs the pill shape → rounded-[28px] container.
|
|
15
|
+
* - Uses mode="popLayout" so SearchBar can re-enter after this exits.
|
|
16
|
+
* - Focus: double-rAF pattern syncs focus with layout animation frame.
|
|
17
|
+
*/
|
|
18
|
+
import * as React from "react";
|
|
19
|
+
import type { SearchInternalProps, SearchProps } from "./search.types";
|
|
20
|
+
type SearchViewDockedProps = Pick<SearchProps, "query" | "onQueryChange" | "onSearch" | "active" | "onActiveChange" | "leadingIcon" | "trailingIcon" | "placeholder" | "textAlign" | "styleType" | "hasGap" | "children" | "viewClassName" | "aria-label"> & SearchInternalProps & {
|
|
21
|
+
onKeyDown: (e: React.KeyboardEvent) => void;
|
|
22
|
+
activeIndex: number;
|
|
23
|
+
};
|
|
24
|
+
export declare function SearchViewDocked({ query, onQueryChange, onSearch, active, onActiveChange, leadingIcon, trailingIcon, placeholder, textAlign, styleType, hasGap, children, viewClassName, "aria-label": ariaLabel, searchId, listboxId, onKeyDown, activeIndex, }: SearchViewDockedProps): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file search-view-fullscreen.tsx
|
|
3
|
+
* MD3 Expressive SearchView — FullScreen variant.
|
|
4
|
+
*
|
|
5
|
+
* Renders a full-screen overlay via React Portal.
|
|
6
|
+
* Using Portal avoids z-index stacking context issues and ensures
|
|
7
|
+
* the overlay always covers the entire viewport correctly.
|
|
8
|
+
*
|
|
9
|
+
* Animation (Option B — MD3 morphing):
|
|
10
|
+
* - Shares `layoutId` with SearchBar. After SearchBar exits via its own
|
|
11
|
+
* AnimatePresence (mode="popLayout"), this view claims the layoutId and
|
|
12
|
+
* Framer Motion morphs the pill shape → full-screen rect (CornerFull → CornerNone).
|
|
13
|
+
* - mode="popLayout" on this AnimatePresence enables SearchBar to re-enter
|
|
14
|
+
* after this view exits.
|
|
15
|
+
* - Focus: double-rAF pattern syncs focus with layout animation frame.
|
|
16
|
+
*
|
|
17
|
+
* Header height: 72dp per SearchViewTokens.FullScreenHeaderContainerHeight.
|
|
18
|
+
* ESC key closes the view (handled by useSearchKeyboard).
|
|
19
|
+
*/
|
|
20
|
+
import * as React from "react";
|
|
21
|
+
import type { SearchInternalProps, SearchProps } from "./search.types";
|
|
22
|
+
type SearchViewFullScreenProps = Pick<SearchProps, "query" | "onQueryChange" | "onSearch" | "active" | "onActiveChange" | "leadingIcon" | "trailingIcon" | "placeholder" | "textAlign" | "styleType" | "children" | "viewClassName" | "aria-label"> & SearchInternalProps & {
|
|
23
|
+
onKeyDown: (e: React.KeyboardEvent) => void;
|
|
24
|
+
activeIndex: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* SearchView FullScreen — full-screen overlay via React Portal.
|
|
28
|
+
*
|
|
29
|
+
* The `layoutId` shared with SearchBar enables Framer Motion to animate
|
|
30
|
+
* the shape morphing from the pill (rounded-full) to a full-screen rect.
|
|
31
|
+
*
|
|
32
|
+
* Contained style: no divider, background preserved.
|
|
33
|
+
* Divided style: HorizontalDivider between header and results.
|
|
34
|
+
*/
|
|
35
|
+
export declare function SearchViewFullScreen({ query, onQueryChange, onSearch, active, onActiveChange, leadingIcon, trailingIcon, placeholder, textAlign, styleType, children, viewClassName, "aria-label": ariaLabel, searchId, listboxId, onKeyDown, activeIndex, }: SearchViewFullScreenProps): React.ReactPortal | null;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file search.tsx
|
|
3
|
+
* MD3 Expressive Search — Orchestrator component.
|
|
4
|
+
*
|
|
5
|
+
* Composes SearchBar (collapsed) + SearchView (expanded) into a single
|
|
6
|
+
* developer-facing API. Routes to the correct SearchView variant based on props.
|
|
7
|
+
*
|
|
8
|
+
* Developer usage:
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const [query, setQuery] = useState("");
|
|
11
|
+
* const [active, setActive] = useState(false);
|
|
12
|
+
*
|
|
13
|
+
* <Search
|
|
14
|
+
* query={query}
|
|
15
|
+
* onQueryChange={setQuery}
|
|
16
|
+
* onSearch={(q) => doSearch(q)}
|
|
17
|
+
* active={active}
|
|
18
|
+
* onActiveChange={setActive}
|
|
19
|
+
* variant="docked"
|
|
20
|
+
* styleType="contained"
|
|
21
|
+
* >
|
|
22
|
+
* {results.map((r) => (
|
|
23
|
+
* <div key={r.id} id={`${YOUR_LISTBOX_ID}-option-0`} role="option">
|
|
24
|
+
* {r.label}
|
|
25
|
+
* </div>
|
|
26
|
+
* ))}
|
|
27
|
+
* </Search>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
import type { SearchProps } from "./search.types";
|
|
31
|
+
import { useSearch } from "./search-context";
|
|
32
|
+
/**
|
|
33
|
+
* MD3 Expressive Search — Orchestrator component.
|
|
34
|
+
*
|
|
35
|
+
* Renders a SearchBar (collapsed pill) and the appropriate SearchView
|
|
36
|
+
* (docked popup or fullscreen overlay) based on `variant`.
|
|
37
|
+
*
|
|
38
|
+
* The component is fully controlled:
|
|
39
|
+
* - `active` / `onActiveChange` manage open/close state.
|
|
40
|
+
* - `query` / `onQueryChange` manage input value.
|
|
41
|
+
*
|
|
42
|
+
* Shared `searchId` (React.useId) links SearchBar and SearchView via
|
|
43
|
+
* Framer Motion `layoutId` for seamless animated transitions.
|
|
44
|
+
*/
|
|
45
|
+
declare function SearchComponent({ query, onQueryChange, onSearch, active, onActiveChange, variant, styleType, hasGap, leadingIcon, trailingIcon, placeholder, textAlign, children, id, "aria-label": ariaLabel, className, viewClassName, }: SearchProps): import("react/jsx-runtime").JSX.Element;
|
|
46
|
+
/** MD3 Expressive Search component with `Search.useSearch` context accessor. */
|
|
47
|
+
export declare const Search: typeof SearchComponent & {
|
|
48
|
+
useSearch: typeof useSearch;
|
|
49
|
+
};
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file search.tokens.ts
|
|
3
|
+
* MD3 Expressive Search — Design tokens ported from:
|
|
4
|
+
* - SearchBarTokens.kt (v0_210)
|
|
5
|
+
* - SearchViewTokens.kt (v0_210)
|
|
6
|
+
*
|
|
7
|
+
* All dimensional values are in px (1dp = 1px on web).
|
|
8
|
+
* Colors reference CSS custom properties — do NOT hardcode hex.
|
|
9
|
+
* @see docs/m3/search/SearchBarTokens.kt
|
|
10
|
+
* @see docs/m3/search/SearchViewTokens.kt
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Height and shape tokens for Search variants.
|
|
14
|
+
* Maps directly from MD3 Kotlin token files.
|
|
15
|
+
*/
|
|
16
|
+
export declare const SearchTokens: {
|
|
17
|
+
readonly heights: {
|
|
18
|
+
/** SearchBarTokens.ContainerHeight = 56dp */
|
|
19
|
+
readonly bar: 56;
|
|
20
|
+
/** SearchViewTokens.DockedHeaderContainerHeight = 56dp */
|
|
21
|
+
readonly dockedHeader: 56;
|
|
22
|
+
/** SearchViewTokens.FullScreenHeaderContainerHeight = 72dp */
|
|
23
|
+
readonly fullScreenHeader: 72;
|
|
24
|
+
};
|
|
25
|
+
/** SearchBarTokens.AvatarSize = 30dp */
|
|
26
|
+
readonly avatarSize: 30;
|
|
27
|
+
/** Standard icon size for leading/trailing icons. */
|
|
28
|
+
readonly iconSize: 20;
|
|
29
|
+
/** Touch target for interactive icons per MD3 a11y spec. */
|
|
30
|
+
readonly iconTouchTarget: 48;
|
|
31
|
+
/** Gap between SearchBar and results list when hasGap=true. */
|
|
32
|
+
readonly dropdownGap: 2;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* CSS custom property references for Search colors.
|
|
36
|
+
* Maps to --md-sys-color-* tokens in the MD3 theme system.
|
|
37
|
+
*
|
|
38
|
+
* SearchBarTokens.kt:
|
|
39
|
+
* - ContainerColor → SurfaceContainerHigh
|
|
40
|
+
* - LeadingIconColor → OnSurface
|
|
41
|
+
* - TrailingIconColor → OnSurfaceVariant
|
|
42
|
+
* - InputTextColor → OnSurface
|
|
43
|
+
* - SupportingTextColor → OnSurfaceVariant (placeholder)
|
|
44
|
+
*
|
|
45
|
+
* SearchViewTokens.kt:
|
|
46
|
+
* - ContainerColor → SurfaceContainerHigh
|
|
47
|
+
* - DividerColor → Outline
|
|
48
|
+
*/
|
|
49
|
+
export declare const SEARCH_COLORS: {
|
|
50
|
+
/** SearchBarTokens.ContainerColor → surface-container-high */
|
|
51
|
+
readonly container: "var(--md-sys-color-surface-container-high)";
|
|
52
|
+
/** SearchBarTokens.LeadingIconColor → on-surface */
|
|
53
|
+
readonly leadingIcon: "var(--md-sys-color-on-surface)";
|
|
54
|
+
/** SearchBarTokens.TrailingIconColor → on-surface-variant */
|
|
55
|
+
readonly trailingIcon: "var(--md-sys-color-on-surface-variant)";
|
|
56
|
+
/** SearchBarTokens.InputTextColor → on-surface */
|
|
57
|
+
readonly inputText: "var(--md-sys-color-on-surface)";
|
|
58
|
+
/** SearchBarTokens.SupportingTextColor → on-surface-variant (placeholder) */
|
|
59
|
+
readonly supportingText: "var(--md-sys-color-on-surface-variant)";
|
|
60
|
+
/** SearchViewTokens.DividerColor → outline */
|
|
61
|
+
readonly divider: "var(--md-sys-color-outline)";
|
|
62
|
+
/** Focus indicator → secondary */
|
|
63
|
+
readonly focusIndicator: "var(--md-sys-color-secondary)";
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* SearchBarTokens.InputTextFont = BodyLarge (16sp / 24sp line-height).
|
|
67
|
+
* SearchBarTokens.SupportingTextFont = BodyLarge.
|
|
68
|
+
*/
|
|
69
|
+
export declare const SEARCH_TYPOGRAPHY: {
|
|
70
|
+
/** BodyLarge — used for input text and placeholder. */
|
|
71
|
+
readonly bodyLarge: "text-[16px] leading-6 font-normal tracking-[0.5px]";
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Spring animation for SearchBar width expand (inactive → active).
|
|
75
|
+
* Matches MD3 FastSpatial motion scheme.
|
|
76
|
+
*/
|
|
77
|
+
export declare const SEARCH_BAR_EXPAND_SPRING: {
|
|
78
|
+
type: "spring";
|
|
79
|
+
stiffness: number;
|
|
80
|
+
damping: number;
|
|
81
|
+
mass: number;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Spring animation for Docked SearchView dropdown reveal (slide + fade).
|
|
85
|
+
* Offset Y: -8px on enter, opacity 0→1.
|
|
86
|
+
*/
|
|
87
|
+
export declare const SEARCH_DOCKED_REVEAL_SPRING: {
|
|
88
|
+
type: "spring";
|
|
89
|
+
stiffness: number;
|
|
90
|
+
damping: number;
|
|
91
|
+
mass: number;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Spring animation for FullScreen SearchView shape morphing.
|
|
95
|
+
* Lower stiffness + mass gives a smoother pill→fullscreen morph.
|
|
96
|
+
*/
|
|
97
|
+
export declare const SEARCH_FULLSCREEN_SPRING: {
|
|
98
|
+
type: "spring";
|
|
99
|
+
stiffness: number;
|
|
100
|
+
damping: number;
|
|
101
|
+
mass: number;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Exit transition for SearchBar when mode="popLayout" is used.
|
|
105
|
+
* Fast fade-out so SearchView can claim the layoutId quickly.
|
|
106
|
+
*/
|
|
107
|
+
export declare const SEARCH_BAR_EXIT_SPRING: {
|
|
108
|
+
type: "spring";
|
|
109
|
+
stiffness: number;
|
|
110
|
+
damping: number;
|
|
111
|
+
mass: number;
|
|
112
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file search.types.ts
|
|
3
|
+
* MD3 Expressive Search — TypeScript prop definitions.
|
|
4
|
+
* Spec: https://m3.material.io/components/search/overview
|
|
5
|
+
* Reference: docs/m3/search/SearchBar.kt (MD3 Expressive)
|
|
6
|
+
*/
|
|
7
|
+
import type * as React from "react";
|
|
8
|
+
/**
|
|
9
|
+
* Display variant for the expanded SearchView.
|
|
10
|
+
*
|
|
11
|
+
* - `docked`: Popup dropdown below the SearchBar. For medium/large screens.
|
|
12
|
+
* - `fullscreen`: Full-screen dialog overlay. For compact/mobile screens.
|
|
13
|
+
*/
|
|
14
|
+
export type SearchVariant = "docked" | "fullscreen";
|
|
15
|
+
/**
|
|
16
|
+
* Visual style type for the SearchView.
|
|
17
|
+
*
|
|
18
|
+
* - `contained`: No divider between the input area and results.
|
|
19
|
+
* The container background is preserved continuously (recommended).
|
|
20
|
+
* - `divided`: A HorizontalDivider separates the input area from results.
|
|
21
|
+
*/
|
|
22
|
+
export type SearchStyleType = "contained" | "divided";
|
|
23
|
+
/**
|
|
24
|
+
* Internal props shared between SearchBar and SearchView sub-components.
|
|
25
|
+
* Not part of the public API.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export interface SearchInternalProps {
|
|
29
|
+
/** Unique ID generated by useId(), used as Framer Motion layoutId. */
|
|
30
|
+
searchId: string;
|
|
31
|
+
/** Unique ID for the results listbox, used for aria-controls. */
|
|
32
|
+
listboxId: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Props for the `<Search>` component (orchestrator).
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* const [query, setQuery] = useState("");
|
|
40
|
+
* const [active, setActive] = useState(false);
|
|
41
|
+
*
|
|
42
|
+
* <Search
|
|
43
|
+
* query={query}
|
|
44
|
+
* onQueryChange={setQuery}
|
|
45
|
+
* onSearch={(q) => console.log("search:", q)}
|
|
46
|
+
* active={active}
|
|
47
|
+
* onActiveChange={setActive}
|
|
48
|
+
* variant="docked"
|
|
49
|
+
* styleType="contained"
|
|
50
|
+
* >
|
|
51
|
+
* <SearchResultsList />
|
|
52
|
+
* </Search>
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export interface SearchProps {
|
|
56
|
+
/** Current search query value (controlled). */
|
|
57
|
+
query: string;
|
|
58
|
+
/** Called when user types in the search input. */
|
|
59
|
+
onQueryChange: (query: string) => void;
|
|
60
|
+
/** Called when user submits search (Enter key or suggestion click). */
|
|
61
|
+
onSearch: (query: string) => void;
|
|
62
|
+
/** Whether the SearchView is expanded/open (controlled). */
|
|
63
|
+
active: boolean;
|
|
64
|
+
/** Called when the Search open/close state should change. */
|
|
65
|
+
onActiveChange: (active: boolean) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Display variant for the expanded state.
|
|
68
|
+
* @default "docked"
|
|
69
|
+
*/
|
|
70
|
+
variant?: SearchVariant;
|
|
71
|
+
/**
|
|
72
|
+
* Visual style for the SearchView container.
|
|
73
|
+
* @default "contained"
|
|
74
|
+
*/
|
|
75
|
+
styleType?: SearchStyleType;
|
|
76
|
+
/**
|
|
77
|
+
* Whether to add a 2dp gap between the input header and the results list.
|
|
78
|
+
* Only applies when `variant="docked"`.
|
|
79
|
+
* @default false
|
|
80
|
+
*/
|
|
81
|
+
hasGap?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Icon rendered at the leading edge of the search input.
|
|
84
|
+
* Defaults to a built-in search icon.
|
|
85
|
+
*/
|
|
86
|
+
leadingIcon?: React.ReactNode;
|
|
87
|
+
/**
|
|
88
|
+
* Icon or action rendered at the trailing edge of the search input.
|
|
89
|
+
* Common examples: mic, camera, QR code.
|
|
90
|
+
*/
|
|
91
|
+
trailingIcon?: React.ReactNode;
|
|
92
|
+
/** Placeholder text shown when the input is empty. @default "Search" */
|
|
93
|
+
placeholder?: string;
|
|
94
|
+
/**
|
|
95
|
+
* Align the placeholder text to left, center, or right.
|
|
96
|
+
* Typed text will always remain left-aligned.
|
|
97
|
+
* @default "left"
|
|
98
|
+
*/
|
|
99
|
+
textAlign?: "left" | "center" | "right";
|
|
100
|
+
/**
|
|
101
|
+
* Search results or suggestions rendered inside the SearchView.
|
|
102
|
+
* Use `role="option"` on each item for accessibility.
|
|
103
|
+
*/
|
|
104
|
+
children?: React.ReactNode;
|
|
105
|
+
/**
|
|
106
|
+
* Override the auto-generated input element ID.
|
|
107
|
+
* Auto-generated via React.useId() if not provided.
|
|
108
|
+
*/
|
|
109
|
+
id?: string;
|
|
110
|
+
/**
|
|
111
|
+
* Accessible label for the search landmark.
|
|
112
|
+
* @default "Search"
|
|
113
|
+
*/
|
|
114
|
+
"aria-label"?: string;
|
|
115
|
+
/** Additional CSS classes for the SearchBar root element. */
|
|
116
|
+
className?: string;
|
|
117
|
+
/** Additional CSS classes for the SearchView container. */
|
|
118
|
+
viewClassName?: string;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Return type for `useSearchKeyboard`.
|
|
122
|
+
* @internal
|
|
123
|
+
*/
|
|
124
|
+
export interface UseSearchKeyboardReturn {
|
|
125
|
+
/** Currently highlighted suggestion index. -1 = none. */
|
|
126
|
+
activeIndex: number;
|
|
127
|
+
/** KeyDown handler — attach to the search input. */
|
|
128
|
+
handleKeyDown: (e: React.KeyboardEvent) => void;
|
|
129
|
+
/** Reset activeIndex (e.g., when query changes). */
|
|
130
|
+
resetActiveIndex: () => void;
|
|
131
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
interface TrailingActionProps {
|
|
3
|
+
query: string;
|
|
4
|
+
trailingIcon?: React.ReactNode;
|
|
5
|
+
onClear: () => void;
|
|
6
|
+
}
|
|
7
|
+
/** Clear button when query is non-empty, otherwise the trailing icon slot. */
|
|
8
|
+
export declare function TrailingAction({ query, trailingIcon, onClear, }: TrailingActionProps): import("react/jsx-runtime").JSX.Element | null;
|
|
9
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bug-on/md3-react",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Material Design 3 Expressive React components",
|
|
5
5
|
"author": "Bug Ổn",
|
|
6
6
|
"license": "MIT",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"@types/react": "^19.0.0",
|
|
69
69
|
"@types/react-dom": "^19.0.0",
|
|
70
70
|
"@vitejs/plugin-react": "^6.0.1",
|
|
71
|
+
"@vitest/coverage-v8": "4.1.4",
|
|
71
72
|
"jsdom": "^29.0.0",
|
|
72
73
|
"tsup": "^8.4.0",
|
|
73
74
|
"typescript": "5.8.3",
|