@firecms/ui 3.2.0 → 3.3.0-canary.451aa49

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.
@@ -14,8 +14,8 @@ export function Skeleton({
14
14
  }: SkeletonProps) {
15
15
  return <span
16
16
  style={{
17
- width: width ? `${width}px` : "100%",
18
- height: height ? `${height}px` : "12px"
17
+ width: width !== undefined ? `${width}px` : undefined,
18
+ height: height !== undefined ? `${height}px` : undefined
19
19
  }}
20
20
  className={
21
21
  cls(
@@ -23,6 +23,8 @@ export function Skeleton({
23
23
  "bg-surface-accent-200 dark:bg-surface-accent-800 rounded-md",
24
24
  "animate-pulse",
25
25
  "max-w-full max-h-full",
26
+ width === undefined ? "w-full" : "",
27
+ height === undefined ? "h-3" : "",
26
28
  className)
27
29
  }/>;
28
30
  }
@@ -1,16 +1,24 @@
1
- import React, { useRef, useState, useEffect } from "react";
1
+ import React, { createContext, useContext, useRef, useState, useEffect } from "react";
2
2
  import * as TabsPrimitive from "@radix-ui/react-tabs";
3
3
  import { cls } from "../util";
4
4
  import { defaultBorderMixin } from "../styles";
5
5
  import { IconButton } from "./IconButton";
6
6
  import { ChevronLeftIcon, ChevronRightIcon } from "../icons";
7
7
 
8
+ type TabsMode = "primary" | "secondary";
9
+ const TabsModeContext = createContext<TabsMode>("primary");
10
+
8
11
  export type TabsProps = {
9
12
  value: string,
10
13
  children: React.ReactNode,
11
14
  innerClassName?: string,
12
15
  className?: string,
13
- onValueChange: (value: string) => void
16
+ onValueChange: (value: string) => void,
17
+ /**
18
+ * "primary" renders the default pill-style tabs.
19
+ * "secondary" renders underline-style tabs suitable for inner/nested panels.
20
+ */
21
+ mode?: TabsMode
14
22
  };
15
23
 
16
24
  export function Tabs({
@@ -18,7 +26,8 @@ export function Tabs({
18
26
  onValueChange,
19
27
  className,
20
28
  innerClassName,
21
- children
29
+ children,
30
+ mode = "primary"
22
31
  }: TabsProps) {
23
32
  const scrollContainerRef = useRef<HTMLDivElement>(null);
24
33
  const [showLeftScroll, setShowLeftScroll] = useState(false);
@@ -67,7 +76,8 @@ export function Tabs({
67
76
  }
68
77
  };
69
78
 
70
- return <TabsPrimitive.Root value={value} onValueChange={onValueChange} className={cls("flex flex-row items-center min-w-0", className)}>
79
+ return <TabsModeContext.Provider value={mode}>
80
+ <TabsPrimitive.Root value={value} onValueChange={onValueChange} className={cls("flex flex-row items-center min-w-0", className)}>
71
81
  {isScrollable && (
72
82
  <button
73
83
  type="button"
@@ -78,7 +88,9 @@ export function Tabs({
78
88
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400",
79
89
  "disabled:pointer-events-none disabled:opacity-0",
80
90
  "text-surface-600 dark:text-surface-400 hover:bg-surface-200 dark:hover:bg-surface-800",
81
- "mr-1 bg-surface-50 dark:bg-surface-900 border", defaultBorderMixin
91
+ mode === "primary" && "mr-1 bg-surface-50 dark:bg-surface-900 border",
92
+ mode === "primary" && defaultBorderMixin,
93
+ mode === "secondary" && "mr-1"
82
94
  )}
83
95
  >
84
96
  <ChevronLeftIcon size="small" />
@@ -91,10 +103,10 @@ export function Tabs({
91
103
  style={{ scrollbarWidth: "none", msOverflowStyle: "none" }}
92
104
  >
93
105
  <TabsPrimitive.List className={cls(
94
- "border",
95
- defaultBorderMixin,
96
- "gap-2",
97
- "inline-flex h-10 items-center justify-center rounded-md bg-surface-50 p-1 text-surface-600 dark:bg-surface-900 dark:text-surface-400",
106
+ mode === "primary" && "border",
107
+ mode === "primary" && defaultBorderMixin,
108
+ mode === "primary" && "gap-2 inline-flex h-10 items-center justify-center rounded-md bg-surface-50 p-1 text-surface-600 dark:bg-surface-900 dark:text-surface-400",
109
+ mode === "secondary" && "gap-1 inline-flex h-9 items-center text-surface-500 dark:text-surface-400",
98
110
  innerClassName)
99
111
  }>
100
112
  {children}
@@ -110,13 +122,16 @@ export function Tabs({
110
122
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400",
111
123
  "disabled:pointer-events-none disabled:opacity-0",
112
124
  "text-surface-600 dark:text-surface-400 hover:bg-surface-200 dark:hover:bg-surface-800",
113
- "ml-1 bg-surface-50 dark:bg-surface-900 border", defaultBorderMixin
125
+ mode === "primary" && "ml-1 bg-surface-50 dark:bg-surface-900 border",
126
+ mode === "primary" && defaultBorderMixin,
127
+ mode === "secondary" && "ml-1"
114
128
  )}
115
129
  >
116
130
  <ChevronRightIcon size="small" />
117
131
  </button>
118
132
  )}
119
133
  </TabsPrimitive.Root>
134
+ </TabsModeContext.Provider>
120
135
  }
121
136
 
122
137
  export type TabProps = {
@@ -134,15 +149,28 @@ export function Tab({
134
149
  children,
135
150
  disabled
136
151
  }: TabProps) {
152
+ const mode = useContext(TabsModeContext);
153
+
154
+ const primaryClasses = cls(
155
+ "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-white transition-all",
156
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400 focus-visible:ring-offset-2",
157
+ "disabled:pointer-events-none disabled:opacity-50",
158
+ "data-[state=active]:bg-white data-[state=active]:text-surface-900 dark:data-[state=active]:bg-surface-950 dark:data-[state=active]:text-surface-50",
159
+ );
160
+
161
+ const secondaryClasses = cls(
162
+ "inline-flex items-center justify-center whitespace-nowrap px-3 py-1.5 text-sm font-medium transition-all",
163
+ "border-b-2 border-transparent -mb-px",
164
+ "focus-visible:outline-none",
165
+ "disabled:pointer-events-none disabled:opacity-50",
166
+ "hover:text-surface-700 dark:hover:text-surface-300",
167
+ "data-[state=active]:border-b-primary data-[state=active]:text-primary dark:data-[state=active]:border-b-primary dark:data-[state=active]:text-primary-dark",
168
+ );
169
+
137
170
  return <TabsPrimitive.Trigger value={value}
138
171
  disabled={disabled}
139
172
  className={cls(
140
- "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-white transition-all",
141
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400 focus-visible:ring-offset-2",
142
- "disabled:pointer-events-none disabled:opacity-50",
143
- "data-[state=active]:bg-white data-[state=active]:text-surface-900 dark:data-[state=active]:bg-surface-950 dark:data-[state=active]:text-surface-50",
144
- // "data-[state=active]:border",
145
- // defaultBorderMixin,
173
+ mode === "secondary" ? secondaryClasses : primaryClasses,
146
174
  className,
147
175
  innerClassName)}>
148
176
  {children}
@@ -1,8 +1,7 @@
1
1
  "use client";
2
2
  import * as React from "react";
3
3
  import { useLayoutEffect } from "react";
4
- import * as ReactDOM from "react-dom";
5
- import { cls, debounce } from "../util";
4
+ import { debounce } from "../util";
6
5
 
7
6
  type State = {
8
7
  outerHeightStyle: number;
@@ -13,33 +12,6 @@ function getStyleValue(value: string) {
13
12
  return parseInt(value, 10) || 0;
14
13
  }
15
14
 
16
- const styles: {
17
- shadow: React.CSSProperties;
18
- } = {
19
- shadow: {
20
- // Visibility needed to hide the extra text area on iPads
21
- visibility: "hidden",
22
- // Remove from the content flow
23
- position: "absolute",
24
- // Ignore the scrollbar width
25
- overflow: "hidden",
26
- height: 0,
27
- top: 0,
28
- left: 0,
29
- // Create a new layer, increase the isolation of the computed values
30
- transform: "translateZ(0)"
31
- }
32
- };
33
-
34
- function isEmpty(obj: State) {
35
- return (
36
- obj === undefined ||
37
- obj === null ||
38
- Object.keys(obj).length === 0 ||
39
- (obj.outerHeightStyle === 0 && !obj.overflow)
40
- );
41
- }
42
-
43
15
  export const TextareaAutosize = React.forwardRef(function TextareaAutosize(
44
16
  props: TextareaAutosizeProps,
45
17
  ref: React.ForwardedRef<Element>
@@ -60,166 +32,96 @@ export const TextareaAutosize = React.forwardRef(function TextareaAutosize(
60
32
  } = props;
61
33
 
62
34
  const { current: isControlled } = React.useRef(value != null);
63
- const inputRef = React.useRef<HTMLInputElement>(null);
35
+ const inputRef = React.useRef<HTMLTextAreaElement>(null);
64
36
  const handleRef = useForkRef(ref, inputRef);
65
- const shadowRef = React.useRef<HTMLTextAreaElement>(null);
66
- const renders = React.useRef(0);
67
- const [state, setState] = React.useState<State>({
68
- outerHeightStyle: 0
69
- });
70
-
71
- const getUpdatedState = React.useCallback(() => {
72
-
73
- const input = inputRef.current!;
74
- if (typeof window === "undefined") {
75
- return {
76
- outerHeightStyle: 0
77
- };
78
- }
79
37
 
80
- const containerWindow = window;
81
- const computedStyle = containerWindow.getComputedStyle(input);
38
+ const syncHeight = React.useCallback(() => {
39
+ const el = inputRef.current;
40
+ if (!el || typeof window === "undefined") return;
41
+ if (el.offsetWidth === 0) return;
82
42
 
83
- // If input's width is shrunk and it's not visible, don't sync height.
84
- if (computedStyle.width === "0px") {
85
- return {
86
- outerHeightStyle: 0
87
- };
88
- }
43
+ const cs = window.getComputedStyle(el);
44
+ const paddingY =
45
+ getStyleValue(cs.paddingTop) + getStyleValue(cs.paddingBottom);
46
+ const borderY =
47
+ getStyleValue(cs.borderTopWidth) + getStyleValue(cs.borderBottomWidth);
48
+ const boxSizing = cs.boxSizing;
89
49
 
90
- const sizeReferenceElement = sizeRef?.current ?? shadowRef.current!;
91
- const inputShallow = shadowRef.current!;
50
+ // ── measure by temporarily collapsing the real element ──
51
+ const prevHeight = el.style.height;
52
+ const prevOverflow = el.style.overflowY;
53
+ el.style.overflowY = "hidden";
54
+ el.style.height = "0px";
92
55
 
93
- sizeReferenceElement.style.width = computedStyle.width;
94
- inputShallow.value = input.value || props.placeholder || "x";
95
- if (inputShallow.value.slice(-1) === "\n") {
96
- // Certain fonts which overflow the line height will cause the textarea
97
- // to report a different scrollHeight depending on whether the last line
98
- // is empty. Make it non-empty to avoid this issue.
99
- inputShallow.value += " ";
100
- }
56
+ // scrollHeight = content + padding (always, regardless of box-sizing)
57
+ const scrollH = el.scrollHeight;
101
58
 
102
- const boxSizing = computedStyle.boxSizing;
103
- const padding =
104
- getStyleValue(computedStyle.paddingBottom) + getStyleValue(computedStyle.paddingTop);
105
- const border =
106
- getStyleValue(computedStyle.borderBottomWidth) + getStyleValue(computedStyle.borderTopWidth);
107
- const minHeight = getStyleValue(computedStyle.minHeight);
59
+ // Measure single-row height for minRows / maxRows
60
+ const prevValue = el.value;
61
+ el.value = "x";
62
+ const singleRowScrollH = el.scrollHeight;
63
+ el.value = prevValue;
108
64
 
109
- // The height of the inner content
110
- const innerHeight = sizeReferenceElement.scrollHeight;
65
+ // Restore immediately — all of this happens before paint (useLayoutEffect)
66
+ el.style.height = prevHeight;
67
+ el.style.overflowY = prevOverflow;
111
68
 
112
- // Measure height of a textarea with a single row
113
- inputShallow.value = "x";
114
- const singleRowHeight = sizeReferenceElement.scrollHeight;
69
+ const lineHeight = singleRowScrollH - paddingY;
115
70
 
116
- // The height of the outer content
117
- let outerHeight = innerHeight;
71
+ let targetHeight = scrollH; // includes padding
118
72
 
119
73
  if (minRows) {
120
- outerHeight = Math.max(Number(minRows) * singleRowHeight, outerHeight);
74
+ targetHeight = Math.max(
75
+ Number(minRows) * lineHeight + paddingY,
76
+ targetHeight
77
+ );
121
78
  }
122
- if (maxRows) {
123
- outerHeight = Math.min(Number(maxRows) * singleRowHeight, outerHeight);
124
- }
125
- outerHeight = Math.max(outerHeight, singleRowHeight, minHeight);
126
-
127
- // Take the box sizing into account for applying this value as a style.
128
- const outerHeightStyle = outerHeight + (!ignoreBoxSizing && boxSizing === "border-box" ? padding + border : 0);
129
79
 
130
- const overflow = Math.abs(outerHeight - innerHeight) <= 1;
80
+ const unclampedHeight = targetHeight;
131
81
 
132
- return {
133
- outerHeightStyle,
134
- overflow
135
- };
136
- }, [maxRows, minRows, props.placeholder]);
137
-
138
- const updateState = React.useCallback((prevState: State, newState: State) => {
139
- const {
140
- outerHeightStyle,
141
- overflow
142
- } = newState;
143
- // Need a large enough difference to update the height.
144
- // This prevents infinite rendering loop.
145
- if (
146
- renders.current < 20 &&
147
- ((outerHeightStyle > 0 &&
148
- Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) ||
149
- prevState.overflow !== overflow)
150
- ) {
151
- renders.current += 1;
152
- return {
153
- overflow,
154
- outerHeightStyle
155
- };
156
- }
157
- if (process.env.NODE_ENV !== "production") {
158
- if (renders.current === 20) {
159
- console.error(
160
- [
161
- "MUI: Too many re-renders. The layout is unstable.",
162
- "TextareaAutosize limits the number of renders to prevent an infinite loop."
163
- ].join("\n")
164
- );
165
- }
82
+ if (maxRows) {
83
+ targetHeight = Math.min(
84
+ Number(maxRows) * lineHeight + paddingY,
85
+ targetHeight
86
+ );
166
87
  }
167
- return prevState;
168
- }, []);
169
88
 
170
- const syncHeight = React.useCallback(() => {
171
- const newState = getUpdatedState();
89
+ // For border-box, height CSS prop = content + padding + border.
90
+ // scrollHeight already includes padding, so only add border.
91
+ const extra =
92
+ !ignoreBoxSizing && boxSizing === "border-box" ? borderY : 0;
93
+ const finalHeight = Math.ceil(targetHeight + extra);
172
94
 
173
- if (isEmpty(newState)) {
174
- return;
175
- }
176
- if (onResize) {
177
- onResize(newState);
178
- }
179
-
180
- setState((prevState) => {
181
- return updateState(prevState, newState);
182
- });
183
- }, [getUpdatedState, onResize, updateState]);
95
+ const shouldScroll =
96
+ Math.abs(unclampedHeight - targetHeight) > 1;
184
97
 
185
- const syncHeightWithFlushSync = React.useCallback(() => {
186
- const newState = getUpdatedState();
98
+ el.style.height = `${finalHeight}px`;
99
+ el.style.overflowY = shouldScroll ? "auto" : "hidden";
187
100
 
188
- if (isEmpty(newState)) {
189
- return;
101
+ if (onResize) {
102
+ onResize({ outerHeightStyle: finalHeight, overflow: !shouldScroll });
190
103
  }
104
+ }, [maxRows, minRows, ignoreBoxSizing, onResize]);
191
105
 
192
- // In React 18, state updates in a ResizeObserver's callback are happening after the paint which causes flickering
193
- // when doing some visual updates in it. Using flushSync ensures that the dom will be painted after the states updates happen
194
- // Related issue - https://github.com/facebook/react/issues/24331
195
- ReactDOM.flushSync(() => {
196
- setState((prevState) => {
197
- return updateState(prevState, newState);
198
- });
199
- });
200
- }, [getUpdatedState, updateState]);
106
+ // ── sync on every layout ──
107
+ useLayoutEffect(() => {
108
+ syncHeight();
109
+ });
201
110
 
111
+ // ── sync on window resize / element resize ──
202
112
  React.useEffect(() => {
203
113
  const handleResize = debounce(() => {
204
- renders.current = 0;
205
-
206
- // If the TextareaAutosize component is replaced by Suspense with a fallback, the last
207
- // ResizeObserver's handler that runs because of the change in the layout is trying to
208
- // access a dom node that is no longer there (as the fallback component is being shown instead).
209
114
  if (inputRef.current) {
210
- syncHeightWithFlushSync();
115
+ syncHeight();
211
116
  }
212
117
  });
213
- let resizeObserver: ResizeObserver;
214
118
 
215
119
  const input = inputRef.current!;
216
- const containerWindow = window;
217
- if (typeof window === "undefined") {
218
- return;
219
- }
120
+ if (typeof window === "undefined") return;
220
121
 
221
- containerWindow.addEventListener("resize", handleResize);
122
+ window.addEventListener("resize", handleResize);
222
123
 
124
+ let resizeObserver: ResizeObserver | undefined;
223
125
  if (typeof ResizeObserver !== "undefined") {
224
126
  resizeObserver = new ResizeObserver(handleResize);
225
127
  resizeObserver.observe(input);
@@ -227,67 +129,35 @@ export const TextareaAutosize = React.forwardRef(function TextareaAutosize(
227
129
 
228
130
  return () => {
229
131
  handleResize.clear();
230
- containerWindow.removeEventListener("resize", handleResize);
231
- if (resizeObserver) {
232
- resizeObserver.disconnect();
233
- }
132
+ window.removeEventListener("resize", handleResize);
133
+ resizeObserver?.disconnect();
234
134
  };
235
- }, [syncHeightWithFlushSync]);
236
-
237
- useLayoutEffect(() => {
238
- syncHeight();
239
- });
240
-
241
- React.useEffect(() => {
242
- renders.current = 0;
243
- }, [value]);
135
+ }, [syncHeight]);
244
136
 
245
137
  const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
246
- renders.current = 0;
247
-
248
138
  if (!isControlled) {
249
139
  syncHeight();
250
140
  }
251
-
252
141
  if (onChange) {
253
142
  onChange(event);
254
143
  }
255
144
  };
256
145
 
257
146
  return (
258
- <React.Fragment>
259
- <textarea
260
- value={value}
261
- onChange={handleChange}
262
- className={props.className}
263
- ref={handleRef}
264
- onFocus={onFocus}
265
- onBlur={onBlur}
266
- // Apply the rows prop to get a "correct" first SSR paint
267
- rows={minRows as number}
268
- style={{
269
- height: state.outerHeightStyle,
270
- // Need a large enough difference to allow scrolling.
271
- // This prevents infinite rendering loop.
272
- overflow: state.overflow ? "hidden" : undefined,
273
- ...style,
274
- }}
275
- onScroll={onScroll}
276
- {...other}
277
- />
278
- <textarea
279
- aria-hidden
280
- className={cls(props.className, props.shadowClassName)}
281
- readOnly
282
- ref={shadowRef}
283
- tabIndex={-1}
284
- style={{
285
- padding: 0,
286
- ...styles.shadow,
287
- ...style,
288
- }}
289
- />
290
- </React.Fragment>
147
+ <textarea
148
+ value={value}
149
+ onChange={handleChange}
150
+ className={props.className}
151
+ ref={handleRef}
152
+ onFocus={onFocus}
153
+ onBlur={onBlur}
154
+ rows={minRows as number}
155
+ style={{
156
+ ...style,
157
+ }}
158
+ onScroll={onScroll}
159
+ {...other}
160
+ />
291
161
  );
292
162
  }) as React.FC<TextareaAutosizeProps & { ref?: React.ForwardedRef<Element> }>;
293
163
 
@@ -337,11 +207,6 @@ export type TextareaAutosizeProps = Omit<React.InputHTMLAttributes<HTMLTextAreaE
337
207
  function useForkRef<Instance>(
338
208
  ...refs: Array<React.Ref<Instance> | undefined>
339
209
  ): React.RefCallback<Instance> | null {
340
- /**
341
- * This will create a new function if the refs passed to this hook change and are all defined.
342
- * This means react will call the old forkRef with `null` and the new forkRef
343
- * with the ref. Cleanup naturally emerges from this behavior.
344
- */
345
210
  return React.useMemo(() => {
346
211
  if (refs.every((ref) => ref == null)) {
347
212
  return null;
@@ -22,9 +22,9 @@ export type TooltipProps = {
22
22
  className?: string,
23
23
  container?: HTMLElement,
24
24
  style?: React.CSSProperties;
25
- };
25
+ } & Omit<React.HTMLAttributes<HTMLDivElement>, "title">;
26
26
 
27
- export const Tooltip = ({
27
+ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(({
28
28
  open,
29
29
  defaultOpen,
30
30
  side = "bottom",
@@ -39,8 +39,9 @@ export const Tooltip = ({
39
39
  asChild = false,
40
40
  container,
41
41
  className,
42
- style
43
- }: TooltipProps) => {
42
+ style,
43
+ ...props
44
+ }, ref) => {
44
45
 
45
46
  useInjectStyles("Tooltip", styles);
46
47
 
@@ -58,7 +59,7 @@ export const Tooltip = ({
58
59
  {children}
59
60
  </TooltipPrimitive.Trigger>
60
61
  : <TooltipPrimitive.Trigger asChild={true}>
61
- <div style={style} className={className}>
62
+ <div style={style} className={className} ref={ref} {...props}>
62
63
  {children}
63
64
  </div>
64
65
  </TooltipPrimitive.Trigger>;
@@ -83,7 +84,7 @@ export const Tooltip = ({
83
84
  </TooltipPrimitive.Root>
84
85
  </TooltipPrimitive.Provider>
85
86
  );
86
- };
87
+ });
87
88
 
88
89
  const styles = `
89
90
 
@@ -30,7 +30,9 @@ export * from "./Menubar";
30
30
  export * from "./MultiSelect";
31
31
  export * from "./Paper";
32
32
  export * from "./RadioGroup";
33
+ export * from "./ResizablePanels";
33
34
  export * from "./SearchBar";
35
+ export * from "./SearchableSelect";
34
36
  export * from "./Select";
35
37
  export * from "./Separator";
36
38
  export * from "./Slider";
@@ -0,0 +1,47 @@
1
+ import React from "react";
2
+
3
+ import { IconProps } from "./Icon";
4
+
5
+ const sizeMap: Record<string, number> = {
6
+ smallest: 16,
7
+ small: 20,
8
+ medium: 24,
9
+ large: 28,
10
+ };
11
+
12
+ /**
13
+ * Firebase Firestore flame icon (monochrome, uses currentColor).
14
+ * @group Icons
15
+ */
16
+ export function FirestoreIcon(props: IconProps) {
17
+ const s = typeof props.size === "number"
18
+ ? props.size
19
+ : sizeMap[props.size ?? "medium"] ?? 24;
20
+
21
+ return (
22
+ <svg
23
+ xmlns="http://www.w3.org/2000/svg"
24
+ className={props.className}
25
+ fill={"currentColor"}
26
+ width={s}
27
+ height={s}
28
+ viewBox="0 0 73 91"
29
+ >
30
+ <path
31
+ d="M22.575 87.933A52.16 52.16 0 0034.787 90.513c5.84.204 11.395-1.004 16.359-3.298a70.68 70.68 0 01-15.948-10.013c-2.98 4.778-7.393 8.548-12.623 10.731z"
32
+ opacity=".7"
33
+ />
34
+ <path
35
+ d="M35.2 77.205c-10.505-9.714-16.878-23.776-16.339-39.2.018-.499.045-1.001.075-1.5a39.51 39.51 0 00-5.866-.855 38.77 38.77 0 00-8.34.997A53.07 53.07 0 00.022 53.236c-.544 15.58 8.884 29.191 22.553 34.697 5.23-2.18 9.642-5.948 12.625-10.728z"
36
+ opacity=".6"
37
+ />
38
+ <path
39
+ d="M35.2 77.205a31.63 31.63 0 004.096-13.428c.452-12.985-8.278-24.155-20.36-27.273-.03.5-.058 1.002-.076 1.502-.536 15.421 5.835 29.483 16.34 39.199z"
40
+ opacity=".7"
41
+ />
42
+ <path
43
+ d="M37.944 0a73.99 73.99 0 00-15.603 21.156 72.82 72.82 0 00-3.41 15.349c12.082 3.117 20.812 14.288 20.36 27.275a31.58 31.58 0 01-4.098 13.425 70.76 70.76 0 0015.948 10.013c11.951-5.523 20.43-17.41 20.919-31.467.318-9.11-3.181-17.228-8.126-24.081C58.711 24.424 37.944 0 37.944 0z"
44
+ />
45
+ </svg>
46
+ );
47
+ }
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import { Icon, IconProps } from "../Icon";
3
+ /**
4
+ * @group Icons
5
+ */
6
+ export const DatabaseIcon = React.forwardRef<HTMLSpanElement, IconProps>((props, ref) => {
7
+ return <Icon {...props} iconKey={"database"} ref={ref}/>
8
+ });
9
+
10
+ DatabaseIcon.displayName = "DatabaseIcon";
@@ -3,6 +3,7 @@ export * from "./cool_icon_keys";
3
3
  export * from "./Icon";
4
4
  export * from "./GitHubIcon";
5
5
  export * from "./HandleIcon";
6
+ export * from "./FirestoreIcon";
6
7
  export * from "./components/_10kIcon";
7
8
  export * from "./components/_10mpIcon";
8
9
  export * from "./components/_11mpIcon";
@@ -522,6 +523,7 @@ export * from "./components/DataThresholdingIcon";
522
523
  export * from "./components/DataUsageIcon";
523
524
  export * from "./components/DatasetIcon";
524
525
  export * from "./components/DatasetLinkedIcon";
526
+ export * from "./components/DatabaseIcon";
525
527
  export * from "./components/DateRangeIcon";
526
528
  export * from "./components/DeblurIcon";
527
529
  export * from "./components/DeckIcon";