Skip to content

Commit 3f7eb7a

Browse files
committed
Merge branch 'actions-preflight-panel'
* actions-preflight-panel: Added changelog entries Allow item select to be nullable Split out item select component Updated action components More efficient grid Added actions Added details Separated action panel and item select A fairly nice select panel Simplified actions management Some more structure to action preflight panel Basic actions management
2 parents 738a019 + 9ea48eb commit 3f7eb7a

File tree

16 files changed

+740
-8
lines changed

16 files changed

+740
-8
lines changed

global.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace global {
2+
declare module "*.module.scss" {
3+
const content: { [className: string]: string };
4+
export default content;
5+
}
6+
7+
declare module "*.module.sass" {
8+
const content: { [className: string]: string };
9+
export default content;
10+
}
11+
}

packages/form-components/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## [0.2.0] - 2025-02-20
4+
5+
- Add `ItemSelect` component for general selections (items must have a `name`
6+
property)
7+
- Add `ActionPreflight` component for staging actions that require configuration
8+
and confirmation
9+
- Add appropriate stories for new components
10+
311
## [0.1.2] - 2025-02-15
412

513
Added `files` specifier to `package.json` to ensure that all `dist` files are

packages/form-components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@macrostrat/form-components",
3-
"version": "0.1.2",
3+
"version": "0.2.0",
44
"description": "Form components for user input into Macrostrat apps",
55
"type": "module",
66
"source": "src/index.ts",
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import type { Meta, StoryFn, StoryObj } from "@storybook/react";
2+
import h from "@macrostrat/hyper";
3+
import { ActionDef, ActionsPreflightPanel } from ".";
4+
import Box from "ui-box";
5+
import { FormGroup, NumericInput, Spinner } from "@blueprintjs/core";
6+
import {
7+
NullableSlider,
8+
ToasterContext,
9+
useToaster,
10+
} from "@macrostrat/ui-components";
11+
import {
12+
BaseDataTypeSelect,
13+
exampleMapLayers,
14+
MapLayer,
15+
} from "../item-select/examples";
16+
import { ItemSelect } from "../item-select";
17+
18+
export enum SelectionActionType {
19+
Delete = "delete",
20+
Heal = "heal",
21+
ChangeType = "changeType",
22+
ChangeLayer = "changeLayer",
23+
AdjustWidth = "adjustWidth",
24+
AdjustCertainty = "adjustCertainty",
25+
ReverseLines = "reverseLines",
26+
RecalculateTopology = "recalculateTopology",
27+
}
28+
29+
type MapboardActionDef =
30+
| ActionDef<SelectionActionType.Delete>
31+
| ActionDef<SelectionActionType.Heal>
32+
| ActionDef<SelectionActionType.RecalculateTopology>
33+
| ActionDef<SelectionActionType.ChangeType, string>
34+
| ActionDef<SelectionActionType.ChangeLayer, ChangeLayerState>
35+
| ActionDef<SelectionActionType.AdjustWidth, number>
36+
| ActionDef<SelectionActionType.AdjustCertainty, number | null>
37+
| ActionDef<SelectionActionType.ReverseLines>;
38+
39+
const actions: MapboardActionDef[] = [
40+
{
41+
id: SelectionActionType.Delete,
42+
name: "Delete",
43+
icon: "trash",
44+
description: "Delete selected features",
45+
intent: "danger",
46+
},
47+
{
48+
id: SelectionActionType.Heal,
49+
name: "Heal",
50+
icon: "changes",
51+
description: "Heal selected features",
52+
},
53+
{
54+
id: SelectionActionType.RecalculateTopology,
55+
name: "Recalculate topology",
56+
icon: "polygon-filter",
57+
description: "Recalculate the topology of selected features",
58+
},
59+
{
60+
id: SelectionActionType.ChangeType,
61+
name: "Change type",
62+
icon: "edit",
63+
detailsForm: ChangeDataTypeForm,
64+
isReady(state) {
65+
return state != null;
66+
},
67+
},
68+
{
69+
id: SelectionActionType.ChangeLayer,
70+
name: "Change layer",
71+
icon: "layers",
72+
detailsForm: ChangeLayerForm,
73+
isReady(state) {
74+
return state?.selectedLayerID != null;
75+
},
76+
},
77+
{
78+
id: SelectionActionType.AdjustWidth,
79+
name: "Adjust width",
80+
icon: "horizontal-distribution",
81+
disabled: true,
82+
detailsForm: AdjustWidthForm,
83+
defaultState: 5,
84+
isReady(state) {
85+
return state != null;
86+
},
87+
},
88+
{
89+
id: SelectionActionType.AdjustCertainty,
90+
name: "Adjust certainty",
91+
icon: "confirm",
92+
disabled: true,
93+
detailsForm: AdjustCertaintyForm,
94+
},
95+
{
96+
id: SelectionActionType.ReverseLines,
97+
name: "Reverse lines",
98+
icon: "swap-horizontal",
99+
disabled: true,
100+
},
101+
];
102+
103+
function InstrumentedActionsPanel(props) {
104+
const Toaster = useToaster();
105+
return h(ActionsPreflightPanel, {
106+
onRunAction(action: ActionDef, state) {
107+
Toaster.show({
108+
message: h("div.action", [
109+
h("h3", action.name),
110+
h("pre", JSON.stringify(state, null, 2)),
111+
]),
112+
});
113+
},
114+
...props,
115+
});
116+
}
117+
118+
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
119+
export default {
120+
title: "Form components/Actions preflight",
121+
component: InstrumentedActionsPanel,
122+
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
123+
argTypes: {},
124+
decorators: [
125+
(Story: StoryFn<typeof ActionsPreflightPanel>) =>
126+
h(Box, { width: "450px" }, h(ToasterContext, h(Story))),
127+
],
128+
} as Meta<typeof ActionsPreflightPanel>;
129+
130+
export const Primary: StoryObj<typeof ActionsPreflightPanel> = {
131+
args: {
132+
actions,
133+
},
134+
};
135+
136+
export const Compact: StoryObj<typeof ActionsPreflightPanel> = {
137+
args: {
138+
actions,
139+
compact: true,
140+
},
141+
};
142+
143+
interface ChangeLayerState {
144+
selectedLayerID: number;
145+
}
146+
147+
function ChangeDataTypeForm({ state, setState }) {
148+
return h(BaseDataTypeSelect, { state, setState });
149+
}
150+
151+
function ChangeLayerForm({
152+
state,
153+
setState,
154+
}: {
155+
state: ChangeLayerState | null;
156+
setState(state: ChangeLayerState): void;
157+
}) {
158+
const layers = exampleMapLayers;
159+
const currentLayer = null;
160+
161+
const selectedLayerID = state?.selectedLayerID ?? currentLayer;
162+
const possibleLayers = layers.filter((d) => d.id != selectedLayerID);
163+
const currentLayerItem = layers.find((d) => d.id == selectedLayerID);
164+
165+
return h(ItemSelect<MapLayer>, {
166+
items: possibleLayers,
167+
selectedItem: currentLayerItem,
168+
onSelectItem: (layer) => {
169+
setState({ selectedLayerID: layer.id });
170+
},
171+
label: "layer",
172+
icon: "layers",
173+
});
174+
}
175+
176+
function AdjustWidthForm({ state, setState }) {
177+
return h(
178+
FormGroup,
179+
{ label: "Width", labelInfo: "pixels" },
180+
h(NumericInput, {
181+
min: 0,
182+
max: 10,
183+
value: state,
184+
majorStepSize: 1,
185+
minorStepSize: 0.2,
186+
onValueChange(value) {
187+
setState(Math.max(Math.min(value, 10), 0));
188+
},
189+
})
190+
);
191+
}
192+
193+
function AdjustCertaintyForm({ state, setState }) {
194+
return h(
195+
FormGroup,
196+
{ label: "Certainty" },
197+
h(NullableSlider, {
198+
min: 0,
199+
max: 10,
200+
value: state,
201+
onChange(value) {
202+
setState(value);
203+
},
204+
})
205+
);
206+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
.actions-preflight {
2+
position: relative;
3+
display: grid;
4+
grid-template-columns: auto 1fr;
5+
grid-template-rows: auto 1fr;
6+
gap: 1rem;
7+
&.compact {
8+
display: grid;
9+
10+
grid-template-columns: 1fr auto;
11+
12+
.actions-list {
13+
grid-column: 1/2;
14+
grid-row: span 1;
15+
width: 100%;
16+
:global(.bp5-menu) {
17+
width: 100%;
18+
}
19+
}
20+
21+
.action-details {
22+
grid-column: span 2;
23+
grid-row: 2/2;
24+
}
25+
26+
.action-button {
27+
grid-column: 2/2;
28+
grid-row: 1/1;
29+
}
30+
}
31+
}
32+
33+
.actions-list {
34+
grid-column: 1/2;
35+
grid-row: span 2;
36+
37+
:global(.bp5-menu) {
38+
border-radius: 6px;
39+
width: 12em;
40+
}
41+
}
42+
43+
.description {
44+
color: var(--secondary-color);
45+
}
46+
47+
.action-details {
48+
min-height: 100%;
49+
grid-column: 2/2;
50+
box-shadow: none;
51+
padding: 1rem;
52+
border-radius: 6px;
53+
position: relative;
54+
background-color: var(--panel-secondary-background-color);
55+
&:global(.bp5-non-ideal-state) {
56+
grid-row: span 2;
57+
}
58+
&>:first-child {
59+
margin-top: 0;
60+
}
61+
&>:last-child {
62+
margin-bottom: 0;
63+
}
64+
}
65+
66+
.spacer {
67+
flex-grow: 1;
68+
}
69+
70+
.action-button:global(.bp5-button) {
71+
border-radius: 6px;
72+
:global(.bp5-button-text) {
73+
flex: 1 1 auto;
74+
font-weight: 600;
75+
}
76+
}

0 commit comments

Comments
 (0)