basefn 1.3.0 → 1.4.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.
Files changed (42) hide show
  1. package/package.json +3 -3
  2. package/src/Basefn.res +50 -0
  3. package/src/Basefn.res.mjs +40 -0
  4. package/src/components/Basefn__AlertDialog.css +119 -0
  5. package/src/components/Basefn__AlertDialog.res +80 -0
  6. package/src/components/Basefn__AlertDialog.res.mjs +106 -0
  7. package/src/components/Basefn__AppLayout.css +10 -0
  8. package/src/components/Basefn__AspectRatio.css +16 -0
  9. package/src/components/Basefn__AspectRatio.res +26 -0
  10. package/src/components/Basefn__AspectRatio.res.mjs +33 -0
  11. package/src/components/Basefn__ButtonGroup.css +58 -0
  12. package/src/components/Basefn__ButtonGroup.res +33 -0
  13. package/src/components/Basefn__ButtonGroup.res.mjs +38 -0
  14. package/src/components/Basefn__Card.css +15 -0
  15. package/src/components/Basefn__ContextMenu.css +78 -0
  16. package/src/components/Basefn__ContextMenu.res +98 -0
  17. package/src/components/Basefn__ContextMenu.res.mjs +93 -0
  18. package/src/components/Basefn__Drawer.css +31 -0
  19. package/src/components/Basefn__Dropdown.res +3 -1
  20. package/src/components/Basefn__Dropdown.res.mjs +22 -43
  21. package/src/components/Basefn__HoverCard.css +93 -0
  22. package/src/components/Basefn__HoverCard.res +99 -0
  23. package/src/components/Basefn__HoverCard.res.mjs +109 -0
  24. package/src/components/Basefn__Modal.css +38 -0
  25. package/src/components/Basefn__Popover.css +98 -0
  26. package/src/components/Basefn__Popover.res +95 -0
  27. package/src/components/Basefn__Popover.res.mjs +107 -0
  28. package/src/components/Basefn__ScrollArea.css +76 -0
  29. package/src/components/Basefn__ScrollArea.res +60 -0
  30. package/src/components/Basefn__ScrollArea.res.mjs +63 -0
  31. package/src/components/Basefn__Skeleton.css +83 -0
  32. package/src/components/Basefn__Skeleton.res +55 -0
  33. package/src/components/Basefn__Skeleton.res.mjs +62 -0
  34. package/src/components/Basefn__Stepper.css +36 -0
  35. package/src/components/Basefn__Tabs.css +18 -0
  36. package/src/components/Basefn__Toggle.css +87 -0
  37. package/src/components/Basefn__Toggle.res +65 -0
  38. package/src/components/Basefn__Toggle.res.mjs +71 -0
  39. package/src/components/Basefn__ToggleGroup.css +110 -0
  40. package/src/components/Basefn__ToggleGroup.res +106 -0
  41. package/src/components/Basefn__ToggleGroup.res.mjs +89 -0
  42. package/src/components/Basefn__Topbar.css +44 -0
@@ -48,3 +48,18 @@
48
48
  box-shadow: var(--basefn-shadow-lg);
49
49
  transform: translateY(-2px);
50
50
  }
