@launchpad-ui/dropdown 0.1.0

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/README.md ADDED
@@ -0,0 +1,14 @@
1
+ # @launchpad-ui/dropdown
2
+
3
+ > An element that displays a list of actions or options to a user.
4
+
5
+ [![See it on NPM!](https://img.shields.io/npm/v/@launchpad-ui/dropdown?style=for-the-badge)](https://www.npmjs.com/package/@launchpad-ui/dropdown)
6
+ [![How big is this package in your project?](https://img.shields.io/bundlephobia/minzip/@launchpad-ui/dropdown?style=for-the-badge)](https://bundlephobia.com/result?p=@launchpad-ui/dropdown)
7
+
8
+ ## Installation
9
+
10
+ ```sh
11
+ $ yarn add @launchpad-ui/dropdown
12
+ # or
13
+ $ npm install @launchpad-ui/dropdown
14
+ ```
@@ -0,0 +1,43 @@
1
+ import type { PopoverPlacement } from '@launchpad-ui/popover';
2
+ import { Button } from '@launchpad-ui/button';
3
+ import { Component } from 'react';
4
+ declare type DropdownState = {
5
+ isOpen?: boolean;
6
+ } & Record<string | number, unknown>;
7
+ declare type DropdownProps<T extends string | object | number> = {
8
+ isOpen?: boolean;
9
+ placement?: PopoverPlacement;
10
+ onSelect?: (item: T, stateChanges: DropdownState) => void;
11
+ onStateChange?: (state: Record<string | number, unknown>) => void;
12
+ disabled?: boolean;
13
+ targetClassName?: string;
14
+ children: React.ReactNode;
15
+ onInteraction?: (nextIsOpen: boolean) => void;
16
+ popoverClassName?: string;
17
+ enforceFocus?: boolean;
18
+ [key: string]: unknown;
19
+ };
20
+ declare class Dropdown<T extends string | object | number> extends Component<DropdownProps<T>, DropdownState> {
21
+ triggerElement: HTMLElement | null | Button;
22
+ refHandlers: {
23
+ trigger: (node: HTMLElement | null | Button) => void;
24
+ };
25
+ state: {
26
+ isOpen: boolean;
27
+ };
28
+ componentDidUpdate(prevProps: DropdownProps<T>, prevState: DropdownState): void;
29
+ render(): JSX.Element;
30
+ renderTrigger(): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
31
+ renderContent(): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
32
+ handleSelect: (item: T) => void;
33
+ handlePopoverInteraction: (nextIsOpen: boolean) => void;
34
+ parseChildren(): {
35
+ target: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
36
+ content: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
37
+ };
38
+ updateState(state: DropdownState, selectedItem?: T): void;
39
+ isControlledProp(key: string): boolean;
40
+ }
41
+ export { Dropdown };
42
+ export type { DropdownProps };
43
+ //# sourceMappingURL=Dropdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Dropdown.d.ts","sourceRoot":"","sources":["../src/Dropdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAG9C,OAAO,EAA0B,SAAS,EAAE,MAAM,OAAO,CAAC;AAE1D,aAAK,aAAa,GAAG;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,CAAC;AAErC,aAAK,aAAa,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,MAAM,IAAI;IACvD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,YAAY,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1D,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAClE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF,cAAM,QAAQ,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,MAAM,CAAE,SAAQ,SAAS,CAClE,aAAa,CAAC,CAAC,CAAC,EAChB,aAAa,CACd;IACC,cAAc,EAAE,WAAW,GAAG,IAAI,GAAG,MAAM,CAAQ;IACnD,WAAW;wBACO,WAAW,GAAG,IAAI,GAAG,MAAM;MAG3C;IAEF,KAAK;;MAEH;IAEF,kBAAkB,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,aAAa;IA0BxE,MAAM;IA4BN,aAAa;IASb,aAAa;IAMb,YAAY,SAAU,CAAC,UAErB;IAEF,wBAAwB,eAAgB,OAAO,UAE7C;IAEF,aAAa;;;;IAQb,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,YAAY,CAAC,EAAE,CAAC;IA6BlD,gBAAgB,CAAC,GAAG,EAAE,MAAM;CAG7B;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC;AACpB,YAAY,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,18 @@
1
+ /// <reference types="react" />
2
+ import { Button, ButtonKind, ButtonSize } from '@launchpad-ui/button';
3
+ declare type DropdownButtonProps = {
4
+ hideCaret?: boolean;
5
+ kind?: ButtonKind;
6
+ size?: ButtonSize;
7
+ className?: string;
8
+ disabled?: boolean;
9
+ children?: React.ReactNode;
10
+ onClick?(v: React.MouseEvent): void;
11
+ tooltip?: string;
12
+ tooltipOptions?: object;
13
+ testId?: string;
14
+ };
15
+ declare const DropdownButton: import("react").ForwardRefExoticComponent<DropdownButtonProps & import("react").RefAttributes<Button>>;
16
+ export { DropdownButton };
17
+ export type { DropdownButtonProps };
18
+ //# sourceMappingURL=DropdownButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DropdownButton.d.ts","sourceRoot":"","sources":["../src/DropdownButton.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAItE,aAAK,mBAAmB,GAAG;IACzB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,QAAA,MAAM,cAAc,wGAQlB,CAAC;AAIH,OAAO,EAAE,cAAc,EAAE,CAAC;AAC1B,YAAY,EAAE,mBAAmB,EAAE,CAAC"}
@@ -0,0 +1,5 @@
1
+ export type { DropdownProps } from './Dropdown';
2
+ export type { DropdownButtonProps } from './DropdownButton';
3
+ export { Dropdown } from './Dropdown';
4
+ export { DropdownButton } from './DropdownButton';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,130 @@
1
+ // ../../scripts/react-shim.js
2
+ import * as React from "react";
3
+
4
+ // src/Dropdown.tsx
5
+ import { Button } from "@launchpad-ui/button";
6
+ import { Popover } from "@launchpad-ui/popover";
7
+ import cx from "clsx";
8
+ import { Children, cloneElement, Component } from "react";
9
+ var Dropdown = class extends Component {
10
+ constructor() {
11
+ super(...arguments);
12
+ this.triggerElement = null;
13
+ this.refHandlers = {
14
+ trigger: (node) => {
15
+ this.triggerElement = node;
16
+ }
17
+ };
18
+ this.state = {
19
+ isOpen: this.isControlledProp("isOpen") ? this.props.isOpen : false
20
+ };
21
+ this.handleSelect = (item) => {
22
+ this.updateState({ isOpen: false }, item);
23
+ };
24
+ this.handlePopoverInteraction = (nextIsOpen) => {
25
+ this.updateState({ isOpen: nextIsOpen });
26
+ };
27
+ }
28
+ componentDidUpdate(prevProps, prevState) {
29
+ if (this.props.isOpen !== prevProps.isOpen) {
30
+ this.setState({ isOpen: this.props.isOpen });
31
+ }
32
+ if (prevState.isOpen !== this.state.isOpen && this.state.isOpen === false) {
33
+ requestAnimationFrame(() => {
34
+ const current = this.triggerElement;
35
+ if (!current) {
36
+ return;
37
+ }
38
+ const buttonElement = current instanceof Button && current.getDomElement?.() || current;
39
+ const hasModal = buttonElement?.closest?.(".has-modal");
40
+ !hasModal && buttonElement?.focus();
41
+ });
42
+ }
43
+ }
44
+ render() {
45
+ const {
46
+ placement,
47
+ disabled,
48
+ targetClassName,
49
+ isOpen: isOpenProp,
50
+ onInteraction,
51
+ ...rest
52
+ } = this.props;
53
+ const { isOpen } = this.state;
54
+ const popoverTargetClassname = cx("Dropdown", targetClassName);
55
+ return /* @__PURE__ */ React.createElement(Popover, {
56
+ isOpen,
57
+ placement,
58
+ onInteraction: onInteraction || this.handlePopoverInteraction,
59
+ restrictHeight: false,
60
+ disabled,
61
+ targetClassName: popoverTargetClassname,
62
+ ...rest
63
+ }, this.renderTrigger(), this.renderContent());
64
+ }
65
+ renderTrigger() {
66
+ return cloneElement(this.parseChildren().target, {
67
+ "aria-haspopup": true,
68
+ "aria-expanded": this.state.isOpen ? true : false,
69
+ ref: this.refHandlers.trigger,
70
+ isopen: this.state.isOpen?.toString()
71
+ });
72
+ }
73
+ renderContent() {
74
+ return cloneElement(this.parseChildren().content, {
75
+ onSelect: this.handleSelect
76
+ });
77
+ }
78
+ parseChildren() {
79
+ const [targetChild, contentChild] = Children.toArray(this.props.children);
80
+ return {
81
+ target: targetChild,
82
+ content: contentChild
83
+ };
84
+ }
85
+ updateState(state, selectedItem) {
86
+ const nextState = {};
87
+ const stateChanges = {};
88
+ this.setState((currentState) => {
89
+ Object.keys(state).forEach((key) => {
90
+ if (currentState[key] !== state[key]) {
91
+ stateChanges[key] = state[key];
92
+ }
93
+ if (!this.isControlledProp(key)) {
94
+ nextState[key] = state[key];
95
+ }
96
+ });
97
+ return nextState;
98
+ }, () => {
99
+ if (selectedItem !== void 0 && selectedItem !== null) {
100
+ this.props.onSelect?.(selectedItem, stateChanges);
101
+ }
102
+ if (Object.keys(stateChanges).length) {
103
+ this.props.onStateChange?.(stateChanges);
104
+ }
105
+ });
106
+ }
107
+ isControlledProp(key) {
108
+ return this.props[key] !== void 0;
109
+ }
110
+ };
111
+
112
+ // src/DropdownButton.tsx
113
+ import { Button as Button2 } from "@launchpad-ui/button";
114
+ import { ExpandMore, IconSize } from "@launchpad-ui/icons";
115
+ import { forwardRef } from "react";
116
+ var DropdownButton = forwardRef((props, ref) => {
117
+ const { children, hideCaret, ...rest } = props;
118
+ return /* @__PURE__ */ React.createElement(Button2, {
119
+ ...rest,
120
+ ref
121
+ }, children, " ", !hideCaret && /* @__PURE__ */ React.createElement(ExpandMore, {
122
+ size: IconSize.SMALL
123
+ }));
124
+ });
125
+ DropdownButton.displayName = "DropdownButton";
126
+ export {
127
+ Dropdown,
128
+ DropdownButton
129
+ };
130
+ //# sourceMappingURL=index.es.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../scripts/react-shim.js", "../src/Dropdown.tsx", "../src/DropdownButton.tsx"],
4
+ "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import type { PopoverPlacement } from '@launchpad-ui/popover';\n\nimport { Button } from '@launchpad-ui/button';\nimport { Popover } from '@launchpad-ui/popover';\nimport cx from 'clsx';\nimport { Children, cloneElement, Component } from 'react';\n\ntype DropdownState = {\n isOpen?: boolean;\n} & Record<string | number, unknown>;\n\ntype DropdownProps<T extends string | object | number> = {\n isOpen?: boolean;\n placement?: PopoverPlacement;\n onSelect?: (item: T, stateChanges: DropdownState) => void;\n onStateChange?: (state: Record<string | number, unknown>) => void;\n disabled?: boolean;\n targetClassName?: string;\n children: React.ReactNode;\n onInteraction?: (nextIsOpen: boolean) => void;\n popoverClassName?: string;\n enforceFocus?: boolean;\n [key: string]: unknown;\n};\n\nclass Dropdown<T extends string | object | number> extends Component<\n DropdownProps<T>,\n DropdownState\n> {\n triggerElement: HTMLElement | null | Button = null;\n refHandlers = {\n trigger: (node: HTMLElement | null | Button) => {\n this.triggerElement = node;\n },\n };\n\n state = {\n isOpen: this.isControlledProp('isOpen') ? (this.props.isOpen as boolean) : false,\n };\n\n componentDidUpdate(prevProps: DropdownProps<T>, prevState: DropdownState) {\n if (this.props.isOpen !== prevProps.isOpen) {\n this.setState({ isOpen: this.props.isOpen });\n }\n\n // Focus the button upon closing for convenient tabbing\n if (prevState.isOpen !== this.state.isOpen && this.state.isOpen === false) {\n requestAnimationFrame(() => {\n const current = this.triggerElement;\n if (!current) {\n return;\n }\n // Button can either be our own custom Button component or the native html button\n const buttonElement = (current instanceof Button && current.getDomElement?.()) || current;\n\n // If a dropdown menu item triggers a modal, we do not want to focus the trigger. Instead\n // we let the modal components control their own focus.\n // Note that this is not ideal since closing the modal will not cause the dropdown trigger\n // to regain focus.\n const hasModal = (buttonElement as HTMLElement)?.closest?.('.has-modal');\n\n !hasModal && buttonElement?.focus();\n });\n }\n }\n\n render() {\n const {\n placement,\n disabled,\n targetClassName,\n isOpen: isOpenProp,\n onInteraction,\n ...rest\n } = this.props;\n const { isOpen } = this.state;\n const popoverTargetClassname = cx('Dropdown', targetClassName);\n\n return (\n <Popover\n isOpen={isOpen}\n placement={placement}\n onInteraction={onInteraction || this.handlePopoverInteraction}\n restrictHeight={false}\n disabled={disabled}\n targetClassName={popoverTargetClassname}\n {...rest}\n >\n {this.renderTrigger()}\n {this.renderContent()}\n </Popover>\n );\n }\n\n renderTrigger() {\n return cloneElement(this.parseChildren().target, {\n 'aria-haspopup': true,\n 'aria-expanded': this.state.isOpen ? true : false,\n ref: this.refHandlers.trigger,\n isopen: this.state.isOpen?.toString(),\n });\n }\n\n renderContent() {\n return cloneElement(this.parseChildren().content, {\n onSelect: this.handleSelect,\n });\n }\n\n handleSelect = (item: T) => {\n this.updateState({ isOpen: false }, item);\n };\n\n handlePopoverInteraction = (nextIsOpen: boolean) => {\n this.updateState({ isOpen: nextIsOpen });\n };\n\n parseChildren() {\n const [targetChild, contentChild] = Children.toArray(this.props.children);\n return {\n target: targetChild as React.ReactElement,\n content: contentChild as React.ReactElement,\n };\n }\n\n updateState(state: DropdownState, selectedItem?: T) {\n const nextState: DropdownState = {};\n const stateChanges: DropdownState = {};\n this.setState(\n (currentState) => {\n Object.keys(state).forEach((key) => {\n if (currentState[key] !== state[key]) {\n stateChanges[key] = state[key];\n }\n\n if (!this.isControlledProp(key)) {\n nextState[key] = state[key];\n }\n });\n\n return nextState;\n },\n () => {\n if (selectedItem !== undefined && selectedItem !== null) {\n this.props.onSelect?.(selectedItem, stateChanges);\n }\n\n if (Object.keys(stateChanges).length) {\n this.props.onStateChange?.(stateChanges);\n }\n }\n );\n }\n\n isControlledProp(key: string) {\n return this.props[key] !== undefined;\n }\n}\n\nexport { Dropdown };\nexport type { DropdownProps };\n", "import { Button, ButtonKind, ButtonSize } from '@launchpad-ui/button';\nimport { ExpandMore, IconSize } from '@launchpad-ui/icons';\nimport { forwardRef } from 'react';\n\ntype DropdownButtonProps = {\n hideCaret?: boolean;\n kind?: ButtonKind;\n size?: ButtonSize;\n className?: string;\n disabled?: boolean;\n children?: React.ReactNode;\n onClick?(v: React.MouseEvent): void;\n tooltip?: string;\n tooltipOptions?: object;\n testId?: string;\n};\n\nconst DropdownButton = forwardRef<Button, DropdownButtonProps>((props, ref) => {\n const { children, hideCaret, ...rest } = props;\n\n return (\n <Button {...rest} ref={ref}>\n {children} {!hideCaret && <ExpandMore size={IconSize.SMALL} />}\n </Button>\n );\n});\n\nDropdownButton.displayName = 'DropdownButton';\n\nexport { DropdownButton };\nexport type { DropdownButtonProps };\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/dist/index.js ADDED
@@ -0,0 +1,160 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod));
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/index.ts
23
+ var src_exports = {};
24
+ __export(src_exports, {
25
+ Dropdown: () => Dropdown,
26
+ DropdownButton: () => DropdownButton
27
+ });
28
+ module.exports = __toCommonJS(src_exports);
29
+
30
+ // ../../scripts/react-shim.js
31
+ var React = __toESM(require("react"));
32
+
33
+ // src/Dropdown.tsx
34
+ var import_button = require("@launchpad-ui/button");
35
+ var import_popover = require("@launchpad-ui/popover");
36
+ var import_clsx = __toESM(require("clsx"));
37
+ var import_react = require("react");
38
+ var Dropdown = class extends import_react.Component {
39
+ constructor() {
40
+ super(...arguments);
41
+ this.triggerElement = null;
42
+ this.refHandlers = {
43
+ trigger: (node) => {
44
+ this.triggerElement = node;
45
+ }
46
+ };
47
+ this.state = {
48
+ isOpen: this.isControlledProp("isOpen") ? this.props.isOpen : false
49
+ };
50
+ this.handleSelect = (item) => {
51
+ this.updateState({ isOpen: false }, item);
52
+ };
53
+ this.handlePopoverInteraction = (nextIsOpen) => {
54
+ this.updateState({ isOpen: nextIsOpen });
55
+ };
56
+ }
57
+ componentDidUpdate(prevProps, prevState) {
58
+ if (this.props.isOpen !== prevProps.isOpen) {
59
+ this.setState({ isOpen: this.props.isOpen });
60
+ }
61
+ if (prevState.isOpen !== this.state.isOpen && this.state.isOpen === false) {
62
+ requestAnimationFrame(() => {
63
+ const current = this.triggerElement;
64
+ if (!current) {
65
+ return;
66
+ }
67
+ const buttonElement = current instanceof import_button.Button && current.getDomElement?.() || current;
68
+ const hasModal = buttonElement?.closest?.(".has-modal");
69
+ !hasModal && buttonElement?.focus();
70
+ });
71
+ }
72
+ }
73
+ render() {
74
+ const {
75
+ placement,
76
+ disabled,
77
+ targetClassName,
78
+ isOpen: isOpenProp,
79
+ onInteraction,
80
+ ...rest
81
+ } = this.props;
82
+ const { isOpen } = this.state;
83
+ const popoverTargetClassname = (0, import_clsx.default)("Dropdown", targetClassName);
84
+ return /* @__PURE__ */ React.createElement(import_popover.Popover, {
85
+ isOpen,
86
+ placement,
87
+ onInteraction: onInteraction || this.handlePopoverInteraction,
88
+ restrictHeight: false,
89
+ disabled,
90
+ targetClassName: popoverTargetClassname,
91
+ ...rest
92
+ }, this.renderTrigger(), this.renderContent());
93
+ }
94
+ renderTrigger() {
95
+ return (0, import_react.cloneElement)(this.parseChildren().target, {
96
+ "aria-haspopup": true,
97
+ "aria-expanded": this.state.isOpen ? true : false,
98
+ ref: this.refHandlers.trigger,
99
+ isopen: this.state.isOpen?.toString()
100
+ });
101
+ }
102
+ renderContent() {
103
+ return (0, import_react.cloneElement)(this.parseChildren().content, {
104
+ onSelect: this.handleSelect
105
+ });
106
+ }
107
+ parseChildren() {
108
+ const [targetChild, contentChild] = import_react.Children.toArray(this.props.children);
109
+ return {
110
+ target: targetChild,
111
+ content: contentChild
112
+ };
113
+ }
114
+ updateState(state, selectedItem) {
115
+ const nextState = {};
116
+ const stateChanges = {};
117
+ this.setState((currentState) => {
118
+ Object.keys(state).forEach((key) => {
119
+ if (currentState[key] !== state[key]) {
120
+ stateChanges[key] = state[key];
121
+ }
122
+ if (!this.isControlledProp(key)) {
123
+ nextState[key] = state[key];
124
+ }
125
+ });
126
+ return nextState;
127
+ }, () => {
128
+ if (selectedItem !== void 0 && selectedItem !== null) {
129
+ this.props.onSelect?.(selectedItem, stateChanges);
130
+ }
131
+ if (Object.keys(stateChanges).length) {
132
+ this.props.onStateChange?.(stateChanges);
133
+ }
134
+ });
135
+ }
136
+ isControlledProp(key) {
137
+ return this.props[key] !== void 0;
138
+ }
139
+ };
140
+
141
+ // src/DropdownButton.tsx
142
+ var import_button2 = require("@launchpad-ui/button");
143
+ var import_icons = require("@launchpad-ui/icons");
144
+ var import_react2 = require("react");
145
+ var DropdownButton = (0, import_react2.forwardRef)((props, ref) => {
146
+ const { children, hideCaret, ...rest } = props;
147
+ return /* @__PURE__ */ React.createElement(import_button2.Button, {
148
+ ...rest,
149
+ ref
150
+ }, children, " ", !hideCaret && /* @__PURE__ */ React.createElement(import_icons.ExpandMore, {
151
+ size: import_icons.IconSize.SMALL
152
+ }));
153
+ });
154
+ DropdownButton.displayName = "DropdownButton";
155
+ // Annotate the CommonJS export names for ESM import in node:
156
+ 0 && (module.exports = {
157
+ Dropdown,
158
+ DropdownButton
159
+ });
160
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts", "../../../scripts/react-shim.js", "../src/Dropdown.tsx", "../src/DropdownButton.tsx"],
4
+ "sourcesContent": ["export type { DropdownProps } from './Dropdown';\nexport type { DropdownButtonProps } from './DropdownButton';\nexport { Dropdown } from './Dropdown';\nexport { DropdownButton } from './DropdownButton';\n", "import * as React from 'react';\nexport { React };\n", "import type { PopoverPlacement } from '@launchpad-ui/popover';\n\nimport { Button } from '@launchpad-ui/button';\nimport { Popover } from '@launchpad-ui/popover';\nimport cx from 'clsx';\nimport { Children, cloneElement, Component } from 'react';\n\ntype DropdownState = {\n isOpen?: boolean;\n} & Record<string | number, unknown>;\n\ntype DropdownProps<T extends string | object | number> = {\n isOpen?: boolean;\n placement?: PopoverPlacement;\n onSelect?: (item: T, stateChanges: DropdownState) => void;\n onStateChange?: (state: Record<string | number, unknown>) => void;\n disabled?: boolean;\n targetClassName?: string;\n children: React.ReactNode;\n onInteraction?: (nextIsOpen: boolean) => void;\n popoverClassName?: string;\n enforceFocus?: boolean;\n [key: string]: unknown;\n};\n\nclass Dropdown<T extends string | object | number> extends Component<\n DropdownProps<T>,\n DropdownState\n> {\n triggerElement: HTMLElement | null | Button = null;\n refHandlers = {\n trigger: (node: HTMLElement | null | Button) => {\n this.triggerElement = node;\n },\n };\n\n state = {\n isOpen: this.isControlledProp('isOpen') ? (this.props.isOpen as boolean) : false,\n };\n\n componentDidUpdate(prevProps: DropdownProps<T>, prevState: DropdownState) {\n if (this.props.isOpen !== prevProps.isOpen) {\n this.setState({ isOpen: this.props.isOpen });\n }\n\n // Focus the button upon closing for convenient tabbing\n if (prevState.isOpen !== this.state.isOpen && this.state.isOpen === false) {\n requestAnimationFrame(() => {\n const current = this.triggerElement;\n if (!current) {\n return;\n }\n // Button can either be our own custom Button component or the native html button\n const buttonElement = (current instanceof Button && current.getDomElement?.()) || current;\n\n // If a dropdown menu item triggers a modal, we do not want to focus the trigger. Instead\n // we let the modal components control their own focus.\n // Note that this is not ideal since closing the modal will not cause the dropdown trigger\n // to regain focus.\n const hasModal = (buttonElement as HTMLElement)?.closest?.('.has-modal');\n\n !hasModal && buttonElement?.focus();\n });\n }\n }\n\n render() {\n const {\n placement,\n disabled,\n targetClassName,\n isOpen: isOpenProp,\n onInteraction,\n ...rest\n } = this.props;\n const { isOpen } = this.state;\n const popoverTargetClassname = cx('Dropdown', targetClassName);\n\n return (\n <Popover\n isOpen={isOpen}\n placement={placement}\n onInteraction={onInteraction || this.handlePopoverInteraction}\n restrictHeight={false}\n disabled={disabled}\n targetClassName={popoverTargetClassname}\n {...rest}\n >\n {this.renderTrigger()}\n {this.renderContent()}\n </Popover>\n );\n }\n\n renderTrigger() {\n return cloneElement(this.parseChildren().target, {\n 'aria-haspopup': true,\n 'aria-expanded': this.state.isOpen ? true : false,\n ref: this.refHandlers.trigger,\n isopen: this.state.isOpen?.toString(),\n });\n }\n\n renderContent() {\n return cloneElement(this.parseChildren().content, {\n onSelect: this.handleSelect,\n });\n }\n\n handleSelect = (item: T) => {\n this.updateState({ isOpen: false }, item);\n };\n\n handlePopoverInteraction = (nextIsOpen: boolean) => {\n this.updateState({ isOpen: nextIsOpen });\n };\n\n parseChildren() {\n const [targetChild, contentChild] = Children.toArray(this.props.children);\n return {\n target: targetChild as React.ReactElement,\n content: contentChild as React.ReactElement,\n };\n }\n\n updateState(state: DropdownState, selectedItem?: T) {\n const nextState: DropdownState = {};\n const stateChanges: DropdownState = {};\n this.setState(\n (currentState) => {\n Object.keys(state).forEach((key) => {\n if (currentState[key] !== state[key]) {\n stateChanges[key] = state[key];\n }\n\n if (!this.isControlledProp(key)) {\n nextState[key] = state[key];\n }\n });\n\n return nextState;\n },\n () => {\n if (selectedItem !== undefined && selectedItem !== null) {\n this.props.onSelect?.(selectedItem, stateChanges);\n }\n\n if (Object.keys(stateChanges).length) {\n this.props.onStateChange?.(stateChanges);\n }\n }\n );\n }\n\n isControlledProp(key: string) {\n return this.props[key] !== undefined;\n }\n}\n\nexport { Dropdown };\nexport type { DropdownProps };\n", "import { Button, ButtonKind, ButtonSize } from '@launchpad-ui/button';\nimport { ExpandMore, IconSize } from '@launchpad-ui/icons';\nimport { forwardRef } from 'react';\n\ntype DropdownButtonProps = {\n hideCaret?: boolean;\n kind?: ButtonKind;\n size?: ButtonSize;\n className?: string;\n disabled?: boolean;\n children?: React.ReactNode;\n onClick?(v: React.MouseEvent): void;\n tooltip?: string;\n tooltipOptions?: object;\n testId?: string;\n};\n\nconst DropdownButton = forwardRef<Button, DropdownButtonProps>((props, ref) => {\n const { children, hideCaret, ...rest } = props;\n\n return (\n <Button {...rest} ref={ref}>\n {children} {!hideCaret && <ExpandMore size={IconSize.SMALL} />}\n </Button>\n );\n});\n\nDropdownButton.displayName = 'DropdownButton';\n\nexport { DropdownButton };\nexport type { DropdownButtonProps };\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@launchpad-ui/dropdown",
3
+ "version": "0.1.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "An element that displays a list of actions or options to a user.",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "main": "dist/index.js",
12
+ "module": "dist/index.es.js",
13
+ "types": "dist/index.d.ts",
14
+ "sideEffects": [
15
+ "**/*.css"
16
+ ],
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.es.js",
21
+ "require": "./dist/index.js"
22
+ },
23
+ "./package.json": "./package.json"
24
+ },
25
+ "source": "src/index.ts",
26
+ "dependencies": {
27
+ "@launchpad-ui/button": "~0.1.6",
28
+ "@launchpad-ui/icons": "~0.1.5",
29
+ "@launchpad-ui/popover": "~0.1.2",
30
+ "@launchpad-ui/tokens": "~0.1.4",
31
+ "clsx": "^1.1.1"
32
+ },
33
+ "peerDependencies": {
34
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
35
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "react": "^18.1.0",
39
+ "react-dom": "^18.1.0"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc --project tsconfig.build.json && node build.js",
43
+ "clean": "rm -rf dist",
44
+ "e2e": "exit 0",
45
+ "lint": "eslint '**/*.{ts,tsx,js}'",
46
+ "test": "vitest run --coverage"
47
+ }
48
+ }