@a-type/ui 2.1.5 → 2.1.7

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.
@@ -1,6 +1,13 @@
1
1
  import { debounce } from '@a-type/utils';
2
2
  import clsx from 'clsx';
3
- import { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react';
3
+ import {
4
+ CSSProperties,
5
+ ReactNode,
6
+ useEffect,
7
+ useLayoutEffect,
8
+ useRef,
9
+ useState,
10
+ } from 'react';
4
11
 
5
12
  interface Layout {
6
13
  attach(container: HTMLElement): () => void;
@@ -12,70 +19,19 @@ interface MasonryLayoutConfig {
12
19
  gap: number;
13
20
  }
14
21
 
15
- class MasonryLayout implements Layout {
16
- private containerResizeObserver: ResizeObserver | null = null;
17
- private containerMutationObserver: MutationObserver | null = null;
18
- private childSizeObserver: ResizeObserver;
19
- private childMutationObserver: MutationObserver;
20
-
21
- private container: HTMLElement | null = null;
22
-
23
- private columns: number = 0;
22
+ abstract class MasonryLayout implements Layout {
23
+ protected container: HTMLElement | null = null;
24
+ protected columns: number = 0;
24
25
 
25
26
  constructor(private config: MasonryLayoutConfig) {
26
27
  this.columns =
27
28
  typeof config.columns === 'function' ? config.columns(0) : config.columns;
28
- this.childSizeObserver = new ResizeObserver(this.handleChildResize);
29
- this.childMutationObserver = new MutationObserver(this.relayout);
30
29
  this.relayout();
31
30
  }
32
31
 
33
- attach = (container: HTMLElement) => {
34
- this.containerResizeObserver?.disconnect();
35
- this.containerMutationObserver?.disconnect();
36
-
37
- this.container = container;
38
-
39
- this.containerResizeObserver = new ResizeObserver(
40
- this.handleContainerResize,
41
- );
42
- this.containerMutationObserver = new MutationObserver(
43
- this.handleContainerMutation,
44
- );
45
- this.containerResizeObserver.observe(container);
46
- this.containerMutationObserver.observe(container, { childList: true });
47
-
48
- container.style.setProperty('position', 'relative');
49
- container.style.setProperty('overflow', 'hidden');
50
- container.style.setProperty('visibility', 'visible');
51
- container.childNodes.forEach((node) => {
52
- if (node instanceof HTMLElement) {
53
- this.setupChild(node);
54
- }
55
- });
56
-
57
- this.updateFromContainerSize(container.offsetWidth);
58
-
59
- this.relayout();
32
+ abstract attach(container: HTMLElement): () => void;
60
33
 
61
- return () => {
62
- this.containerResizeObserver?.disconnect();
63
- this.containerMutationObserver?.disconnect();
64
- container.style.removeProperty('position');
65
- container.style.removeProperty('overflow');
66
- this.container = null;
67
- };
68
- };
69
-
70
- private setupChild = (child: HTMLElement) => {
71
- child.style.setProperty('position', 'absolute');
72
- // hide until laid out
73
- child.style.setProperty('visibility', 'hidden');
74
- this.childSizeObserver.observe(child);
75
- this.childMutationObserver.observe(child, {
76
- attributeFilter: ['data-span'],
77
- });
78
- };
34
+ abstract setupChild(child: HTMLElement): void;
79
35
 
80
36
  updateConfig = (config: MasonryLayoutConfig) => {
81
37
  const gapChanged = config.gap !== this.config.gap;
@@ -89,12 +45,7 @@ class MasonryLayout implements Layout {
89
45
  }
90
46
  };
91
47
 
92
- private handleContainerResize = (entries: ResizeObserverEntry[]) => {
93
- const containerWidth = entries[0].contentRect.width;
94
- this.updateFromContainerSize(containerWidth);
95
- };
96
-
97
- private updateFromContainerSize = (containerWidth: number) => {
48
+ protected updateFromContainerSize = (containerWidth: number) => {
98
49
  if (typeof this.config.columns === 'function') {
99
50
  const newValue = this.config.columns(containerWidth);
100
51
  if (newValue !== this.columns) {
@@ -106,35 +57,7 @@ class MasonryLayout implements Layout {
106
57
  return false;
107
58
  };
108
59
 
109
- private handleContainerMutation = (entries: MutationRecord[]) => {
110
- for (const entry of entries) {
111
- entry.addedNodes.forEach((node) => {
112
- if (node instanceof HTMLElement) {
113
- this.setupChild(node);
114
- }
115
- });
116
- entry.removedNodes.forEach((node) => {
117
- if (node instanceof HTMLElement) {
118
- this.childSizeObserver?.unobserve(node);
119
- }
120
- });
121
- }
122
- this.relayout();
123
- };
124
-
125
- private handleChildResize = (entries: ResizeObserverEntry[]) => {
126
- // only worry about height changes
127
- for (const entry of entries) {
128
- const lastSeenHeight = entry.target.getAttribute('data-last-height');
129
- const currentHeight = entry.contentRect.height;
130
- entry.target.setAttribute('data-last-height', currentHeight.toString());
131
- if (lastSeenHeight && lastSeenHeight !== currentHeight.toString()) {
132
- this.relayout();
133
- }
134
- }
135
- };
136
-
137
- private relayout = debounce(() => {
60
+ protected relayout = debounce(() => {
138
61
  if (!this.container) {
139
62
  return;
140
63
  }
@@ -187,11 +110,115 @@ class MasonryLayout implements Layout {
187
110
  }, 100);
188
111
  }
189
112
 
190
- class ServerLayout implements Layout {
191
- attach(container: HTMLElement): () => void {
113
+ class ClientLayout extends MasonryLayout {
114
+ private containerResizeObserver: ResizeObserver | null = null;
115
+ private containerMutationObserver: MutationObserver | null = null;
116
+ private childSizeObserver: ResizeObserver;
117
+ private childMutationObserver: MutationObserver;
118
+
119
+ constructor(config: MasonryLayoutConfig) {
120
+ super(config);
121
+ this.childSizeObserver = new ResizeObserver(this.handleChildResize);
122
+ this.childMutationObserver = new MutationObserver(this.relayout);
123
+ }
124
+
125
+ private handleContainerMutation = (entries: MutationRecord[]) => {
126
+ for (const entry of entries) {
127
+ entry.addedNodes.forEach((node) => {
128
+ if (node instanceof HTMLElement) {
129
+ this.setupChild(node);
130
+ }
131
+ });
132
+ entry.removedNodes.forEach((node) => {
133
+ if (node instanceof HTMLElement) {
134
+ this.childSizeObserver?.unobserve(node);
135
+ }
136
+ });
137
+ }
138
+ this.relayout();
139
+ };
140
+
141
+ private handleChildResize = (entries: ResizeObserverEntry[]) => {
142
+ // only worry about height changes
143
+ for (const entry of entries) {
144
+ const lastSeenHeight = entry.target.getAttribute('data-last-height');
145
+ const currentHeight = entry.contentRect.height;
146
+ entry.target.setAttribute('data-last-height', currentHeight.toString());
147
+ if (lastSeenHeight && lastSeenHeight !== currentHeight.toString()) {
148
+ this.relayout();
149
+ }
150
+ }
151
+ };
152
+
153
+ attach = (container: HTMLElement) => {
154
+ this.containerResizeObserver?.disconnect();
155
+ this.containerMutationObserver?.disconnect();
156
+
157
+ this.container = container;
158
+
159
+ this.containerResizeObserver = new ResizeObserver(
160
+ this.handleContainerResize,
161
+ );
162
+ this.containerMutationObserver = new MutationObserver(
163
+ this.handleContainerMutation,
164
+ );
165
+ this.containerResizeObserver.observe(container);
166
+ this.containerMutationObserver.observe(container, { childList: true });
167
+
168
+ container.style.setProperty('position', 'relative');
169
+ container.style.setProperty('overflow', 'hidden');
170
+ container.style.setProperty('visibility', 'visible');
171
+ container.childNodes.forEach((node) => {
172
+ if (node instanceof HTMLElement) {
173
+ this.setupChild(node);
174
+ }
175
+ });
176
+
177
+ this.updateFromContainerSize(container.offsetWidth);
178
+
179
+ return () => {
180
+ this.containerResizeObserver?.disconnect();
181
+ this.containerMutationObserver?.disconnect();
182
+ container.style.removeProperty('position');
183
+ container.style.removeProperty('overflow');
184
+ this.container = null;
185
+ };
186
+ };
187
+
188
+ setupChild = (child: HTMLElement) => {
189
+ child.style.setProperty('position', 'absolute');
190
+ // hide until laid out
191
+ child.style.setProperty('visibility', 'hidden');
192
+ this.childSizeObserver.observe(child);
193
+ this.childMutationObserver.observe(child, {
194
+ attributeFilter: ['data-span'],
195
+ });
196
+ };
197
+
198
+ private handleContainerResize = (entries: ResizeObserverEntry[]) => {
199
+ const containerWidth = entries[0].contentRect.width;
200
+ this.updateFromContainerSize(containerWidth);
201
+ };
202
+ }
203
+
204
+ class ServerLayout extends MasonryLayout {
205
+ attach = (container: HTMLElement): (() => void) => {
206
+ this.container = container;
207
+ this.container.style.setProperty('position', 'relative');
208
+ this.container.style.setProperty('overflow', 'hidden');
209
+ this.container.style.setProperty('visibility', 'visible');
210
+ container.childNodes.forEach((node) => {
211
+ if (node instanceof HTMLElement) {
212
+ this.setupChild(node);
213
+ }
214
+ });
215
+ this.updateFromContainerSize(container.offsetWidth);
192
216
  return () => {};
217
+ };
218
+ setupChild(child: HTMLElement): void {
219
+ child.style.setProperty('position', 'absolute');
220
+ child.style.setProperty('visibility', 'visible');
193
221
  }
194
- updateConfig(config: MasonryLayoutConfig): void {}
195
222
  }
196
223
 
197
224
  function pickTrack(tracks: number[], trackSpan: number) {
@@ -226,15 +253,15 @@ export function Masonry({
226
253
  }: MasonryProps) {
227
254
  const [layout] = useState<Layout>(() => {
228
255
  if (typeof window === 'undefined') {
229
- return new ServerLayout();
256
+ return new ServerLayout({ columns, gap });
230
257
  }
231
- return new MasonryLayout({ columns, gap });
258
+ return new ClientLayout({ columns, gap });
232
259
  });
233
- useEffect(() => {
260
+ useIsomorphicLayoutEffect(() => {
234
261
  layout.updateConfig({ columns, gap });
235
262
  }, [layout, columns, gap]);
236
263
  const ref = useRef<HTMLDivElement>(null);
237
- useEffect(() => {
264
+ useIsomorphicLayoutEffect(() => {
238
265
  if (ref.current) {
239
266
  return layout.attach(ref.current);
240
267
  }
@@ -254,3 +281,12 @@ export function Masonry({
254
281
  export function masonrySpan(span: number) {
255
282
  return { 'data-span': span };
256
283
  }
284
+
285
+ function useIsomorphicLayoutEffect(
286
+ effect: () => void | (() => void),
287
+ deps: any[] = [],
288
+ ) {
289
+ const isBrowser = typeof window !== 'undefined';
290
+ const useIsoEffect = isBrowser ? useLayoutEffect : useEffect;
291
+ return useIsoEffect(effect, deps);
292
+ }
@@ -8,21 +8,21 @@ export interface NoteProps extends HTMLAttributes<HTMLDivElement> {
8
8
  export function Note({ className, children, ...rest }: NoteProps) {
9
9
  return (
10
10
  <div className={classNames(className)} {...rest}>
11
- <div className="flex flex-row ">
12
- <div className="flex-1 p-2 border border-solid border-primary-dark bg-primary-wash color-black relative text-sm italic border-r-0">
11
+ <div className="layer-components:(flex flex-row)">
12
+ <div className="layer-components:(flex-1 p-2 border border-solid border-primary-dark bg-primary-wash color-black relative text-sm italic) layer-variants:border-r-0">
13
13
  {children}
14
14
  </div>
15
15
  <div
16
- className="flex flex-col items-stretch justify-stretch flex-[0_0_20px]"
16
+ className="layer-components:(flex flex-col items-stretch justify-stretch flex-[0_0_20px])"
17
17
  aria-hidden
18
18
  >
19
- <div className="border-0 border-solid border-primary-dark border-b-1px border-l-1px flex-[0_0_20px] w-[20px] h-[20px] relative">
20
- <div className="absolute w-1px bg-primary-dark h-26px rotate--45 left-9px top--4px transform-origin-cc" />
19
+ <div className="layer-components:(border-0 border-solid border-primary-dark flex-[0_0_20px] w-[20px] h-[20px] relative) layer-variants:(border-b-1px border-l-1px)">
20
+ <div className="layer-components:(absolute w-1px bg-primary-dark h-26px rotate--45 left-9px top--4px transform-origin-cc)" />
21
21
  <div
22
- className={`border-solid box-content border-transparent border-r-primary-wash border-13px w-0 h-0 rotate--45 translate--7px transform-origin-br`}
22
+ className={`layer-components:(border-solid box-content border-transparent border-r-primary-wash border-13px w-0 h-0 rotate--45 translate--7px transform-origin-br)`}
23
23
  />
24
24
  </div>
25
- <div className="bg-primary-wash flex-1 border-0 border-solid border-primary-dark border-r-1px border-b-1px" />
25
+ <div className="layer-components:(bg-primary-wash flex-1 border-0 border-solid border-primary-dark) layer-variants:(border-r-1px border-b-1px)" />
26
26
  </div>
27
27
  </div>
28
28
  </div>