forked from cerc-io/snowballtools-base
input forward ref react-hook-form
This commit is contained in:
parent
abbda18fc7
commit
6e7385b118
@ -1,11 +1,18 @@
|
|||||||
import { ReactNode, useMemo } from 'react';
|
import {
|
||||||
import { ComponentPropsWithoutRef } from 'react';
|
forwardRef,
|
||||||
import { InputTheme, inputTheme } from './Input.theme';
|
ReactNode,
|
||||||
|
useMemo,
|
||||||
|
ComponentPropsWithoutRef,
|
||||||
|
} from 'react';
|
||||||
|
import { FieldValues, UseFormRegister } from 'react-hook-form';
|
||||||
|
|
||||||
import { WarningIcon } from 'components/shared/CustomIcon';
|
import { WarningIcon } from 'components/shared/CustomIcon';
|
||||||
import { cloneIcon } from 'utils/cloneIcon';
|
import { cloneIcon } from 'utils/cloneIcon';
|
||||||
import { cn } from 'utils/classnames';
|
import { cn } from 'utils/classnames';
|
||||||
|
|
||||||
export interface InputProps
|
import { InputTheme, inputTheme } from './Input.theme';
|
||||||
|
|
||||||
|
export interface InputProps<T extends FieldValues = FieldValues>
|
||||||
extends InputTheme,
|
extends InputTheme,
|
||||||
Omit<ComponentPropsWithoutRef<'input'>, 'size'> {
|
Omit<ComponentPropsWithoutRef<'input'>, 'size'> {
|
||||||
label?: string;
|
label?: string;
|
||||||
@ -13,93 +20,108 @@ export interface InputProps
|
|||||||
leftIcon?: ReactNode;
|
leftIcon?: ReactNode;
|
||||||
rightIcon?: ReactNode;
|
rightIcon?: ReactNode;
|
||||||
helperText?: string;
|
helperText?: string;
|
||||||
|
|
||||||
|
// react-hook-form optional register
|
||||||
|
register?: ReturnType<UseFormRegister<T>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Input = ({
|
const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||||
className,
|
(
|
||||||
label,
|
{
|
||||||
description,
|
className,
|
||||||
leftIcon,
|
label,
|
||||||
rightIcon,
|
description,
|
||||||
helperText,
|
leftIcon,
|
||||||
size,
|
rightIcon,
|
||||||
state,
|
helperText,
|
||||||
appearance,
|
register,
|
||||||
...props
|
size,
|
||||||
}: InputProps) => {
|
state,
|
||||||
const styleProps = useMemo(
|
appearance,
|
||||||
() => ({
|
...props
|
||||||
size: size || 'md',
|
},
|
||||||
state: state || 'default',
|
ref,
|
||||||
appearance, // Pass appearance to inputTheme
|
) => {
|
||||||
}),
|
const styleProps = useMemo(
|
||||||
[size, state, appearance],
|
() => ({
|
||||||
);
|
size: size || 'md',
|
||||||
|
state: state || 'default',
|
||||||
|
appearance, // Pass appearance to inputTheme
|
||||||
|
}),
|
||||||
|
[size, state, appearance],
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
container: containerCls,
|
container: containerCls,
|
||||||
label: labelCls,
|
label: labelCls,
|
||||||
description: descriptionCls,
|
description: descriptionCls,
|
||||||
input: inputCls,
|
input: inputCls,
|
||||||
icon: iconCls,
|
icon: iconCls,
|
||||||
iconContainer: iconContainerCls,
|
iconContainer: iconContainerCls,
|
||||||
helperText: helperTextCls,
|
helperText: helperTextCls,
|
||||||
helperIcon: helperIconCls,
|
helperIcon: helperIconCls,
|
||||||
} = inputTheme({ ...styleProps });
|
} = inputTheme({ ...styleProps });
|
||||||
|
|
||||||
|
const renderLabels = useMemo(() => {
|
||||||
|
if (!label && !description) return null;
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-y-1">
|
||||||
|
<p className={labelCls()}>{label}</p>
|
||||||
|
<p className={descriptionCls()}>{description}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [labelCls, descriptionCls, label, description]);
|
||||||
|
|
||||||
|
const renderLeftIcon = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<div className={iconContainerCls({ class: 'left-0 pl-4' })}>
|
||||||
|
{cloneIcon(leftIcon, { className: iconCls(), 'aria-hidden': true })}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [cloneIcon, iconCls, iconContainerCls, leftIcon]);
|
||||||
|
|
||||||
|
const renderRightIcon = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<div className={iconContainerCls({ class: 'pr-4 right-0' })}>
|
||||||
|
{cloneIcon(rightIcon, { className: iconCls(), 'aria-hidden': true })}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [cloneIcon, iconCls, iconContainerCls, rightIcon]);
|
||||||
|
|
||||||
|
const renderHelperText = useMemo(() => {
|
||||||
|
if (!helperText) return null;
|
||||||
|
return (
|
||||||
|
<div className={helperTextCls()}>
|
||||||
|
{state &&
|
||||||
|
cloneIcon(<WarningIcon className={helperIconCls()} />, {
|
||||||
|
'aria-hidden': true,
|
||||||
|
})}
|
||||||
|
<p>{helperText}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [cloneIcon, state, helperIconCls, helperText, helperTextCls]);
|
||||||
|
|
||||||
const renderLabels = useMemo(() => {
|
|
||||||
if (!label && !description) return null;
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-y-1">
|
<div className="flex flex-col gap-y-2 w-full">
|
||||||
<p className={labelCls()}>{label}</p>
|
{renderLabels}
|
||||||
<p className={descriptionCls()}>{description}</p>
|
<div className={containerCls({ class: className })}>
|
||||||
|
{leftIcon && renderLeftIcon}
|
||||||
|
<input
|
||||||
|
{...(register ? register : {})}
|
||||||
|
className={cn(inputCls(), {
|
||||||
|
'pl-10': leftIcon,
|
||||||
|
})}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
{rightIcon && renderRightIcon}
|
||||||
|
</div>
|
||||||
|
{renderHelperText}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, [labelCls, descriptionCls, label, description]);
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const renderLeftIcon = useMemo(() => {
|
Input.displayName = 'Input';
|
||||||
return (
|
|
||||||
<div className={iconContainerCls({ class: 'left-0 pl-4' })}>
|
|
||||||
{cloneIcon(leftIcon, { className: iconCls(), 'aria-hidden': true })}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}, [cloneIcon, iconCls, iconContainerCls, leftIcon]);
|
|
||||||
|
|
||||||
const renderRightIcon = useMemo(() => {
|
export { Input };
|
||||||
return (
|
|
||||||
<div className={iconContainerCls({ class: 'pr-4 right-0' })}>
|
|
||||||
{cloneIcon(rightIcon, { className: iconCls(), 'aria-hidden': true })}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}, [cloneIcon, iconCls, iconContainerCls, rightIcon]);
|
|
||||||
|
|
||||||
const renderHelperText = useMemo(() => {
|
|
||||||
if (!helperText) return null;
|
|
||||||
return (
|
|
||||||
<div className={helperTextCls()}>
|
|
||||||
{state &&
|
|
||||||
cloneIcon(<WarningIcon className={helperIconCls()} />, {
|
|
||||||
'aria-hidden': true,
|
|
||||||
})}
|
|
||||||
<p>{helperText}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}, [cloneIcon, state, helperIconCls, helperText, helperTextCls]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-y-2 w-full">
|
|
||||||
{renderLabels}
|
|
||||||
<div className={containerCls({ class: className })}>
|
|
||||||
{leftIcon && renderLeftIcon}
|
|
||||||
<input
|
|
||||||
className={cn(inputCls(), {
|
|
||||||
'pl-10': leftIcon,
|
|
||||||
})}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
{rightIcon && renderRightIcon}
|
|
||||||
</div>
|
|
||||||
{renderHelperText}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
51
packages/frontend/src/components/shared/Table/Table.tsx
Normal file
51
packages/frontend/src/components/shared/Table/Table.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Header: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
|
<thead className="text-left">{children}</thead>
|
||||||
|
);
|
||||||
|
const Body: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
|
<tbody className="text-left">{children}</tbody>
|
||||||
|
);
|
||||||
|
const Row: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
|
<tr className="text-left">{children}</tr>
|
||||||
|
);
|
||||||
|
const ColumnHeaderCell: React.FC<{ children: React.ReactNode }> = ({
|
||||||
|
children,
|
||||||
|
}) => (
|
||||||
|
<th className="px-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
{children}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
const RowHeaderCell: React.FC<{ children: React.ReactNode }> = ({
|
||||||
|
children,
|
||||||
|
}) => (
|
||||||
|
<th
|
||||||
|
scope="row"
|
||||||
|
className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
const Cell: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
{children}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Table: React.FC<{ children: React.ReactNode }> & {
|
||||||
|
Header: typeof Header;
|
||||||
|
Body: typeof Body;
|
||||||
|
Row: typeof Row;
|
||||||
|
ColumnHeaderCell: typeof ColumnHeaderCell;
|
||||||
|
RowHeaderCell: typeof RowHeaderCell;
|
||||||
|
Cell: typeof Cell;
|
||||||
|
} = ({ children }) => <table className="min-w-full">{children}</table>;
|
||||||
|
|
||||||
|
Table.Header = Header;
|
||||||
|
Table.Body = Body;
|
||||||
|
Table.Row = Row;
|
||||||
|
Table.ColumnHeaderCell = ColumnHeaderCell;
|
||||||
|
Table.RowHeaderCell = RowHeaderCell;
|
||||||
|
Table.Cell = Cell;
|
||||||
|
|
||||||
|
export { Table };
|
1
packages/frontend/src/components/shared/Table/index.ts
Normal file
1
packages/frontend/src/components/shared/Table/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Table';
|
@ -1,12 +1,13 @@
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Typography,
|
|
||||||
Alert,
|
Alert,
|
||||||
Button,
|
Button,
|
||||||
} from '@snowballtools/material-tailwind-react-fork';
|
} from '@snowballtools/material-tailwind-react-fork';
|
||||||
|
|
||||||
import { useGQLClient } from '../../../../../../../context/GQLClientContext';
|
import { useGQLClient } from '../../../../../../../context/GQLClientContext';
|
||||||
|
import { Heading } from 'components/shared/Heading';
|
||||||
|
import { Table } from 'components/shared/Table';
|
||||||
|
|
||||||
const Config = () => {
|
const Config = () => {
|
||||||
const { id, orgSlug } = useParams();
|
const { id, orgSlug } = useParams();
|
||||||
@ -38,37 +39,44 @@ const Config = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Figure out DNS Provider if possible and update appropriatly
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6 w-full">
|
<div className="flex flex-col gap-6 w-full">
|
||||||
<div>
|
<div>
|
||||||
<Typography variant="h5">Configure DNS</Typography>
|
<Heading className="text-sky-950 text-lg font-medium leading-normal">
|
||||||
<Typography variant="small">
|
Setup domain name
|
||||||
|
</Heading>
|
||||||
|
<p className="text-blue-gray-500">
|
||||||
Add the following records to your domain.
|
Add the following records to your domain.
|
||||||
<a href="https://www.namecheap.com/" target="_blank" rel="noreferrer">
|
<a href="https://www.namecheap.com/" target="_blank" rel="noreferrer">
|
||||||
<span className="underline">Go to NameCheap</span> ^
|
<span className="underline">Go to NameCheap</span>
|
||||||
</a>
|
</a>
|
||||||
</Typography>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table className="rounded-lg w-3/4 text-blue-gray-600">
|
<Table>
|
||||||
<tbody>
|
<Table.Header>
|
||||||
<tr className="border-b-2 border-gray-300">
|
<Table.Row>
|
||||||
<th className="text-left p-2">Type</th>
|
<Table.ColumnHeaderCell>Type</Table.ColumnHeaderCell>
|
||||||
<th className="text-left p-2">Name</th>
|
<Table.ColumnHeaderCell>Host</Table.ColumnHeaderCell>
|
||||||
<th className="text-left p-2">Value</th>
|
<Table.ColumnHeaderCell>Value</Table.ColumnHeaderCell>
|
||||||
</tr>
|
</Table.Row>
|
||||||
<tr className="border-b-2 border-gray-300">
|
</Table.Header>
|
||||||
<td className="text-left p-2">A</td>
|
|
||||||
<td className="text-left p-2">@</td>
|
<Table.Body>
|
||||||
<td className="text-left p-2">56.49.19.21</td>
|
<Table.Row>
|
||||||
</tr>
|
<Table.RowHeaderCell>A</Table.RowHeaderCell>
|
||||||
<tr>
|
<Table.Cell>@</Table.Cell>
|
||||||
<td className="text-left p-2">CNAME</td>
|
<Table.Cell>56.49.19.21</Table.Cell>
|
||||||
<td className="text-left p-2">www</td>
|
</Table.Row>
|
||||||
<td className="text-left p-2">cname.snowballtools.xyz</td>
|
|
||||||
</tr>
|
<Table.Row>
|
||||||
</tbody>
|
<Table.RowHeaderCell>CNAME</Table.RowHeaderCell>
|
||||||
</table>
|
<Table.Cell>www</Table.Cell>
|
||||||
|
<Table.Cell>cname.snowballtools.xyz</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Body>
|
||||||
|
</Table>
|
||||||
|
|
||||||
<Alert color="blue">
|
<Alert color="blue">
|
||||||
<i>^</i>It can take up to 48 hours for these updates to reflect
|
<i>^</i>It can take up to 48 hours for these updates to reflect
|
||||||
|
48
packages/frontend/src/stories/Components/Table.stories.tsx
Normal file
48
packages/frontend/src/stories/Components/Table.stories.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { StoryObj, Meta } from '@storybook/react';
|
||||||
|
|
||||||
|
import { Table } from 'components/shared/Table';
|
||||||
|
|
||||||
|
const meta: Meta<typeof Table> = {
|
||||||
|
title: 'Components/Table',
|
||||||
|
component: Table,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof Table>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: ({}) => (
|
||||||
|
<Table>
|
||||||
|
<Table.Header>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.ColumnHeaderCell>Full name</Table.ColumnHeaderCell>
|
||||||
|
<Table.ColumnHeaderCell>Email</Table.ColumnHeaderCell>
|
||||||
|
<Table.ColumnHeaderCell>Group</Table.ColumnHeaderCell>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Header>
|
||||||
|
|
||||||
|
<Table.Body>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.RowHeaderCell>Danilo Sousa</Table.RowHeaderCell>
|
||||||
|
<Table.Cell>danilo@example.com</Table.Cell>
|
||||||
|
<Table.Cell>Developer</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
|
||||||
|
<Table.Row>
|
||||||
|
<Table.RowHeaderCell>Zahra Ambessa</Table.RowHeaderCell>
|
||||||
|
<Table.Cell>zahra@example.com</Table.Cell>
|
||||||
|
<Table.Cell>Admin</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
|
||||||
|
<Table.Row>
|
||||||
|
<Table.RowHeaderCell>Jasper Eriksson</Table.RowHeaderCell>
|
||||||
|
<Table.Cell>jasper@example.com</Table.Cell>
|
||||||
|
<Table.Cell>Developer</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Body>
|
||||||
|
</Table>
|
||||||
|
),
|
||||||
|
args: {},
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user