mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-20 16:43:53 +01:00
Better insight into settings updates
This commit is contained in:
@@ -3,6 +3,7 @@ import type { HTMLAttributes, ReactNode } from 'react';
|
||||
import { forwardRef, useImperativeHandle, useRef } from 'react';
|
||||
import type { HotkeyAction } from '../../hooks/useHotKey';
|
||||
import { useFormattedHotkey, useHotKey } from '../../hooks/useHotKey';
|
||||
import { trackEvent } from '../../lib/analytics';
|
||||
import { Icon } from './Icon';
|
||||
|
||||
export type ButtonProps = Omit<HTMLAttributes<HTMLButtonElement>, 'color' | 'onChange'> & {
|
||||
@@ -28,6 +29,7 @@ export type ButtonProps = Omit<HTMLAttributes<HTMLButtonElement>, 'color' | 'onC
|
||||
leftSlot?: ReactNode;
|
||||
rightSlot?: ReactNode;
|
||||
hotkeyAction?: HotkeyAction;
|
||||
event?: string | { id: string; [attr: string]: number | string };
|
||||
};
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
||||
@@ -48,6 +50,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
||||
hotkeyAction,
|
||||
title,
|
||||
onClick,
|
||||
event,
|
||||
...props
|
||||
}: ButtonProps,
|
||||
ref,
|
||||
@@ -107,7 +110,12 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
||||
type={type}
|
||||
className={classes}
|
||||
disabled={disabled || isLoading}
|
||||
onClick={onClick}
|
||||
onClick={(e) => {
|
||||
onClick?.(e);
|
||||
if (event != null) {
|
||||
trackEvent('button', 'click', typeof event === 'string' ? { id: event } : event);
|
||||
}
|
||||
}}
|
||||
onDoubleClick={(e) => {
|
||||
// Kind of a hack? This prevents double-clicks from going through buttons. For example, when
|
||||
// double-clicking the workspace header to toggle window maximization
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import { trackEvent } from '../../lib/analytics';
|
||||
import { Icon } from './Icon';
|
||||
import { HStack } from './Stacks';
|
||||
|
||||
@@ -11,6 +12,7 @@ export interface CheckboxProps {
|
||||
disabled?: boolean;
|
||||
inputWrapperClassName?: string;
|
||||
hideLabel?: boolean;
|
||||
event?: string;
|
||||
}
|
||||
|
||||
export function Checkbox({
|
||||
@@ -21,6 +23,7 @@ export function Checkbox({
|
||||
disabled,
|
||||
title,
|
||||
hideLabel,
|
||||
event,
|
||||
}: CheckboxProps) {
|
||||
return (
|
||||
<HStack
|
||||
@@ -37,7 +40,12 @@ export function Checkbox({
|
||||
)}
|
||||
type="checkbox"
|
||||
disabled={disabled}
|
||||
onChange={() => onChange(checked === 'indeterminate' ? true : !checked)}
|
||||
onChange={() => {
|
||||
onChange(checked === 'indeterminate' ? true : !checked);
|
||||
if (event != null) {
|
||||
trackEvent('button', 'click', { id: event, checked: checked ? 'on' : 'off' });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<Icon
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import classNames from 'classnames';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { trackEvent } from '../../lib/analytics';
|
||||
import { Icon } from './Icon';
|
||||
|
||||
interface Props extends HTMLAttributes<HTMLAnchorElement> {
|
||||
href: string;
|
||||
event?: string;
|
||||
}
|
||||
|
||||
export function Link({ href, children, className, ...other }: Props) {
|
||||
export function Link({ href, children, className, event, ...other }: Props) {
|
||||
const isExternal = href.match(/^https?:\/\//);
|
||||
|
||||
className = classNames(className, 'relative underline hover:text-violet-600');
|
||||
@@ -19,6 +21,12 @@ export function Link({ href, children, className, ...other }: Props) {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={classNames(className, 'pr-4 inline-flex items-center')}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (event != null) {
|
||||
trackEvent('link', 'click', { id: event });
|
||||
}
|
||||
}}
|
||||
{...other}
|
||||
>
|
||||
<span className="underline">{children}</span>
|
||||
|
||||
@@ -2,6 +2,7 @@ import classNames from 'classnames';
|
||||
import type { CSSProperties, ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useOsInfo } from '../../hooks/useOsInfo';
|
||||
import { trackEvent } from '../../lib/analytics';
|
||||
import type { ButtonProps } from './Button';
|
||||
import { Button } from './Button';
|
||||
import type { RadioDropdownItem } from './RadioDropdown';
|
||||
@@ -20,6 +21,7 @@ export interface SelectProps<T extends string> {
|
||||
onChange: (value: T) => void;
|
||||
size?: ButtonProps['size'];
|
||||
className?: string;
|
||||
event?: string;
|
||||
}
|
||||
|
||||
export function Select<T extends string>({
|
||||
@@ -33,6 +35,7 @@ export function Select<T extends string>({
|
||||
leftSlot,
|
||||
onChange,
|
||||
className,
|
||||
event,
|
||||
size = 'md',
|
||||
}: SelectProps<T>) {
|
||||
const osInfo = useOsInfo();
|
||||
@@ -40,6 +43,13 @@ export function Select<T extends string>({
|
||||
const id = `input-${name}`;
|
||||
const isInvalidSelection = options.find((o) => 'value' in o && o.value === value) == null;
|
||||
|
||||
const handleChange = (value: T) => {
|
||||
onChange?.(value);
|
||||
if (event != null) {
|
||||
trackEvent('select', 'click', { id: event, value });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
@@ -79,7 +89,7 @@ export function Select<T extends string>({
|
||||
<select
|
||||
value={value}
|
||||
style={selectBackgroundStyles}
|
||||
onChange={(e) => onChange(e.target.value as T)}
|
||||
onChange={(e) => handleChange(e.target.value as T)}
|
||||
onFocus={() => setFocused(true)}
|
||||
onBlur={() => setFocused(false)}
|
||||
className={classNames('pr-7 w-full outline-none bg-transparent')}
|
||||
@@ -98,7 +108,7 @@ export function Select<T extends string>({
|
||||
) : (
|
||||
// Use custom "select" component until Tauri can be configured to have select menus not always appear in
|
||||
// light mode
|
||||
<RadioDropdown value={value} onChange={onChange} items={options}>
|
||||
<RadioDropdown value={value} onChange={handleChange} items={options}>
|
||||
<Button
|
||||
className="w-full text-sm font-mono"
|
||||
justify="start"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import { memo, useEffect, useRef } from 'react';
|
||||
import { trackEvent } from '../../../lib/analytics';
|
||||
import { Icon } from '../Icon';
|
||||
import type { RadioDropdownProps } from '../RadioDropdown';
|
||||
import { RadioDropdown } from '../RadioDropdown';
|
||||
@@ -95,7 +96,14 @@ export function Tabs({
|
||||
onChange={t.options.onChange}
|
||||
>
|
||||
<button
|
||||
onClick={isActive ? undefined : () => onChangeValue(t.value)}
|
||||
onClick={
|
||||
isActive
|
||||
? undefined
|
||||
: () => {
|
||||
trackEvent('tab', 'click', { label, tab: t.value });
|
||||
onChangeValue(t.value);
|
||||
}
|
||||
}
|
||||
className={btnClassName}
|
||||
>
|
||||
{option && 'shortLabel' in option
|
||||
@@ -113,7 +121,14 @@ export function Tabs({
|
||||
return (
|
||||
<button
|
||||
key={t.value}
|
||||
onClick={() => onChangeValue(t.value)}
|
||||
onClick={
|
||||
isActive
|
||||
? undefined
|
||||
: () => {
|
||||
trackEvent('tab', 'click', { label, tab: t.value });
|
||||
onChangeValue(t.value);
|
||||
}
|
||||
}
|
||||
className={btnClassName}
|
||||
>
|
||||
{t.label}
|
||||
|
||||
Reference in New Issue
Block a user