51
+
52
+ /* Mobile responsiveness */
53
+ @media (max-width: 768px) {
54
+ .basefn-card__header {
55
+ padding: 0.75rem 1rem;
56
+ }
57
+
58
+ .basefn-card__body {
59
+ padding: 1rem;
60
+ }
61
+
62
+ .basefn-card__footer {
63
+ padding: 0.75rem 1rem;
64
+ }
65
+ }
@@ -0,0 +1,78 @@
1
+ @import '../styles/variables.css';
2
+
3
+ .basefn-context-menu {
4
+ display: contents;
5
+ }
6
+
7
+ .basefn-context-menu__backdrop {
8
+ position: fixed;
9
+ inset: 0;
10
+ z-index: 99;
11
+ }
12
+
13
+ .basefn-context-menu__menu {
14
+ position: fixed;
15
+ z-index: 100;
16
+ min-width: 180px;
17
+ background: var(--basefn-surface-elevated);
18
+ border: var(--basefn-border-width) solid var(--basefn-border-primary);
19
+ border-radius: var(--basefn-radius-lg);
20
+ box-shadow: var(--basefn-shadow-lg);
21
+ padding: var(--basefn-spacing-xs);
22
+ animation: basefn-context-menu-fade-in var(--basefn-transition-fast);
23
+ }
24
+
25
+ @keyframes basefn-context-menu-fade-in {
26
+ from {
27
+ opacity: 0;
28
+ transform: scale(0.95);
29
+ }
30
+ to {
31
+ opacity: 1;
32
+ transform: scale(1);
33
+ }
34
+ }
35
+
36
+ .basefn-context-menu__item {
37
+ display: flex;
38
+ align-items: center;
39
+ width: 100%;
40
+ padding: var(--basefn-spacing-sm) var(--basefn-spacing-md);
41
+ font-family: var(--basefn-font-family);
42
+ font-size: var(--basefn-font-size-sm);
43
+ color: var(--basefn-text-primary);
44
+ background: transparent;
45
+ border: none;
46
+ border-radius: var(--basefn-radius-sm);
47
+ cursor: pointer;
48
+ text-align: left;
49
+ transition: background var(--basefn-transition-fast);
50
+ }
51
+
52
+ .basefn-context-menu__item:hover:not(.basefn-context-menu__item--disabled) {
53
+ background: var(--basefn-bg-tertiary);
54
+ }
55
+
56
+ .basefn-context-menu__item:focus-visible {
57
+ outline: none;
58
+ background: var(--basefn-bg-tertiary);
59
+ }
60
+
61
+ .basefn-context-menu__item--disabled {
62
+ opacity: 0.5;
63
+ cursor: not-allowed;
64
+ }
65
+
66
+ .basefn-context-menu__item--danger {
67
+ color: var(--basefn-color-error);
68
+ }
69
+
70
+ .basefn-context-menu__item--danger:hover:not(.basefn-context-menu__item--disabled) {
71
+ background: var(--basefn-alert-error-bg);
72
+ }
73
+
74
+ .basefn-context-menu__separator {
75
+ height: 1px;
76
+ margin: var(--basefn-spacing-xs) 0;
77
+ background: var(--basefn-border-primary);
78
+ }
@@ -0,0 +1,98 @@
1
+ %%raw(`import './Basefn__ContextMenu.css'`)
2
+
3
+ open Xote
4
+
5
+ type menuItem = {
6
+ label: string,
7
+ onClick: unit => unit,
8
+ disabled?: bool,
9
+ danger?: bool,
10
+ }
11
+
12
+ type menuContent =
13
+ | Item(menuItem)
14
+ | Separator
15
+
16
+ @jsx.component
17
+ let make = (
18
+ ~trigger: Component.node,
19
+ ~items: array<menuContent>,
20
+ ~className: option<string>=?,
21
+ ) => {
22
+ let isOpen = Signal.make(false)
23
+ let position = Signal.make((0, 0))
24
+
25
+ let handleContextMenu = evt => {
26
+ let _ = Obj.magic(evt)["preventDefault"]()
27
+ let x: int = Obj.magic(evt)["clientX"]
28
+ let y: int = Obj.magic(evt)["clientY"]
29
+ Signal.set(position, (x, y))
30
+ Signal.set(isOpen, true)
31
+ }
32
+
33
+ let handleClose = () => {
34
+ Signal.set(isOpen, false)
35
+ }
36
+
37
+ let handleItemClick = (onClick: unit => unit, disabled: bool) => {
38
+ switch disabled {
39
+ | true => ()
40
+ | false => {
41
+ onClick()
42
+ handleClose()
43
+ }
44
+ }
45
+ }
46
+
47
+ let getWrapperClass = () => {
48
+ let baseClass = "basefn-context-menu"
49
+ let customClass = switch className {
50
+ | Some(c) => " " ++ c
51
+ | None => ""
52
+ }
53
+ baseClass ++ customClass
54
+ }
55
+
56
+ let menuContent = Computed.make(() => {
57
+ if Signal.get(isOpen) {
58
+ let (x, y) = Signal.get(position)
59
+ let styleStr = "left: " ++ Int.toString(x) ++ "px; top: " ++ Int.toString(y) ++ "px;"
60
+ [
61
+ <div class="basefn-context-menu__backdrop" onClick={_ => handleClose()} />,
62
+ <div class="basefn-context-menu__menu" style={styleStr}>
63
+ {items
64
+ ->Array.mapWithIndex((item, index) => {
65
+ switch item {
66
+ | Item({label, onClick, ?disabled, ?danger}) => {
67
+ let disabled = disabled->Option.getOr(false)
68
+ let danger = danger->Option.getOr(false)
69
+ let itemClass =
70
+ "basefn-context-menu__item" ++
71
+ (disabled ? " basefn-context-menu__item--disabled" : "") ++
72
+ (danger ? " basefn-context-menu__item--danger" : "")
73
+
74
+ <button
75
+ key={Int.toString(index)}
76
+ class={itemClass}
77
+ onClick={_ => handleItemClick(onClick, disabled)}
78
+ disabled={disabled}
79
+ >
80
+ {Component.text(label)}
81
+ </button>
82
+ }
83
+ | Separator => <div key={Int.toString(index)} class="basefn-context-menu__separator" />
84
+ }
85
+ })
86
+ ->Component.fragment}
87
+ </div>,
88
+ ]
89
+ } else {
90
+ []
91
+ }
92
+ })
93
+
94
+ <div class={getWrapperClass()} onContextMenu={handleContextMenu}>
95
+ {trigger}
96
+ {Component.signalFragment(menuContent)}
97
+ </div>
98
+ }
@@ -0,0 +1,93 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Xote from "xote/src/Xote.res.mjs";
4
+ import * as Xote__JSX from "xote/src/Xote__JSX.res.mjs";
5
+ import * as Core__Option from "@rescript/core/src/Core__Option.res.mjs";
6
+
7
+ import './Basefn__ContextMenu.css'
8
+ ;
9
+
10
+ function Basefn__ContextMenu(props) {
11
+ let className = props.className;
12
+ let items = props.items;
13
+ let isOpen = Xote.Signal.make(false, undefined, undefined);
14
+ let position = Xote.Signal.make([
15
+ 0,
16
+ 0
17
+ ], undefined, undefined);
18
+ let handleContextMenu = evt => {
19
+ evt.preventDefault();
20
+ let x = evt.clientX;
21
+ let y = evt.clientY;
22
+ Xote.Signal.set(position, [
23
+ x,
24
+ y
25
+ ]);
26
+ Xote.Signal.set(isOpen, true);
27
+ };
28
+ let getWrapperClass = () => {
29
+ let customClass = className !== undefined ? " " + className : "";
30
+ return "basefn-context-menu" + customClass;
31
+ };
32
+ let menuContent = Xote.Computed.make(() => {
33
+ if (!Xote.Signal.get(isOpen)) {
34
+ return [];
35
+ }
36
+ let match = Xote.Signal.get(position);
37
+ let styleStr = "left: " + match[0].toString() + "px; top: " + match[1].toString() + "px;";
38
+ return [
39
+ Xote__JSX.Elements.jsx("div", {
40
+ class: "basefn-context-menu__backdrop",
41
+ onClick: param => Xote.Signal.set(isOpen, false)
42
+ }),
43
+ Xote__JSX.Elements.jsx("div", {
44
+ class: "basefn-context-menu__menu",
45
+ style: styleStr,
46
+ children: Xote.Component.fragment(items.map((item, index) => {
47
+ if (typeof item !== "object") {
48
+ return Xote__JSX.Elements.jsxKeyed("div", {
49
+ class: "basefn-context-menu__separator"
50
+ }, index.toString(), undefined);
51
+ }
52
+ let match = item._0;
53
+ let onClick = match.onClick;
54
+ let disabled = Core__Option.getOr(match.disabled, false);
55
+ let danger = Core__Option.getOr(match.danger, false);
56
+ let itemClass = "basefn-context-menu__item" + (
57
+ disabled ? " basefn-context-menu__item--disabled" : ""
58
+ ) + (
59
+ danger ? " basefn-context-menu__item--danger" : ""
60
+ );
61
+ return Xote__JSX.Elements.jsxKeyed("button", {
62
+ class: itemClass,
63
+ disabled: disabled,
64
+ onClick: param => {
65
+ if (disabled) {
66
+ return;
67
+ } else {
68
+ onClick();
69
+ return Xote.Signal.set(isOpen, false);
70
+ }
71
+ },
72
+ children: Xote.Component.text(match.label)
73
+ }, index.toString(), undefined);
74
+ }))
75
+ })
76
+ ];
77
+ }, undefined);
78
+ return Xote__JSX.Elements.jsxs("div", {
79
+ class: getWrapperClass(),
80
+ onContextMenu: handleContextMenu,
81
+ children: Xote__JSX.array([
82
+ props.trigger,
83
+ Xote.Component.signalFragment(menuContent)
84
+ ])
85
+ });
86
+ }
87
+
88
+ let make = Basefn__ContextMenu;
89
+
90
+ export {
91
+ make,
92
+ }
93
+ /* Not a pure module */
@@ -166,3 +166,34 @@
166
166
  transform: translateY(0);
