@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/index.js CHANGED
@@ -236,7 +236,7 @@ function generateM3Theme(sourceColorHex, mode = "light") {
236
236
  function applyTheme(sourceColorHex, mode = "light", root = document.documentElement) {
237
237
  const colors = generateM3Theme(sourceColorHex, mode);
238
238
  for (const [key, value] of Object.entries(colors)) {
239
- const kebabKey = key.replace(/[A-Z]/g, (m38) => `-${m38.toLowerCase()}`);
239
+ const kebabKey = key.replace(/[A-Z]/g, (m43) => `-${m43.toLowerCase()}`);
240
240
  root.style.setProperty(`--md-sys-color-${kebabKey}`, value);
241
241
  root.style.setProperty(`--color-m3-${kebabKey}`, value);
242
242
  }
@@ -6720,6 +6720,825 @@ var RadioGroupComponent = React31__namespace.forwardRef(
6720
6720
  );
6721
6721
  RadioGroupComponent.displayName = "RadioGroup";
6722
6722
  var RadioGroup2 = React31__namespace.memo(RadioGroupComponent);
6723
+ function useSearchKeyboard({
6724
+ active,
6725
+ onActiveChange,
6726
+ onSearch,
6727
+ query,
6728
+ itemCount,
6729
+ onSelectSuggestion
6730
+ }) {
6731
+ const [activeIndex, setActiveIndex] = React31__namespace.useState(-1);
6732
+ const resetKeyRef = React31__namespace.useRef(`${active}:${query}`);
6733
+ const currentKey = `${active}:${query}`;
6734
+ if (resetKeyRef.current !== currentKey) {
6735
+ resetKeyRef.current = currentKey;
6736
+ setActiveIndex(-1);
6737
+ }
6738
+ const handleKeyDown = React31__namespace.useCallback(
6739
+ (e) => {
6740
+ if (!active) return;
6741
+ switch (e.key) {
6742
+ case "ArrowDown": {
6743
+ e.preventDefault();
6744
+ setActiveIndex((i) => i < itemCount - 1 ? i + 1 : i);
6745
+ break;
6746
+ }
6747
+ case "ArrowUp": {
6748
+ e.preventDefault();
6749
+ setActiveIndex((i) => i > -1 ? i - 1 : -1);
6750
+ break;
6751
+ }
6752
+ case "Enter": {
6753
+ e.preventDefault();
6754
+ if (activeIndex >= 0 && onSelectSuggestion) {
6755
+ onSelectSuggestion(activeIndex);
6756
+ } else {
6757
+ onSearch(query);
6758
+ }
6759
+ break;
6760
+ }
6761
+ case "Escape": {
6762
+ e.preventDefault();
6763
+ onActiveChange(false);
6764
+ break;
6765
+ }
6766
+ }
6767
+ },
6768
+ [
6769
+ active,
6770
+ activeIndex,
6771
+ itemCount,
6772
+ onActiveChange,
6773
+ onSearch,
6774
+ onSelectSuggestion,
6775
+ query
6776
+ ]
6777
+ );
6778
+ const resetActiveIndex = React31__namespace.useCallback(() => {
6779
+ setActiveIndex(-1);
6780
+ }, []);
6781
+ return { activeIndex, handleKeyDown, resetActiveIndex };
6782
+ }
6783
+
6784
+ // src/ui/search/search.tokens.ts
6785
+ var SearchTokens = {
6786
+ // ── Heights ─────────────────────────────────────────────────────────────
6787
+ heights: {
6788
+ /** SearchBarTokens.ContainerHeight = 56dp */
6789
+ bar: 56,
6790
+ /** SearchViewTokens.DockedHeaderContainerHeight = 56dp */
6791
+ dockedHeader: 56,
6792
+ /** SearchViewTokens.FullScreenHeaderContainerHeight = 72dp */
6793
+ fullScreenHeader: 72
6794
+ },
6795
+ // ── Avatar ────────────────────────────────────────────────────────────
6796
+ /** SearchBarTokens.AvatarSize = 30dp */
6797
+ avatarSize: 30,
6798
+ // ── Icon ───────────────────────────────────────────────────────────────
6799
+ /** Standard icon size for leading/trailing icons. */
6800
+ iconSize: 20,
6801
+ /** Touch target for interactive icons per MD3 a11y spec. */
6802
+ iconTouchTarget: 48,
6803
+ // ── Gap ────────────────────────────────────────────────────────────────
6804
+ /** Gap between SearchBar and results list when hasGap=true. */
6805
+ dropdownGap: 2
6806
+ };
6807
+ var SEARCH_COLORS = {
6808
+ /** SearchBarTokens.ContainerColor → surface-container-high */
6809
+ container: "var(--md-sys-color-surface-container-high)",
6810
+ /** SearchBarTokens.LeadingIconColor → on-surface */
6811
+ leadingIcon: "var(--md-sys-color-on-surface)",
6812
+ /** SearchBarTokens.TrailingIconColor → on-surface-variant */
6813
+ trailingIcon: "var(--md-sys-color-on-surface-variant)",
6814
+ /** SearchBarTokens.InputTextColor → on-surface */
6815
+ inputText: "var(--md-sys-color-on-surface)",
6816
+ /** SearchBarTokens.SupportingTextColor → on-surface-variant (placeholder) */
6817
+ supportingText: "var(--md-sys-color-on-surface-variant)",
6818
+ /** SearchViewTokens.DividerColor → outline */
6819
+ divider: "var(--md-sys-color-outline)",
6820
+ /** Focus indicator → secondary */
6821
+ focusIndicator: "var(--md-sys-color-secondary)"
6822
+ };
6823
+ var SEARCH_TYPOGRAPHY = {
6824
+ /** BodyLarge — used for input text and placeholder. */
6825
+ bodyLarge: "text-[16px] leading-6 font-normal tracking-[0.5px]"
6826
+ };
6827
+ var SEARCH_BAR_EXPAND_SPRING = {
6828
+ type: "spring",
6829
+ stiffness: 380,
6830
+ damping: 38,
6831
+ mass: 1
6832
+ };
6833
+ var SEARCH_DOCKED_REVEAL_SPRING = {
6834
+ type: "spring",
6835
+ stiffness: 400,
6836
+ damping: 35,
6837
+ mass: 0.8
6838
+ };
6839
+ var SEARCH_FULLSCREEN_SPRING = {
6840
+ type: "spring",
6841
+ stiffness: 300,
6842
+ damping: 30,
6843
+ mass: 0.9
6844
+ };
6845
+ var SEARCH_BAR_EXIT_SPRING = {
6846
+ type: "spring",
6847
+ stiffness: 500,
6848
+ damping: 40,
6849
+ mass: 0.6
6850
+ };
6851
+ var PLACEHOLDER_SPRING = {
6852
+ type: "spring",
6853
+ stiffness: 350,
6854
+ damping: 30,
6855
+ mass: 0.8
6856
+ };
6857
+ function AnimatedPlaceholder({
6858
+ text,
6859
+ textAlign,
6860
+ visible,
6861
+ focused,
6862
+ children,
6863
+ className
6864
+ }) {
6865
+ const shouldReduceMotion = react.useReducedMotion();
6866
+ const containerRef = React31__namespace.useRef(null);
6867
+ const spanRef = React31__namespace.useRef(null);
6868
+ const [xOffset, setXOffset] = React31__namespace.useState(0);
6869
+ const recalculate = React31__namespace.useCallback(() => {
6870
+ const container = containerRef.current;
6871
+ const span = spanRef.current;
6872
+ if (!container || !span || textAlign === "left") {
6873
+ setXOffset(0);
6874
+ return;
6875
+ }
6876
+ const containerWidth = container.offsetWidth;
6877
+ const textWidth = span.offsetWidth;
6878
+ if (textAlign === "center") {
6879
+ setXOffset(Math.max(0, (containerWidth - textWidth) / 2));
6880
+ } else {
6881
+ setXOffset(Math.max(0, containerWidth - textWidth));
6882
+ }
6883
+ }, [textAlign]);
6884
+ React31__namespace.useLayoutEffect(() => {
6885
+ recalculate();
6886
+ }, [recalculate]);
6887
+ React31__namespace.useEffect(() => {
6888
+ const container = containerRef.current;
6889
+ if (!container) return;
6890
+ const observer = new ResizeObserver(recalculate);
6891
+ observer.observe(container);
6892
+ return () => observer.disconnect();
6893
+ }, [recalculate]);
6894
+ const targetX = focused || !visible ? 0 : xOffset;
6895
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6896
+ "div",
6897
+ {
6898
+ ref: containerRef,
6899
+ className: cn("relative flex-1 min-w-0", className),
6900
+ children: [
6901
+ children,
6902
+ /* @__PURE__ */ jsxRuntime.jsx(
6903
+ react.m.span,
6904
+ {
6905
+ ref: (el) => {
6906
+ spanRef.current = el;
6907
+ },
6908
+ "aria-hidden": "true",
6909
+ className: cn(
6910
+ "pointer-events-none absolute inset-y-0 left-0",
6911
+ "flex items-center whitespace-nowrap select-none",
6912
+ SEARCH_TYPOGRAPHY.bodyLarge
6913
+ ),
6914
+ style: { color: SEARCH_COLORS.supportingText },
6915
+ animate: {
6916
+ x: targetX,
6917
+ opacity: visible ? 1 : 0
6918
+ },
6919
+ transition: shouldReduceMotion ? { duration: 0 } : PLACEHOLDER_SPRING,
6920
+ children: text
6921
+ }
6922
+ )
6923
+ ]
6924
+ }
6925
+ );
6926
+ }
6927
+ function DefaultSearchIcon() {
6928
+ return /* @__PURE__ */ jsxRuntime.jsx(
6929
+ "span",
6930
+ {
6931
+ className: "material-symbols-rounded select-none leading-none",
6932
+ style: { fontSize: SearchTokens.iconSize },
6933
+ "aria-hidden": "true",
6934
+ children: "search"
6935
+ }
6936
+ );
6937
+ }
6938
+ function SearchBar({
6939
+ query,
6940
+ onQueryChange,
6941
+ onSearch,
6942
+ active,
6943
+ onActiveChange,
6944
+ leadingIcon,
6945
+ trailingIcon,
6946
+ placeholder = "Search",
6947
+ textAlign = "left",
6948
+ className,
6949
+ "aria-label": ariaLabel = "Search",
6950
+ searchId,
6951
+ listboxId,
6952
+ onKeyDown,
6953
+ activeIndex
6954
+ }) {
6955
+ const shouldReduceMotion = react.useReducedMotion();
6956
+ const inputRef = React31__namespace.useRef(null);
6957
+ const prevActiveRef = React31__namespace.useRef(active);
6958
+ const isRestoringFocusRef = React31__namespace.useRef(false);
6959
+ React31__namespace.useEffect(() => {
6960
+ var _a;
6961
+ let rafId;
6962
+ if (prevActiveRef.current === true && active === false) {
6963
+ isRestoringFocusRef.current = true;
6964
+ (_a = inputRef.current) == null ? void 0 : _a.focus();
6965
+ rafId = requestAnimationFrame(() => {
6966
+ isRestoringFocusRef.current = false;
6967
+ });
6968
+ }
6969
+ prevActiveRef.current = active;
6970
+ return () => {
6971
+ if (rafId) cancelAnimationFrame(rafId);
6972
+ };
6973
+ }, [active]);
6974
+ const handleFocus = () => {
6975
+ if (!active && !isRestoringFocusRef.current) {
6976
+ onActiveChange(true);
6977
+ }
6978
+ };
6979
+ const handleFormSubmit = (e) => {
6980
+ e.preventDefault();
6981
+ onSearch(query);
6982
+ };
6983
+ const activeDescendant = activeIndex >= 0 ? `${listboxId}-option-${activeIndex}` : void 0;
6984
+ return (
6985
+ /*
6986
+ * AnimatePresence mode="popLayout":
6987
+ * When SearchView opens (active=true), SearchBar plays its exit animation
6988
+ * first, then unmounts — releasing the shared layoutId for SearchView to
6989
+ * claim and morph from the pill shape.
6990
+ */
6991
+ /* @__PURE__ */ jsxRuntime.jsx(react.AnimatePresence, { mode: "popLayout", children: !active && /* @__PURE__ */ jsxRuntime.jsxs(
6992
+ react.m.div,
6993
+ {
6994
+ layout: !shouldReduceMotion,
6995
+ layoutId: shouldReduceMotion ? void 0 : searchId,
6996
+ transition: shouldReduceMotion ? void 0 : SEARCH_BAR_EXPAND_SPRING,
6997
+ className: cn("relative", className),
6998
+ style: { height: SearchTokens.heights.bar },
6999
+ initial: shouldReduceMotion ? false : { opacity: 0, scale: 0.95 },
7000
+ animate: { opacity: 1, scale: 1 },
7001
+ exit: shouldReduceMotion ? {} : { opacity: 0, scale: 0.95, transition: SEARCH_BAR_EXIT_SPRING },
7002
+ children: [
7003
+ /* @__PURE__ */ jsxRuntime.jsx(
7004
+ "div",
7005
+ {
7006
+ className: "absolute inset-0 rounded-full",
7007
+ style: { backgroundColor: SEARCH_COLORS.container },
7008
+ "aria-hidden": "true"
7009
+ }
7010
+ ),
7011
+ /* @__PURE__ */ jsxRuntime.jsx(
7012
+ "search",
7013
+ {
7014
+ "aria-label": ariaLabel,
7015
+ className: "relative flex h-full items-center gap-2 rounded-full px-4",
7016
+ children: /* @__PURE__ */ jsxRuntime.jsxs("form", { className: "contents", onSubmit: handleFormSubmit, children: [
7017
+ /* @__PURE__ */ jsxRuntime.jsx(
7018
+ "span",
7019
+ {
7020
+ className: "flex shrink-0 items-center justify-center",
7021
+ style: { color: SEARCH_COLORS.leadingIcon },
7022
+ "aria-hidden": "true",
7023
+ children: leadingIcon != null ? leadingIcon : /* @__PURE__ */ jsxRuntime.jsx(DefaultSearchIcon, {})
7024
+ }
7025
+ ),
7026
+ /* @__PURE__ */ jsxRuntime.jsx(
7027
+ AnimatedPlaceholder,
7028
+ {
7029
+ text: placeholder,
7030
+ textAlign,
7031
+ visible: !query,
7032
+ focused: active,
7033
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7034
+ "input",
7035
+ {
7036
+ ref: inputRef,
7037
+ id: searchId,
7038
+ type: "search",
7039
+ role: "combobox",
7040
+ "aria-expanded": active,
7041
+ "aria-controls": listboxId,
7042
+ "aria-autocomplete": "list",
7043
+ "aria-activedescendant": activeDescendant,
7044
+ "aria-label": placeholder,
7045
+ value: query,
7046
+ placeholder,
7047
+ className: cn(
7048
+ "w-full bg-transparent border-none outline-none",
7049
+ "text-[16px] leading-6 font-normal tracking-[0.5px]",
7050
+ "placeholder:text-transparent"
7051
+ ),
7052
+ style: { color: SEARCH_COLORS.inputText },
7053
+ onFocus: handleFocus,
7054
+ onChange: (e) => onQueryChange(e.target.value),
7055
+ onKeyDown
7056
+ }
7057
+ )
7058
+ }
7059
+ ),
7060
+ trailingIcon && /* @__PURE__ */ jsxRuntime.jsx(
7061
+ "span",
7062
+ {
7063
+ className: "flex shrink-0 items-center justify-center",
7064
+ style: { color: SEARCH_COLORS.trailingIcon },
7065
+ "aria-hidden": "true",
7066
+ children: trailingIcon
7067
+ }
7068
+ )
7069
+ ] })
7070
+ }
7071
+ )
7072
+ ]
7073
+ },
7074
+ searchId
7075
+ ) })
7076
+ );
7077
+ }
7078
+ var SearchContext = React31__namespace.createContext(null);
7079
+ function SearchProvider({
7080
+ children,
7081
+ value
7082
+ }) {
7083
+ return /* @__PURE__ */ jsxRuntime.jsx(SearchContext.Provider, { value, children });
7084
+ }
7085
+ function useSearch() {
7086
+ const context = React31__namespace.useContext(SearchContext);
7087
+ if (!context) {
7088
+ return { listboxId: "", activeIndex: -1 };
7089
+ }
7090
+ return context;
7091
+ }
7092
+ function useClickOutside(handler, enabled = true) {
7093
+ const ref = React31.useRef(null);
7094
+ React31.useEffect(() => {
7095
+ if (!enabled) return;
7096
+ const listener = (event) => {
7097
+ const el = ref.current;
7098
+ if (!el || el.contains(event.target)) {
7099
+ return;
7100
+ }
7101
+ handler();
7102
+ };
7103
+ document.addEventListener("mousedown", listener);
7104
+ document.addEventListener("touchstart", listener);
7105
+ return () => {
7106
+ document.removeEventListener("mousedown", listener);
7107
+ document.removeEventListener("touchstart", listener);
7108
+ };
7109
+ }, [handler, enabled]);
7110
+ return ref;
7111
+ }
7112
+ function useSearchViewFocus(inputRef, active) {
7113
+ React31__namespace.useEffect(() => {
7114
+ if (!active) return;
7115
+ let raf2;
7116
+ const raf1 = requestAnimationFrame(() => {
7117
+ raf2 = requestAnimationFrame(() => {
7118
+ var _a;
7119
+ (_a = inputRef.current) == null ? void 0 : _a.focus();
7120
+ });
7121
+ });
7122
+ return () => {
7123
+ cancelAnimationFrame(raf1);
7124
+ if (raf2) cancelAnimationFrame(raf2);
7125
+ };
7126
+ }, [active, inputRef]);
7127
+ }
7128
+ function TrailingAction({
7129
+ query,
7130
+ trailingIcon,
7131
+ onClear
7132
+ }) {
7133
+ if (query) {
7134
+ return /* @__PURE__ */ jsxRuntime.jsx(
7135
+ IconButton,
7136
+ {
7137
+ size: "sm",
7138
+ style: { color: SEARCH_COLORS.trailingIcon },
7139
+ "aria-label": "Clear search",
7140
+ onClick: onClear,
7141
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7142
+ "span",
7143
+ {
7144
+ className: "material-symbols-rounded select-none leading-none",
7145
+ style: { fontSize: 20 },
7146
+ "aria-hidden": "true",
7147
+ children: "close"
7148
+ }
7149
+ )
7150
+ }
7151
+ );
7152
+ }
7153
+ if (trailingIcon) {
7154
+ return /* @__PURE__ */ jsxRuntime.jsx(
7155
+ "span",
7156
+ {
7157
+ className: "flex shrink-0 items-center justify-center",
7158
+ style: { color: SEARCH_COLORS.trailingIcon },
7159
+ "aria-hidden": "true",
7160
+ children: trailingIcon
7161
+ }
7162
+ );
7163
+ }
7164
+ return null;
7165
+ }
7166
+ function SearchViewDocked({
7167
+ query,
7168
+ onQueryChange,
7169
+ onSearch,
7170
+ active,
7171
+ onActiveChange,
7172
+ leadingIcon,
7173
+ trailingIcon,
7174
+ placeholder = "Search",
7175
+ textAlign = "left",
7176
+ styleType = "contained",
7177
+ hasGap = false,
7178
+ children,
7179
+ viewClassName,
7180
+ "aria-label": ariaLabel = "Search",
7181
+ searchId,
7182
+ listboxId,
7183
+ onKeyDown,
7184
+ activeIndex
7185
+ }) {
7186
+ const shouldReduceMotion = react.useReducedMotion();
7187
+ const inputRef = React31__namespace.useRef(null);
7188
+ useSearchViewFocus(inputRef, active);
7189
+ const dropdownRef = useClickOutside(() => {
7190
+ onActiveChange(false);
7191
+ }, active);
7192
+ const handleFormSubmit = (e) => {
7193
+ e.preventDefault();
7194
+ onSearch(query);
7195
+ };
7196
+ const activeDescendant = activeIndex >= 0 ? `${listboxId}-option-${activeIndex}` : void 0;
7197
+ return (
7198
+ /*
7199
+ * mode="popLayout": when SearchView exits, it plays exit animation first,
7200
+ * then unmounts — freeing the layoutId for SearchBar to re-enter and morph back.
7201
+ */
7202
+ /* @__PURE__ */ jsxRuntime.jsx(react.AnimatePresence, { mode: "popLayout", children: active && /* @__PURE__ */ jsxRuntime.jsxs(
7203
+ react.m.div,
7204
+ {
7205
+ ref: dropdownRef,
7206
+ layoutId: shouldReduceMotion ? void 0 : searchId,
7207
+ className: cn(
7208
+ // DockedContainerShape = CornerExtraLarge = 28dp radius
7209
+ "rounded-[28px] overflow-hidden",
7210
+ viewClassName
7211
+ ),
7212
+ style: { backgroundColor: SEARCH_COLORS.container },
7213
+ initial: shouldReduceMotion ? {} : { opacity: 0, y: -8 },
7214
+ animate: { opacity: 1, y: 0 },
7215
+ exit: shouldReduceMotion ? {} : { opacity: 0, y: -8 },
7216
+ transition: shouldReduceMotion ? { duration: 0 } : SEARCH_DOCKED_REVEAL_SPRING,
7217
+ children: [
7218
+ /* @__PURE__ */ jsxRuntime.jsx(
7219
+ "search",
7220
+ {
7221
+ "aria-label": ariaLabel,
7222
+ className: "flex items-center gap-2 px-4",
7223
+ style: { height: SearchTokens.heights.dockedHeader },
7224
+ children: /* @__PURE__ */ jsxRuntime.jsxs("form", { className: "contents", onSubmit: handleFormSubmit, children: [
7225
+ /* @__PURE__ */ jsxRuntime.jsx(
7226
+ "span",
7227
+ {
7228
+ className: "flex shrink-0 items-center justify-center",
7229
+ style: { color: SEARCH_COLORS.leadingIcon },
7230
+ "aria-hidden": "true",
7231
+ children: leadingIcon
7232
+ }
7233
+ ),
7234
+ /* @__PURE__ */ jsxRuntime.jsx(
7235
+ AnimatedPlaceholder,
7236
+ {
7237
+ text: placeholder,
7238
+ textAlign,
7239
+ visible: !query,
7240
+ focused: active,
7241
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7242
+ "input",
7243
+ {
7244
+ ref: inputRef,
7245
+ id: `${searchId}-view`,
7246
+ type: "search",
7247
+ role: "combobox",
7248
+ "aria-expanded": true,
7249
+ "aria-controls": listboxId,
7250
+ "aria-autocomplete": "list",
7251
+ "aria-activedescendant": activeDescendant,
7252
+ "aria-label": placeholder,
7253
+ value: query,
7254
+ placeholder,
7255
+ className: cn(
7256
+ "w-full bg-transparent border-none outline-none",
7257
+ "text-[16px] leading-6 font-normal tracking-[0.5px]",
7258
+ "placeholder:text-transparent"
7259
+ ),
7260
+ style: { color: SEARCH_COLORS.inputText },
7261
+ onChange: (e) => onQueryChange(e.target.value),
7262
+ onKeyDown
7263
+ }
7264
+ )
7265
+ }
7266
+ ),
7267
+ /* @__PURE__ */ jsxRuntime.jsx(
7268
+ TrailingAction,
7269
+ {
7270
+ query,
7271
+ trailingIcon,
7272
+ onClear: () => onQueryChange("")
7273
+ }
7274
+ )
7275
+ ] })
7276
+ }
7277
+ ),
7278
+ hasGap && children && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-0.5", "aria-hidden": "true" }),
7279
+ styleType === "divided" && children && /* @__PURE__ */ jsxRuntime.jsx(
7280
+ "hr",
7281
+ {
7282
+ className: "border-0 border-t",
7283
+ style: { borderColor: SEARCH_COLORS.divider }
7284
+ }
7285
+ ),
7286
+ children && /* @__PURE__ */ jsxRuntime.jsx(
7287
+ "div",
7288
+ {
7289
+ id: listboxId,
7290
+ role: "listbox",
7291
+ "aria-label": `${ariaLabel} results`,
7292
+ className: "min-h-30",
7293
+ children
7294
+ }
7295
+ )
7296
+ ]
7297
+ },
7298
+ `${searchId}-view`
7299
+ ) })
7300
+ );
7301
+ }
7302
+ function ArrowBackIcon2() {
7303
+ return /* @__PURE__ */ jsxRuntime.jsx(
7304
+ "span",
7305
+ {
7306
+ className: "material-symbols-rounded select-none leading-none",
7307
+ style: { fontSize: 24 },
7308
+ "aria-hidden": "true",
7309
+ children: "arrow_back"
7310
+ }
7311
+ );
7312
+ }
7313
+ function SearchViewFullScreen({
7314
+ query,
7315
+ onQueryChange,
7316
+ onSearch,
7317
+ active,
7318
+ onActiveChange,
7319
+ leadingIcon,
7320
+ trailingIcon,
7321
+ placeholder = "Search",
7322
+ textAlign = "left",
7323
+ styleType = "contained",
7324
+ children,
7325
+ viewClassName,
7326
+ "aria-label": ariaLabel = "Search",
7327
+ searchId,
7328
+ listboxId,
7329
+ onKeyDown,
7330
+ activeIndex
7331
+ }) {
7332
+ const shouldReduceMotion = react.useReducedMotion();
7333
+ const inputRef = React31__namespace.useRef(null);
7334
+ useSearchViewFocus(inputRef, active);
7335
+ const [mounted, setMounted] = React31__namespace.useState(false);
7336
+ React31__namespace.useEffect(() => {
7337
+ setMounted(true);
7338
+ }, []);
7339
+ const handleFormSubmit = (e) => {
7340
+ e.preventDefault();
7341
+ onSearch(query);
7342
+ };
7343
+ const handleEscape = (e) => {
7344
+ if (e.key === "Escape") {
7345
+ e.stopPropagation();
7346
+ onActiveChange(false);
7347
+ }
7348
+ };
7349
+ const activeDescendant = activeIndex >= 0 ? `${listboxId}-option-${activeIndex}` : void 0;
7350
+ if (!mounted) return null;
7351
+ const content = (
7352
+ /*
7353
+ * mode="popLayout": when FullScreen exits, it plays exit animation first,
7354
+ * then unmounts — freeing the layoutId for SearchBar to re-enter and morph back
7355
+ * to the pill shape.
7356
+ */
7357
+ /* @__PURE__ */ jsxRuntime.jsx(react.AnimatePresence, { mode: "popLayout", children: active && /* @__PURE__ */ jsxRuntime.jsxs(
7358
+ react.m.div,
7359
+ {
7360
+ layoutId: shouldReduceMotion ? void 0 : searchId,
7361
+ role: "dialog",
7362
+ "aria-modal": "true",
7363
+ "aria-label": ariaLabel,
7364
+ className: cn(
7365
+ "fixed inset-0 z-50 flex flex-col overflow-hidden",
7366
+ // CornerNone — shape is resolved by Framer Motion layout animation
7367
+ "rounded-none",
7368
+ viewClassName
7369
+ ),
7370
+ style: { backgroundColor: SEARCH_COLORS.container },
7371
+ initial: shouldReduceMotion ? {} : { opacity: 0 },
7372
+ animate: { opacity: 1 },
7373
+ exit: shouldReduceMotion ? {} : { opacity: 0 },
7374
+ transition: shouldReduceMotion ? { duration: 0 } : SEARCH_FULLSCREEN_SPRING,
7375
+ onKeyDown: handleEscape,
7376
+ children: [
7377
+ /* @__PURE__ */ jsxRuntime.jsx(
7378
+ "search",
7379
+ {
7380
+ "aria-label": ariaLabel,
7381
+ className: "flex shrink-0 items-center gap-2 px-4",
7382
+ style: { height: SearchTokens.heights.fullScreenHeader },
7383
+ children: /* @__PURE__ */ jsxRuntime.jsxs("form", { className: "contents", onSubmit: handleFormSubmit, children: [
7384
+ /* @__PURE__ */ jsxRuntime.jsx(
7385
+ IconButton,
7386
+ {
7387
+ size: "sm",
7388
+ style: { color: SEARCH_COLORS.leadingIcon },
7389
+ "aria-label": "Close search",
7390
+ onClick: (e) => {
7391
+ e.stopPropagation();
7392
+ onActiveChange(false);
7393
+ },
7394
+ children: leadingIcon != null ? leadingIcon : /* @__PURE__ */ jsxRuntime.jsx(ArrowBackIcon2, {})
7395
+ }
7396
+ ),
7397
+ /* @__PURE__ */ jsxRuntime.jsx(
7398
+ AnimatedPlaceholder,
7399
+ {
7400
+ text: placeholder,
7401
+ textAlign,
7402
+ visible: !query,
7403
+ focused: active,
7404
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7405
+ "input",
7406
+ {
7407
+ ref: inputRef,
7408
+ id: `${searchId}-fs`,
7409
+ type: "search",
7410
+ role: "combobox",
7411
+ "aria-expanded": true,
7412
+ "aria-controls": listboxId,
7413
+ "aria-autocomplete": "list",
7414
+ "aria-activedescendant": activeDescendant,
7415
+ "aria-label": placeholder,
7416
+ value: query,
7417
+ placeholder,
7418
+ className: cn(
7419
+ "w-full bg-transparent border-none outline-none",
7420
+ "text-[16px] leading-6 font-normal tracking-[0.5px]",
7421
+ "placeholder:text-transparent"
7422
+ ),
7423
+ style: { color: SEARCH_COLORS.inputText },
7424
+ onChange: (e) => onQueryChange(e.target.value),
7425
+ onKeyDown
7426
+ }
7427
+ )
7428
+ }
7429
+ ),
7430
+ /* @__PURE__ */ jsxRuntime.jsx(
7431
+ TrailingAction,
7432
+ {
7433
+ query,
7434
+ trailingIcon,
7435
+ onClear: () => onQueryChange("")
7436
+ }
7437
+ )
7438
+ ] })
7439
+ }
7440
+ ),
7441
+ styleType === "divided" && /* @__PURE__ */ jsxRuntime.jsx(
7442
+ "hr",
7443
+ {
7444
+ className: "border-0 border-t shrink-0",
7445
+ style: { borderColor: SEARCH_COLORS.divider }
7446
+ }
7447
+ ),
7448
+ /* @__PURE__ */ jsxRuntime.jsx(
7449
+ "div",
7450
+ {
7451
+ id: listboxId,
7452
+ role: "listbox",
7453
+ tabIndex: 0,
7454
+ "aria-label": `${ariaLabel} results`,
7455
+ className: "flex-1 overflow-y-auto outline-none",
7456
+ onClick: (e) => {
7457
+ if (e.target === e.currentTarget) onActiveChange(false);
7458
+ },
7459
+ onKeyDown: (e) => {
7460
+ if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) {
7461
+ e.preventDefault();
7462
+ onActiveChange(false);
7463
+ }
7464
+ },
7465
+ children
7466
+ }
7467
+ )
7468
+ ]
7469
+ },
7470
+ `${searchId}-fs`
7471
+ ) })
7472
+ );
7473
+ return reactDom.createPortal(content, document.body);
7474
+ }
7475
+ function SearchComponent({
7476
+ query,
7477
+ onQueryChange,
7478
+ onSearch,
7479
+ active,
7480
+ onActiveChange,
7481
+ variant = "docked",
7482
+ styleType = "contained",
7483
+ hasGap = false,
7484
+ leadingIcon,
7485
+ trailingIcon,
7486
+ placeholder = "Search",
7487
+ textAlign = "left",
7488
+ children,
7489
+ id,
7490
+ "aria-label": ariaLabel = "Search",
7491
+ className,
7492
+ viewClassName
7493
+ }) {
7494
+ const generatedId = React31__namespace.useId();
7495
+ const searchId = id != null ? id : generatedId;
7496
+ const listboxId = `${searchId}-listbox`;
7497
+ const itemCount = React31__namespace.Children.count(children);
7498
+ const { activeIndex, handleKeyDown } = useSearchKeyboard({
7499
+ active,
7500
+ onActiveChange,
7501
+ onSearch,
7502
+ query,
7503
+ itemCount
7504
+ });
7505
+ const sharedProps = {
7506
+ query,
7507
+ onQueryChange,
7508
+ onSearch,
7509
+ active,
7510
+ onActiveChange,
7511
+ leadingIcon,
7512
+ trailingIcon,
7513
+ placeholder,
7514
+ textAlign,
7515
+ "aria-label": ariaLabel,
7516
+ searchId,
7517
+ listboxId,
7518
+ onKeyDown: handleKeyDown,
7519
+ activeIndex
7520
+ };
7521
+ return /* @__PURE__ */ jsxRuntime.jsx(SearchProvider, { value: { activeIndex, listboxId }, children: /* @__PURE__ */ jsxRuntime.jsxs(react.m.div, { className, style: { minHeight: 56 }, children: [
7522
+ /* @__PURE__ */ jsxRuntime.jsx(SearchBar, __spreadValues({}, sharedProps)),
7523
+ variant === "fullscreen" ? /* @__PURE__ */ jsxRuntime.jsx(
7524
+ SearchViewFullScreen,
7525
+ __spreadProps(__spreadValues({}, sharedProps), {
7526
+ styleType,
7527
+ viewClassName,
7528
+ children
7529
+ })
7530
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
7531
+ SearchViewDocked,
7532
+ __spreadProps(__spreadValues({}, sharedProps), {
7533
+ styleType,
7534
+ hasGap,
7535
+ viewClassName,
7536
+ children
7537
+ })
7538
+ )
7539
+ ] }) });
7540
+ }
7541
+ var Search = Object.assign(SearchComponent, { useSearch });
6723
7542
  function coerceValue(value, min, max) {
6724
7543
  return Math.max(min, Math.min(max, value));
6725
7544
  }
