basefn 1.0.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 (154) hide show
  1. package/README.md +104 -0
  2. package/package.json +82 -0
  3. package/rescript.json +32 -0
  4. package/src/Basefn.css +14 -0
  5. package/src/Basefn.res +105 -0
  6. package/src/Basefn.res.mjs +114 -0
  7. package/src/Basefn__Dom.res +9 -0
  8. package/src/Basefn__Dom.res.mjs +24 -0
  9. package/src/Basefn__Utils.res +15 -0
  10. package/src/Basefn__Utils.res.mjs +32 -0
  11. package/src/Demo.res +1417 -0
  12. package/src/Demo.res.mjs +2328 -0
  13. package/src/Eita.res.mjs +105 -0
  14. package/src/Eita__Accordion.res.mjs +77 -0
  15. package/src/Eita__Alert.res.mjs +81 -0
  16. package/src/Eita__AppLayout.res.mjs +100 -0
  17. package/src/Eita__Avatar.res.mjs +40 -0
  18. package/src/Eita__Badge.res.mjs +65 -0
  19. package/src/Eita__Breadcrumb.res.mjs +53 -0
  20. package/src/Eita__Button.res.mjs +47 -0
  21. package/src/Eita__Card.res.mjs +60 -0
  22. package/src/Eita__Checkbox.res.mjs +36 -0
  23. package/src/Eita__Dom.res.mjs +16 -0
  24. package/src/Eita__Drawer.res.mjs +112 -0
  25. package/src/Eita__Dropdown.res.mjs +96 -0
  26. package/src/Eita__Grid.res.mjs +24 -0
  27. package/src/Eita__Input.res.mjs +54 -0
  28. package/src/Eita__Kbd.res.mjs +42 -0
  29. package/src/Eita__Label.res.mjs +24 -0
  30. package/src/Eita__Modal.res.mjs +93 -0
  31. package/src/Eita__Progress.res.mjs +101 -0
  32. package/src/Eita__Radio.res.mjs +38 -0
  33. package/src/Eita__Select.res.mjs +40 -0
  34. package/src/Eita__Separator.res.mjs +70 -0
  35. package/src/Eita__Sidebar.res.mjs +103 -0
  36. package/src/Eita__Slider.res.mjs +89 -0
  37. package/src/Eita__Spinner.res.mjs +69 -0
  38. package/src/Eita__Stepper.res.mjs +114 -0
  39. package/src/Eita__Switch.res.mjs +84 -0
  40. package/src/Eita__Tabs.res.mjs +57 -0
  41. package/src/Eita__Textarea.res.mjs +39 -0
  42. package/src/Eita__Timeline.res.mjs +86 -0
  43. package/src/Eita__Toast.res.mjs +112 -0
  44. package/src/Eita__Tooltip.res.mjs +60 -0
  45. package/src/Eita__Topbar.res.mjs +96 -0
  46. package/src/Eita__Typography.res.mjs +183 -0
  47. package/src/Eita__Utils.res.mjs +32 -0
  48. package/src/Example.res +111 -0
  49. package/src/Example.res.mjs +176 -0
  50. package/src/components/Basefn__Accordion.css +70 -0
  51. package/src/components/Basefn__Accordion.res +79 -0
  52. package/src/components/Basefn__Accordion.res.mjs +77 -0
  53. package/src/components/Basefn__Alert.css +79 -0
  54. package/src/components/Basefn__Alert.res +68 -0
  55. package/src/components/Basefn__Alert.res.mjs +78 -0
  56. package/src/components/Basefn__AppLayout.css +100 -0
  57. package/src/components/Basefn__AppLayout.res +74 -0
  58. package/src/components/Basefn__AppLayout.res.mjs +100 -0
  59. package/src/components/Basefn__Avatar.css +25 -0
  60. package/src/components/Basefn__Avatar.res +23 -0
  61. package/src/components/Basefn__Avatar.res.mjs +40 -0
  62. package/src/components/Basefn__Badge.css +71 -0
  63. package/src/components/Basefn__Badge.res +43 -0
  64. package/src/components/Basefn__Badge.res.mjs +65 -0
  65. package/src/components/Basefn__Breadcrumb.css +36 -0
  66. package/src/components/Basefn__Breadcrumb.res +45 -0
  67. package/src/components/Basefn__Breadcrumb.res.mjs +53 -0
  68. package/src/components/Basefn__Button.css +83 -0
  69. package/src/components/Basefn__Button.res +32 -0
  70. package/src/components/Basefn__Button.res.mjs +54 -0
  71. package/src/components/Basefn__Card.css +50 -0
  72. package/src/components/Basefn__Card.res +45 -0
  73. package/src/components/Basefn__Card.res.mjs +60 -0
  74. package/src/components/Basefn__Checkbox.css +72 -0
  75. package/src/components/Basefn__Checkbox.res +25 -0
  76. package/src/components/Basefn__Checkbox.res.mjs +36 -0
  77. package/src/components/Basefn__Drawer.css +168 -0
  78. package/src/components/Basefn__Drawer.res +86 -0
  79. package/src/components/Basefn__Drawer.res.mjs +112 -0
  80. package/src/components/Basefn__Dropdown.css +76 -0
  81. package/src/components/Basefn__Dropdown.res +85 -0
  82. package/src/components/Basefn__Dropdown.res.mjs +96 -0
  83. package/src/components/Basefn__Grid.css +11 -0
  84. package/src/components/Basefn__Grid.res +296 -0
  85. package/src/components/Basefn__Grid.res.mjs +263 -0
  86. package/src/components/Basefn__Icon.css +12 -0
  87. package/src/components/Basefn__Icon.res +196 -0
  88. package/src/components/Basefn__Icon.res.mjs +183 -0
  89. package/src/components/Basefn__Input.css +44 -0
  90. package/src/components/Basefn__Input.res +48 -0
  91. package/src/components/Basefn__Input.res.mjs +63 -0
  92. package/src/components/Basefn__Kbd.css +65 -0
  93. package/src/components/Basefn__Kbd.res +27 -0
  94. package/src/components/Basefn__Kbd.res.mjs +42 -0
  95. package/src/components/Basefn__Label.css +22 -0
  96. package/src/components/Basefn__Label.res +18 -0
  97. package/src/components/Basefn__Label.res.mjs +24 -0
  98. package/src/components/Basefn__Modal.css +100 -0
  99. package/src/components/Basefn__Modal.res +74 -0
  100. package/src/components/Basefn__Modal.res.mjs +93 -0
  101. package/src/components/Basefn__Progress.css +69 -0
  102. package/src/components/Basefn__Progress.res +88 -0
  103. package/src/components/Basefn__Progress.res.mjs +101 -0
  104. package/src/components/Basefn__Radio.css +72 -0
  105. package/src/components/Basefn__Radio.res +35 -0
  106. package/src/components/Basefn__Radio.res.mjs +38 -0
  107. package/src/components/Basefn__Select.css +44 -0
  108. package/src/components/Basefn__Select.res +33 -0
  109. package/src/components/Basefn__Select.res.mjs +40 -0
  110. package/src/components/Basefn__Separator.css +85 -0
  111. package/src/components/Basefn__Separator.res +45 -0
  112. package/src/components/Basefn__Separator.res.mjs +70 -0
  113. package/src/components/Basefn__Sidebar.css +141 -0
  114. package/src/components/Basefn__Sidebar.res +95 -0
  115. package/src/components/Basefn__Sidebar.res.mjs +107 -0
  116. package/src/components/Basefn__Slider.css +97 -0
  117. package/src/components/Basefn__Slider.res +68 -0
  118. package/src/components/Basefn__Slider.res.mjs +89 -0
  119. package/src/components/Basefn__Spinner.css +63 -0
  120. package/src/components/Basefn__Spinner.res +44 -0
  121. package/src/components/Basefn__Spinner.res.mjs +69 -0
  122. package/src/components/Basefn__Stepper.css +141 -0
  123. package/src/components/Basefn__Stepper.res +86 -0
  124. package/src/components/Basefn__Stepper.res.mjs +114 -0
  125. package/src/components/Basefn__Switch.css +80 -0
  126. package/src/components/Basefn__Switch.res +62 -0
  127. package/src/components/Basefn__Switch.res.mjs +84 -0
  128. package/src/components/Basefn__Tabs.css +54 -0
  129. package/src/components/Basefn__Tabs.res +73 -0
  130. package/src/components/Basefn__Tabs.res.mjs +57 -0
  131. package/src/components/Basefn__Textarea.css +41 -0
  132. package/src/components/Basefn__Textarea.res +28 -0
  133. package/src/components/Basefn__Textarea.res.mjs +41 -0
  134. package/src/components/Basefn__ThemeToggle.css +5 -0
  135. package/src/components/Basefn__ThemeToggle.res +29 -0
  136. package/src/components/Basefn__ThemeToggle.res.mjs +49 -0
  137. package/src/components/Basefn__Timeline.css +144 -0
  138. package/src/components/Basefn__Timeline.res +70 -0
  139. package/src/components/Basefn__Timeline.res.mjs +86 -0
  140. package/src/components/Basefn__Toast.css +100 -0
  141. package/src/components/Basefn__Toast.res +92 -0
  142. package/src/components/Basefn__Toast.res.mjs +112 -0
  143. package/src/components/Basefn__Tooltip.css +84 -0
  144. package/src/components/Basefn__Tooltip.res +42 -0
  145. package/src/components/Basefn__Tooltip.res.mjs +60 -0
  146. package/src/components/Basefn__Topbar.css +130 -0
  147. package/src/components/Basefn__Topbar.res +92 -0
  148. package/src/components/Basefn__Topbar.res.mjs +91 -0
  149. package/src/components/Basefn__Typography.css +120 -0
  150. package/src/components/Basefn__Typography.res +96 -0
  151. package/src/components/Basefn__Typography.res.mjs +175 -0
  152. package/src/styles/Basefn__Theme.res +63 -0
  153. package/src/styles/Basefn__Theme.res.mjs +65 -0
  154. package/src/styles/variables.css +199 -0
