Skip to content

Commit

Permalink
Migration
Browse files Browse the repository at this point in the history
  • Loading branch information
geoperez committed Feb 13, 2025
1 parent 7420e10 commit cc8a702
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/AwaitableMetric/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Metric } from '@tremor/react';
import type { PropsWithChildren } from 'react';
import { twMerge } from 'tailwind-merge';
import type { ClassNameComponent } from '../constants';
import { Metric } from '../Metric';

export const AwaitableMetric = ({ className, children }: PropsWithChildren<ClassNameComponent>) =>
children == null ? (
Expand Down
164 changes: 164 additions & 0 deletions src/BaseInput/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import React, { type ReactNode, useState, useRef } from 'react';
import { hasValue, getSelectButtonColors } from '../theme';
import { tremorTwMerge } from '../tremorTwMerge';
import { mergeRefs } from '../reactUtils';

const ExclamationFilledIcon = ({ ...props }) => (
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor' {...props}>
<title>Exclamation</title>
<path d='M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM11 15V17H13V15H11ZM11 7V13H13V7H11Z' />
</svg>
);

export interface BaseInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
type?: 'text' | 'email' | 'url' | 'number' | 'search' | 'tel';
defaultValue?: string | number;
value?: string | number;
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
icon?: React.ElementType | React.JSXElementConstructor<any>;
error?: boolean;
errorMessage?: string;
disabled?: boolean;
stepper?: ReactNode;
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
onValueChange?: (value: any) => void;
makeInputClassName: (className: string) => string;
pattern?: string;
}

export const BaseInput = React.forwardRef<HTMLInputElement, BaseInputProps>((props, ref) => {
const {
value,
defaultValue,
type,
placeholder = 'Type...',
icon,
error = false,
errorMessage,
disabled = false,
stepper,
makeInputClassName,
className,
onChange,
onValueChange,
autoFocus,
pattern,
...other
} = props;
const [isFocused, setIsFocused] = useState(autoFocus || false);

const Icon = icon;

const inputRef = useRef<HTMLInputElement>(null);

const hasSelection = hasValue(value || defaultValue);

React.useEffect(() => {
const handleFocus = () => setIsFocused(true);
const handleBlur = () => setIsFocused(false);

const node = inputRef.current;
if (node) {
node.addEventListener('focus', handleFocus);
node.addEventListener('blur', handleBlur);

// Autofocus logic
if (autoFocus) {
node.focus();
}
}

return () => {
if (node) {
node.removeEventListener('focus', handleFocus);
node.removeEventListener('blur', handleBlur);
}
};
}, [autoFocus]);

return (
<>
<div
className={tremorTwMerge(
makeInputClassName('root'),
// common
'relative w-full flex items-center min-w-[10rem] outline-none rounded-tremor-default transition duration-100 border',
// light
'shadow-tremor-input',
// dark
'dark:shadow-dark-tremor-input',
getSelectButtonColors(hasSelection, disabled, error),
isFocused &&
tremorTwMerge(
// common
'ring-2',
// light
'border-tremor-brand-subtle ring-tremor-brand-muted',
// light
'dark:border-dark-tremor-brand-subtle dark:ring-dark-tremor-brand-muted',
),
className,
)}
>
{Icon ? (
<Icon
className={tremorTwMerge(
makeInputClassName('icon'),
// common
'shrink-0 h-5 w-5 mx-2.5 absolute left-0 flex items-center',
// light
'text-tremor-content-subtle',
// light
'dark:text-dark-tremor-content-subtle',
)}
/>
) : null}
<input
ref={mergeRefs([inputRef, ref])}
defaultValue={defaultValue}
value={value}
type={type}
className={tremorTwMerge(
makeInputClassName('input'),
// common
'w-full bg-transparent focus:outline-none focus:ring-0 border-none text-tremor-default rounded-tremor-default transition duration-100 py-2',
// light
'text-tremor-content-emphasis',
// dark
'dark:text-dark-tremor-content-emphasis',
'[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none',
error ? 'pr-8' : 'pr-3',
Icon ? 'pl-10' : 'pl-3',
disabled
? 'placeholder:text-tremor-content-subtle dark:placeholder:text-dark-tremor-content-subtle'
: 'placeholder:text-tremor-content dark:placeholder:text-dark-tremor-content',
)}
placeholder={placeholder}
disabled={disabled}
data-testid='base-input'
onChange={(e) => {
onChange?.(e);
onValueChange?.(e.target.value);
}}
pattern={pattern}
{...other}
/>
{error ? (
<ExclamationFilledIcon
className={tremorTwMerge(
makeInputClassName('errorIcon'),
'text-red-500 shrink-0 h-5 w-5 absolute right-0 flex items-center',
type === 'number' ? (stepper ? 'mr-20' : 'mr-3') : 'mx-2.5',
)}
/>
) : null}
{stepper ?? null}
</div>
{error && errorMessage ? (
<p className={tremorTwMerge(makeInputClassName('errorMessage'), 'text-sm text-red-500 mt-1')}>
{errorMessage}
</p>
) : null}
</>
);
});
2 changes: 1 addition & 1 deletion src/Form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
NumberInput,
Select,
SelectItem,
TextInput,
Textarea,
} from '@tremor/react';
import { useState } from 'react';
Expand All @@ -19,6 +18,7 @@ import { extractData, getFieldBaseProps, onMultiSelectChange, onSelectChange } f
import { Text } from '../TextElements';
import { Flex } from '../Flex';
import { Button } from '../Button';
import { TextInput } from '../TextInput';

