pentatrion-design 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/main.ts +33 -0
- package/.storybook/manager.ts +8 -0
- package/.storybook/preview-head.html +2 -0
- package/.storybook/preview.ts +26 -0
- package/.storybook/public/bg-map.jpg +0 -0
- package/.storybook/public/favicon-32x32.png +0 -0
- package/.storybook/public/favicon.svg +28 -0
- package/.storybook/public/movies.json +2153 -0
- package/.storybook/public/population.json +1178 -0
- package/.storybook/public/town-74.geojson +8995 -0
- package/.storybook/public/town-74.json +4062 -0
- package/.storybook/theme.ts +14 -0
- package/Makefile +14 -0
- package/README.md +40 -0
- package/TODO.md +5 -0
- package/dist/index.d.ts +1110 -0
- package/dist/index.js +3707 -0
- package/dist/redux/index.d.ts +43 -0
- package/dist/redux/index.js +583 -0
- package/dist/types.d-DlHa_IaW.d.ts +14 -0
- package/images/logo-shadow.svg +173 -0
- package/images/logo.svg +76 -0
- package/package.json +85 -0
- package/postcss.config.js +7 -0
- package/src/components/autocomplete/Autocomplete.stories.tsx +89 -0
- package/src/components/autocomplete/Autocomplete.tsx +316 -0
- package/src/components/autocomplete/AutocompleteOption.tsx +35 -0
- package/src/components/autocomplete/LazyAutocomplete.tsx +130 -0
- package/src/components/autocomplete/SimpleAutocomplete.tsx +74 -0
- package/src/components/autocomplete/index.ts +6 -0
- package/src/components/autocomplete/useAutocompleteContext.ts +20 -0
- package/src/components/autocomplete/util.ts +19 -0
- package/src/components/badge/Badge.stories.tsx +72 -0
- package/src/components/badge/Badge.tsx +70 -0
- package/src/components/button/Button.stories.tsx +189 -0
- package/src/components/button/Button.test.tsx +20 -0
- package/src/components/button/Button.tsx +200 -0
- package/src/components/button/ButtonGroup.stories.tsx +46 -0
- package/src/components/button/ButtonGroup.tsx +29 -0
- package/src/components/button/index.ts +2 -0
- package/src/components/card/Card.stories.tsx +21 -0
- package/src/components/card/config.ts +4 -0
- package/src/components/card/index.ts +1 -0
- package/src/components/code/Code.tsx +15 -0
- package/src/components/context-menu/ContextMenu.stories.tsx +22 -0
- package/src/components/context-menu/ContextMenu.tsx +175 -0
- package/src/components/context-menu/ContextMenuItem.tsx +34 -0
- package/src/components/context-menu/index.ts +2 -0
- package/src/components/dialog/Dialog.stories.tsx +270 -0
- package/src/components/dialog/Dialog.tsx +46 -0
- package/src/components/dialog/index.ts +1 -0
- package/src/components/dialog/util.ts +29 -0
- package/src/components/dropdown-menu/DropdownMenu.stories.tsx +19 -0
- package/src/components/dropdown-menu/DropdownMenu.tsx +12 -0
- package/src/components/dropdown-menu/DropdownMenuContent.tsx +46 -0
- package/src/components/dropdown-menu/DropdownMenuItem.tsx +33 -0
- package/src/components/dropdown-menu/DropdownMenuTrigger.tsx +42 -0
- package/src/components/dropdown-menu/index.ts +7 -0
- package/src/components/dropdown-menu/interface.d.ts +13 -0
- package/src/components/dropdown-menu/useDropdownMenu.tsx +115 -0
- package/src/components/dropdown-menu/useDropdownMenuContext.ts +16 -0
- package/src/components/dropdown-menu-nested/DropdownMenuNested.stories.tsx +33 -0
- package/src/components/dropdown-menu-nested/DropdownMenuNested.tsx +19 -0
- package/src/components/dropdown-menu-nested/MenuContext.ts +15 -0
- package/src/components/dropdown-menu-nested/MenuItem.tsx +43 -0
- package/src/components/dropdown-menu-nested/MenuItemWithChildren.tsx +204 -0
- package/src/components/dropdown-menu-nested/index.ts +3 -0
- package/src/components/flash/Flash.stories.tsx +34 -0
- package/src/components/flash/Flash.tsx +24 -0
- package/src/components/highlight/Highlight.stories.tsx +128 -0
- package/src/components/highlight/Highlight.tsx +29 -0
- package/src/components/href/Href.stories.tsx +55 -0
- package/src/components/href/Href.tsx +18 -0
- package/src/components/input/Checkbox.stories.tsx +52 -0
- package/src/components/input/Checkbox.tsx +41 -0
- package/src/components/input/Input.stories.tsx +103 -0
- package/src/components/input/Input.test.tsx +10 -0
- package/src/components/input/Input.tsx +60 -0
- package/src/components/input/RadioGroup.stories.tsx +76 -0
- package/src/components/input/RadioGroup.tsx +84 -0
- package/src/components/input/Toggle.stories.tsx +29 -0
- package/src/components/input/Toggle.tsx +35 -0
- package/src/components/input/index.ts +4 -0
- package/src/components/input-field/InputField.stories.tsx +227 -0
- package/src/components/input-field/InputField.tsx +62 -0
- package/src/components/loader/Loader.stories.tsx +37 -0
- package/src/components/loader/Loader.tsx +54 -0
- package/src/components/modal/Components.tsx +58 -0
- package/src/components/modal/Modal.stories.tsx +113 -0
- package/src/components/modal/Modal.tsx +13 -0
- package/src/components/modal/ModalContent.tsx +46 -0
- package/src/components/modal/ModalTrigger.tsx +41 -0
- package/src/components/modal/index.ts +7 -0
- package/src/components/modal/interface.d.ts +10 -0
- package/src/components/modal/useModal.ts +48 -0
- package/src/components/modal/useModalContext.ts +16 -0
- package/src/components/notification/Notification.stories.tsx +51 -0
- package/src/components/notification/NotificationsContext.ts +79 -0
- package/src/components/notification/NotificationsProvider.tsx +58 -0
- package/src/components/notification/index.ts +2 -0
- package/src/components/popover/Components.tsx +45 -0
- package/src/components/popover/Popover.stories.tsx +46 -0
- package/src/components/popover/Popover.test.tsx +21 -0
- package/src/components/popover/Popover.tsx +12 -0
- package/src/components/popover/PopoverContent.tsx +46 -0
- package/src/components/popover/PopoverTrigger.tsx +42 -0
- package/src/components/popover/index.ts +7 -0
- package/src/components/popover/interface.d.ts +13 -0
- package/src/components/popover/usePopover.tsx +80 -0
- package/src/components/popover/usePopoverContext.ts +16 -0
- package/src/components/portal/Portal.tsx +12 -0
- package/src/components/quick-settings/QuickSettings.stories.tsx +182 -0
- package/src/components/resize-area/ResizeArea.stories.css +40 -0
- package/src/components/resize-area/ResizeArea.stories.tsx +30 -0
- package/src/components/resize-area/ResizeArea.tsx +79 -0
- package/src/components/scroll/Scroll.stories.tsx +81 -0
- package/src/components/scroll/Scroll.tsx +111 -0
- package/src/components/select/Select.stories.tsx +248 -0
- package/src/components/select/Select.tsx +362 -0
- package/src/components/select/SelectOption.tsx +29 -0
- package/src/components/select/SelectSelection.tsx +13 -0
- package/src/components/select/index.ts +5 -0
- package/src/components/select/interface.d.ts +36 -0
- package/src/components/select/useSelectContext.ts +19 -0
- package/src/components/select/util.ts +12 -0
- package/src/components/snack/Snack.stories.tsx +33 -0
- package/src/components/snack/Snack.tsx +47 -0
- package/src/components/sortable/Sortable.stories.tsx +98 -0
- package/src/components/sortable/Sortable.tsx +37 -0
- package/src/components/sortable/index.ts +2 -0
- package/src/components/sortable/types.d.ts +12 -0
- package/src/components/steps/Step.tsx +41 -0
- package/src/components/steps/Steps.stories.tsx +141 -0
- package/src/components/steps/Steps.tsx +49 -0
- package/src/components/steps/index.ts +3 -0
- package/src/components/steps/util.ts +3 -0
- package/src/components/table/Table.stories.tsx +36 -0
- package/src/components/table/Table.tsx +13 -0
- package/src/components/table/TableBody.tsx +9 -0
- package/src/components/table/TableCell.tsx +16 -0
- package/src/components/table/TableFooter.tsx +9 -0
- package/src/components/table/TableHeader.tsx +14 -0
- package/src/components/table/TableHeaderCell.tsx +14 -0
- package/src/components/table/TableRow.tsx +14 -0
- package/src/components/table/index.ts +7 -0
- package/src/components/tabs/Tabs.stories.tsx +66 -0
- package/src/components/tabs/Tabs.tsx +80 -0
- package/src/components/tooltip/SimpleTooltip.tsx +17 -0
- package/src/components/tooltip/Tooltip.stories.tsx +129 -0
- package/src/components/tooltip/Tooltip.test.tsx +18 -0
- package/src/components/tooltip/Tooltip.tsx +13 -0
- package/src/components/tooltip/TooltipContent.tsx +41 -0
- package/src/components/tooltip/TooltipTrigger.tsx +36 -0
- package/src/components/tooltip/index.ts +6 -0
- package/src/components/tooltip/interface.d.ts +15 -0
- package/src/components/tooltip/useTooltip.ts +87 -0
- package/src/components/tooltip/useTooltipContext.ts +16 -0
- package/src/env.d.ts +27 -0
- package/src/hooks/index.ts +13 -0
- package/src/hooks/useCombinedRefs.test.tsx +42 -0
- package/src/hooks/useCombinedRefs.ts +21 -0
- package/src/hooks/useCopyToClipboard.ts +28 -0
- package/src/hooks/useDebounce.ts +59 -0
- package/src/hooks/useEventCallback.ts +13 -0
- package/src/hooks/useEventListener.ts +86 -0
- package/src/hooks/useFetch.tsx +21 -0
- package/src/hooks/useIsClosing.ts +25 -0
- package/src/hooks/useIsMounted.tsx +16 -0
- package/src/hooks/useIsomorphicLayoutEffect.ts +4 -0
- package/src/hooks/useNotifications.tsx +13 -0
- package/src/hooks/useOnClickOutside.ts +35 -0
- package/src/hooks/usePrevious.ts +9 -0
- package/src/hooks/useRipple.test.tsx +52 -0
- package/src/hooks/useRipple.tsx +79 -0
- package/src/index.css +14 -0
- package/src/index.ts +39 -0
- package/src/lib/arrUtil.ts +8 -0
- package/src/lib/error.ts +18 -0
- package/src/lib/fetch.test.ts +107 -0
- package/src/lib/fetch.ts +115 -0
- package/src/lib/index.ts +3 -0
- package/src/lib/tailwindVariants.ts +35 -0
- package/src/redux/index.ts +1 -0
- package/src/redux/notification/components/NotificationConsumer.tsx +52 -0
- package/src/redux/notification/errorCatcherMiddleware.ts +17 -0
- package/src/redux/notification/hooks/useNotification.ts +26 -0
- package/src/redux/notification/index.ts +5 -0
- package/src/redux/notification/notificationSlice.ts +68 -0
- package/src/styles/Animation.stories.tsx +43 -0
- package/src/styles/Shadow.stories.tsx +43 -0
- package/src/styles/Themes.stories.tsx +31 -0
- package/src/styles/Typography.stories.tsx +101 -0
- package/src/tailwind/base.css +188 -0
- package/src/tailwind/components-input-outline.css +62 -0
- package/src/tailwind/components-resize-area.css +112 -0
- package/src/tailwind/components-step.css +161 -0
- package/src/tailwind/components.css +51 -0
- package/src/tailwind/keyframes.ts +69 -0
- package/src/tailwind/pentatrionTw.ts +147 -0
- package/src/tailwind/utilities-dialog.css +103 -0
- package/src/tailwind/utilities.css +3 -0
- package/src/types.d.ts +12 -0
- package/tailwind.config.js +18 -0
- package/tests/README.md +32 -0
- package/tests/setup.ts +1 -0
- package/tsconfig.json +14 -0
- package/tsup.config.ts +23 -0
- package/vite.config.ts +24 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
ComponentPropsWithRef,
|
|
4
|
+
ReactElement,
|
|
5
|
+
cloneElement,
|
|
6
|
+
isValidElement,
|
|
7
|
+
useEffect,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
} from "react";
|
|
11
|
+
import {
|
|
12
|
+
FloatingFocusManager,
|
|
13
|
+
FloatingPortal,
|
|
14
|
+
autoUpdate,
|
|
15
|
+
flip,
|
|
16
|
+
offset,
|
|
17
|
+
shift,
|
|
18
|
+
size,
|
|
19
|
+
useDismiss,
|
|
20
|
+
useFloating,
|
|
21
|
+
useInteractions,
|
|
22
|
+
useListNavigation,
|
|
23
|
+
useRole,
|
|
24
|
+
useTypeahead,
|
|
25
|
+
} from "@floating-ui/react";
|
|
26
|
+
|
|
27
|
+
import { ContextMenuItemProps } from "./ContextMenuItem";
|
|
28
|
+
import { Dialog } from "../dialog";
|
|
29
|
+
import { useEventCallback, useRefDebounce } from "~/hooks";
|
|
30
|
+
|
|
31
|
+
interface Props extends ComponentPropsWithRef<"div"> {
|
|
32
|
+
children: ReactElement[] | ReactElement;
|
|
33
|
+
eventName?: "contextmenu" | "maplibre-contextmenu";
|
|
34
|
+
}
|
|
35
|
+
export default function ContextMenu({ children, style, eventName = "contextmenu" }: Props) {
|
|
36
|
+
const [activeIndex, setActiveIndex] = useState<number | null>(null);
|
|
37
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
38
|
+
const isOpenDebounceRef = useRefDebounce(isOpen, 500);
|
|
39
|
+
const contextEvent = useRef<MouseEvent | CustomEvent | null>(null);
|
|
40
|
+
const listItemsRef = useRef<(HTMLButtonElement | null)[]>([]);
|
|
41
|
+
const listContentRef = useRef(
|
|
42
|
+
Children.map(children, (child) =>
|
|
43
|
+
isValidElement<ContextMenuItemProps>(child) ? child.props.label : null,
|
|
44
|
+
) as (string | null)[],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const { refs, floatingStyles, context } = useFloating({
|
|
48
|
+
open: isOpen,
|
|
49
|
+
onOpenChange: setIsOpen,
|
|
50
|
+
middleware: [
|
|
51
|
+
offset({ mainAxis: 5, alignmentAxis: 4 }),
|
|
52
|
+
flip(),
|
|
53
|
+
size({
|
|
54
|
+
apply({ elements, availableHeight }) {
|
|
55
|
+
const firstChild = elements.floating.firstElementChild as HTMLElement;
|
|
56
|
+
if (firstChild) {
|
|
57
|
+
firstChild.style.maxHeight = `${Math.min(availableHeight, 300)}px`;
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
padding: 10,
|
|
61
|
+
}),
|
|
62
|
+
shift({ padding: 10 }),
|
|
63
|
+
],
|
|
64
|
+
/**
|
|
65
|
+
* it is better to be less demanding on small screen sizes to see the content displayed correctly
|
|
66
|
+
*/
|
|
67
|
+
placement: window.matchMedia("(min-width: 700px)").matches ? "right-start" : undefined,
|
|
68
|
+
strategy: "fixed",
|
|
69
|
+
whileElementsMounted: autoUpdate,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const role = useRole(context, { role: "menu" });
|
|
73
|
+
const dismiss = useDismiss(context);
|
|
74
|
+
|
|
75
|
+
const listNavigation = useListNavigation(context, {
|
|
76
|
+
listRef: listItemsRef,
|
|
77
|
+
onNavigate: setActiveIndex,
|
|
78
|
+
activeIndex,
|
|
79
|
+
});
|
|
80
|
+
const typeahead = useTypeahead(context, {
|
|
81
|
+
enabled: isOpen,
|
|
82
|
+
listRef: listContentRef,
|
|
83
|
+
onMatch: setActiveIndex,
|
|
84
|
+
activeIndex,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const { getFloatingProps, getItemProps } = useInteractions([
|
|
88
|
+
role,
|
|
89
|
+
dismiss,
|
|
90
|
+
listNavigation,
|
|
91
|
+
typeahead,
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
const onContextMenuStable = useEventCallback(function onContextMenu(e: MouseEvent | CustomEvent) {
|
|
95
|
+
/**
|
|
96
|
+
* if the context menu has just been closed we probably do not want a click to immediately
|
|
97
|
+
* reopen a new context menu
|
|
98
|
+
*/
|
|
99
|
+
if (isOpenDebounceRef.current) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
|
|
104
|
+
const originalEvent = e instanceof MouseEvent ? e : (e.detail.originalEvent as MouseEvent);
|
|
105
|
+
contextEvent.current = e;
|
|
106
|
+
|
|
107
|
+
refs.setPositionReference({
|
|
108
|
+
getBoundingClientRect() {
|
|
109
|
+
return {
|
|
110
|
+
width: 0,
|
|
111
|
+
height: 0,
|
|
112
|
+
x: originalEvent.clientX,
|
|
113
|
+
y: originalEvent.clientY,
|
|
114
|
+
top: originalEvent.clientY,
|
|
115
|
+
right: originalEvent.clientX,
|
|
116
|
+
bottom: originalEvent.clientY,
|
|
117
|
+
left: originalEvent.clientX,
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
setIsOpen(true);
|
|
123
|
+
isOpenDebounceRef.current = true;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
document.addEventListener(eventName, onContextMenuStable);
|
|
128
|
+
return () => {
|
|
129
|
+
document.removeEventListener(eventName, onContextMenuStable);
|
|
130
|
+
};
|
|
131
|
+
}, [eventName, onContextMenuStable]);
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<FloatingPortal>
|
|
135
|
+
{isOpen && (
|
|
136
|
+
<FloatingFocusManager context={context} initialFocus={refs.floating}>
|
|
137
|
+
<div
|
|
138
|
+
className="z-dialog"
|
|
139
|
+
ref={refs.setFloating}
|
|
140
|
+
style={{ ...floatingStyles, ...style }}
|
|
141
|
+
{...getFloatingProps()}
|
|
142
|
+
>
|
|
143
|
+
<Dialog
|
|
144
|
+
placement={context.placement}
|
|
145
|
+
className="animate-fade-in-list z-context-menu max-h-80 overflow-auto"
|
|
146
|
+
>
|
|
147
|
+
{Children.map(
|
|
148
|
+
children,
|
|
149
|
+
(child, index) =>
|
|
150
|
+
isValidElement<ContextMenuItemProps>(child) &&
|
|
151
|
+
cloneElement(
|
|
152
|
+
child,
|
|
153
|
+
getItemProps({
|
|
154
|
+
tabIndex: activeIndex === index ? 0 : -1,
|
|
155
|
+
ref(node: HTMLButtonElement) {
|
|
156
|
+
listItemsRef.current[index] = node;
|
|
157
|
+
},
|
|
158
|
+
onClick() {
|
|
159
|
+
child.props.onClick?.(contextEvent.current);
|
|
160
|
+
setIsOpen(false);
|
|
161
|
+
},
|
|
162
|
+
onMouseUp() {
|
|
163
|
+
child.props.onClick?.(contextEvent.current);
|
|
164
|
+
setIsOpen(false);
|
|
165
|
+
},
|
|
166
|
+
}),
|
|
167
|
+
),
|
|
168
|
+
)}
|
|
169
|
+
</Dialog>
|
|
170
|
+
</div>
|
|
171
|
+
</FloatingFocusManager>
|
|
172
|
+
)}
|
|
173
|
+
</FloatingPortal>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React, { ComponentPropsWithRef, ReactNode, forwardRef } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type ContextMenuItemMouseEvent =
|
|
5
|
+
| React.MouseEvent<HTMLButtonElement>
|
|
6
|
+
| MouseEvent
|
|
7
|
+
| CustomEvent
|
|
8
|
+
| null;
|
|
9
|
+
|
|
10
|
+
export interface ContextMenuItemProps extends Omit<ComponentPropsWithRef<"button">, "onClick"> {
|
|
11
|
+
label: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
icon?: ReactNode;
|
|
14
|
+
onClick?: (e: ContextMenuItemMouseEvent) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const ContextMenuItem = forwardRef<HTMLButtonElement, ContextMenuItemProps>(
|
|
18
|
+
({ label, disabled, icon, className, ...props }, ref) => {
|
|
19
|
+
return (
|
|
20
|
+
<button
|
|
21
|
+
{...props}
|
|
22
|
+
className={clsx(["option", className])}
|
|
23
|
+
ref={ref}
|
|
24
|
+
role="menuitem"
|
|
25
|
+
disabled={disabled}
|
|
26
|
+
>
|
|
27
|
+
{icon}
|
|
28
|
+
<span className="content">{label}</span>
|
|
29
|
+
</button>
|
|
30
|
+
);
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export default ContextMenuItem;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { Meta } from "@storybook/react";
|
|
2
|
+
import Dialog from "./Dialog";
|
|
3
|
+
import { Button } from "../button";
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Components/Dialog",
|
|
7
|
+
} satisfies Meta;
|
|
8
|
+
export default meta;
|
|
9
|
+
|
|
10
|
+
export const Basic = () => {
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex gap-8 flex-col mb-4">
|
|
13
|
+
<div>
|
|
14
|
+
<Dialog>
|
|
15
|
+
<div className="description p-2">coucou</div>
|
|
16
|
+
</Dialog>
|
|
17
|
+
</div>
|
|
18
|
+
<div>
|
|
19
|
+
<Dialog placement="top" color="gray" style={{ width: 300 }}>
|
|
20
|
+
<div className="description p-2">infos</div>
|
|
21
|
+
<div style={{ left: "18px" }} className="p8n-arrow"></div>
|
|
22
|
+
</Dialog>
|
|
23
|
+
</div>
|
|
24
|
+
<div>
|
|
25
|
+
<Dialog placement="bottom" color="red" style={{ width: 300 }}>
|
|
26
|
+
<div className="description p-2">infos</div>
|
|
27
|
+
<div style={{ left: "18px" }} className="p8n-arrow"></div>
|
|
28
|
+
</Dialog>
|
|
29
|
+
</div>
|
|
30
|
+
<div>
|
|
31
|
+
<Dialog placement="left" color="blue" style={{ width: 300 }}>
|
|
32
|
+
<div className="description p-2">infos</div>
|
|
33
|
+
<div style={{ top: "12px" }} className="p8n-arrow"></div>
|
|
34
|
+
</Dialog>
|
|
35
|
+
</div>
|
|
36
|
+
<div>
|
|
37
|
+
<Dialog placement="right" color="green" style={{ width: 300 }}>
|
|
38
|
+
<div className="description p-2">infos</div>
|
|
39
|
+
<div style={{ top: "12px" }} className="p8n-arrow"></div>
|
|
40
|
+
</Dialog>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div>
|
|
44
|
+
<Dialog placement="top" color="orange" style={{ width: 300 }}>
|
|
45
|
+
<div className="p-1 float-right">
|
|
46
|
+
<Button icon variant="text" color="gray">
|
|
47
|
+
<i className="fe-cancel"></i>
|
|
48
|
+
</Button>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div className="description p-2">infos</div>
|
|
52
|
+
<div style={{ left: "18px", bottom: "-8px" }} className="p8n-arrow"></div>
|
|
53
|
+
</Dialog>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div>
|
|
57
|
+
<Dialog placement="top" color="blue" style={{ width: 300 }}>
|
|
58
|
+
<div className="p-1 float-right">
|
|
59
|
+
<Button icon variant="text" color="gray">
|
|
60
|
+
<i className="fe-cancel"></i>
|
|
61
|
+
</Button>
|
|
62
|
+
</div>
|
|
63
|
+
<header className="flex items-center px-2 pt-2">
|
|
64
|
+
<h4>Heading</h4>
|
|
65
|
+
</header>
|
|
66
|
+
<div className="description p-2">description</div>
|
|
67
|
+
<div style={{ left: "18px", bottom: "-8px" }} className="p8n-arrow"></div>
|
|
68
|
+
</Dialog>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div>
|
|
72
|
+
<Dialog placement="top" color="yellow" style={{ width: 300 }}>
|
|
73
|
+
<div className="p-1 float-right">
|
|
74
|
+
<Button icon variant="text" color="gray">
|
|
75
|
+
<i className="fe-cancel"></i>
|
|
76
|
+
</Button>
|
|
77
|
+
</div>
|
|
78
|
+
<header className="flex items-center px-2 pt-2">
|
|
79
|
+
<h4>Heading</h4>
|
|
80
|
+
</header>
|
|
81
|
+
<div className="description p-2">description</div>
|
|
82
|
+
<footer className="relative z-10 px-2 pb-2">
|
|
83
|
+
<div className="flex justify-between">
|
|
84
|
+
<Button variant="text" color="gray">
|
|
85
|
+
Annuler
|
|
86
|
+
</Button>
|
|
87
|
+
<Button color="yellow">Valider</Button>
|
|
88
|
+
</div>
|
|
89
|
+
</footer>
|
|
90
|
+
<div style={{ left: "18px", bottom: "-8px" }} className="p8n-arrow"></div>
|
|
91
|
+
</Dialog>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const Options = () => {
|
|
98
|
+
return (
|
|
99
|
+
<div className="flex gap-8 flex-col mb-4">
|
|
100
|
+
<div>
|
|
101
|
+
<Dialog placement="bottom" style={{ width: 300 }}>
|
|
102
|
+
<div className="option">Default state 1</div>
|
|
103
|
+
<div className="option active">Active state 2 (keyboard navigation)</div>
|
|
104
|
+
<div className="option">Default state 3</div>
|
|
105
|
+
<div className="option selected">Selected state 4</div>
|
|
106
|
+
<div className="option">Default state 5</div>
|
|
107
|
+
<div className="option">Default state 6</div>
|
|
108
|
+
</Dialog>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div>
|
|
112
|
+
<Dialog placement="bottom" style={{ width: 300 }}>
|
|
113
|
+
<div className="option">
|
|
114
|
+
<i className="fe-star mr-2"></i>
|
|
115
|
+
<span>Default state 1</span>
|
|
116
|
+
</div>
|
|
117
|
+
<div className="option">
|
|
118
|
+
<i className="fe-marker mr-2"></i>
|
|
119
|
+
<span>State 2</span>
|
|
120
|
+
</div>
|
|
121
|
+
</Dialog>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div>
|
|
125
|
+
<Dialog placement="bottom" style={{ width: 300 }}>
|
|
126
|
+
<div className="option">
|
|
127
|
+
<span className="bullet">A</span>
|
|
128
|
+
<span>Itinéraire depuis ce point</span>
|
|
129
|
+
</div>
|
|
130
|
+
<div className="option text-gray-6">
|
|
131
|
+
<i className="fe-point-inter mr-2"></i>
|
|
132
|
+
<span>Ajouter un point</span>
|
|
133
|
+
</div>
|
|
134
|
+
<div className="option">
|
|
135
|
+
<span className="bullet">B</span>
|
|
136
|
+
<span>Itinéraire vers ce point</span>
|
|
137
|
+
</div>
|
|
138
|
+
<div className="option">
|
|
139
|
+
<i className="fe-plus mr-2"></i>
|
|
140
|
+
<span>Prolonger l'itinéraire jusqu'ici</span>
|
|
141
|
+
</div>
|
|
142
|
+
</Dialog>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div>
|
|
146
|
+
<Dialog placement="bottom" style={{ width: 300 }}>
|
|
147
|
+
<div className="option">
|
|
148
|
+
<span className="bullet">A</span>
|
|
149
|
+
<span>Itinéraire depuis ce point</span>
|
|
150
|
+
</div>
|
|
151
|
+
<div className="option text-gray-6">
|
|
152
|
+
<i className="fe-point-inter mr-2"></i>
|
|
153
|
+
<span className="text-sm">point intermédiaire</span>
|
|
154
|
+
</div>
|
|
155
|
+
<div className="option">
|
|
156
|
+
<span className="bullet">B</span>
|
|
157
|
+
<span>Déplacer ce point</span>
|
|
158
|
+
</div>
|
|
159
|
+
<div className="option text-gray-6">
|
|
160
|
+
<i className="fe-point-inter mr-2"></i>
|
|
161
|
+
<span className="text-sm">point intermédiaire</span>
|
|
162
|
+
</div>
|
|
163
|
+
<div className="option">
|
|
164
|
+
<span className="bullet">C</span>
|
|
165
|
+
<span>Déplacer ce point</span>
|
|
166
|
+
</div>
|
|
167
|
+
<div className="option text-gray-6">
|
|
168
|
+
<i className="fe-point-inter mr-2"></i>
|
|
169
|
+
<span className="text-sm">point intermédiaire</span>
|
|
170
|
+
</div>
|
|
171
|
+
<div className="option">
|
|
172
|
+
<span className="bullet">D</span>
|
|
173
|
+
<span>Itinéraire vers ce point</span>
|
|
174
|
+
</div>
|
|
175
|
+
<div className="option">
|
|
176
|
+
<i className="fe-plus mr-2"></i>
|
|
177
|
+
<span>Prolonger l'itinéraire jusqu'ici</span>
|
|
178
|
+
</div>
|
|
179
|
+
</Dialog>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<div>
|
|
183
|
+
<Dialog placement="bottom" style={{ width: 300 }}>
|
|
184
|
+
<div className="overflow-auto max-h-56">
|
|
185
|
+
<div className="option">
|
|
186
|
+
<span className="bullet">A</span>
|
|
187
|
+
<span>Itinéraire depuis ce point</span>
|
|
188
|
+
</div>
|
|
189
|
+
<div className="option text-gray-6">
|
|
190
|
+
<i className="fe-point-inter mr-2"></i>
|
|
191
|
+
<span className="text-sm">point intermédiaire</span>
|
|
192
|
+
</div>
|
|
193
|
+
<div className="option">
|
|
194
|
+
<span className="bullet">B</span>
|
|
195
|
+
<span>Déplacer ce point</span>
|
|
196
|
+
</div>
|
|
197
|
+
<div className="option text-gray-6">
|
|
198
|
+
<i className="fe-point-inter mr-2"></i>
|
|
199
|
+
<span className="text-sm">point intermédiaire</span>
|
|
200
|
+
</div>
|
|
201
|
+
<div className="option">
|
|
202
|
+
<span className="bullet">C</span>
|
|
203
|
+
<span>Déplacer ce point</span>
|
|
204
|
+
</div>
|
|
205
|
+
<div className="option text-gray-6">
|
|
206
|
+
<i className="fe-point-inter mr-2"></i>
|
|
207
|
+
<span className="text-sm">point intermédiaire</span>
|
|
208
|
+
</div>
|
|
209
|
+
<div className="option">
|
|
210
|
+
<span className="bullet">D</span>
|
|
211
|
+
<span>Itinéraire vers ce point</span>
|
|
212
|
+
</div>
|
|
213
|
+
<div className="option text-gray-6">
|
|
214
|
+
<i className="fe-point-inter mr-2"></i>
|
|
215
|
+
<span className="text-sm">point intermédiaire</span>
|
|
216
|
+
</div>
|
|
217
|
+
<div className="option">
|
|
218
|
+
<span className="bullet">E</span>
|
|
219
|
+
<span>Itinéraire vers ce point</span>
|
|
220
|
+
</div>
|
|
221
|
+
<div className="option text-gray-6">
|
|
222
|
+
<i className="fe-point-inter mr-2"></i>
|
|
223
|
+
<span className="text-sm">point intermédiaire</span>
|
|
224
|
+
</div>
|
|
225
|
+
<div className="option">
|
|
226
|
+
<span className="bullet">F</span>
|
|
227
|
+
<span>Itinéraire vers ce point</span>
|
|
228
|
+
</div>
|
|
229
|
+
<div className="option text-gray-6">
|
|
230
|
+
<i className="fe-point-inter mr-2"></i>
|
|
231
|
+
<span className="text-sm">point intermédiaire</span>
|
|
232
|
+
</div>
|
|
233
|
+
<div className="option">
|
|
234
|
+
<span className="bullet">G</span>
|
|
235
|
+
<span>Itinéraire vers ce point</span>
|
|
236
|
+
</div>
|
|
237
|
+
<div className="option text-gray-6">
|
|
238
|
+
<i className="fe-point-inter mr-2"></i>
|
|
239
|
+
<span className="text-sm">point intermédiaire</span>
|
|
240
|
+
</div>
|
|
241
|
+
<div className="option">
|
|
242
|
+
<span className="bullet">H</span>
|
|
243
|
+
<span>Itinéraire vers ce point</span>
|
|
244
|
+
</div>
|
|
245
|
+
<div className="option text-gray-6">
|
|
246
|
+
<i className="fe-point-inter mr-2"></i>
|
|
247
|
+
<span className="text-sm">point intermédiaire</span>
|
|
248
|
+
</div>
|
|
249
|
+
<div className="option">
|
|
250
|
+
<span className="bullet">I</span>
|
|
251
|
+
<span>Itinéraire vers ce point</span>
|
|
252
|
+
</div>
|
|
253
|
+
<div className="option text-gray-6">
|
|
254
|
+
<i className="fe-point-inter mr-2"></i>
|
|
255
|
+
<span className="text-sm">point intermédiaire</span>
|
|
256
|
+
</div>
|
|
257
|
+
<div className="option">
|
|
258
|
+
<span className="bullet">J</span>
|
|
259
|
+
<span>Itinéraire vers ce point</span>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
<div className="option">
|
|
263
|
+
<i className="fe-plus"></i>
|
|
264
|
+
<span>Prolonger l'itinéraire jusqu'ici</span>
|
|
265
|
+
</div>
|
|
266
|
+
</Dialog>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Placement } from "@floating-ui/react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { ComponentProps, ReactNode } from "react";
|
|
4
|
+
import type { ThemeColor } from "~/types.d";
|
|
5
|
+
|
|
6
|
+
interface Props extends ComponentProps<"div"> {
|
|
7
|
+
color?: ThemeColor;
|
|
8
|
+
placement?: Placement;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const dialogVariants = {
|
|
13
|
+
color(color?: ThemeColor) {
|
|
14
|
+
if (!color) {
|
|
15
|
+
return "bg-gray-0";
|
|
16
|
+
}
|
|
17
|
+
return clsx(
|
|
18
|
+
"border-t-4",
|
|
19
|
+
{
|
|
20
|
+
yellow: "border-t-yellow-3 bg-gray-0",
|
|
21
|
+
gray: "border-t-gray-3 bg-gray-0",
|
|
22
|
+
red: "border-t-red-3 bg-red-1",
|
|
23
|
+
blue: "border-t-blue-3 bg-blue-1",
|
|
24
|
+
green: "border-t-green-3 bg-green-1",
|
|
25
|
+
orange: "border-t-orange-3 bg-orange-1",
|
|
26
|
+
}[color],
|
|
27
|
+
);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default function Dialog({ placement, color, children, className, ...rest }: Props) {
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
className={clsx(
|
|
35
|
+
"rounded-2xl relative shadow dark:shadow-dark",
|
|
36
|
+
dialogVariants.color(color),
|
|
37
|
+
className,
|
|
38
|
+
)}
|
|
39
|
+
data-placement={placement}
|
|
40
|
+
data-color={color}
|
|
41
|
+
{...rest}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Dialog, dialogVariants } from "./Dialog";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Placement, Side, UseFloatingReturn } from "@floating-ui/react";
|
|
2
|
+
|
|
3
|
+
function getSide(placement: Placement): Side {
|
|
4
|
+
return placement.split("-")[0] as Side;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function computeArrowStyle({ middlewareData, placement }: UseFloatingReturn) {
|
|
8
|
+
if (!middlewareData.arrow) {
|
|
9
|
+
return {
|
|
10
|
+
display: "none",
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { x, y } = middlewareData.arrow;
|
|
15
|
+
const staticSide: "bottom" | "left" | "top" | "right" = (
|
|
16
|
+
{
|
|
17
|
+
top: "bottom",
|
|
18
|
+
right: "left",
|
|
19
|
+
bottom: "top",
|
|
20
|
+
left: "right",
|
|
21
|
+
} as const
|
|
22
|
+
)[getSide(placement)];
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
left: x != null ? `${x}px` : "",
|
|
26
|
+
top: y != null ? `${y}px` : "",
|
|
27
|
+
[staticSide]: "-6px",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from ".";
|
|
2
|
+
import { Meta } from "@storybook/react";
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "Components/DropdownMenu",
|
|
6
|
+
component: DropdownMenu,
|
|
7
|
+
} satisfies Meta<typeof DropdownMenu>;
|
|
8
|
+
export default meta;
|
|
9
|
+
|
|
10
|
+
export const Basic = () => (
|
|
11
|
+
<DropdownMenu>
|
|
12
|
+
<DropdownMenuTrigger>Menu</DropdownMenuTrigger>
|
|
13
|
+
<DropdownMenuContent>
|
|
14
|
+
<DropdownMenuItem onClick={() => console.log("Undo")}>Your profile</DropdownMenuItem>
|
|
15
|
+
<DropdownMenuItem disabled>Settings</DropdownMenuItem>
|
|
16
|
+
<DropdownMenuItem>Sign out</DropdownMenuItem>
|
|
17
|
+
</DropdownMenuContent>
|
|
18
|
+
</DropdownMenu>
|
|
19
|
+
);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { DropdownMenuOptions } from "./interface";
|
|
3
|
+
import { DropdownMenuContext, useDropdownMenu } from ".";
|
|
4
|
+
|
|
5
|
+
interface Props extends DropdownMenuOptions {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function DropdownMenu({ children, ...options }: Props) {
|
|
10
|
+
const popover = useDropdownMenu(options);
|
|
11
|
+
return <DropdownMenuContext.Provider value={popover}>{children}</DropdownMenuContext.Provider>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ComponentProps, forwardRef } from "react";
|
|
2
|
+
import { useDropdownMenuContext } from ".";
|
|
3
|
+
import {
|
|
4
|
+
FloatingFocusManager,
|
|
5
|
+
FloatingList,
|
|
6
|
+
FloatingPortal,
|
|
7
|
+
useMergeRefs,
|
|
8
|
+
} from "@floating-ui/react";
|
|
9
|
+
import clsx from "clsx";
|
|
10
|
+
import { Dialog } from "../dialog";
|
|
11
|
+
|
|
12
|
+
const DropdownMenuContent = forwardRef<HTMLDivElement, ComponentProps<"div">>(
|
|
13
|
+
({ style, children, ...props }, propRef) => {
|
|
14
|
+
const context = useDropdownMenuContext();
|
|
15
|
+
const floatingContext = context.context;
|
|
16
|
+
|
|
17
|
+
const ref = useMergeRefs([context.refs.setFloating, propRef]);
|
|
18
|
+
if (!context.open) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return (
|
|
22
|
+
<FloatingPortal>
|
|
23
|
+
<FloatingFocusManager context={floatingContext} modal={context.modal}>
|
|
24
|
+
<div
|
|
25
|
+
ref={ref}
|
|
26
|
+
style={{ ...context.floatingStyles, ...style }}
|
|
27
|
+
{...context.getFloatingProps(props)}
|
|
28
|
+
>
|
|
29
|
+
<Dialog
|
|
30
|
+
placement={context.placement}
|
|
31
|
+
className={clsx(`border-${context.color}-2`, "animate-fade-in-list")}
|
|
32
|
+
>
|
|
33
|
+
<div className="box">
|
|
34
|
+
<FloatingList elementsRef={context.elementsRef} labelsRef={context.labelsRef}>
|
|
35
|
+
{children}
|
|
36
|
+
</FloatingList>
|
|
37
|
+
</div>
|
|
38
|
+
</Dialog>
|
|
39
|
+
</div>
|
|
40
|
+
</FloatingFocusManager>
|
|
41
|
+
</FloatingPortal>
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export default DropdownMenuContent;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useListItem } from "@floating-ui/react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { useDropdownMenuContext } from ".";
|
|
4
|
+
import { ComponentProps, MouseEvent, ReactNode } from "react";
|
|
5
|
+
|
|
6
|
+
interface Props extends ComponentProps<"button"> {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function DropdownMenuItem({ children, onClick, ...rest }: Props) {
|
|
11
|
+
const { activeIndex, getItemProps, handleSelect } = useDropdownMenuContext();
|
|
12
|
+
|
|
13
|
+
const { ref, index } = useListItem({ label: children?.toString() });
|
|
14
|
+
const isActive = activeIndex === index;
|
|
15
|
+
return (
|
|
16
|
+
<button
|
|
17
|
+
className={clsx("option", isActive && "bg-gray-1")}
|
|
18
|
+
ref={ref}
|
|
19
|
+
role="option"
|
|
20
|
+
aria-selected={isActive}
|
|
21
|
+
tabIndex={isActive ? 0 : -1}
|
|
22
|
+
{...getItemProps({
|
|
23
|
+
...rest,
|
|
24
|
+
onClick: (e: MouseEvent<HTMLButtonElement>) => {
|
|
25
|
+
onClick?.(e);
|
|
26
|
+
handleSelect();
|
|
27
|
+
},
|
|
28
|
+
})}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</button>
|
|
32
|
+
);
|
|
33
|
+
}
|