@@ -8653,7 +9472,9 @@ function SnackbarHost({ state, className }) {
8653
9472
  ) });
8654
9473
  }
8655
9474
  SnackbarHost.displayName = "SnackbarHost";
8656
- var SnackbarContext = React31__namespace.createContext(null);
9475
+ var SnackbarContext = React31__namespace.createContext(
9476
+ null
9477
+ );
8657
9478
  function SnackbarProvider({ children }) {
8658
9479
  const state = useSnackbarState();
8659
9480
  const contextValue = React31__namespace.useMemo(
@@ -12023,12 +12844,22 @@ exports.RadioGroup = RadioGroup2;
12023
12844
  exports.RangeSlider = RangeSlider;
12024
12845
  exports.RichTooltip = RichTooltip;
12025
12846
  exports.Ripple = Ripple;
12847
+ exports.SEARCH_BAR_EXPAND_SPRING = SEARCH_BAR_EXPAND_SPRING;
12848
+ exports.SEARCH_COLORS = SEARCH_COLORS;
12849
+ exports.SEARCH_DOCKED_REVEAL_SPRING = SEARCH_DOCKED_REVEAL_SPRING;
12850
+ exports.SEARCH_FULLSCREEN_SPRING = SEARCH_FULLSCREEN_SPRING;
12851
+ exports.SEARCH_TYPOGRAPHY = SEARCH_TYPOGRAPHY;
12026
12852
  exports.SEARCH_VIEW_SPRING = SEARCH_VIEW_SPRING;
12027
12853
  exports.ScrollArea = ScrollArea;
12028
12854
  exports.ScrollAreaScrollbar = ScrollAreaScrollbar;
12855
+ exports.Search = Search;
12029
12856
  exports.SearchAppBar = SearchAppBar;
12857
+ exports.SearchBar = SearchBar;
12858
+ exports.SearchTokens = SearchTokens;
12030
12859
  exports.SearchView = SearchView;
12031
12860
  exports.SearchViewContainer = SearchViewContainer;
12861
+ exports.SearchViewDocked = SearchViewDocked;
12862
+ exports.SearchViewFullScreen = SearchViewFullScreen;
12032
12863
  exports.Slider = Slider;
12033
12864
  exports.SliderColors = SliderColors;
12034
12865
  exports.SliderTokens = SliderTokens;
@@ -12068,6 +12899,7 @@ exports.useDOMRipple = useRipple;
12068
12899
  exports.useMediaQuery = useMediaQuery;
12069
12900
  exports.useRipple = useRipple2;
12070
12901
  exports.useRippleState = useRippleState;
12902
+ exports.useSearchKeyboard = useSearchKeyboard;
12071
12903
  exports.useSnackbar = useSnackbar;
12072
12904
  exports.useSnackbarState = useSnackbarState;
12073
12905
  exports.useTheme = useTheme;