-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
27 changed files
with
509 additions
and
424 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,15 @@ | ||
## 1.11.0 | ||
`feature`: `FieldVisibility` now accepts the `children` prop to allow a custom dropdown button. | ||
`feature`: `EnumInput` can now be passed options of of type `SelectOption | string` for more customizability. | ||
`feature`: Enhanced the way to customize actions for `ModelForm` and `ModelTable`. Can alter the underlying button by the `actionOptions.actionProps` prop or by expanding the model component down to its primitive components: `EditAction, CancelEditAction, SubmitAction, DeleteAction`. | ||
|
||
## 1.10.2 | ||
`bugfix`: Fixed delay on form values populating `FormDisplay`. | ||
`upgrade`: [email protected] | ||
|
||
## 1.10.1 | ||
`bugfix`: z-index of select components were using `react-select`'s default z-index and not the override version. | ||
|
||
|
||
## 1.10.0 | ||
- `feature`: Can now set which rows are "selected" to trigger its selected-row background color. | ||
- `bugfix`: Table resizing now does not allow you to go less than the content width for a brief moment | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { | ||
type ComponentProps, | ||
type ReactNode, | ||
createContext, | ||
useEffect, | ||
useRef, | ||
useState, | ||
} from 'react'; | ||
|
||
import { type StoreApi, createStore } from 'zustand'; | ||
|
||
import type { Button } from '@/lib/components/ui/button'; | ||
import type { DataType } from '@/types'; | ||
|
||
export enum Action { | ||
SUBMIT = 'SUBMIT', | ||
DELETE = 'DELETE', | ||
EDIT = 'EDIT', | ||
CANCEL_EDIT = 'CANCEL_EDIT', | ||
} | ||
|
||
export type OnActionTrigger<TParams, TReturn> = | ||
| ((params: TParams) => Promise<TReturn>) | ||
| ((params: TParams) => void); | ||
|
||
export interface ActionParams<D extends DataType> { | ||
data: D; | ||
changedData: D; | ||
onEdit: () => void; | ||
onCancelEdit: () => void; | ||
} | ||
|
||
export type ActionsType<D extends DataType> = Partial< | ||
Record<Action, OnActionTrigger<ActionParams<D>, void> | null> | ||
>; | ||
|
||
export type ActionsPropsType = Partial< | ||
Record<Action, ComponentProps<typeof Button>> | ||
>; | ||
|
||
export interface ActionState<D extends DataType> { | ||
showActions?: boolean; | ||
actions?: ActionsType<D>; | ||
actionProps?: ActionsPropsType; | ||
} | ||
|
||
export const ActionStoreContext = createContext< | ||
StoreApi<ActionState<any>> | undefined | ||
>(undefined); | ||
|
||
export type ActionStoreProviderProps<D extends DataType> = ActionState<D> & { | ||
children?: ReactNode; | ||
}; | ||
|
||
export const ActionStoreProvider = <D extends DataType>({ | ||
children, | ||
...actionState | ||
}: ActionStoreProviderProps<D>) => { | ||
const isMounted = useRef(false); | ||
const [store] = useState(() => createStore(() => actionState)); | ||
/* | ||
biome-ignore lint/correctness/useExhaustiveDependencies: | ||
The reference to tableState does not matter, only the contents. | ||
*/ | ||
useEffect(() => { | ||
if (isMounted.current) store.setState(actionState); | ||
else isMounted.current = true; | ||
}, [...Object.values(actionState), store]); | ||
|
||
return ( | ||
<ActionStoreContext.Provider value={store}> | ||
{children} | ||
</ActionStoreContext.Provider> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import type { ComponentProps } from 'react'; | ||
|
||
import { X } from 'lucide-react'; | ||
|
||
import { Button } from '@/lib/components/ui/button'; | ||
|
||
import { Action } from './ActionContext'; | ||
import { useActionStore } from './useActionStore'; | ||
import { useGetActionParams } from './useGetActionParams'; | ||
|
||
export interface CancelEditActionProps extends ComponentProps<typeof Button> {} | ||
|
||
export const CancelEditAction = ({ | ||
size, | ||
variant = size === 'icon' ? 'ghost' : 'outline', | ||
children = size === 'icon' ? <X className="h-4 w-4" /> : 'Cancel', | ||
...buttonProps | ||
}: CancelEditActionProps) => { | ||
const onCancelEditProp = useActionStore( | ||
(state) => state.actions?.[Action.CANCEL_EDIT], | ||
); | ||
const getActionParams = useGetActionParams(); | ||
const { onCancelEdit } = getActionParams({}); | ||
const onCancelEditHandler = | ||
onCancelEditProp === undefined ? onCancelEdit : onCancelEditProp; | ||
|
||
return ( | ||
onCancelEditHandler && ( | ||
<Button | ||
variant={variant} | ||
size={size} | ||
onClick={onCancelEdit} | ||
onKeyUp={(e) => e.key === 'Enter' && onCancelEdit()} | ||
{...buttonProps} | ||
> | ||
{children} | ||
</Button> | ||
) | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import type { ComponentProps } from 'react'; | ||
|
||
import { Trash2 } from 'lucide-react'; | ||
|
||
import { useFormStore } from '@/Form'; | ||
import { Button } from '@/lib/components/ui/button'; | ||
|
||
import { Action } from './ActionContext'; | ||
import { useActionStore } from './useActionStore'; | ||
import { useGetActionParams } from './useGetActionParams'; | ||
|
||
export interface DeleteActionProps extends ComponentProps<typeof Button> {} | ||
|
||
export const DeleteAction = ({ | ||
size, | ||
variant = size === 'icon' ? 'ghost-destructive' : 'destructive', | ||
children = size === 'icon' ? <Trash2 className="h-4 w-4" /> : 'Delete', | ||
...buttonProps | ||
}: DeleteActionProps) => { | ||
const getActionParams = useGetActionParams(); | ||
const handleSubmit = useFormStore((state) => state.handleSubmit); | ||
const onDelete = useActionStore((state) => state.actions?.[Action.DELETE]); | ||
|
||
const onDeleteHandler = handleSubmit(async () => { | ||
await onDelete?.(getActionParams({})); | ||
}); | ||
|
||
return ( | ||
onDelete && ( | ||
<Button | ||
variant={variant} | ||
size={size} | ||
onClick={onDeleteHandler} | ||
onKeyUp={(e) => e.key === 'Enter' && onDeleteHandler()} | ||
{...buttonProps} | ||
> | ||
{children} | ||
</Button> | ||
) | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import type { ComponentProps } from 'react'; | ||
|
||
import { SquarePen } from 'lucide-react'; | ||
|
||
import { Button } from '@/lib/components/ui/button'; | ||
|
||
import { Action } from './ActionContext'; | ||
import { useActionStore } from './useActionStore'; | ||
import { useGetActionParams } from './useGetActionParams'; | ||
|
||
export interface EditActionProps extends ComponentProps<typeof Button> {} | ||
|
||
export const EditAction = ({ | ||
size, | ||
variant = size === 'icon' ? 'ghost' : 'default', | ||
children = size === 'icon' ? <SquarePen className="h-4 w-4" /> : 'Edit', | ||
...buttonProps | ||
}: EditActionProps) => { | ||
const onEditProp = useActionStore((state) => state.actions?.[Action.EDIT]); | ||
const getActionParams = useGetActionParams(); | ||
const { onEdit } = getActionParams({}); | ||
const onEditHandler = onEditProp === undefined ? onEdit : onEditProp; | ||
|
||
return ( | ||
onEditHandler && ( | ||
<Button | ||
variant={variant} | ||
size={size} | ||
onClick={onEdit} | ||
onKeyUp={(e) => e.key === 'Enter' && onEdit()} | ||
{...buttonProps} | ||
> | ||
{children} | ||
</Button> | ||
) | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import type { ComponentProps } from 'react'; | ||
|
||
import { Save } from 'lucide-react'; | ||
|
||
import { useFormStore } from '@/Form'; | ||
import { Button } from '@/lib/components/ui/button'; | ||
import type { DataType } from '@/types'; | ||
|
||
import { Action } from './ActionContext'; | ||
import { useActionStore } from './useActionStore'; | ||
import { useGetActionParams } from './useGetActionParams'; | ||
|
||
export interface SubmitActionProps extends ComponentProps<typeof Button> {} | ||
|
||
export const SubmitAction = ({ | ||
size, | ||
variant = size === 'icon' ? 'ghost-success' : 'default', | ||
children = size === 'icon' ? <Save className="h-4 w-4" /> : 'Save', | ||
...buttonProps | ||
}: SubmitActionProps) => { | ||
const getActionParams = useGetActionParams(); | ||
const handleSubmit = useFormStore((state) => state.handleSubmit); | ||
const onSubmit = useActionStore((state) => state.actions?.[Action.SUBMIT]); | ||
const updateProps = useActionStore( | ||
(state) => state.actionProps?.[Action.SUBMIT], | ||
); | ||
|
||
const onSubmitHandler = handleSubmit(async (formData: DataType) => { | ||
await onSubmit?.(getActionParams(formData)); | ||
}); | ||
|
||
return ( | ||
onSubmit && ( | ||
<Button | ||
variant={variant} | ||
size={size} | ||
onClick={onSubmitHandler} | ||
onKeyUp={(e) => e.key === 'Enter' && onSubmitHandler()} | ||
{...updateProps} | ||
{...buttonProps} | ||
> | ||
{updateProps?.children ?? children} | ||
</Button> | ||
) | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { useContext } from 'react'; | ||
|
||
import { useStore } from 'zustand'; | ||
|
||
import type { DataType, StoreSelector } from '@/types'; | ||
|
||
import { type ActionState, ActionStoreContext } from './ActionContext'; | ||
|
||
export function useActionStore<D extends DataType>(): ActionState<D>; | ||
export function useActionStore<D extends DataType, T>( | ||
selector: StoreSelector<ActionState<D>, T>, | ||
): T; | ||
|
||
export function useActionStore<D extends DataType, T>( | ||
selector?: StoreSelector<ActionState<D>, T>, | ||
) { | ||
const actionStore = useContext(ActionStoreContext); | ||
if (actionStore === undefined) { | ||
throw new Error('useActionStore must be used within ActionStoreProvider'); | ||
} | ||
|
||
const selected = selector | ||
? useStore(actionStore, selector) | ||
: useStore(actionStore); | ||
|
||
return selected; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { useFormStore } from '@/Form'; | ||
import { useLensesStore } from '@/Lenses'; | ||
import { DataLens, type DataType } from '@/types'; | ||
|
||
import type { ActionParams } from './ActionContext'; | ||
|
||
export const useGetActionParams = <D extends DataType>(): (( | ||
formValues: D, | ||
) => ActionParams<D>) => { | ||
const setLens = useLensesStore((state) => state.setLens); | ||
|
||
const defaultValues = useFormStore( | ||
(state) => state.formState.defaultValues as D, | ||
); | ||
const dirtyFields = useFormStore((state) => state.formState.dirtyFields); | ||
const reset = useFormStore((state) => state.reset); | ||
|
||
const getChangedData = (formValues: D) => | ||
Object.fromEntries( | ||
Object.entries(formValues).filter((entry) => dirtyFields[entry[0]]), | ||
) as D; | ||
const onEdit = () => setLens(DataLens.INPUT); | ||
const onCancelEdit = () => { | ||
setLens(DataLens.DISPLAY); | ||
reset(); | ||
}; | ||
|
||
return (formValues) => ({ | ||
data: { ...defaultValues }, | ||
changedData: getChangedData(formValues), | ||
onEdit, | ||
onCancelEdit, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.