Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
17 kB ยท 582 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583"use client";
import { type Announcements, DndContext, type DndContextProps, type DragEndEvent, DragOverlay, type DraggableSyntheticListeners, type DropAnimation, KeyboardSensor, MouseSensor, type ScreenReaderInstructions, TouchSensor, type UniqueIdentifier, closestCenter, closestCorners, defaultDropAnimationSideEffects, useSensor, useSensors,} from "@dnd-kit/core";import { restrictToHorizontalAxis, restrictToParentElement, restrictToVerticalAxis,} from "@dnd-kit/modifiers";import { SortableContext, type SortableContextProps, arrayMove, horizontalListSortingStrategy, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy,} from "@dnd-kit/sortable";import { CSS } from "@dnd-kit/utilities";import { Slot } from "@radix-ui/react-slot";import * as React from "react";import * as ReactDOM from "react-dom";
import { composeEventHandlers, useComposedRefs } from "@/lib/composition";import { cn } from "@/lib/utils";
const orientationConfig = { vertical: { modifiers: [restrictToVerticalAxis, restrictToParentElement], strategy: verticalListSortingStrategy, collisionDetection: closestCenter, }, horizontal: { modifiers: [restrictToHorizontalAxis, restrictToParentElement], strategy: horizontalListSortingStrategy, collisionDetection: closestCenter, }, mixed: { modifiers: [restrictToParentElement], strategy: undefined, collisionDetection: closestCorners, },};
const ROOT_NAME = "Sortable";const CONTENT_NAME = "SortableContent";const ITEM_NAME = "SortableItem";const ITEM_HANDLE_NAME = "SortableItemHandle";const OVERLAY_NAME = "SortableOverlay";
const SORTABLE_ERRORS = { [ROOT_NAME]: `\`${ROOT_NAME}\` components must be within \`${ROOT_NAME}\``, [CONTENT_NAME]: `\`${CONTENT_NAME}\` must be within \`${ROOT_NAME}\``, [ITEM_NAME]: `\`${ITEM_NAME}\` must be within \`${CONTENT_NAME}\``, [ITEM_HANDLE_NAME]: `\`${ITEM_HANDLE_NAME}\` must be within \`${ITEM_NAME}\``, [OVERLAY_NAME]: `\`${OVERLAY_NAME}\` must be within \`${ROOT_NAME}\``,} as const;
interface SortableRootContextValue<T> { id: string; items: UniqueIdentifier[]; modifiers: DndContextProps["modifiers"]; strategy: SortableContextProps["strategy"]; activeId: UniqueIdentifier | null; setActiveId: (id: UniqueIdentifier | null) => void; getItemValue: (item: T) => UniqueIdentifier; flatCursor: boolean;}
const SortableRootContext = React.createContext<SortableRootContextValue<unknown> | null>(null);SortableRootContext.displayName = ROOT_NAME;
function useSortableContext(name: keyof typeof SORTABLE_ERRORS) { const context = React.useContext(SortableRootContext); if (!context) { throw new Error(SORTABLE_ERRORS[name]); } return context;}
interface GetItemValue<T> { /** * Callback that returns a unique identifier for each sortable item. Required for array of objects. * @example getItemValue={(item) => item.id} */ getItemValue: (item: T) => UniqueIdentifier;}
type SortableProps<T> = DndContextProps & { value: T[]; onValueChange?: (items: T[]) => void; onMove?: ( event: DragEndEvent & { activeIndex: number; overIndex: number }, ) => void; strategy?: SortableContextProps["strategy"]; orientation?: "vertical" | "horizontal" | "mixed"; flatCursor?: boolean;} & (T extends object ? GetItemValue<T> : Partial<GetItemValue<T>>);
function Sortable<T>(props: SortableProps<T>) { const { value, onValueChange, collisionDetection, modifiers, strategy, onMove, orientation = "vertical", flatCursor = false, getItemValue: getItemValueProp, accessibility, ...sortableProps } = props; const id = React.useId(); const [activeId, setActiveId] = React.useState<UniqueIdentifier | null>(null);
const sensors = useSensors( useSensor(MouseSensor), useSensor(TouchSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }), ); const config = React.useMemo( () => orientationConfig[orientation], [orientation], );
const getItemValue = React.useCallback( (item: T): UniqueIdentifier => { if (typeof item === "object" && !getItemValueProp) { throw new Error( "getItemValue is required when using array of objects.", ); } return getItemValueProp ? getItemValueProp(item) : (item as UniqueIdentifier); }, [getItemValueProp], );
const items = React.useMemo(() => { return value.map((item) => getItemValue(item)); }, [value, getItemValue]);
const onDragEnd = React.useCallback( (event: DragEndEvent) => { const { active, over } = event; if (over && active.id !== over?.id) { const activeIndex = value.findIndex( (item) => getItemValue(item) === active.id, ); const overIndex = value.findIndex( (item) => getItemValue(item) === over.id, );
if (onMove) { onMove({ ...event, activeIndex, overIndex }); } else { onValueChange?.(arrayMove(value, activeIndex, overIndex)); } } setActiveId(null); }, [value, onValueChange, onMove, getItemValue], );
const announcements: Announcements = React.useMemo( () => ({ onDragStart({ active }) { const activeValue = active.id.toString(); return `Grabbed sortable item "${activeValue}". Current position is ${ active.data.current?.sortable.index + 1 } of ${value.length}. Use arrow keys to move, space to drop.`; }, onDragOver({ active, over }) { if (over) { const overIndex = over.data.current?.sortable.index ?? 0; const activeIndex = active.data.current?.sortable.index ?? 0; const moveDirection = overIndex > activeIndex ? "down" : "up"; const activeValue = active.id.toString(); return `Sortable item "${activeValue}" moved ${moveDirection} to position ${ overIndex + 1 } of ${value.length}.`; } return "Sortable item is no longer over a droppable area. Press escape to cancel."; }, onDragEnd({ active, over }) { const activeValue = active.id.toString(); if (over) { const overIndex = over.data.current?.sortable.index ?? 0; return `Sortable item "${activeValue}" dropped at position ${ overIndex + 1 } of ${value.length}.`; } return `Sortable item "${activeValue}" dropped. No changes were made.`; }, onDragCancel({ active }) { const activeIndex = active.data.current?.sortable.index ?? 0; const activeValue = active.id.toString(); return `Sorting cancelled. Sortable item "${activeValue}" returned to position ${ activeIndex + 1 } of ${value.length}.`; }, onDragMove({ active, over }) { if (over) { const overIndex = over.data.current?.sortable.index ?? 0; const activeIndex = active.data.current?.sortable.index ?? 0; const moveDirection = overIndex > activeIndex ? "down" : "up"; const activeValue = active.id.toString(); return `Sortable item "${activeValue}" is moving ${moveDirection} to position ${ overIndex + 1 } of ${value.length}.`; } return "Sortable item is no longer over a droppable area. Press escape to cancel."; }, }), [value], );
const screenReaderInstructions: ScreenReaderInstructions = React.useMemo( () => ({ draggable: ` To pick up a sortable item, press space or enter. While dragging, use the ${ orientation === "vertical" ? "up and down" : orientation === "horizontal" ? "left and right" : "arrow" } keys to move the item. Press space or enter again to drop the item in its new position, or press escape to cancel. `, }), [orientation], );
const contextValue = React.useMemo( () => ({ id, items, modifiers: modifiers ?? config.modifiers, strategy: strategy ?? config.strategy, activeId, setActiveId, getItemValue, flatCursor, }), [ id, items, modifiers, strategy, config.modifiers, config.strategy, activeId, getItemValue, flatCursor, ], );
return ( <SortableRootContext.Provider value={contextValue as SortableRootContextValue<unknown>} > <DndContext collisionDetection={collisionDetection ?? config.collisionDetection} modifiers={modifiers ?? config.modifiers} sensors={sensors} {...sortableProps} id={id} onDragStart={composeEventHandlers( sortableProps.onDragStart, ({ active }) => setActiveId(active.id), )} onDragEnd={composeEventHandlers(sortableProps.onDragEnd, onDragEnd)} onDragCancel={composeEventHandlers(sortableProps.onDragCancel, () => setActiveId(null), )} accessibility={{ announcements, screenReaderInstructions, ...accessibility, }} /> </SortableRootContext.Provider> );}
const SortableContentContext = React.createContext<boolean>(false);SortableContentContext.displayName = CONTENT_NAME;
interface SortableContentProps extends React.ComponentPropsWithoutRef<"div"> { strategy?: SortableContextProps["strategy"]; children: React.ReactNode; asChild?: boolean; withoutSlot?: boolean;}
const SortableContent = React.forwardRef<HTMLDivElement, SortableContentProps>( (props, forwardedRef) => { const { strategy: strategyProp, asChild, withoutSlot, children, ...contentProps } = props; const context = useSortableContext(CONTENT_NAME);
const ContentPrimitive = asChild ? Slot : "div";
return ( <SortableContentContext.Provider value={true}> <SortableContext items={context.items} strategy={strategyProp ?? context.strategy} > {withoutSlot ? ( children ) : ( <ContentPrimitive {...contentProps} ref={forwardedRef}> {children} </ContentPrimitive> )} </SortableContext> </SortableContentContext.Provider> ); },);SortableContent.displayName = CONTENT_NAME;
interface SortableItemContextValue { id: string; attributes: React.HTMLAttributes<HTMLElement>; listeners: DraggableSyntheticListeners | undefined; setActivatorNodeRef: (node: HTMLElement | null) => void; isDragging?: boolean; disabled?: boolean;}
const SortableItemContext = React.createContext<SortableItemContextValue | null>(null);SortableItemContext.displayName = ITEM_NAME;
interface SortableItemProps extends React.ComponentPropsWithoutRef<"div"> { value: UniqueIdentifier; asHandle?: boolean; asChild?: boolean; disabled?: boolean;}
const SortableItem = React.forwardRef<HTMLDivElement, SortableItemProps>( (props, forwardedRef) => { const { value, style, asHandle, asChild, disabled, className, ...itemProps } = props; const inSortableContent = React.useContext(SortableContentContext); const inSortableOverlay = React.useContext(SortableOverlayContext);
if (!inSortableContent && !inSortableOverlay) { throw new Error(SORTABLE_ERRORS[ITEM_NAME]); }
if (value === "") { throw new Error(`\`${ITEM_NAME}\` value cannot be an empty string`); }
const context = useSortableContext(ITEM_NAME); const id = React.useId(); const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging, } = useSortable({ id: value, disabled });
const composedRef = useComposedRefs(forwardedRef, (node) => { if (disabled) return; setNodeRef(node); if (asHandle) setActivatorNodeRef(node); });
const composedStyle = React.useMemo<React.CSSProperties>(() => { return { transform: CSS.Translate.toString(transform), transition, ...style, }; }, [transform, transition, style]);
const itemContext = React.useMemo<SortableItemContextValue>( () => ({ id, attributes, listeners, setActivatorNodeRef, isDragging, disabled, }), [id, attributes, listeners, setActivatorNodeRef, isDragging, disabled], );
const ItemPrimitive = asChild ? Slot : "div";
return ( <SortableItemContext.Provider value={itemContext}> <ItemPrimitive id={id} data-dragging={isDragging ? "" : undefined} {...itemProps} {...(asHandle ? attributes : {})} {...(asHandle ? listeners : {})} tabIndex={disabled ? undefined : 0} ref={composedRef} style={composedStyle} className={cn( "focus-visible:ring-ring focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden", { "touch-none select-none": asHandle, "cursor-default": context.flatCursor, "data-dragging:cursor-grabbing": !context.flatCursor, "cursor-grab": !isDragging && asHandle && !context.flatCursor, "opacity-50": isDragging, "pointer-events-none opacity-50": disabled, }, className, )} /> </SortableItemContext.Provider> ); },);SortableItem.displayName = ITEM_NAME;
interface SortableItemHandleProps extends React.ComponentPropsWithoutRef<"button"> { asChild?: boolean;}
const SortableItemHandle = React.forwardRef< HTMLButtonElement, SortableItemHandleProps>((props, forwardedRef) => { const { asChild, disabled, className, ...itemHandleProps } = props; const itemContext = React.useContext(SortableItemContext); if (!itemContext) { throw new Error(SORTABLE_ERRORS[ITEM_HANDLE_NAME]); } const context = useSortableContext(ITEM_HANDLE_NAME);
const isDisabled = disabled ?? itemContext.disabled;
const composedRef = useComposedRefs(forwardedRef, (node) => { if (!isDisabled) return; itemContext.setActivatorNodeRef(node); });
const HandlePrimitive = asChild ? Slot : "button";
return ( <HandlePrimitive type="button" aria-controls={itemContext.id} data-dragging={itemContext.isDragging ? "" : undefined} {...itemHandleProps} {...itemContext.attributes} {...itemContext.listeners} ref={composedRef} className={cn( "select-none disabled:pointer-events-none disabled:opacity-50", context.flatCursor ? "cursor-default" : "cursor-grab data-dragging:cursor-grabbing", className, )} disabled={isDisabled} /> );});SortableItemHandle.displayName = ITEM_HANDLE_NAME;
const SortableOverlayContext = React.createContext(false);SortableOverlayContext.displayName = OVERLAY_NAME;
const dropAnimation: DropAnimation = { sideEffects: defaultDropAnimationSideEffects({ styles: { active: { opacity: "0.4", }, }, }),};
interface SortableOverlayProps extends Omit< React.ComponentPropsWithoutRef<typeof DragOverlay>, "children"> { container?: Element | DocumentFragment | null; children?: | ((params: { value: UniqueIdentifier }) => React.ReactNode) | React.ReactNode;}
function SortableOverlay(props: SortableOverlayProps) { const { container: containerProp, children, ...overlayProps } = props; const context = useSortableContext(OVERLAY_NAME);
const [mounted, setMounted] = React.useState(false); React.useLayoutEffect(() => setMounted(true), []);
const container = containerProp ?? (mounted ? globalThis.document?.body : null);
if (!container) return null;
return ReactDOM.createPortal( <DragOverlay dropAnimation={dropAnimation} modifiers={context.modifiers} className={cn(!context.flatCursor && "cursor-grabbing")} {...overlayProps} > <SortableOverlayContext.Provider value={true}> {context.activeId ? typeof children === "function" ? children({ value: context.activeId }) : children : null} </SortableOverlayContext.Provider> </DragOverlay>, container, );}
const Root = Sortable;const Content = SortableContent;const Item = SortableItem;const ItemHandle = SortableItemHandle;const Overlay = SortableOverlay;
export { Root, Content, Item, ItemHandle, Overlay, // Sortable, SortableContent, SortableItem, SortableItemHandle, SortableOverlay,};