export const Form = <T, TData>({
initialData,
Expand Down
28 changes: 28 additions & 0 deletions src/Metric/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from "react";
import type { Color } from "../constants";
import { getColorClassNames, colorPalette } from "../theme";
import { tremorTwMerge } from "../tremorTwMerge";

export interface MetricProps extends React.HTMLAttributes<HTMLParagraphElement> {
color?: Color;
}

export const Metric = React.forwardRef<HTMLParagraphElement, MetricProps>((props, ref) => {
const { color, children, className, ...other } = props;
return (
<p
ref={ref}
className={tremorTwMerge(
"font-semibold text-tremor-metric",
color
? getColorClassNames(color, colorPalette.darkText).textColor
: "text-tremor-content-strong dark:text-dark-tremor-content-strong",
className,
)}
{...other}
>
{children}
</p>
);
});

2 changes: 1 addition & 1 deletion src/SearchBox/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Dismiss24Regular, Search24Regular } from '@fluentui/react-icons';
import { TextInput } from '@tremor/react';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { useDebounce } from '../hooks';
import { TextInput } from '../TextInput';

export type SearchBoxSettings = {
placeholder?: string;
Expand Down
21 changes: 21 additions & 0 deletions src/TextInput/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { type BaseInputProps, BaseInput } from '../BaseInput';
import { makeClassName } from '../theme';

export type TextInputProps = Omit<BaseInputProps, 'stepper' | 'makeInputClassName'> & {
defaultValue?: string;
value?: string;
onValueChange?: (value: string) => void;
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
icon?: React.ElementType | React.JSXElementConstructor<any>;
error?: boolean;
errorMessage?: string;
disabled?: boolean;
};

const makeTextInputClassName = makeClassName('TextInput');

export const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>((props, ref) => {
const { type = 'text', ...other } = props;
return <BaseInput ref={ref} type={type} makeInputClassName={makeTextInputClassName} {...other} />;
});
2 changes: 1 addition & 1 deletion src/TremorContainer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { type PropsWithChildren } from 'react';
import type { PropsWithChildren } from 'react';
import tw from 'tailwind-styled-components';

const MainApp = tw.main<{ $hasToolbar?: boolean }>`
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export * from './Grid';
export * from './HorizontalSelector';
export * from './InfoDialog';
export * from './Menu';
export * from './Metric';
export * from './DialogHeader';
export * from './NavBar';
export * from './NoData';
Expand All @@ -33,6 +34,7 @@ export * from './ReadOnlyForm';
export * from './SearchBox';
export * from './Table';
export * from './TableCell';
export * from './TextInput';
export * from './TremorContainer';
export * from './UnoLogo';
export * from './Tooltip';
Expand Down
15 changes: 15 additions & 0 deletions src/reactUtils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
export function mergeRefs<T = any>(
refs: Array<React.MutableRefObject<T> | React.LegacyRef<T>>,
): React.RefCallback<T> {
return (value) => {
// biome-ignore lint/complexity/noForEach: <explanation>
refs.forEach((ref) => {
if (typeof ref === "function") {
ref(value);
} else if (ref != null) {
(ref as React.MutableRefObject<T | null>).current = value;
}
});
};
}

0 comments on commit cc8a702

Please # to comment.