@olundot/ui 0.20.1 → 0.20.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @olundot/ui
2
2
 
3
+ ## 0.20.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 8a28822: `FilterToolbar layout="grid"`은 640px 미만에서 `filters`도 시트로 보낸다. 줄에는 검색, 접힌 필터 버튼, `actions`만 남고, 시트에는 `filters`를 순서대로 둔 뒤 `collapsible`을 둔다.
8
+
9
+ - 폭을 재기 전 첫 화면에서도 640px 미만이면 줄 필터를 펼치지 않는다. 컨테이너 쿼리로 숨기고, 폭을 잰 뒤에는 줄에 그리지 않는다.
10
+ - `collapsible` 없이 `filters`만 있어도 640px 미만에서 버튼과 시트가 생긴다.
11
+ - `activeLineFilterCount`: `filters` 중 적용한 조건 수. 640px 미만에서 버튼 배지, 버튼 이름, 적용 개수 줄의 개수에 더한다.
12
+ - `onClearLineFilters`: 640px 미만의 "해제"가 `onClearFilters`와 함께 부른다.
13
+ - 격자의 `filters`와 `collapsible` 컨트롤은 줄과 시트를 오가며 새로 그려지므로 값은 `value`로 넘긴다. `defaultValue`로 둔 값은 시트를 닫으면 남지 않는다.
14
+ - 두 prop을 주지 않으면 640px 미만 개수와 "해제"는 `collapsible` 조건만 다룬다. 640px 이상 격자와 `layout="flow"`는 그대로다.
15
+ - 격자에서 `collapseBelow="never"`도 `collapsible`을 줄에 펼치지 않는다.
16
+ - 격자는 툴바 폭을 컨테이너 쿼리와 같은 기준(내용 상자, CSS `zoom` 전)으로 잰다.
17
+
3
18
  ## 0.20.1
4
19
 
5
20
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -903,6 +903,10 @@ interface FilterToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
903
903
  collapseBelow?: FilterToolbarCollapseBelow;
904
904
  activeFilterCount?: number;
905
905
  onClearFilters?: () => void;
906
+ /** grid: applied conditions among `filters`. Under 640px the count adds them. */
907
+ activeLineFilterCount?: number;
908
+ /** grid: under 640px 해제 calls it with `onClearFilters`. */
909
+ onClearLineFilters?: () => void;
906
910
  collapsedLabel?: string;
907
911
  resultSummary?: React.ReactNode;
908
912
  clearAction?: React.ReactNode;
package/dist/index.js CHANGED
@@ -5737,14 +5737,8 @@ var collapsibleClassNames = {
5737
5737
  trigger: "hidden"
5738
5738
  }
5739
5739
  };
5740
- var gridCollapsibleClassNames = {
5741
- sm: { inline: "hidden", trigger: "" },
5742
- md: { inline: "hidden", trigger: "" },
5743
- lg: { inline: "hidden", trigger: "" },
5744
- never: { inline: "flex @[640px]:hidden", trigger: "hidden @[640px]:inline" }
5745
- };
5746
5740
  var gridSearchClassName = "order-1 min-w-[min(160px,100%)] max-w-full flex-[1_1_0%] @[640px]:min-w-0 @[640px]:flex-[3_1_0%]";
5747
- var gridFiltersClassName = "order-3 flex min-w-0 flex-[1_0_100%] flex-wrap items-center gap-[var(--space-2)] @[640px]:contents";
5741
+ var gridFiltersClassName = "order-3 hidden min-w-0 flex-[1_0_100%] flex-wrap items-center gap-[var(--space-2)] @[640px]:contents";
5748
5742
  var gridCellClassName = cn(
5749
5743
  "contents @[640px]:block @[640px]:order-3 @[640px]:min-w-0 @[640px]:flex-[3_1_0%]",
5750
5744
  "@[640px]:[&:has([data-slot=select-trigger])]:flex-[2_1_0%]",
@@ -5766,6 +5760,10 @@ function filterCells(node, prefix = "") {
5766
5760
  return React31.isValidElement(child) && child.type === React31.Fragment ? filterCells(child.props.children, `${key}/`) : [{ key, cell: child }];
5767
5761
  });
5768
5762
  }