@@ -0,0 +1,97 @@
1
+ .basefn-slider {
2
+ width: 100%;
3
+ }
4
+
5
+ .basefn-slider__wrapper {
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: 0.5rem;
9
+ }
10
+
11
+ .basefn-slider__header {
12
+ display: flex;
13
+ justify-content: space-between;
14
+ align-items: center;
15
+ }
16
+
17
+ .basefn-slider__label {
18
+ font-size: 0.875rem;
19
+ font-weight: 500;
20
+ color: #374151;
21
+ }
22
+
23
+ .basefn-slider__value {
24
+ font-size: 0.875rem;
25
+ color: #6b7280;
26
+ font-weight: 600;
27
+ }
28
+
29
+ .basefn-slider__input {
30
+ -webkit-appearance: none;
31
+ width: 100%;
32
+ height: 6px;
33
+ border-radius: 9999px;
34
+ background: #e5e7eb;
35
+ outline: none;
36
+ cursor: pointer;
37
+ }
38
+
39
+ .basefn-slider__input:disabled {
40
+ opacity: 0.5;
41
+ cursor: not-allowed;
42
+ }
43
+
44
+ /* Webkit (Chrome, Safari, Edge) */
45
+ .basefn-slider__input::-webkit-slider-thumb {
46
+ -webkit-appearance: none;
47
+ appearance: none;
48
+ width: 18px;
49
+ height: 18px;
50
+ border-radius: 50%;
51
+ background: #3b82f6;
52
+ cursor: pointer;
53
+ transition: transform 0.1s, box-shadow 0.1s;
54
+ }
55
+
56
+ .basefn-slider__input::-webkit-slider-thumb:hover {
57
+ transform: scale(1.1);
58
+ box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
59
+ }
60
+
61
+ .basefn-slider__input::-webkit-slider-thumb:active {
62
+ transform: scale(1.05);
63
+ box-shadow: 0 0 0 6px rgba(59, 130, 246, 0.15);
64
+ }
65
+
66
+ /* Firefox */
67
+ .basefn-slider__input::-moz-range-thumb {
68
+ width: 18px;
69
+ height: 18px;
70
+ border-radius: 50%;
71
+ background: #3b82f6;
72
+ cursor: pointer;
73
+ border: none;
74
+ transition: transform 0.1s, box-shadow 0.1s;
75
+ }
76
+
77
+ .basefn-slider__input::-moz-range-thumb:hover {
78
+ transform: scale(1.1);
79
+ box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
80
+ }
81
+
82
+ .basefn-slider__input::-moz-range-thumb:active {
83
+ transform: scale(1.05);
84
+ box-shadow: 0 0 0 6px rgba(59, 130, 246, 0.15);
85
+ }
86
+
87
+ .basefn-slider__markers {
88
+ display: flex;
89
+ justify-content: space-between;
90
+ padding: 0 0.25rem;
91
+ margin-top: 0.25rem;
92
+ }
93
+
94
+ .basefn-slider__marker {
95
+ font-size: 0.75rem;
96
+ color: #9ca3af;
97
+ }
@@ -0,0 +1,68 @@
1
+ %%raw(`import './Basefn__Slider.css'`)
2
+
3
+ open Xote
4
+
5
+ @jsx.component
6
+ let make = (
7
+ ~value: Signal.t<float>,
8
+ ~onChange: option<float => unit>=?,
9
+ ~min: float=0.0,
10
+ ~max: float=100.0,
11
+ ~step: float=1.0,
12
+ ~label: option<string>=?,
13
+ ~showValue: bool=true,
14
+ ~disabled: bool=false,
15
+ ~markers: option<array<string>>=?,
16
+ ) => {
17
+ let handleInput = evt => {
18
+ let target = Obj.magic(evt)["target"]
19
+ let newValue = Float.fromString(target["value"])->Option.getOr(0.0)
20
+ Signal.set(value, newValue)
21
+ switch onChange {
22
+ | Some(callback) => callback(newValue)
23
+ | None => ()
24
+ }
25
+ }
26
+
27
+ <div class="basefn-slider">
28
+ <div class="basefn-slider__wrapper">
29
+ {switch (label, showValue) {
30
+ | (Some(_), _) | (None, true) =>
31
+ <div class="basefn-slider__header">
32
+ {switch label {
33
+ | Some(labelText) =>
34
+ <span class="basefn-slider__label"> {Component.text(labelText)} </span>
35
+ | None => <> </>
36
+ }}
37
+ {showValue
38
+ ? <span class="basefn-slider__value">
39
+ {Component.textSignal(() => Float.toString(Signal.get(value)))}
40
+ </span>
41
+ : <> </>}
42
+ </div>
43
+ | _ => <> </>
44
+ }}
45
+ <input
46
+ type_="range"
47
+ class="basefn-slider__input"
48
+ min={Float.toString(min)}
49
+ max={Float.toString(max)}
50
+ step={Float.toString(step)}
51
+ value={value}
52
+ onInput={handleInput}
53
+ disabled
54
+ />
55
+ {switch markers {
56
+ | Some(markerLabels) =>
57
+ <div class="basefn-slider__markers">
58
+ {markerLabels
59
+ ->Array.map(marker => {
60
+ <span class="basefn-slider__marker"> {Component.text(marker)} </span>
61
+ })
62
+ ->Component.fragment}
63
+ </div>
64
+ | None => <> </>
65
+ }}
66
+ </div>
67
+ </div>
68
+ }
@@ -0,0 +1,89 @@
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__Float from "@rescript/core/src/Core__Float.res.mjs";
6
+ import * as Core__Option from "@rescript/core/src/Core__Option.res.mjs";
7
+
8
+ import './Basefn__Slider.css'
9
+ ;
10
+
11
+ function Basefn__Slider(props) {
12
+ let markers = props.markers;
13
+ let __disabled = props.disabled;
14
+ let __showValue = props.showValue;
15
+ let label = props.label;
16
+ let __step = props.step;
17
+ let __max = props.max;
18
+ let __min = props.min;
19
+ let onChange = props.onChange;
20
+ let value = props.value;
21
+ let min = __min !== undefined ? __min : 0.0;
22
+ let max = __max !== undefined ? __max : 100.0;
23
+ let step = __step !== undefined ? __step : 1.0;
24
+ let showValue = __showValue !== undefined ? __showValue : true;
25
+ let disabled = __disabled !== undefined ? __disabled : false;
26
+ let handleInput = evt => {
27
+ let target = evt.target;
28
+ let newValue = Core__Option.getOr(Core__Float.fromString(target.value), 0.0);
29
+ Xote.Signal.set(value, newValue);
30
+ if (onChange !== undefined) {
31
+ return onChange(newValue);
32
+ }
33
+ };
34
+ let tmp;
35
+ let exit = 0;
36
+ if (label !== undefined || showValue) {
37
+ exit = 1;
38
+ } else {
39
+ tmp = Xote__JSX.jsx(Xote__JSX.jsxFragment, {});
40
+ }
41
+ if (exit === 1) {
42
+ tmp = Xote__JSX.Elements.jsxs("div", {
43
+ class: "basefn-slider__header",
44
+ children: Xote__JSX.array([
45
+ label !== undefined ? Xote__JSX.Elements.jsx("span", {
46
+ class: "basefn-slider__label",
47
+ children: Xote.Component.text(label)
48
+ }) : Xote__JSX.jsx(Xote__JSX.jsxFragment, {}),
49
+ showValue ? Xote__JSX.Elements.jsx("span", {
50
+ class: "basefn-slider__value",
51
+ children: Xote.Component.textSignal(() => Xote.Signal.get(value).toString())
52
+ }) : Xote__JSX.jsx(Xote__JSX.jsxFragment, {})
53
+ ])
54
+ });
55
+ }
56
+ return Xote__JSX.Elements.jsx("div", {
57
+ class: "basefn-slider",
58
+ children: Xote__JSX.Elements.jsxs("div", {
59
+ class: "basefn-slider__wrapper",
60
+ children: Xote__JSX.array([
61
+ tmp,
62
+ Xote__JSX.Elements.jsx("input", {
63
+ class: "basefn-slider__input",
64
+ type: "range",
65
+ value: value,
66
+ disabled: disabled,
67
+ min: min.toString(),
68
+ max: max.toString(),
69
+ step: step.toString(),
70
+ onInput: handleInput
71
+ }),
72
+ markers !== undefined ? Xote__JSX.Elements.jsx("div", {
73
+ class: "basefn-slider__markers",
74
+ children: Xote.Component.fragment(markers.map(marker => Xote__JSX.Elements.jsx("span", {
75
+ class: "basefn-slider__marker",
76
+ children: Xote.Component.text(marker)
77
+ })))
78
+ }) : Xote__JSX.jsx(Xote__JSX.jsxFragment, {})
79
+ ])
80
+ })
81
+ });
82
+ }
83
+
84
+ let make = Basefn__Slider;
85
+
86
+ export {
87
+ make,
88
+ }
89
+ /* Not a pure module */
@@ -0,0 +1,63 @@
1
+ .basefn-spinner-container {
2
+ display: inline-flex;
3
+ flex-direction: column;
4
+ align-items: center;
5
+ gap: 0.5rem;
6
+ }
7
+
8
+ .basefn-spinner {
9
+ display: inline-block;
10
+ border: 3px solid rgba(0, 0, 0, 0.1);
11
+ border-top-color: currentColor;
12
+ border-radius: 50%;
13
+ animation: basefn-spinner-rotate 0.8s linear infinite;
14
+ }
15
+
16
+ .basefn-spinner__label {
17
+ font-size: 0.875rem;
18
+ color: #6b7280;
19
+ }
20
+
21
+ /* Sizes */
22
+ .basefn-spinner--sm {
23
+ width: 1rem;
24
+ height: 1rem;
25
+ }
26
+
27
+ .basefn-spinner--md {
28
+ width: 1.5rem;
29
+ height: 1.5rem;
30
+ }
31
+
32
+ .basefn-spinner--lg {
33
+ width: 2rem;
34
+ height: 2rem;
35
+ }
36
+
37
+ .basefn-spinner--xl {
38
+ width: 3rem;
39
+ height: 3rem;
40
+ }
41
+
42
+ /* Variants */
43
+ .basefn-spinner--default {
44
+ color: #6b7280;
45
+ }
46
+
47
+ .basefn-spinner--primary {
48
+ color: #3b82f6;
49
+ }
50
+
51
+ .basefn-spinner--secondary {
52
+ color: #1f2937;
53
+ }
54
+
55
+ /* Animations */
56
+ @keyframes basefn-spinner-rotate {
57
+ 0% {
58
+ transform: rotate(0deg);
59
+ }
60
+ 100% {
61
+ transform: rotate(360deg);
62
+ }
63
+ }
@@ -0,0 +1,44 @@
1
+ %%raw(`import './Basefn__Spinner.css'`)
2
+
3
+ open Xote
4
+
5
+ type size = Sm | Md | Lg | Xl
6
+
7
+ type variant = Default | Primary | Secondary
8
+
9
+ let sizeToString = (size: size) => {
10
+ switch size {
11
+ | Sm => "sm"
12
+ | Md => "md"
13
+ | Lg => "lg"
14
+ | Xl => "xl"
15
+ }
16
+ }
17
+
18
+ let variantToString = (variant: variant) => {
19
+ switch variant {
20
+ | Default => "default"
21
+ | Primary => "primary"
22
+ | Secondary => "secondary"
23
+ }
24
+ }
25
+
26
+ @jsx.component
27
+ let make = (~size: size=Md, ~variant: variant=Default, ~label: string="") => {
28
+ let getClassName = () => {
29
+ let sizeClass = "basefn-spinner--" ++ sizeToString(size)
30
+ let variantClass = "basefn-spinner--" ++ variantToString(variant)
31
+ "basefn-spinner " ++ sizeClass ++ " " ++ variantClass
32
+ }
33
+
34
+ let showLabel = label !== ""
35
+
36
+ <div class="basefn-spinner-container">
37
+ <div class={getClassName()} role="status" ariaLabel="Loading" />
38
+ {if showLabel {
39
+ <span class="basefn-spinner__label"> {Component.text(label)} </span>
40
+ } else {
41
+ <empty />
42
+ }}
43
+ </div>
44
+ }
@@ -0,0 +1,69 @@
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
+
6
+ import './Basefn__Spinner.css'
7
+ ;
8
+
9
+ function sizeToString(size) {
10
+ switch (size) {
11
+ case "Sm" :
12
+ return "sm";
13
+ case "Md" :
14
+ return "md";
15
+ case "Lg" :
16
+ return "lg";
17
+ case "Xl" :
18
+ return "xl";
19
+ }
20
+ }
21
+
22
+ function variantToString(variant) {
23
+ switch (variant) {
24
+ case "Default" :
25
+ return "default";
26
+ case "Primary" :
27
+ return "primary";
28
+ case "Secondary" :
29
+ return "secondary";
30
+ }
31
+ }
32
+
33
+ function Basefn__Spinner(props) {
34
+ let __label = props.label;
35
+ let __variant = props.variant;
36
+ let __size = props.size;
37
+ let size = __size !== undefined ? __size : "Md";
38
+ let variant = __variant !== undefined ? __variant : "Default";
39
+ let label = __label !== undefined ? __label : "";
40
+ let getClassName = () => {
41
+ let sizeClass = "basefn-spinner--" + sizeToString(size);
42
+ let variantClass = "basefn-spinner--" + variantToString(variant);
43
+ return "basefn-spinner " + sizeClass + " " + variantClass;
44
+ };
45
+ let showLabel = label !== "";
46
+ return Xote__JSX.Elements.jsxs("div", {
47
+ class: "basefn-spinner-container",
48
+ children: Xote__JSX.array([
49
+ Xote__JSX.Elements.jsx("div", {
50
+ class: getClassName(),
51
+ role: "status",
52
+ "aria-label": "Loading"
53
+ }),
54
+ showLabel ? Xote__JSX.Elements.jsx("span", {
55
+ class: "basefn-spinner__label",
56
+ children: Xote.Component.text(label)
57
+ }) : Xote__JSX.Elements.jsx("empty", {})
58
+ ])
59
+ });
60
+ }
61
+
62
+ let make = Basefn__Spinner;
63
+
64
+ export {
65
+ sizeToString,
66
+ variantToString,
67
+ make,
68
+ }
69
+ /* Not a pure module */
@@ -0,0 +1,141 @@
1
+ .basefn-stepper {
2
+ width: 100%;
3
+ }
4
+
5
+ .basefn-stepper--horizontal {
6
+ display: flex;
7
+ align-items: flex-start;
8
+ }
9
+
10
+ .basefn-stepper--vertical {
11
+ display: flex;
12
+ flex-direction: column;
13
+ }
14
+
15
+ .basefn-stepper__step {
16
+ position: relative;
17
+ flex: 1;
18
+ display: flex;
19
+ align-items: flex-start;
20
+ }
21
+
22
+ .basefn-stepper--vertical .basefn-stepper__step {
23
+ flex-direction: column;
24
+ margin-bottom: 2rem;
25
+ }
26
+
27
+ .basefn-stepper--vertical .basefn-stepper__step:last-child {
28
+ margin-bottom: 0;
29
+ }
30
+
31
+ .basefn-stepper__step-header {
32
+ display: flex;
33
+ align-items: center;
34
+ gap: 0.75rem;
35
+ }
36
+
37
+ .basefn-stepper--horizontal .basefn-stepper__step-header {
38
+ flex-direction: column;
39
+ text-align: center;
40
+ }
41
+
42
+ .basefn-stepper__step-indicator {
43
+ display: flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ width: 2.5rem;
47
+ height: 2.5rem;
48
+ border-radius: 50%;
49
+ background-color: #e5e7eb;
50
+ color: #6b7280;
51
+ font-weight: 600;
52
+ font-size: 0.875rem;
53
+ flex-shrink: 0;
54
+ transition: all 0.2s;
55
+ position: relative;
56
+ z-index: 2;
57
+ }
58
+
59
+ .basefn-stepper__step--active .basefn-stepper__step-indicator {
60
+ background-color: #3b82f6;
61
+ color: white;
62
+ }
63
+
64
+ .basefn-stepper__step--completed .basefn-stepper__step-indicator {
65
+ background-color: #10b981;
66
+ color: white;
67
+ }
68
+
69
+ .basefn-stepper__step--error .basefn-stepper__step-indicator {
70
+ background-color: #ef4444;
71
+ color: white;
72
+ }
73
+
74
+ .basefn-stepper__step-content {
75
+ flex: 1;
76
+ }
77
+
78
+ .basefn-stepper__step-title {
79
+ font-weight: 600;
80
+ font-size: 0.875rem;
81
+ color: #1f2937;
82
+ margin-bottom: 0.25rem;
83
+ }
84
+
85
+ .basefn-stepper__step--inactive .basefn-stepper__step-title {
86
+ color: #9ca3af;
87
+ }
88
+
89
+ .basefn-stepper__step-description {
90
+ font-size: 0.75rem;
91
+ color: #6b7280;
92
+ line-height: 1.4;
93
+ }
94
+
95
+ .basefn-stepper__step--inactive .basefn-stepper__step-description {
96
+ color: #d1d5db;
97
+ }
98
+
99
+ /* Connector lines */
100
+ .basefn-stepper__connector {
101
+ position: absolute;
102
+ background-color: #e5e7eb;
103
+ transition: background-color 0.2s;
104
+ z-index: 1;
105
+ }
106
+
107
+ .basefn-stepper--horizontal .basefn-stepper__connector {
108
+ height: 2px;
109
+ top: 1.25rem;
110
+ left: 50%;
111
+ width: 100%;
112
+ transform: translateX(1.25rem);
113
+ }
114
+
115
+ .basefn-stepper--horizontal .basefn-stepper__step:last-child .basefn-stepper__connector {
116
+ display: none;
117
+ }
118
+
119
+ .basefn-stepper--vertical .basefn-stepper__connector {
120
+ width: 2px;
121
+ left: 1.25rem;
122
+ top: 2.5rem;
123
+ bottom: -2rem;
124
+ }
125
+
126
+ .basefn-stepper--vertical .basefn-stepper__step:last-child .basefn-stepper__connector {
127
+ display: none;
128
+ }
129
+
130
+ .basefn-stepper__step--completed .basefn-stepper__connector {
131
+ background-color: #10b981;
132
+ }
133
+
134
+ /* Optional clickable steps */
135
+ .basefn-stepper__step--clickable .basefn-stepper__step-header {
136
+ cursor: pointer;
137
+ }
138
+
139
+ .basefn-stepper__step--clickable .basefn-stepper__step-indicator:hover {
140
+ transform: scale(1.05);
141
+ }
@@ -0,0 +1,86 @@
1
+ %%raw(`import './Basefn__Stepper.css'`)
2
+
3
+ open Xote
4
+
5
+ type orientation = Horizontal | Vertical
6
+
7
+ type stepStatus = Inactive | Active | Completed | Error
8
+
9
+ type step = {
10
+ title: string,
11
+ description: option<string>,
12
+ status: stepStatus,
13
+ }
14
+
15
+ let statusToString = (status: stepStatus) => {
16
+ switch status {
17
+ | Inactive => "inactive"
18
+ | Active => "active"
19
+ | Completed => "completed"
20
+ | Error => "error"
21
+ }
22
+ }
23
+
24
+ @jsx.component
25
+ let make = (
26
+ ~steps: array<step>,
27
+ ~currentStep: Signal.t<int>,
28
+ ~orientation: orientation=Horizontal,
29
+ ~onStepClick: option<int => unit>=?,
30
+ ) => {
31
+ let handleStepClick = (index: int, status: stepStatus) => {
32
+ switch onStepClick {
33
+ | Some(callback) =>
34
+ // Only allow clicking on completed steps or current step in non-linear mode
35
+ switch status {
36
+ | Completed | Active => callback(index)
37
+ | _ => ()
38
+ }
39
+ | None => ()
40
+ }
41
+ }
42
+
43
+ let getStepperClass = () => {
44
+ let orientationClass = switch orientation {
45
+ | Horizontal => "basefn-stepper--horizontal"
46
+ | Vertical => "basefn-stepper--vertical"
47
+ }
48
+ "basefn-stepper " ++ orientationClass
49
+ }
50
+
51
+ let getStepClass = (status: stepStatus, clickable: bool) => {
52
+ let statusClass = "basefn-stepper__step--" ++ statusToString(status)
53
+ let clickableClass = clickable ? " basefn-stepper__step--clickable" : ""
54
+ "basefn-stepper__step " ++ statusClass ++ clickableClass
55
+ }
56
+
57
+ <div class={getStepperClass()}>
58
+ {steps
59
+ ->Array.mapWithIndex((step, index) => {
60
+ let isClickable =
61
+ onStepClick->Option.isSome && (step.status == Completed || step.status == Active)
62
+
63
+ <div key={Int.toString(index)} class={getStepClass(step.status, isClickable)}>
64
+ <div class="basefn-stepper__step-header" onClick={_ => handleStepClick(index, step.status)}>
65
+ <div class="basefn-stepper__step-indicator">
66
+ {switch step.status {
67
+ | Completed => Component.text("\u2713")
68
+ | Error => Component.text("\u00d7")
69
+ | _ => Component.text(Int.toString(index + 1))
70
+ }}
71
+ </div>
72
+ <div class="basefn-stepper__step-content">
73
+ <div class="basefn-stepper__step-title"> {Component.text(step.title)} </div>
74
+ {switch step.description {
75
+ | Some(desc) =>
76
+ <div class="basefn-stepper__step-description"> {Component.text(desc)} </div>
77
+ | None => <> </>
78
+ }}
79
+ </div>
80
+ </div>
81
+ <div class="basefn-stepper__connector" />
82
+ </div>
83
+ })
84
+ ->Component.fragment}
85
+ </div>
86
+ }