167
167
  }
168
168
  }
169
+
170
+ /* Mobile responsiveness */
171
+ @media (max-width: 768px) {
172
+ .basefn-drawer--left,
173
+ .basefn-drawer--right {
174
+ width: 85vw;
175
+ max-width: 320px;
176
+ }
177
+
178
+ .basefn-drawer--sm.basefn-drawer--left,
179
+ .basefn-drawer--sm.basefn-drawer--right,
180
+ .basefn-drawer--md.basefn-drawer--left,
181
+ .basefn-drawer--md.basefn-drawer--right,
182
+ .basefn-drawer--lg.basefn-drawer--left,
183
+ .basefn-drawer--lg.basefn-drawer--right {
184
+ width: 85vw;
185
+ max-width: 320px;
186
+ }
187
+
188
+ .basefn-drawer__header {
189
+ padding: 1rem;
190
+ }
191
+
192
+ .basefn-drawer__body {
193
+ padding: 1rem;
194
+ }
195
+
196
+ .basefn-drawer__footer {
197
+ padding: 1rem;
198
+ }
199
+ }
@@ -51,7 +51,9 @@ let make = (
51
51
  {items
52
52
  ->Array.mapWithIndex((item, index) => {
53
53
  switch item {
54
- | Item({label, onClick, disabled, danger}) => {
54
+ | Item({label, onClick, ?disabled, ?danger}) => {
55
+ let disabled = disabled->Option.getOr(false)
56
+ let danger = danger->Option.getOr(false)
55
57
  let className =
56
58
  "basefn-dropdown__item" ++
57
59
  (disabled ? " basefn-dropdown__item--disabled" : "") ++ (
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as Xote from "xote/src/Xote.res.mjs";
4
4
  import * as Xote__JSX from "xote/src/Xote__JSX.res.mjs";
5
+ import * as Core__Option from "@rescript/core/src/Core__Option.res.mjs";
5
6
 
6
7
  import './Basefn__Dropdown.css'
7
8
  ;
@@ -27,49 +28,27 @@ function Basefn__Dropdown(props) {
27
28
  }, index.toString(), undefined);
28
29
  }
29
30
  let match = item._0;
30
- let disabled = match.disabled;
31
- if (disabled !== undefined) {
32
- let danger = match.danger;
33
- if (danger !== undefined) {
34
- let onClick = match.onClick;
35
- let className = "basefn-dropdown__item" + (
36
- disabled ? " basefn-dropdown__item--disabled" : ""
37
- ) + (
38
- danger ? " basefn-dropdown__item--danger" : ""
39
- );
40
- return Xote__JSX.Elements.jsxKeyed("button", {
41
- class: className,
42
- disabled: disabled,
43
- onClick: param => {
44
- if (disabled) {
45
- return;
46
- } else {
47
- onClick();
48
- return Xote.Signal.set(isOpen, false);
49
- }
50
- },
51
- children: Xote.Component.text(match.label)
52
- }, index.toString(), undefined);
53
- }
54
- throw {
55
- RE_EXN_ID: "Match_failure",
56
- _1: [
57
- "Basefn__Dropdown.res",
58
- 53,
59
- 12
60
- ],
61
- Error: new Error()
62
- };
63
- }
64
- throw {
65
- RE_EXN_ID: "Match_failure",
66
- _1: [
67
- "Basefn__Dropdown.res",
68
- 53,
69
- 12
70
- ],
71
- Error: new Error()
72
- };
31
+ let onClick = match.onClick;
32
+ let disabled = Core__Option.getOr(match.disabled, false);
33
+ let danger = Core__Option.getOr(match.danger, false);
34
+ let className = "basefn-dropdown__item" + (
35
+ disabled ? " basefn-dropdown__item--disabled" : ""
36
+ ) + (
37
+ danger ? " basefn-dropdown__item--danger" : ""
38
+ );
39
+ return Xote__JSX.Elements.jsxKeyed("button", {
40
+ class: className,
41
+ disabled: disabled,
42
+ onClick: param => {
43
+ if (disabled) {
44
+ return;
45
+ } else {
46
+ onClick();
47
+ return Xote.Signal.set(isOpen, false);
48
+ }
49
+ },
50
+ children: Xote.Component.text(match.label)
51
+ }, index.toString(), undefined);
73
52
  }))
74
53
  })];