5763
+ function contentWidth(element) {
5764
+ const style = getComputedStyle(element);
5765
+ return element.clientWidth - (parseFloat(style.paddingLeft) || 0) - (parseFloat(style.paddingRight) || 0);
5766
+ }
5769
5767
  function warnGridFilters(count) {
5770
5768
  if (typeof process === "undefined" || process.env.NODE_ENV === "production") return;
5771
5769
  console.warn(
@@ -5781,6 +5779,8 @@ var FilterToolbar = React31.forwardRef(
5781
5779
  collapseBelow = "lg",
5782
5780
  activeFilterCount: activeFilterCountProp,
5783
5781
  onClearFilters,
5782
+ activeLineFilterCount,
5783
+ onClearLineFilters,
5784
5784
  collapsedLabel = "\uD544\uD130",
5785
5785
  resultSummary,
5786
5786
  clearAction,
@@ -5796,6 +5796,7 @@ var FilterToolbar = React31.forwardRef(
5796
5796
  null
5797
5797
  );
5798
5798
  const [overlayOpen, setOverlayOpen] = React31.useState(false);
5799
+ const rootRef = React31.useRef(null);
5799
5800
  const triggerRef = React31.useRef(null);
5800
5801
  const rowRef = React31.useRef(null);
5801
5802
  const chipsRef = React31.useRef(null);
@@ -5803,28 +5804,31 @@ var FilterToolbar = React31.forwardRef(
5803
5804
  const pendingFocusRef = React31.useRef(null);
5804
5805
  const setRootRefs = React31.useCallback(
5805
5806
  (element) => {
5807
+ rootRef.current = element;
5806
5808
  setRootElement(element);
5807
5809
  if (typeof ref === "function") ref(element);
5808
5810
  else if (ref) ref.current = element;
5809
5811
  },
5810
5812
  [ref]
5811
5813
  );
5814
+ const isGrid = layout === "grid";
5812
5815
  React31.useEffect(() => {
5813
5816
  if (!rootElement) return;
5814
- const updateWidth = () => {
5815
- setContainerWidth(rootElement.getBoundingClientRect().width);
5817
+ const updateWidth = (entry) => {
5818
+ setContainerWidth(
5819
+ isGrid ? entry?.contentBoxSize?.[0]?.inlineSize ?? contentWidth(rootElement) : rootElement.getBoundingClientRect().width
5820
+ );
5816
5821
  };
5817
5822
  updateWidth();
5818
5823
  if (typeof ResizeObserver === "undefined") return;
5819
- const observer = new ResizeObserver(updateWidth);
5824
+ const observer = new ResizeObserver(([entry]) => updateWidth(entry));
5820
5825
  observer.observe(rootElement);
5821
5826
  return () => observer.disconnect();
5822
- }, [rootElement]);
5827
+ }, [rootElement, isGrid]);
5823
5828
  const handleOverlayOpenChange = React31.useCallback((open) => {
5824
5829
  setOverlayOpen(open);
5825
5830
  if (!open) queueMicrotask(() => triggerRef.current?.focus());
5826
5831
  }, []);
5827
- const isGrid = layout === "grid";
5828
5832
  const gridCells = isGrid ? filterCells(filters) : [];
5829
5833
  const filterCellCount = gridCells.length;
5830
5834
  React31.useEffect(() => {
@@ -5866,33 +5870,67 @@ var FilterToolbar = React31.forwardRef(
5866
5870
  const hasAppliedFilters = activeFilterCount > 0;
5867
5871
  const hasAppliedChips = Boolean(appliedFilters && appliedFilters.length > 0);
5868
5872
  const usesSheet = containerWidth !== null && containerWidth < 640;
5869
- const collapseClasses = (isGrid ? gridCollapsibleClassNames : collapsibleClassNames)[collapseBelow];
5870
- const collapseTriggerLabel = hasAppliedFilters ? `${collapsedLabel}, ${activeFilterCount}\uAC1C \uC801\uC6A9` : collapsedLabel;
5871
- const clearFiltersButton = onClearFilters ? /* @__PURE__ */ jsx32(
5873
+ const gridSheet = isGrid && Boolean(filters);
5874
+ const linesInSheet = gridSheet && usesSheet;
5875
+ const lineFilterCount = gridSheet ? activeLineFilterCount ?? 0 : 0;
5876
+ const narrowFilterCount = activeFilterCount + lineFilterCount;
5877
+ const hasCollapsed = Boolean(collapsible) || gridSheet;
5878
+ const outgrownSheet = isGrid && !collapsible && containerWidth !== null && containerWidth >= 640 && overlayOpen;
5879
+ React31.useEffect(() => {
5880
+ if (!outgrownSheet) return;
5881
+ setOverlayOpen(false);
5882
+ const ownerDocument = rootElement?.ownerDocument;
5883
+ const active = ownerDocument?.activeElement;
5884
+ if (!ownerDocument || active && active !== ownerDocument.body && active.isConnected) return;
5885
+ const cell = rowRef.current?.querySelector('[data-slot="filter-toolbar-cell"]');
5886
+ [...cell?.querySelectorAll(focusableSelector) ?? []].find(canTakeFocus)?.focus();
5887
+ }, [outgrownSheet, rootElement]);
5888
+ const inlineClassName = isGrid ? "hidden" : collapsibleClassNames[collapseBelow].inline;
5889
+ const triggerClassName = isGrid ? collapsible ? "" : "@[640px]:hidden" : collapsibleClassNames[collapseBelow].trigger;
5890
+ const triggerCount = linesInSheet ? narrowFilterCount : activeFilterCount;
5891
+ const collapseTriggerLabel = triggerCount > 0 ? `${collapsedLabel}, ${triggerCount}\uAC1C \uC801\uC6A9` : collapsedLabel;
5892
+ const narrowOnlyCount = !hasAppliedFilters && narrowFilterCount > 0;
5893
+ const countText = narrowFilterCount === activeFilterCount || narrowOnlyCount ? narrowFilterCount : /* @__PURE__ */ jsxs28(Fragment14, { children: [
5894
+ /* @__PURE__ */ jsx32("span", { className: "@[640px]:hidden", children: narrowFilterCount }),
5895
+ /* @__PURE__ */ jsx32("span", { className: "hidden @[640px]:inline", children: activeFilterCount })
5896
+ ] });
5897
+ const clearLineFilters = gridSheet ? onClearLineFilters : void 0;
5898
+ const clearNarrow = onClearFilters && clearLineFilters ? () => {
5899
+ onClearFilters();
5900
+ clearLineFilters();
5901
+ } : onClearFilters ?? clearLineFilters;
5902
+ const clearFilters = () => {
5903
+ const width = containerWidth ?? (rootRef.current ? contentWidth(rootRef.current) : null);
5904
+ (width !== null && width < 640 ? clearNarrow : onClearFilters)?.();
5905
+ };
5906
+ const clearFiltersButton = (linesInSheet ? clearNarrow : onClearFilters) ? /* @__PURE__ */ jsx32(
5872
5907
  "button",
5873
5908
  {
5874
5909
  type: "button",
5875
5910
  "data-slot": "filter-toolbar-clear-filters",
5876
5911
  className: "cursor-pointer border-0 bg-transparent p-0 text-body-sm font-medium text-text-secondary no-underline",
5877
- onClick: onClearFilters,
5912
+ onClick: clearFilters,
5878
5913
  children: "\uD574\uC81C"
5879
5914
  }
5880
5915
  ) : null;
5881
- const collapsedContent = collapsible ? /* @__PURE__ */ jsx32(
5916
+ const collapsedContent = hasCollapsed ? /* @__PURE__ */ jsxs28(
5882
5917
  "div",
5883
5918
  {
5884
5919
  "data-slot": "filter-toolbar-collapsed-content",
5885
5920
  className: "flex flex-col gap-[var(--space-3)] [&>*]:w-full",
5886
- children: collapsible
5921
+ children: [
5922
+ linesInSheet ? filters : null,
5923
+ collapsible
5924
+ ]
5887
5925
  }
5888
5926
  ) : null;
5889
- const collapseTrigger = collapsible ? /* @__PURE__ */ jsxs28(
5927
+ const collapseTrigger = hasCollapsed ? /* @__PURE__ */ jsxs28(
5890
5928
  "span",
5891
5929
  {
5892
5930
  "data-slot": "filter-toolbar-collapse-trigger",
5893
5931
  className: cn(
5894
5932
  "relative order-2 shrink-0 @[640px]:order-4",
5895
- collapseClasses.trigger
5933
+ triggerClassName
5896
5934
  ),
5897
5935
  children: [
5898
5936
  /* @__PURE__ */ jsx32(
@@ -5907,16 +5945,17 @@ var FilterToolbar = React31.forwardRef(
5907
5945
  onClick: () => handleOverlayOpenChange(true)
5908
5946
  }
5909
5947
  ),
5910
- hasAppliedFilters ? /* @__PURE__ */ jsx32(
5948
+ narrowFilterCount > 0 ? /* @__PURE__ */ jsx32(
5911
5949
  "span",
5912
5950
  {
5913
5951
  "aria-hidden": "true",
5914
5952
  "data-slot": "filter-toolbar-collapse-count",
5915
5953
  className: cn(
5916
5954
  chipCountVariants({ tone: "accent", size: "md" }),
5917
- "pointer-events-none absolute right-0 top-0 translate-x-1/3 -translate-y-1/3"
5955
+ "pointer-events-none absolute right-0 top-0 translate-x-1/3 -translate-y-1/3",
5956
+ narrowOnlyCount && "@[640px]:hidden"
5918
5957
  ),
5919
- children: activeFilterCount
5958
+ children: countText
5920
5959
  }
5921
5960
  ) : null
5922
5961
  ]
@@ -5952,7 +5991,7 @@ var FilterToolbar = React31.forwardRef(
5952
5991
  children: search
5953
5992
  }
5954
5993
  ) : null,
5955
- collapsible && usesSheet ? collapseTrigger : null,
5994
+ hasCollapsed && (usesSheet || !collapsible) ? collapseTrigger : null,
5956
5995
  collapsible && !usesSheet ? /* @__PURE__ */ jsxs28(Popover, { open: overlayOpen, onOpenChange: handleOverlayOpenChange, children: [
5957
5996
  /* @__PURE__ */ jsx32(PopoverAnchor, { asChild: true, children: collapseTrigger }),
5958
5997
  overlayOpen ? /* @__PURE__ */ jsxs28(
@@ -5971,7 +6010,7 @@ var FilterToolbar = React31.forwardRef(
5971
6010
  }
5972
6011
  ) : null
5973
6012
  ] }) : null,
5974
- filters ? /* @__PURE__ */ jsx32(
6013
+ filters && !linesInSheet ? /* @__PURE__ */ jsx32(
5975
6014
  "div",
5976
6015
  {
5977
6016
  "data-slot": "filter-toolbar-filters",
@@ -5993,7 +6032,7 @@ var FilterToolbar = React31.forwardRef(
5993
6032
  "data-slot": "filter-toolbar-collapsible",
5994
6033
  className: cn(
5995
6034
  "order-5 min-w-0 items-center gap-[var(--space-2)]",
5996
- collapseClasses.inline
6035
+ inlineClassName
5997
6036
  ),
5998
6037
  children: collapsible
5999
6038
  }
@@ -6009,7 +6048,7 @@ var FilterToolbar = React31.forwardRef(
6009
6048
  ]
6010
6049
  }
6011
6050
  ) : null,
6012
- collapsible && usesSheet && overlayOpen ? /* @__PURE__ */ jsx32(
6051
+ hasCollapsed && usesSheet && overlayOpen ? /* @__PURE__ */ jsx32(
6013
6052
  Sheet,
6014
6053
  {
6015
6054
  open: overlayOpen,
@@ -6070,28 +6109,31 @@ var FilterToolbar = React31.forwardRef(
6070
6109
  ]
6071
6110
  }
6072
6111
  ) : null,
6073
- hasAppliedFilters ? /* @__PURE__ */ jsxs28(
6112
+ narrowFilterCount > 0 ? /* @__PURE__ */ jsxs28(
6074
6113
  "div",
6075
6114
  {
6076
6115
  "data-slot": "filter-toolbar-applied",
6077
6116
  className: cn(
6078
6117
  "flex items-center gap-[var(--space-1)] text-[11px] text-text-tertiary",
6079
- hasAppliedChips && "@[640px]:hidden"
6118
+ (hasAppliedChips || narrowOnlyCount) && "@[640px]:hidden"
6080
6119
  ),
6081
6120
  children: [
6082
6121
  /* @__PURE__ */ jsxs28("span", { children: [
6083
6122
  "\uD544\uD130 ",
6084
- activeFilterCount,
6123
+ countText,
6085
6124
  "\uAC1C \uC801\uC6A9"
6086
6125
  ] }),
6087
- onClearFilters ? /* @__PURE__ */ jsxs28(Fragment14, { children: [
6088
- /* @__PURE__ */ jsx32("span", { "aria-hidden": "true", children: " \xB7 " }),
6126
+ clearNarrow ? /* @__PURE__ */ jsxs28(Fragment14, { children: [
6127
+ /* @__PURE__ */ jsx32("span", { "aria-hidden": "true", className: onClearFilters ? void 0 : "@[640px]:hidden", children: " \xB7 " }),
6089
6128
  /* @__PURE__ */ jsx32(
6090
6129
  "button",
6091
6130
  {
6092
6131
  type: "button",
6093
- className: "cursor-pointer border-0 bg-transparent p-0 text-[11px] text-text-secondary no-underline",
6094
- onClick: onClearFilters,
6132
+ className: cn(
6133
+ "cursor-pointer border-0 bg-transparent p-0 text-[11px] text-text-secondary no-underline",
6134
+ !onClearFilters && "@[640px]:hidden"
6135
+ ),
6136
+ onClick: clearFilters,
6095
6137
  children: "\uD574\uC81C"
6096
6138
  }
6097
6139
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olundot/ui",
3
- "version": "0.20.1",
3
+ "version": "0.20.2",
4
4
  "description": "OLUN. shared React UI components built on OLUN design tokens",
5
5
  "license": "MIT",
6
6
  "keywords": [