kopia lustrzana https://github.com/Tldraw/Tldraw
better reacjis
rodzic
5edf7dd00d
commit
a668447d22
|
@ -1592,3 +1592,83 @@ it from receiving any pointer events or affecting the cursor. */
|
|||
font-size: 12px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* --------------------- Tooltips --------------------- */
|
||||
|
||||
.TooltipContent {
|
||||
border-radius: 4px;
|
||||
padding: 10px 15px;
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
color: var(--violet-11);
|
||||
background-color: white;
|
||||
box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
|
||||
user-select: none;
|
||||
animation-duration: 400ms;
|
||||
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.TooltipContent[data-state='delayed-open'][data-side='top'] {
|
||||
animation-name: slideDownAndFade;
|
||||
}
|
||||
.TooltipContent[data-state='delayed-open'][data-side='right'] {
|
||||
animation-name: slideLeftAndFade;
|
||||
}
|
||||
.TooltipContent[data-state='delayed-open'][data-side='bottom'] {
|
||||
animation-name: slideUpAndFade;
|
||||
}
|
||||
.TooltipContent[data-state='delayed-open'][data-side='left'] {
|
||||
animation-name: slideRightAndFade;
|
||||
}
|
||||
|
||||
.TooltipArrow {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
div[data-radix-popper-content-wrapper] {
|
||||
z-index: 200 !important; /* var(--layer-canvas); */
|
||||
}
|
||||
|
||||
@keyframes slideUpAndFade {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(2px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideRightAndFade {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDownAndFade {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideLeftAndFade {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
|
@ -216,9 +216,9 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
isPrecise: boolean;
|
||||
}>;
|
||||
point: ObjectValidator< {
|
||||
type: "point";
|
||||
x: number;
|
||||
y: number;
|
||||
type: "point";
|
||||
}>;
|
||||
}, never>;
|
||||
end: UnionValidator<"type", {
|
||||
|
@ -230,9 +230,9 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
isPrecise: boolean;
|
||||
}>;
|
||||
point: ObjectValidator< {
|
||||
type: "point";
|
||||
x: number;
|
||||
y: number;
|
||||
type: "point";
|
||||
}>;
|
||||
}, never>;
|
||||
bend: Validator<number>;
|
||||
|
@ -1048,9 +1048,9 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|||
size: EnumStyleProp<"l" | "m" | "s" | "xl">;
|
||||
spline: EnumStyleProp<"cubic" | "line">;
|
||||
points: DictValidator<string, {
|
||||
id: string;
|
||||
x: number;
|
||||
y: number;
|
||||
id: string;
|
||||
index: IndexKey;
|
||||
}>;
|
||||
};
|
||||
|
@ -1112,7 +1112,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
verticalAlign: "end" | "middle" | "start";
|
||||
url: string;
|
||||
text: string;
|
||||
reacji: Record<string, number> | undefined;
|
||||
reacji: Record<string, string[]> | undefined;
|
||||
};
|
||||
type: "note";
|
||||
x: number;
|
||||
|
@ -1137,7 +1137,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
verticalAlign: "end" | "middle" | "start";
|
||||
url: string;
|
||||
text: string;
|
||||
reacji: Record<string, number> | undefined;
|
||||
reacji: Record<string, string[]> | undefined;
|
||||
};
|
||||
type: "note";
|
||||
x: number;
|
||||
|
@ -1154,7 +1154,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
// (undocumented)
|
||||
onEditEnd: TLOnEditEndHandler<TLNoteShape>;
|
||||
// (undocumented)
|
||||
onReacjiSelect: (shape: TLNoteShape, reacji: string, step: number) => void;
|
||||
onReacjiSelect: (shape: TLNoteShape, emoji: string, name: string) => void;
|
||||
// (undocumented)
|
||||
static props: {
|
||||
color: EnumStyleProp<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow">;
|
||||
|
@ -1165,7 +1165,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
growY: Validator<number>;
|
||||
url: Validator<string>;
|
||||
text: Validator<string>;
|
||||
reacji: Validator<Record<string, number> | undefined>;
|
||||
reacji: Validator<Record<string, string[]> | undefined>;
|
||||
};
|
||||
// (undocumented)
|
||||
toSvg(shape: TLNoteShape, ctx: SvgExportContext): SVGGElement;
|
||||
|
|
|
@ -1619,7 +1619,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<{\n x: number;\n y: number;\n type: \"point\";\n }>;\n }, never>;\n end: import(\"@tldraw/editor\")."
|
||||
"text": "<{\n type: \"point\";\n x: number;\n y: number;\n }>;\n }, never>;\n end: import(\"@tldraw/editor\")."
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
|
@ -1664,7 +1664,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<{\n x: number;\n y: number;\n type: \"point\";\n }>;\n }, never>;\n bend: import(\"@tldraw/editor\")."
|
||||
"text": "<{\n type: \"point\";\n x: number;\n y: number;\n }>;\n }, never>;\n bend: import(\"@tldraw/editor\")."
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
|
@ -12361,7 +12361,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<string, {\n id: string;\n x: number;\n y: number;\n index: import(\"@tldraw/editor\")."
|
||||
"text": "<string, {\n x: number;\n y: number;\n id: string;\n index: import(\"@tldraw/editor\")."
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
|
@ -13201,7 +13201,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<string, number> | undefined;\n };\n type: \"note\";\n x: number;\n y: number;\n rotation: number;\n index: import(\"@tldraw/editor\")."
|
||||
"text": "<string, string[]> | undefined;\n };\n type: \"note\";\n x: number;\n y: number;\n rotation: number;\n index: import(\"@tldraw/editor\")."
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
|
@ -13294,7 +13294,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<string, number> | undefined;\n };\n type: \"note\";\n x: number;\n y: number;\n rotation: number;\n index: import(\"@tldraw/editor\")."
|
||||
"text": "<string, string[]> | undefined;\n };\n type: \"note\";\n x: number;\n y: number;\n rotation: number;\n index: import(\"@tldraw/editor\")."
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
|
@ -13413,7 +13413,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ", reacji: string, step: number) => void"
|
||||
"text": ", emoji: string, name: string) => void"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
|
@ -13533,7 +13533,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<string, number> | undefined>;\n }"
|
||||
"text": "<string, string[]> | undefined>;\n }"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"@radix-ui/react-select": "^1.2.0",
|
||||
"@radix-ui/react-slider": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.1.1",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@tldraw/editor": "workspace:*",
|
||||
"canvas-size": "^1.2.6",
|
||||
"classnames": "^2.3.2",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import {
|
||||
DefaultFontFamilies,
|
||||
Editor,
|
||||
|
@ -10,8 +11,9 @@ import {
|
|||
noteShapeMigrations,
|
||||
noteShapeProps,
|
||||
toDomPrecision,
|
||||
uniq,
|
||||
} from '@tldraw/editor'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { FC, PropsWithChildren, ReactNode, useEffect, useRef, useState } from 'react'
|
||||
import { Root, createRoot } from 'react-dom/client'
|
||||
import { HyperlinkButton } from '../shared/HyperlinkButton'
|
||||
import { useDefaultColorTheme } from '../shared/ShapeFill'
|
||||
|
@ -99,7 +101,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
<Reacji
|
||||
editor={this.editor}
|
||||
reacji={reacji}
|
||||
onSelect={(reacji, step) => this.onReacjiSelect(shape, reacji, step)}
|
||||
onSelect={(emoji, name) => this.onReacjiSelect(shape, emoji, name)}
|
||||
/>
|
||||
</div>
|
||||
{'url' in shape.props && shape.props.url && (
|
||||
|
@ -109,16 +111,22 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
)
|
||||
}
|
||||
|
||||
onReacjiSelect = (shape: TLNoteShape, reacji: string, step: number) => {
|
||||
const newReacji = Object.assign({}, shape.props.reacji)
|
||||
newReacji[reacji] = (newReacji[reacji] || 0) + step
|
||||
onReacjiSelect = (shape: TLNoteShape, emoji: string, name: string) => {
|
||||
const reacji = Object.assign({}, shape.props.reacji)
|
||||
const includesPersonAlready = reacji[emoji]?.includes(name)
|
||||
reacji[emoji] = includesPersonAlready
|
||||
? reacji[emoji].filter((n) => n !== name)
|
||||
: uniq([...(reacji[emoji] || []), name])
|
||||
if (!reacji[emoji].length) {
|
||||
delete reacji[emoji]
|
||||
}
|
||||
|
||||
this.editor.updateShapes([
|
||||
{
|
||||
...shape,
|
||||
props: {
|
||||
...shape.props,
|
||||
reacji: Object.fromEntries(Object.entries(newReacji).filter(([_, count]) => count > 0)),
|
||||
reacji,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
@ -211,7 +219,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
}
|
||||
}
|
||||
|
||||
type ReacjiBagType = { [key: string]: number }
|
||||
type ReacjiBagType = { [key: string]: string[] }
|
||||
|
||||
function Reacji({
|
||||
editor,
|
||||
|
@ -220,10 +228,11 @@ function Reacji({
|
|||
}: {
|
||||
editor: Editor
|
||||
reacji: ReacjiBagType | undefined
|
||||
onSelect: (reacji: string, step: number) => void
|
||||
onSelect: (emoji: string, name: string) => void
|
||||
}) {
|
||||
const [renderRoot, setRenderRoot] = useState<Root>()
|
||||
const addButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const name = editor.user?.getName() || 'Me'
|
||||
|
||||
useEffect(() => {
|
||||
const div = document.createElement('div')
|
||||
|
@ -239,12 +248,12 @@ function Reacji({
|
|||
}, [])
|
||||
|
||||
const onEmojiSelect = async (emoji: any) => {
|
||||
onSelect(emoji.native, 1)
|
||||
onSelect(emoji.native, name)
|
||||
closeMenu()
|
||||
}
|
||||
|
||||
const onEmojiRetract = (reacji: string) => {
|
||||
onSelect(reacji, -1)
|
||||
const onEmojiRetract = (emoji: string) => {
|
||||
onSelect(emoji, name)
|
||||
}
|
||||
|
||||
const closeMenu = () => {
|
||||
|
@ -268,18 +277,41 @@ function Reacji({
|
|||
return (
|
||||
<div className="tl-note__reacji">
|
||||
{reacji &&
|
||||
Object.entries(reacji).map(([reacji, count]) => (
|
||||
<button key={reacji} onMouseDown={() => onEmojiRetract(reacji)}>
|
||||
{reacji} <span className="tl-note__reacji-count">{count}</span>
|
||||
</button>
|
||||
Object.entries(reacji).map(([reacji, people]) => (
|
||||
<TooltipWrapper tooltip={people.join(', ')}>
|
||||
<button key={reacji} onMouseDown={() => onEmojiRetract(reacji)}>
|
||||
{reacji} <span className="tl-note__reacji-count">{people.length}</span>
|
||||
</button>
|
||||
</TooltipWrapper>
|
||||
))}
|
||||
<button className="tl-note__reacji-add" ref={addButtonRef} onMouseDown={handleOpen}>
|
||||
+
|
||||
</button>
|
||||
<TooltipWrapper tooltip="Add emoji">
|
||||
<button className="tl-note__reacji-add" ref={addButtonRef} onMouseDown={handleOpen}>
|
||||
+
|
||||
</button>
|
||||
</TooltipWrapper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const TooltipWrapper: FC<PropsWithChildren<{ tooltip: string | ReactNode }>> = ({
|
||||
children,
|
||||
tooltip,
|
||||
}) => {
|
||||
return (
|
||||
<Tooltip.Provider delayDuration={200}>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>{children}</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content className="TooltipContent" sideOffset={5}>
|
||||
{tooltip}
|
||||
<Tooltip.Arrow className="TooltipArrow" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</Tooltip.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
function getGrowY(editor: Editor, shape: TLNoteShape, prevGrowY = 0) {
|
||||
const PADDING = 17
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ export type EmojiDialogProps = {
|
|||
left: number
|
||||
onEmojiSelect: (emoji: any) => void
|
||||
onClickOutside: () => void
|
||||
disableFrequentRows?: boolean
|
||||
}
|
||||
|
||||
export default track(function EmojiDialog({
|
||||
|
@ -18,6 +19,7 @@ export default track(function EmojiDialog({
|
|||
left,
|
||||
onEmojiSelect,
|
||||
onClickOutside,
|
||||
disableFrequentRows,
|
||||
}: EmojiDialogProps) {
|
||||
const isDarkMode = useValue('isDarkMode', () => editor.user.getIsDarkMode(), [editor])
|
||||
const ref = useRef(null)
|
||||
|
@ -33,7 +35,7 @@ export default track(function EmojiDialog({
|
|||
editor.on('event', eventListener)
|
||||
|
||||
instance.current = new Picker({
|
||||
maxFrequentRows: 0,
|
||||
maxFrequentRows: disableFrequentRows ? 0 : undefined,
|
||||
onEmojiSelect,
|
||||
onClickOutside,
|
||||
theme,
|
||||
|
@ -48,7 +50,7 @@ export default track(function EmojiDialog({
|
|||
EmojiDialogSingleton = null
|
||||
editor.off('event', eventListener)
|
||||
}
|
||||
}, [editor, theme, onEmojiSelect, onClickOutside])
|
||||
}, [editor, theme, onEmojiSelect, onClickOutside, disableFrequentRows])
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -47,9 +47,9 @@ export const arrowShapeProps: {
|
|||
isPrecise: boolean;
|
||||
} & {}>;
|
||||
point: T.ObjectValidator<{
|
||||
type: "point";
|
||||
x: number;
|
||||
y: number;
|
||||
type: "point";
|
||||
} & {}>;
|
||||
}, never>;
|
||||
end: T.UnionValidator<"type", {
|
||||
|
@ -61,9 +61,9 @@ export const arrowShapeProps: {
|
|||
isPrecise: boolean;
|
||||
} & {}>;
|
||||
point: T.ObjectValidator<{
|
||||
type: "point";
|
||||
x: number;
|
||||
y: number;
|
||||
type: "point";
|
||||
} & {}>;
|
||||
}, never>;
|
||||
bend: T.Validator<number>;
|
||||
|
@ -684,9 +684,9 @@ export const lineShapeProps: {
|
|||
size: EnumStyleProp<"l" | "m" | "s" | "xl">;
|
||||
spline: EnumStyleProp<"cubic" | "line">;
|
||||
points: T.DictValidator<string, {
|
||||
id: string;
|
||||
x: number;
|
||||
y: number;
|
||||
id: string;
|
||||
index: IndexKey;
|
||||
} & {}>;
|
||||
};
|
||||
|
@ -707,7 +707,7 @@ export const noteShapeProps: {
|
|||
growY: T.Validator<number>;
|
||||
url: T.Validator<string>;
|
||||
text: T.Validator<string>;
|
||||
reacji: T.Validator<Record<string, number> | undefined>;
|
||||
reacji: T.Validator<Record<string, string[]> | undefined>;
|
||||
};
|
||||
|
||||
// @internal (undocumented)
|
||||
|
|
|
@ -364,7 +364,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<{\n x: number;\n y: number;\n type: \"point\";\n } & {}>;\n }, never>;\n end: "
|
||||
"text": "<{\n type: \"point\";\n x: number;\n y: number;\n } & {}>;\n }, never>;\n end: "
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
|
@ -409,7 +409,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<{\n x: number;\n y: number;\n type: \"point\";\n } & {}>;\n }, never>;\n bend: "
|
||||
"text": "<{\n type: \"point\";\n x: number;\n y: number;\n } & {}>;\n }, never>;\n bend: "
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
|
@ -2818,7 +2818,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<string, {\n id: string;\n x: number;\n y: number;\n index: "
|
||||
"text": "<string, {\n x: number;\n y: number;\n id: string;\n index: "
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
|
@ -2972,7 +2972,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<string, number> | undefined>;\n}"
|
||||
"text": "<string, string[]> | undefined>;\n}"
|
||||
}
|
||||
],
|
||||
"fileUrlPath": "packages/tlschema/src/shapes/TLNoteShape.ts",
|
||||
|
|
|
@ -20,7 +20,7 @@ export const noteShapeProps = {
|
|||
growY: T.positiveNumber,
|
||||
url: T.linkUrl,
|
||||
text: T.string,
|
||||
reacji: T.dict(T.string, T.positiveInteger).optional(),
|
||||
reacji: T.dict(T.string, T.arrayOf(T.string)).optional(),
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
|
32
yarn.lock
32
yarn.lock
|
@ -5557,6 +5557,37 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-tooltip@npm:^1.0.7":
|
||||
version: 1.0.7
|
||||
resolution: "@radix-ui/react-tooltip@npm:1.0.7"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
"@radix-ui/primitive": "npm:1.0.1"
|
||||
"@radix-ui/react-compose-refs": "npm:1.0.1"
|
||||
"@radix-ui/react-context": "npm:1.0.1"
|
||||
"@radix-ui/react-dismissable-layer": "npm:1.0.5"
|
||||
"@radix-ui/react-id": "npm:1.0.1"
|
||||
"@radix-ui/react-popper": "npm:1.1.3"
|
||||
"@radix-ui/react-portal": "npm:1.0.4"
|
||||
"@radix-ui/react-presence": "npm:1.0.1"
|
||||
"@radix-ui/react-primitive": "npm:1.0.3"
|
||||
"@radix-ui/react-slot": "npm:1.0.2"
|
||||
"@radix-ui/react-use-controllable-state": "npm:1.0.1"
|
||||
"@radix-ui/react-visually-hidden": "npm:1.0.3"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: 8f075a78db9bfe3dac251266feeb771923176d388c3232f9bad8d85417b5d80d2470697e1c7cae6765d3af16e48552ab9810137c2db193bc37e61b97388e92e8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-use-callback-ref@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@radix-ui/react-use-callback-ref@npm:1.0.1"
|
||||
|
@ -23010,6 +23041,7 @@ __metadata:
|
|||
"@radix-ui/react-select": "npm:^1.2.0"
|
||||
"@radix-ui/react-slider": "npm:^1.1.0"
|
||||
"@radix-ui/react-toast": "npm:^1.1.1"
|
||||
"@radix-ui/react-tooltip": "npm:^1.0.7"
|
||||
"@testing-library/jest-dom": "npm:^5.16.5"
|
||||
"@testing-library/react": "npm:^14.0.0"
|
||||
"@tldraw/editor": "workspace:*"
|
||||
|
|
Ładowanie…
Reference in New Issue