75
54
  } else {
@@ -0,0 +1,93 @@
1
+ @import '../styles/variables.css';
2
+
3
+ .basefn-hover-card {
4
+ position: relative;
5
+ display: inline-block;
6
+ }
7
+
8
+ .basefn-hover-card__trigger {
9
+ cursor: pointer;
10
+ }
11
+
12
+ .basefn-hover-card__content {
13
+ position: absolute;
14
+ z-index: 50;
15
+ background: var(--basefn-surface-elevated);
16
+ border: var(--basefn-border-width) solid var(--basefn-border-primary);
17
+ border-radius: var(--basefn-radius-lg);
18
+ box-shadow: var(--basefn-shadow-lg);
19
+ padding: var(--basefn-spacing-lg);
20
+ min-width: 280px;
21
+ max-width: 320px;
22
+ animation: basefn-hover-card-fade-in var(--basefn-transition-fast);
23
+ }
24
+
25
+ @keyframes basefn-hover-card-fade-in {
26
+ from {
27
+ opacity: 0;
28
+ transform: scale(0.95);
29
+ }
30
+ to {
31
+ opacity: 1;
32
+ transform: scale(1);
33
+ }
34
+ }
35
+
36
+ /* Position: Bottom */
37
+ .basefn-hover-card__content--bottom {
38
+ top: 100%;
39
+ margin-top: var(--basefn-spacing-sm);
40
+ }
41
+
42
+ /* Position: Top */
43
+ .basefn-hover-card__content--top {
44
+ bottom: 100%;
45
+ margin-bottom: var(--basefn-spacing-sm);
46
+ }
47
+
48
+ /* Position: Left */
49
+ .basefn-hover-card__content--left {
50
+ right: 100%;
51
+ top: 50%;
52
+ transform: translateY(-50%);
53
+ margin-right: var(--basefn-spacing-sm);
54
+ }
55
+
56
+ /* Position: Right */
57
+ .basefn-hover-card__content--right {
58
+ left: 100%;
59
+ top: 50%;
60
+ transform: translateY(-50%);
61
+ margin-left: var(--basefn-spacing-sm);
62
+ }
63
+
64
+ /* Alignment for top/bottom positions */
65
+ .basefn-hover-card__content--bottom.basefn-hover-card__content--align-start,
66
+ .basefn-hover-card__content--top.basefn-hover-card__content--align-start {
67
+ left: 0;
68
+ }
69
+
70
+ .basefn-hover-card__content--bottom.basefn-hover-card__content--align-center,
71
+ .basefn-hover-card__content--top.basefn-hover-card__content--align-center {
72
+ left: 50%;
73
+ transform: translateX(-50%);
74
+ }
75
+
76
+ .basefn-hover-card__content--bottom.basefn-hover-card__content--align-end,
77
+ .basefn-hover-card__content--top.basefn-hover-card__content--align-end {
78
+ right: 0;
79
+ }
80
+
81
+ /* Alignment for left/right positions */
82
+ .basefn-hover-card__content--left.basefn-hover-card__content--align-start,
83
+ .basefn-hover-card__content--right.basefn-hover-card__content--align-start {
84
+ top: 0;
85
+ transform: none;
86
+ }
87
+
88
+ .basefn-hover-card__content--left.basefn-hover-card__content--align-end,
89
+ .basefn-hover-card__content--right.basefn-hover-card__content--align-end {
90
+ top: auto;
91
+ bottom: 0;
92
+ transform: none;
93
+ }
@@ -0,0 +1,99 @@
1
+ %%raw(`import './Basefn__HoverCard.css'`)
2
+
3
+ open Xote
4
+
5
+ type position = Top | Bottom | Left | Right
6
+
7
+ type align = Start | Center | End
8
+
9
+ let positionToString = (position: position) => {
10
+ switch position {
11
+ | Top => "top"
12
+ | Bottom => "bottom"
13
+ | Left => "left"
14
+ | Right => "right"
15
+ }
16
+ }
17
+
18
+ let alignToString = (align: align) => {
19
+ switch align {
20
+ | Start => "start"
21
+ | Center => "center"
22
+ | End => "end"
23
+ }
24
+ }
25
+
26
+ @jsx.component
27
+ let make = (
28
+ ~trigger: Component.node,
29
+ ~content: Component.node,
30
+ ~position: position=Bottom,
31
+ ~align: align=Center,
32
+ ~openDelay: int=200,
33
+ ~closeDelay: int=100,
34
+ ~className: option<string>=?,
35
+ ) => {
36
+ let isOpen = Signal.make(false)
37
+ let timeoutId: ref<option<Js.Global.timeoutId>> = ref(None)
38
+
39
+ let clearExistingTimeout = () => {
40
+ switch timeoutId.contents {
41
+ | Some(id) => {
42
+ Js.Global.clearTimeout(id)
43
+ timeoutId := None
44
+ }
45
+ | None => ()
46
+ }
47
+ }
48
+
49
+ let handleMouseEnter = _ => {
50
+ clearExistingTimeout()
51
+ let id = Js.Global.setTimeout(() => {
52
+ Signal.set(isOpen, true)
53
+ }, openDelay)
54
+ timeoutId := Some(id)
55
+ }
56
+
57
+ let handleMouseLeave = _ => {
58
+ clearExistingTimeout()
59
+ let id = Js.Global.setTimeout(() => {
60
+ Signal.set(isOpen, false)
61
+ }, closeDelay)
62
+ timeoutId := Some(id)
63
+ }
64
+
65
+ let getHoverCardClassName = () => {
66
+ let baseClass = "basefn-hover-card"
67
+ let customClass = switch className {
68
+ | Some(c) => " " ++ c
69
+ | None => ""
70
+ }
71
+ baseClass ++ customClass
72
+ }
73
+
74
+ let getContentClassName = () => {
75
+ let baseClass = "basefn-hover-card__content"
76
+ let positionClass = " basefn-hover-card__content--" ++ positionToString(position)
77
+ let alignClass = " basefn-hover-card__content--align-" ++ alignToString(align)
78
+ baseClass ++ positionClass ++ alignClass
79
+ }
80
+
81
+ let hoverCardContent = Computed.make(() => {
82
+ if Signal.get(isOpen) {
83
+ [
84
+ <div class={getContentClassName()} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
85
+ {content}
86
+ </div>,
87
+ ]
88
+ } else {
89
+ []
90
+ }
91
+ })
92
+
93
+ <div class={getHoverCardClassName()} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
94
+ <div class="basefn-hover-card__trigger">
95
+ {trigger}
96
+ </div>
97
+ {Component.signalFragment(hoverCardContent)}
98
+ </div>
99
+ }