@fibery/ui-kit 4.0.0 → 4.2.0
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 +18 -0
- package/esfint.config.mjs +0 -17
- package/package.json +7 -10
- package/scripts/generate-icons.mjs +45 -44
- package/src/actions-menu/actions-menu-item.tsx +1 -1
- package/src/actions-menu/context-actions-menu.tsx +1 -1
- package/src/animated-height-container.tsx +1 -1
- package/src/antd/ant-modal.tsx +21 -21
- package/src/antd/ant-upload.tsx +13 -13
- package/src/antd/auto-complete.tsx +10 -10
- package/src/antd/input-number.tsx +55 -45
- package/src/antd/styles.ts +30 -22
- package/src/antd/tabs.tsx +28 -28
- package/src/app-icon-with-fallback.tsx +3 -7
- package/src/app-icon-wrapper.tsx +1 -13
- package/src/breadcrumb.tsx +5 -1
- package/src/color-filter.ts +1 -1
- package/src/color-utils.test.ts +2 -2
- package/src/color-utils.ts +2 -2
- package/src/comment.tsx +2 -2
- package/src/copy-to-clipboard.ts +14 -3
- package/src/count-badge.tsx +3 -11
- package/src/date-picker/contexts.ts +6 -3
- package/src/date-picker/date-range-picker.tsx +1 -2
- package/src/date-picker/single-date-picker.tsx +1 -2
- package/src/day-select/iso-week-day-select.tsx +2 -2
- package/src/design-system/colors.ts +60 -40
- package/src/design-system/def.ts +1 -1
- package/src/dom-utils.ts +5 -12
- package/src/download-file.ts +36 -0
- package/src/dropdown-menu/index.tsx +0 -1
- package/src/emoji-picker/icon-emoji-picker.tsx +3 -3
- package/src/file-item/file-menu-items.tsx +29 -11
- package/src/file-item/file-preview-actions.tsx +52 -11
- package/src/file-item/share-file.ts +53 -0
- package/src/file-item.tsx +9 -9
- package/src/html-styles.ts +0 -1
- package/src/icons/ast/gauge.ts +12 -0
- package/src/icons/ast/index.tsx +449 -447
- package/src/icons/ast/people-edit.ts +12 -0
- package/src/icons/icon.tsx +14 -2
- package/src/icons/icons-integrity.test.ts +145 -0
- package/src/icons/react/gauge.tsx +18 -0
- package/src/icons/react/index.tsx +449 -447
- package/src/icons/react/people-edit.tsx +18 -0
- package/src/icons/svg/gauge.svg +5 -0
- package/src/icons/svg/people-edit.svg +4 -0
- package/src/images-gallery/images-gallery.tsx +2 -3
- package/src/images-gallery/slide-buttons.tsx +51 -20
- package/src/is-in-popup.ts +0 -3
- package/src/lists/actions-menu-row-surface.tsx +1 -1
- package/src/loading-sausage.tsx +3 -3
- package/src/media-query-utils.ts +1 -1
- package/src/mobile-keyboard-aware-popup.tsx +1 -1
- package/src/number-input/decimal.ts +6 -9
- package/src/number-input/number-input-inline-with-autosize.tsx +1 -1
- package/src/online-users.tsx +4 -5
- package/src/palettes/diff-colors.test.ts +1 -1
- package/src/palettes/inspect.defs.colors.neutral-arch.test.ts +98 -30
- package/src/palettes/inspect.defs.colors.neutral-user.test.ts +98 -30
- package/src/palettes/inspect.defs.colors.warm-arch.test.ts +98 -30
- package/src/palettes/inspect.defs.colors.warm-user.test.ts +98 -30
- package/src/palettes/show-colors.test.ts +2 -1
- package/src/palettes/testkit.ts +3 -1
- package/src/popover/index.tsx +24 -15
- package/src/popover/mobile-popover-context.tsx +1 -1
- package/src/popover/modifiers.tsx +1 -1
- package/src/popover/popup-modifiers-context.ts +1 -2
- package/src/rich-input-loader.tsx +1 -1
- package/src/select/components/menu-list-virtua.tsx +15 -16
- package/src/select/components/menu-list-virtualized.tsx +26 -29
- package/src/select/components/menu.tsx +3 -3
- package/src/select/components/option.tsx +0 -1
- package/src/select/index.tsx +2 -1
- package/src/select/reflection.ts +4 -5
- package/src/select/select-in-popover.tsx +1 -3
- package/src/select/select.tsx +5 -3
- package/src/select/styles.ts +1 -7
- package/src/select/util.ts +2 -2
- package/src/thematic.tsx +0 -1
- package/src/theme-provider.test.tsx +1 -1
- package/src/theming.generated.css +118 -58
- package/src/theming.generated.ts +135 -58
- package/src/type-badge.tsx +1 -2
- package/src/use-on-screen-keyboard-data.tsx +1 -1
- package/src/with-data.tsx +1 -1
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import noop from "
|
|
2
|
-
import {createContext} from "react";
|
|
1
|
+
import {noop} from "@fibery/helpers/src/_";
|
|
2
|
+
import {createContext, type Dispatch, type SetStateAction} from "react";
|
|
3
3
|
|
|
4
|
-
export const StartEndFocusContext = createContext
|
|
4
|
+
export const StartEndFocusContext = createContext<{
|
|
5
|
+
focusOnStart: boolean;
|
|
6
|
+
setFocusOnStart: Dispatch<SetStateAction<boolean>>;
|
|
7
|
+
}>({
|
|
5
8
|
focusOnStart: true,
|
|
6
9
|
setFocusOnStart: noop,
|
|
7
10
|
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {stopPropagation} from "@fibery/react/src/stop-propagation";
|
|
2
2
|
import {css} from "@linaria/core";
|
|
3
|
-
import omit from "lodash/omit";
|
|
4
3
|
import moment from "moment";
|
|
5
4
|
import {forwardRef, useCallback, useRef} from "react";
|
|
6
5
|
import {focusable} from "tabbable";
|
|
@@ -111,7 +110,7 @@ export const DateRangePicker = forwardRef<
|
|
|
111
110
|
>
|
|
112
111
|
<DateRangePickerPopupContent
|
|
113
112
|
ref={ref}
|
|
114
|
-
{...
|
|
113
|
+
{...props}
|
|
115
114
|
onChange={onDatesChange}
|
|
116
115
|
value={dateValue}
|
|
117
116
|
onKeyDown={onDatePickerKeyDown}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {stopPropagation} from "@fibery/react/src/stop-propagation";
|
|
2
2
|
import {css, cx} from "@linaria/core";
|
|
3
|
-
import omit from "lodash/omit";
|
|
4
3
|
import type {Moment} from "moment";
|
|
5
4
|
import moment from "moment";
|
|
6
5
|
|
|
@@ -191,7 +190,7 @@ export const SingleDatePickerPopoverContent = forwardRef<
|
|
|
191
190
|
{!allowRelativeDate || activeButton === "exact" ? (
|
|
192
191
|
<SingleDatePickerPopupContent
|
|
193
192
|
ref={ref}
|
|
194
|
-
{...
|
|
193
|
+
{...props}
|
|
195
194
|
onChange={onDateChange}
|
|
196
195
|
value={!isRelativeValue && value ? (value as Moment).toDate() : null}
|
|
197
196
|
onKeyDown={onDatePickerKeyDown}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import range from "lodash/range";
|
|
2
1
|
import moment from "moment";
|
|
3
2
|
|
|
4
3
|
import type {Props} from "./day-select";
|
|
5
4
|
import {DaySelect} from "./day-select";
|
|
6
5
|
|
|
7
|
-
const isoWeekDays =
|
|
6
|
+
const isoWeekDays = Array.from({length: 7}, (_, i) => {
|
|
7
|
+
const day = i + 1;
|
|
8
8
|
return {
|
|
9
9
|
key: String(day),
|
|
10
10
|
title: moment().isoWeekday(day).format("dd").charAt(0),
|
|
@@ -107,8 +107,9 @@ export function defsFromPalette({
|
|
|
107
107
|
level2_6: [base[2], baseDark[6]],
|
|
108
108
|
|
|
109
109
|
// muted bg (code, form fields)
|
|
110
|
-
|
|
110
|
+
level3: [base[3], baseDark[3]],
|
|
111
111
|
level3_1: [base[3], baseDark[1]],
|
|
112
|
+
level3_2: [base[3], baseDark[2]],
|
|
112
113
|
level3_4: [base[3], baseDark[4]],
|
|
113
114
|
level3_6: [base[3], baseDark[6]],
|
|
114
115
|
level3_7: [base[3], baseDark[7]],
|
|
@@ -167,9 +168,9 @@ export function defsFromPalette({
|
|
|
167
168
|
|
|
168
169
|
// --- Input borders (stroke shadows) ---
|
|
169
170
|
const inputStrokes = {
|
|
170
|
-
disabled: [stroke1(alpha(base[6], 0.
|
|
171
|
-
default: [stroke1(alpha(base[6], 0.
|
|
172
|
-
hover: [stroke1(base[7]), stroke1(baseDark[
|
|
171
|
+
disabled: [stroke1(alpha(base[6], 0.6)), stroke1(alpha(baseDark[8], 0.3))],
|
|
172
|
+
default: [stroke1(alpha(base[6], 0.8)), stroke1(alpha(baseDark[8], 0.4))],
|
|
173
|
+
hover: [stroke1(base[7]), stroke1(baseDark[7])],
|
|
173
174
|
focus: [stroke1(base[8]), stroke1(baseDark[9])],
|
|
174
175
|
error: [stroke1(red[11]), stroke1(red[7])],
|
|
175
176
|
errorHover: [stroke1(alpha(red[11], 0.4)), stroke1(alpha(red[7], 0.4))],
|
|
@@ -190,6 +191,15 @@ export function defsFromPalette({
|
|
|
190
191
|
],
|
|
191
192
|
} satisfies Defs;
|
|
192
193
|
|
|
194
|
+
// --- Floating panel (inbox item hover, rich text format bar, batch actions toolbar) ---
|
|
195
|
+
const floatingPanel = {
|
|
196
|
+
bg: raised.level2,
|
|
197
|
+
shadow: [
|
|
198
|
+
steps([stroke1(alpha(base[10], 0.15)), elevationSL(alpha(base[11], 0.15))]),
|
|
199
|
+
steps([stroke1(baseDark[6]), elevationSL(alpha(baseDark[1], 0.5))]),
|
|
200
|
+
],
|
|
201
|
+
} satisfies Defs;
|
|
202
|
+
|
|
193
203
|
// --- Card/container shadows ---
|
|
194
204
|
const cardShadows = {
|
|
195
205
|
subtle: [
|
|
@@ -232,7 +242,8 @@ export function defsFromPalette({
|
|
|
232
242
|
|
|
233
243
|
const shadows = {
|
|
234
244
|
// Elevations
|
|
235
|
-
|
|
245
|
+
// dark uses a subtle dark shadow (baseDark[1]) like entityBoxShadows — NOT a white glow
|
|
246
|
+
surfaceElevationS: [elevationS(blackA[3]), elevationS(alpha(baseDark[1], 0.05))],
|
|
236
247
|
surfaceElevationM: [elevationM(blackA[5]), elevationM(whiteA[5])],
|
|
237
248
|
surfaceElevationL: [elevationL(blackA[5]), elevationL(whiteA[5])],
|
|
238
249
|
|
|
@@ -250,16 +261,6 @@ export function defsFromPalette({
|
|
|
250
261
|
shadowSidebar: [edgeLeft(alpha(base[10], 0.1)), edgeLeft(alpha(baseDark[1], 0.8))],
|
|
251
262
|
} as const;
|
|
252
263
|
|
|
253
|
-
// names proposal: forbid color in keys (they all are colors) — restructure it better (separate shadows, borders etc)
|
|
254
|
-
// inspired by tailwind:
|
|
255
|
-
// bg-* Sets the background color of an element
|
|
256
|
-
// text-* Sets the text color of an element
|
|
257
|
-
// border-* Sets the border color of an element
|
|
258
|
-
// shadow-* Sets the color of box shadows
|
|
259
|
-
// fill-* Sets the fill color of SVG elements
|
|
260
|
-
// stroke-* Sets the stroke color of SVG elements
|
|
261
|
-
// TODO elevation = raised * shadow
|
|
262
|
-
|
|
263
264
|
const transparentAlways = same(transparent);
|
|
264
265
|
const whiteColor = same(white);
|
|
265
266
|
const blackColor = same(black);
|
|
@@ -349,11 +350,10 @@ export function defsFromPalette({
|
|
|
349
350
|
const bgTabHover = [alpha(base[11], 0.1), alpha(baseDark[11], 0.1)] as const;
|
|
350
351
|
|
|
351
352
|
const viewHighlightBg = [accent[3], accentDark[3]] as const;
|
|
352
|
-
const notificationSelected = [accent[3], accentDark[4]] as const;
|
|
353
|
-
const bgCellGridDisabledOrHighlighted = [accent[4], accentDark[5]] as const;
|
|
354
|
-
const notificationSelectedHover = [accent[4], accentDark[6]] as const;
|
|
355
353
|
|
|
356
|
-
const
|
|
354
|
+
const notificationSelected = [accent[3], accentDark[2]] as const;
|
|
355
|
+
const notificationSelectedHover = [accent[4], accentDark[3]] as const;
|
|
356
|
+
const bgCellGridDisabledOrHighlighted = [accent[4], accentDark[5]] as const;
|
|
357
357
|
|
|
358
358
|
const warningColor = [alpha(yellow[6], 0.6), alpha(yellowDark[9], 0.4)] as const;
|
|
359
359
|
const bgBtnGhostAccentHover = [alpha(accent[4], 0.8), alpha(accentDark[4], 0.8)] as const;
|
|
@@ -363,11 +363,14 @@ export function defsFromPalette({
|
|
|
363
363
|
const bgBtnSoftNeutralDefault = [base[3], baseDark[3]] as const;
|
|
364
364
|
const bgBtnSoftNeutralHover = [base[7], baseDark[7]] as const;
|
|
365
365
|
const bgBtnSoftDestructiveHover = [red[6], redDark[6]] as const;
|
|
366
|
+
const bgBtnSoftAccentDefault = [accent[3], accentDark[4]] as const;
|
|
367
|
+
|
|
366
368
|
const borderFocusRingAccent = same(alpha(accent[9], 0.3));
|
|
367
369
|
const borderFocusRingNeutral = same(alpha(base[9], 0.3));
|
|
368
370
|
const borderFocusRingDestructive = same(alpha(red[9], 0.3));
|
|
369
371
|
const textWhiteboardSelectionLockedOrDisabled = [alpha(base[9], 0.5), alpha(baseDark[9], 0.5)] as const;
|
|
370
372
|
|
|
373
|
+
const fgSelection = [alpha(accent[9], 0.2), alpha(accentDark[9], 0.4)] as const;
|
|
371
374
|
const fgSelectedText = same(base[2]);
|
|
372
375
|
const fgSolidActiveText = same(alpha(white, 0.8));
|
|
373
376
|
|
|
@@ -426,15 +429,24 @@ export function defsFromPalette({
|
|
|
426
429
|
// Accent base
|
|
427
430
|
colorAccent: primary.accent,
|
|
428
431
|
|
|
429
|
-
//
|
|
430
|
-
/** @deprecated */
|
|
432
|
+
//to be sorted. these are still in use
|
|
431
433
|
darkenPrimary: same(getDarkenColor(accent[9])),
|
|
432
434
|
whiteColor,
|
|
433
435
|
blackColor,
|
|
434
|
-
mainBg: surface.
|
|
436
|
+
mainBg: surface.level3,
|
|
435
437
|
panelBg: surface.level0,
|
|
438
|
+
panelBorder: [stroke1(alpha(base[8], 0.2)), stroke1(alpha(baseDark[8], 0.2))],
|
|
436
439
|
panelContentBg: surface.level2_1,
|
|
437
|
-
|
|
440
|
+
|
|
441
|
+
//Database settings screens
|
|
442
|
+
dbSettingsSidebarBg: surface.level2,
|
|
443
|
+
dbSettingsSidebarItemHover: surface.level4,
|
|
444
|
+
dbSettingsSidebarIconBg: raised.level1,
|
|
445
|
+
dbSettingsSidebarIconBorder: shadowMenuHeaderAvatar,
|
|
446
|
+
|
|
447
|
+
dbSettingsCardBg: surface.level2,
|
|
448
|
+
dbSettingsCardBorder: [stroke1(alpha(base[6], 0.8)), stroke1(alpha(baseDark[6], 0.6))],
|
|
449
|
+
colorBgRelationContainer: surface.level1_1,
|
|
438
450
|
|
|
439
451
|
// AI colors
|
|
440
452
|
colorAI: fg.ai,
|
|
@@ -444,10 +456,14 @@ export function defsFromPalette({
|
|
|
444
456
|
colorBgAIFloatingPanelContent: surface.level1_3,
|
|
445
457
|
colorBgAISidebarContent: surface.level1,
|
|
446
458
|
|
|
459
|
+
// Floating panels (hover action toolbars, selection toolbar, rich-text format bar)
|
|
460
|
+
colorBgFloatingPanel: floatingPanel.bg,
|
|
461
|
+
shadowFloatingPanel: floatingPanel.shadow,
|
|
462
|
+
|
|
447
463
|
// Other colors
|
|
448
464
|
pageContentBg: surface.level0elevated,
|
|
449
|
-
colorBgPopup:
|
|
450
|
-
menuBg: surface.
|
|
465
|
+
colorBgPopup: [white, baseDark[3]],
|
|
466
|
+
menuBg: surface.level3,
|
|
451
467
|
menuTextColor: fgMenuEtc,
|
|
452
468
|
menuItemHoverColor: base5_6,
|
|
453
469
|
menuSelectedTextColor: fgSelectedText,
|
|
@@ -482,7 +498,7 @@ export function defsFromPalette({
|
|
|
482
498
|
shortcutBorderColor: [alpha(base[8], 0.5), alpha(baseDark[10], 0.2)],
|
|
483
499
|
|
|
484
500
|
// Input
|
|
485
|
-
inputBgColor:
|
|
501
|
+
inputBgColor: [white, baseDark[1]],
|
|
486
502
|
inputDisabledBgColor: base6_6_opacity30,
|
|
487
503
|
inputDisabledBorderColor: inputStrokes.disabled,
|
|
488
504
|
inputCopyBgColor: surface.level3_4,
|
|
@@ -496,6 +512,7 @@ export function defsFromPalette({
|
|
|
496
512
|
inputErrorBorderFocusColor: inputStrokes.errorFocus,
|
|
497
513
|
inputErrorBorderFocusShadow: inputStrokes.errorFocusRing,
|
|
498
514
|
inputBorderWarningColor: inputStrokes.warning,
|
|
515
|
+
//buttons and checkboxes and blockquote (why?)
|
|
499
516
|
buttonPrimaryColor: primary.accent,
|
|
500
517
|
buttonColor: fgAccentText,
|
|
501
518
|
buttonPrimaryTextColor: [base[2], base[6]],
|
|
@@ -560,9 +577,9 @@ export function defsFromPalette({
|
|
|
560
577
|
colorBgListItemGeneral: transparentAlways,
|
|
561
578
|
colorBgListItemGeneralHover: surface.level3_4,
|
|
562
579
|
colorBgListItemGeneralFocus: surface.level4_6,
|
|
563
|
-
colorBgListItemGeneralSelected:
|
|
564
|
-
colorBgListItemGeneralSelectedHover:
|
|
565
|
-
colorBgListItemGeneralSelectedFocus:
|
|
580
|
+
colorBgListItemGeneralSelected: [accent[5], accentDark[5]],
|
|
581
|
+
colorBgListItemGeneralSelectedHover: [accent[6], accentDark[6]],
|
|
582
|
+
colorBgListItemGeneralSelectedFocus: [accent[6], accentDark[6]],
|
|
566
583
|
colorBgListItemGeneralDisabled: transparentAlways,
|
|
567
584
|
colorTextListItemGeneralDisabled: fg.muted,
|
|
568
585
|
colorBgListItemGeneralSelectedDisabled: [accent[5], accentDark[5]],
|
|
@@ -570,7 +587,8 @@ export function defsFromPalette({
|
|
|
570
587
|
|
|
571
588
|
// Search
|
|
572
589
|
colorSearchSubtitle: fg.muted,
|
|
573
|
-
|
|
590
|
+
colorBgSearchPanel: [white, baseDark[2]],
|
|
591
|
+
colorBgSearchRowHover: [base[4], baseDark[3]],
|
|
574
592
|
colorBgSearchRowCreateHover: [accent[4], accentDark[4]],
|
|
575
593
|
opacitySearchItemDone: opacities.drag,
|
|
576
594
|
|
|
@@ -618,7 +636,7 @@ export function defsFromPalette({
|
|
|
618
636
|
entityCardDoneColor: bgEntityCardDone,
|
|
619
637
|
entityCardShadow: cardShadows.subtle,
|
|
620
638
|
entityCardShadowHover: cardShadows.subtle,
|
|
621
|
-
colorBgSegmentedControl: [alpha(base[5], 0.8), alpha(baseDark[
|
|
639
|
+
colorBgSegmentedControl: [alpha(base[5], 0.8), alpha(baseDark[2], 0.8)],
|
|
622
640
|
colorBgMenuHeaderAvatar: surface.level0elevated,
|
|
623
641
|
shadowMenuHeaderAvatar: shadowMenuHeaderAvatar,
|
|
624
642
|
|
|
@@ -630,9 +648,9 @@ export function defsFromPalette({
|
|
|
630
648
|
colorTextMenuItem: [alpha(base[12], 0.9), alpha(baseDark[12], 0.9)],
|
|
631
649
|
colorBgMenuItem: transparentAlways,
|
|
632
650
|
// :hover
|
|
633
|
-
colorBgMenuItemHover: [base[5], baseDark[
|
|
651
|
+
colorBgMenuItemHover: [base[5], baseDark[5]],
|
|
634
652
|
// :focus
|
|
635
|
-
colorBgMenuItemFocus: [base[6], baseDark[
|
|
653
|
+
colorBgMenuItemFocus: [base[6], baseDark[6]],
|
|
636
654
|
// Selected
|
|
637
655
|
colorBgMenuItemSelected: [alpha(accent[5], 0.8), accentDark[3]],
|
|
638
656
|
// :hover
|
|
@@ -700,7 +718,7 @@ export function defsFromPalette({
|
|
|
700
718
|
filterGroup1Bg: surface.level2_6,
|
|
701
719
|
filterGroup2Bg: surface.level3_7,
|
|
702
720
|
filterGroupBorder: [blackA[3], whiteA[3]],
|
|
703
|
-
colorBgNotificationHover: surface.
|
|
721
|
+
colorBgNotificationHover: surface.level3_2,
|
|
704
722
|
colorBgNotificationSelected: notificationSelected,
|
|
705
723
|
colorBgNotificationSelectedHover: notificationSelectedHover,
|
|
706
724
|
disabledInversedTextColor: blackA25,
|
|
@@ -727,6 +745,9 @@ export function defsFromPalette({
|
|
|
727
745
|
|
|
728
746
|
// Views
|
|
729
747
|
viewBg: surface.level1,
|
|
748
|
+
colorBgViewMetaItem: surface.level0elevated,
|
|
749
|
+
collectionViewBg: surface.level1_1,
|
|
750
|
+
collectionViewBorder: inputStrokes.disabled,
|
|
730
751
|
viewSecondaryBg: surface.level2,
|
|
731
752
|
viewHighlightBg: viewHighlightBg,
|
|
732
753
|
|
|
@@ -796,7 +817,7 @@ export function defsFromPalette({
|
|
|
796
817
|
colorIconButtonOutlineNeutral: fg.neutral,
|
|
797
818
|
colorIconButtonOutlineDestructive: fg.destructive,
|
|
798
819
|
// soft
|
|
799
|
-
colorBgButtonSoftAccentDefault:
|
|
820
|
+
colorBgButtonSoftAccentDefault: bgBtnSoftAccentDefault,
|
|
800
821
|
colorBgButtonSoftAccentHover: bgBtnSoftAccentHover,
|
|
801
822
|
colorBgButtonSoftNeutralDefault: bgBtnSoftNeutralDefault,
|
|
802
823
|
colorBgButtonSoftNeutralHover: bgBtnSoftNeutralHover,
|
|
@@ -856,7 +877,10 @@ export function defsFromPalette({
|
|
|
856
877
|
colorTextEntityAvatarHover: primary.accent,
|
|
857
878
|
colorBgMessageHighlighted: surface.level0elevated,
|
|
858
879
|
|
|
859
|
-
//
|
|
880
|
+
// Comment/message row hover (subtle lift over panel, stays darker than the floating actions toolbar in dark theme)
|
|
881
|
+
colorBgCommentHover: surface.level2,
|
|
882
|
+
|
|
883
|
+
colorBgCodeEditor: raised.level1,
|
|
860
884
|
colorBgEditorImageZoomed: surface.level3_6,
|
|
861
885
|
colorMermaidPrimaryColor: [base[2], baseDark[8]],
|
|
862
886
|
colorMermaidPrimaryBorderColor: fg.muted,
|
|
@@ -885,10 +909,6 @@ export function defsFromPalette({
|
|
|
885
909
|
colorTextWhiteboardDefault: fgMenuEtc,
|
|
886
910
|
colorTextWhiteboardDisabled: fg.muted,
|
|
887
911
|
// FIXME bug?
|
|
888
|
-
// Dark uses baseDark[1] (near-black at 60% opacity) as "accent text" on a dark background.
|
|
889
|
-
// This reads as near-invisible dark text on dark bg.
|
|
890
|
-
// Compare with colorUnitHeaderWhiteboardDefault (L772) which correctly uses baseDark[12] for dark.
|
|
891
|
-
// Likely should be baseDark[12] too.
|
|
892
912
|
colorTextWhiteboardAccent: [alpha(base[12], 0.6), alpha(baseDark[1], 0.6)],
|
|
893
913
|
colorTextWhiteboardInversedDefault: whiteColor,
|
|
894
914
|
instrumentsMenuBg: raised.level2,
|
package/src/design-system/def.ts
CHANGED
|
@@ -8,7 +8,7 @@ export function mapDef<T, U>(def: ModeDef<T>, mapper: MapperDef<T, U>): ModeDef<
|
|
|
8
8
|
export function mapDef<T, U>(def: ModeDef<T> | undefined, mapper: MapperDef<T | undefined, U>): ModeDef<U>;
|
|
9
9
|
export function mapDef<T, U>(def: ModeDef<T> | undefined, mapper: MapperDef<T | undefined, U>): ModeDef<U> {
|
|
10
10
|
if (typeof mapper === "function") {
|
|
11
|
-
//
|
|
11
|
+
// eslint-disable-next-line no-param-reassign
|
|
12
12
|
mapper = [mapper, mapper];
|
|
13
13
|
}
|
|
14
14
|
if (def === undefined) {
|
package/src/dom-utils.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export const getPopupContainerElement = (): HTMLElement => {
|
|
7
|
-
if (!popupContainerElement) {
|
|
8
|
-
popupContainerElement =
|
|
9
|
-
document.getElementById("popup-container") || fail("popup-container was not found in document");
|
|
10
|
-
}
|
|
11
|
-
return popupContainerElement;
|
|
12
|
-
};
|
|
1
|
+
// Popups render directly in body, like Radix portals do: later-opened floating
|
|
2
|
+
// elements come later in the DOM and paint above earlier ones, and no host
|
|
3
|
+
// HTML contract is required. Kept as a function so call sites stay unchanged
|
|
4
|
+
// until the custom popover is replaced with a Radix one.
|
|
5
|
+
export const getPopupContainerElement = (): HTMLElement => document.body;
|
|
13
6
|
|
|
14
7
|
type LimitedMouseEvent = Pick<MouseEvent, "button" | "metaKey" | "altKey" | "ctrlKey" | "shiftKey">;
|
|
15
8
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const blobToDataUrl = async (blobUrl: string) => {
|
|
2
|
+
const result = await fetch(blobUrl);
|
|
3
|
+
const blob = await result.blob();
|
|
4
|
+
const dataUrl = await new Promise<string>((resolve, reject) => {
|
|
5
|
+
const reader = new FileReader();
|
|
6
|
+
reader.readAsDataURL(blob);
|
|
7
|
+
reader.onloadend = () => resolve(reader.result as string);
|
|
8
|
+
reader.onerror = () => reject(new Error("Error fetching blob"));
|
|
9
|
+
reader.onabort = () => reject(new Error("Aborted"));
|
|
10
|
+
});
|
|
11
|
+
return dataUrl;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const downloadFile = async (url: string, fileName?: string) => {
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
16
|
+
// @ts-ignore - injected by consumer app
|
|
17
|
+
if (window.__FIBERY_MOBILE_APP_API) {
|
|
18
|
+
let absoluteUrl = new URL(url, document.baseURI).href;
|
|
19
|
+
if (absoluteUrl.startsWith("blob:")) {
|
|
20
|
+
absoluteUrl = await blobToDataUrl(absoluteUrl);
|
|
21
|
+
}
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
23
|
+
// @ts-ignore - injected by consumer app
|
|
24
|
+
window.__FIBERY_MOBILE_APP_API.downloadFile(absoluteUrl, fileName);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const a = document.createElement("a");
|
|
29
|
+
a.href = url;
|
|
30
|
+
a.download = fileName ?? "";
|
|
31
|
+
a.style.display = "none";
|
|
32
|
+
|
|
33
|
+
document.body.appendChild(a);
|
|
34
|
+
a.click();
|
|
35
|
+
document.body.removeChild(a);
|
|
36
|
+
};
|
|
@@ -15,7 +15,6 @@ export const Root = (dropdownMenuProps: DropdownMenuPrimitive.DropdownMenuProps)
|
|
|
15
15
|
export const Trigger = forwardRef<
|
|
16
16
|
HTMLButtonElement,
|
|
17
17
|
DropdownMenuPrimitive.DropdownMenuTriggerProps & {triggerEvent?: string}
|
|
18
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
19
18
|
>(({triggerEvent, className, children, ...otherProps}, ref) => {
|
|
20
19
|
return (
|
|
21
20
|
<DropdownMenuPrimitive.Trigger ref={ref} className={className} {...otherProps}>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import {truthy} from "@fibery/helpers/src/_";
|
|
1
2
|
import {css} from "@linaria/core";
|
|
2
|
-
import compact from "lodash/compact";
|
|
3
3
|
import {useState} from "react";
|
|
4
4
|
|
|
5
5
|
import {Button} from "../button/button";
|
|
@@ -48,7 +48,7 @@ export const IconEmojiPicker: React.FC<IconEmojiPickerProps> = ({
|
|
|
48
48
|
color,
|
|
49
49
|
getHoverColor,
|
|
50
50
|
}) => {
|
|
51
|
-
const items =
|
|
51
|
+
const items = [
|
|
52
52
|
onEmojiSelect && {
|
|
53
53
|
key: "emojis" as const,
|
|
54
54
|
label: "Emojis",
|
|
@@ -80,7 +80,7 @@ export const IconEmojiPicker: React.FC<IconEmojiPickerProps> = ({
|
|
|
80
80
|
/>
|
|
81
81
|
),
|
|
82
82
|
},
|
|
83
|
-
]);
|
|
83
|
+
].filter(truthy);
|
|
84
84
|
|
|
85
85
|
const firstTab = items[0];
|
|
86
86
|
const [activeTab, setActiveTab] = useState(defaultTab || firstTab?.key);
|
|
@@ -7,9 +7,11 @@ import FileDownload from "../icons/react/file-download";
|
|
|
7
7
|
import Link from "../icons/react/link";
|
|
8
8
|
import Pencil from "../icons/react/pencil";
|
|
9
9
|
import RicheditorOpenLink from "../icons/react/richeditor-open-link";
|
|
10
|
+
import Share from "../icons/react/share";
|
|
10
11
|
import {useToast} from "../toast/toast-provider";
|
|
11
12
|
import {isPhoneApp} from "../use-is-phone";
|
|
12
13
|
import {addQueryParam} from "./add-query-param";
|
|
14
|
+
import {canShareFile, shareFile} from "./share-file";
|
|
13
15
|
import type {UploadFileWithContent} from "./types";
|
|
14
16
|
|
|
15
17
|
export function FileMenuItems({
|
|
@@ -26,6 +28,7 @@ export function FileMenuItems({
|
|
|
26
28
|
disabled?: boolean;
|
|
27
29
|
}): ReactNode {
|
|
28
30
|
const toast = useToast();
|
|
31
|
+
const sharingEnabled = isPhoneApp() && canShareFile();
|
|
29
32
|
|
|
30
33
|
return (
|
|
31
34
|
<>
|
|
@@ -39,19 +42,34 @@ export function FileMenuItems({
|
|
|
39
42
|
>
|
|
40
43
|
Copy file URL
|
|
41
44
|
</ActionsMenuItem>
|
|
42
|
-
|
|
43
|
-
<ActionsMenuItem
|
|
44
|
-
{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
{sharingEnabled ? (
|
|
46
|
+
<ActionsMenuItem
|
|
47
|
+
Icon={Share}
|
|
48
|
+
onSelect={() => {
|
|
49
|
+
shareFile(file.url, file.name, {toast}).catch((error) =>
|
|
50
|
+
toast.error({title: "Unable to share file", subTitle: error.message})
|
|
51
|
+
);
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
Share
|
|
50
55
|
</ActionsMenuItem>
|
|
51
56
|
) : (
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
57
|
+
<>
|
|
58
|
+
<a href={file.url} target="_blank" rel="noopener noreferrer">
|
|
59
|
+
<ActionsMenuItem Icon={RicheditorOpenLink}>
|
|
60
|
+
{isPhoneApp() ? "Open in browser" : "Open in new tab"}
|
|
61
|
+
</ActionsMenuItem>
|
|
62
|
+
</a>
|
|
63
|
+
{isPhoneApp() ? (
|
|
64
|
+
<ActionsMenuItem Icon={FileDownload} onSelect={onDownloadClick}>
|
|
65
|
+
Download
|
|
66
|
+
</ActionsMenuItem>
|
|
67
|
+
) : (
|
|
68
|
+
<a download={file.name} href={addQueryParam(file.url, "attachment", "true")}>
|
|
69
|
+
<ActionsMenuItem Icon={FileDownload}>Download</ActionsMenuItem>
|
|
70
|
+
</a>
|
|
71
|
+
)}
|
|
72
|
+
</>
|
|
55
73
|
)}
|
|
56
74
|
{onRenameClick && file.status === "done" ? (
|
|
57
75
|
<>
|
|
@@ -1,30 +1,71 @@
|
|
|
1
|
+
import {useCallback} from "react";
|
|
2
|
+
|
|
1
3
|
import {ActionsMenuItem, ActionsMenuSeparator, AsyncActionsMenuItem} from "../actions-menu";
|
|
4
|
+
import {downloadFile} from "../download-file";
|
|
5
|
+
import {asError} from "../errors";
|
|
2
6
|
import Delete from "../icons/react/delete";
|
|
3
7
|
import FileDownload from "../icons/react/file-download";
|
|
4
8
|
import RicheditorOpenLink from "../icons/react/richeditor-open-link";
|
|
9
|
+
import Share from "../icons/react/share";
|
|
10
|
+
import {useToast} from "../toast/toast-provider";
|
|
5
11
|
import {isPhoneApp} from "../use-is-phone";
|
|
6
12
|
import {addQueryParam} from "./add-query-param";
|
|
13
|
+
import {canShareFile, shareFile} from "./share-file";
|
|
7
14
|
|
|
8
15
|
export function FilePreviewActions({
|
|
9
16
|
url,
|
|
10
17
|
name,
|
|
11
|
-
onDownloadClick,
|
|
12
18
|
onDeleteClick,
|
|
13
19
|
}: {
|
|
14
20
|
url: string;
|
|
15
21
|
name: string;
|
|
16
|
-
onDownloadClick?: () => void;
|
|
17
22
|
onDeleteClick?: () => void | Promise<void>;
|
|
18
23
|
}) {
|
|
24
|
+
const toast = useToast();
|
|
25
|
+
const onDownloadClick = useCallback(
|
|
26
|
+
(e: Event) => {
|
|
27
|
+
e?.preventDefault();
|
|
28
|
+
e?.stopPropagation();
|
|
29
|
+
|
|
30
|
+
downloadFile(addQueryParam(url, "attachment", "true"), name).catch((error) => {
|
|
31
|
+
toast.error({title: "Unable to download file", subTitle: asError(error).message});
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
[url, name, toast]
|
|
35
|
+
);
|
|
36
|
+
|
|
19
37
|
if (isPhoneApp()) {
|
|
20
38
|
return (
|
|
21
39
|
<>
|
|
22
|
-
|
|
23
|
-
<ActionsMenuItem
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
40
|
+
{canShareFile() ? (
|
|
41
|
+
<ActionsMenuItem
|
|
42
|
+
Icon={Share}
|
|
43
|
+
onSelect={() => {
|
|
44
|
+
shareFile(url, name, {toast}).catch((error) =>
|
|
45
|
+
toast.error({title: "Unable to share file", subTitle: asError(error).message})
|
|
46
|
+
);
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
Share
|
|
50
|
+
</ActionsMenuItem>
|
|
51
|
+
) : (
|
|
52
|
+
<>
|
|
53
|
+
<a href={url} target="_blank" rel="noopener noreferrer">
|
|
54
|
+
<ActionsMenuItem Icon={RicheditorOpenLink}>Open in browser</ActionsMenuItem>
|
|
55
|
+
</a>
|
|
56
|
+
<ActionsMenuItem Icon={FileDownload} onSelect={onDownloadClick}>
|
|
57
|
+
Download
|
|
58
|
+
</ActionsMenuItem>
|
|
59
|
+
</>
|
|
60
|
+
)}
|
|
61
|
+
{onDeleteClick ? (
|
|
62
|
+
<>
|
|
63
|
+
<ActionsMenuSeparator />
|
|
64
|
+
<AsyncActionsMenuItem Icon={Delete} onSelect={onDeleteClick} dangerous>
|
|
65
|
+
Delete
|
|
66
|
+
</AsyncActionsMenuItem>
|
|
67
|
+
</>
|
|
68
|
+
) : null}
|
|
28
69
|
</>
|
|
29
70
|
);
|
|
30
71
|
}
|
|
@@ -34,9 +75,9 @@ export function FilePreviewActions({
|
|
|
34
75
|
<a href={url} target="_blank" rel="noopener noreferrer">
|
|
35
76
|
<ActionsMenuItem Icon={RicheditorOpenLink}>Open in new tab</ActionsMenuItem>
|
|
36
77
|
</a>
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
</
|
|
78
|
+
<ActionsMenuItem Icon={FileDownload} onSelect={onDownloadClick}>
|
|
79
|
+
Download
|
|
80
|
+
</ActionsMenuItem>
|
|
40
81
|
{onDeleteClick ? (
|
|
41
82
|
<>
|
|
42
83
|
<ActionsMenuSeparator />
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type {ToastQueue} from "../toast/toast-queue";
|
|
2
|
+
|
|
3
|
+
const blobToDataUrl = (blob: Blob) =>
|
|
4
|
+
new Promise<string>((resolve, reject) => {
|
|
5
|
+
const reader = new FileReader();
|
|
6
|
+
reader.readAsDataURL(blob);
|
|
7
|
+
reader.onloadend = () => resolve(reader.result as string);
|
|
8
|
+
reader.onerror = () => reject(new Error("Error reading file"));
|
|
9
|
+
reader.onabort = () => reject(new Error("Aborted"));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const canShareFile = (): boolean =>
|
|
13
|
+
Boolean(
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
15
|
+
// @ts-ignore - injected by consumer app
|
|
16
|
+
window.__FIBERY_MOBILE_APP_API?.shareFile
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export const shareFile = async (
|
|
20
|
+
url: string,
|
|
21
|
+
fileName?: string,
|
|
22
|
+
opts?: {toast?: ToastQueue; loadingTitle?: string}
|
|
23
|
+
): Promise<void> => {
|
|
24
|
+
let toastId: string | undefined;
|
|
25
|
+
const toastTimeoutId = opts?.toast
|
|
26
|
+
? setTimeout(() => {
|
|
27
|
+
toastId = opts.toast?.loading({title: opts.loadingTitle ?? "Preparing file…"});
|
|
28
|
+
}, 300)
|
|
29
|
+
: undefined;
|
|
30
|
+
const hideLoadingToast = () => {
|
|
31
|
+
clearTimeout(toastTimeoutId);
|
|
32
|
+
if (toastId) {
|
|
33
|
+
opts?.toast?.close(toastId);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const absoluteUrl = url.startsWith("/") ? `${window.origin}${url}` : url;
|
|
39
|
+
const response = await fetch(absoluteUrl);
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw new Error(`Failed to fetch file (${response.status})`);
|
|
42
|
+
}
|
|
43
|
+
const dataUrl = await blobToDataUrl(await response.blob());
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
45
|
+
// @ts-ignore - injected by consumer app
|
|
46
|
+
const sharePromise: Promise<void> = window.__FIBERY_MOBILE_APP_API.shareFile(dataUrl, fileName);
|
|
47
|
+
hideLoadingToast();
|
|
48
|
+
await sharePromise;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
hideLoadingToast();
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
};
|
package/src/file-item.tsx
CHANGED
|
@@ -308,6 +308,8 @@ function FileActionsMenu({
|
|
|
308
308
|
return (
|
|
309
309
|
<ActionsMenu
|
|
310
310
|
onOpenChange={setIsActionsOpened}
|
|
311
|
+
supportsMobile
|
|
312
|
+
mobileTitle={data.name}
|
|
311
313
|
trigger={
|
|
312
314
|
<ActionsButtonCompact
|
|
313
315
|
className={css`
|
|
@@ -317,15 +319,13 @@ function FileActionsMenu({
|
|
|
317
319
|
/>
|
|
318
320
|
}
|
|
319
321
|
>
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
/>
|
|
328
|
-
}
|
|
322
|
+
<FileMenuItems
|
|
323
|
+
file={data}
|
|
324
|
+
disabled={disabled}
|
|
325
|
+
onDownloadClick={onDownloadClick}
|
|
326
|
+
onRemoveClick={onRemoveClick}
|
|
327
|
+
onRenameClick={onRenameClick}
|
|
328
|
+
/>
|
|
329
329
|
</ActionsMenu>
|
|
330
330
|
);
|
|
331
331
|
}
|
package/src/html-styles.ts
CHANGED