@f0rbit/ui 0.1.2

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.
@@ -0,0 +1,139 @@
1
+ import { type JSX, splitProps, For, Show, createContext, useContext } from "solid-js";
2
+
3
+ export type StepStatus = "completed" | "current" | "upcoming";
4
+
5
+ type StepperContextValue = {
6
+ registerStep: () => number;
7
+ };
8
+
9
+ const StepperContext = createContext<StepperContextValue>();
10
+
11
+ export interface StepperProps extends JSX.HTMLAttributes<HTMLDivElement> {
12
+ orientation?: "horizontal" | "vertical";
13
+ children: JSX.Element;
14
+ }
15
+
16
+ export interface StepProps extends JSX.HTMLAttributes<HTMLDivElement> {
17
+ title: string;
18
+ description?: string;
19
+ icon?: JSX.Element;
20
+ status?: StepStatus;
21
+ }
22
+
23
+ const statusClasses: Record<StepStatus, string> = {
24
+ completed: "step-completed",
25
+ current: "step-current",
26
+ upcoming: "step-upcoming",
27
+ };
28
+
29
+ function CheckIcon() {
30
+ return (
31
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
32
+ <polyline points="13 4 6 12 3 9" />
33
+ </svg>
34
+ );
35
+ }
36
+
37
+ type StepContextValue = {
38
+ orientation: () => "horizontal" | "vertical";
39
+ };
40
+
41
+ const StepContext = createContext<StepContextValue>();
42
+
43
+ export function Step(props: StepProps) {
44
+ const [local, rest] = splitProps(props, ["title", "description", "icon", "status", "class"]);
45
+ const stepperCtx = useContext(StepperContext);
46
+ const stepCtx = useContext(StepContext);
47
+
48
+ const stepNumber = stepperCtx?.registerStep() ?? 1;
49
+ const orientation = () => stepCtx?.orientation() ?? "horizontal";
50
+
51
+ const status = () => local.status ?? "upcoming";
52
+
53
+ const isVertical = () => orientation() === "vertical";
54
+
55
+ const classes = () => {
56
+ const parts = ["step", statusClasses[status()]];
57
+ if (isVertical()) {
58
+ parts.push("vertical-connector-item");
59
+ }
60
+ if (local.class) {
61
+ parts.push(local.class);
62
+ }
63
+ return parts.join(" ");
64
+ };
65
+
66
+ const indicatorClasses = () => {
67
+ const parts = ["step-indicator"];
68
+ if (isVertical()) {
69
+ parts.push("vertical-indicator");
70
+ }
71
+ return parts.join(" ");
72
+ };
73
+
74
+ const contentClasses = () => {
75
+ const parts = ["step-content"];
76
+ if (isVertical()) {
77
+ parts.push("vertical-content");
78
+ }
79
+ return parts.join(" ");
80
+ };
81
+
82
+ const connectorClasses = () => {
83
+ const parts = ["step-connector"];
84
+ if (isVertical()) {
85
+ parts.push("vertical-connector");
86
+ }
87
+ return parts.join(" ");
88
+ };
89
+
90
+ const indicator = () => {
91
+ if (status() === "completed") {
92
+ return <CheckIcon />;
93
+ }
94
+ if (local.icon) {
95
+ return local.icon;
96
+ }
97
+ return stepNumber;
98
+ };
99
+
100
+ return (
101
+ <div class={classes()} {...rest}>
102
+ <div class={indicatorClasses()}>{indicator()}</div>
103
+ <div class={contentClasses()}>
104
+ <div class="step-title">{local.title}</div>
105
+ <Show when={local.description}>
106
+ <div class="step-description">{local.description}</div>
107
+ </Show>
108
+ </div>
109
+ <div class={connectorClasses()} />
110
+ </div>
111
+ );
112
+ }
113
+
114
+ export function Stepper(props: StepperProps) {
115
+ const [local, rest] = splitProps(props, ["orientation", "class", "children"]);
116
+
117
+ let stepCounter = 0;
118
+ const registerStep = () => ++stepCounter;
119
+
120
+ const orientation = () => local.orientation ?? "horizontal";
121
+
122
+ const classes = () => {
123
+ const parts = ["stepper", orientation() === "horizontal" ? "stepper-horizontal" : "stepper-vertical"];
124
+ if (local.class) {
125
+ parts.push(local.class);
126
+ }
127
+ return parts.join(" ");
128
+ };
129
+
130
+ return (
131
+ <StepperContext.Provider value={{ registerStep }}>
132
+ <StepContext.Provider value={{ orientation }}>
133
+ <div class={classes()} {...rest}>
134
+ {local.children}
135
+ </div>
136
+ </StepContext.Provider>
137
+ </StepperContext.Provider>
138
+ );
139
+ }
@@ -0,0 +1,120 @@
1
+ import { type JSX, splitProps, createSignal, createContext, useContext, Show, For } from "solid-js";
2
+
3
+ type TabsContextValue = {
4
+ activeTab: () => string;
5
+ setActiveTab: (id: string) => void;
6
+ };
7
+
8
+ const TabsContext = createContext<TabsContextValue>();
9
+
10
+ export interface TabsProps extends JSX.HTMLAttributes<HTMLDivElement> {
11
+ defaultValue?: string;
12
+ value?: string;
13
+ onValueChange?: (value: string) => void;
14
+ children: JSX.Element;
15
+ }
16
+
17
+ export interface TabListProps extends JSX.HTMLAttributes<HTMLDivElement> {
18
+ children: JSX.Element;
19
+ }
20
+
21
+ export interface TabProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
22
+ value: string;
23
+ children: JSX.Element;
24
+ }
25
+
26
+ export interface TabPanelProps extends JSX.HTMLAttributes<HTMLDivElement> {
27
+ value: string;
28
+ children: JSX.Element;
29
+ }
30
+
31
+ export function Tabs(props: TabsProps) {
32
+ const [local, rest] = splitProps(props, ["defaultValue", "value", "onValueChange", "children", "class"]);
33
+
34
+ const [internalValue, setInternalValue] = createSignal(local.defaultValue ?? "");
35
+
36
+ const isControlled = () => local.value !== undefined;
37
+ const activeTab = () => (isControlled() ? local.value! : internalValue());
38
+
39
+ const setActiveTab = (id: string) => {
40
+ if (!isControlled()) {
41
+ setInternalValue(id);
42
+ }
43
+ local.onValueChange?.(id);
44
+ };
45
+
46
+ const classes = () => `tabs ${local.class ?? ""}`.trim();
47
+
48
+ return (
49
+ <TabsContext.Provider value={{ activeTab, setActiveTab }}>
50
+ <div class={classes()} {...rest}>
51
+ {local.children}
52
+ </div>
53
+ </TabsContext.Provider>
54
+ );
55
+ }
56
+
57
+ export function TabList(props: TabListProps) {
58
+ const [local, rest] = splitProps(props, ["children", "class"]);
59
+
60
+ const classes = () => `tab-list ${local.class ?? ""}`.trim();
61
+
62
+ return (
63
+ <div class={classes()} role="tablist" {...rest}>
64
+ {local.children}
65
+ </div>
66
+ );
67
+ }
68
+
69
+ export function Tab(props: TabProps) {
70
+ const [local, rest] = splitProps(props, ["value", "children", "class"]);
71
+ const ctx = useContext(TabsContext);
72
+
73
+ const isActive = () => ctx?.activeTab() === local.value;
74
+
75
+ const handleClick = () => {
76
+ ctx?.setActiveTab(local.value);
77
+ };
78
+
79
+ const classes = () => {
80
+ const parts = ["tab"];
81
+ if (isActive()) {
82
+ parts.push("active");
83
+ }
84
+ if (local.class) {
85
+ parts.push(local.class);
86
+ }
87
+ return parts.join(" ");
88
+ };
89
+
90
+ return (
91
+ <button
92
+ type="button"
93
+ role="tab"
94
+ aria-selected={isActive()}
95
+ tabIndex={isActive() ? 0 : -1}
96
+ class={classes()}
97
+ onClick={handleClick}
98
+ {...rest}
99
+ >
100
+ {local.children}
101
+ </button>
102
+ );
103
+ }
104
+
105
+ export function TabPanel(props: TabPanelProps) {
106
+ const [local, rest] = splitProps(props, ["value", "children", "class"]);
107
+ const ctx = useContext(TabsContext);
108
+
109
+ const isActive = () => ctx?.activeTab() === local.value;
110
+
111
+ const classes = () => `tab-panel ${local.class ?? ""}`.trim();
112
+
113
+ return (
114
+ <Show when={isActive()}>
115
+ <div class={classes()} role="tabpanel" {...rest}>
116
+ {local.children}
117
+ </div>
118
+ </Show>
119
+ );
120
+ }
@@ -0,0 +1,66 @@
1
+ import { type JSX, splitProps, For, Show } from "solid-js";
2
+
3
+ export type TimelineItemVariant = "default" | "success" | "error" | "warning" | "info";
4
+
5
+ export interface TimelineItem {
6
+ id: string | number;
7
+ icon?: JSX.Element;
8
+ title: string | JSX.Element;
9
+ description?: string | JSX.Element;
10
+ timestamp?: string | JSX.Element;
11
+ variant?: TimelineItemVariant;
12
+ }
13
+
14
+ export interface TimelineProps extends JSX.HTMLAttributes<HTMLDivElement> {
15
+ items: TimelineItem[];
16
+ }
17
+
18
+ const variantClasses: Record<TimelineItemVariant, string> = {
19
+ default: "",
20
+ success: "timeline-item-success",
21
+ error: "timeline-item-error",
22
+ warning: "timeline-item-warning",
23
+ info: "timeline-item-info",
24
+ };
25
+
26
+ function DefaultDot() {
27
+ return <div class="timeline-dot" />;
28
+ }
29
+
30
+ export function Timeline(props: TimelineProps) {
31
+ const [local, rest] = splitProps(props, ["items", "class"]);
32
+
33
+ const classes = () => {
34
+ const parts = ["timeline"];
35
+ if (local.class) parts.push(local.class);
36
+ return parts.join(" ");
37
+ };
38
+
39
+ return (
40
+ <div class={classes()} {...rest}>
41
+ <For each={local.items}>
42
+ {(item, index) => (
43
+ <div
44
+ class={`timeline-item vertical-connector-item ${variantClasses[item.variant ?? "default"]}`}
45
+ >
46
+ <div class="timeline-indicator vertical-indicator">
47
+ <Show when={item.icon} fallback={<DefaultDot />}>
48
+ {item.icon}
49
+ </Show>
50
+ </div>
51
+ <div class="vertical-connector" />
52
+ <div class="timeline-content vertical-content">
53
+ <div class="timeline-title">{item.title}</div>
54
+ <Show when={item.description}>
55
+ <div class="timeline-description">{item.description}</div>
56
+ </Show>
57
+ <Show when={item.timestamp}>
58
+ <div class="timeline-timestamp">{item.timestamp}</div>
59
+ </Show>
60
+ </div>
61
+ </div>
62
+ )}
63
+ </For>
64
+ </div>
65
+ );
66
+ }
@@ -0,0 +1,29 @@
1
+ import { type JSX, splitProps } from "solid-js";
2
+
3
+ export interface ToggleProps extends Omit<JSX.InputHTMLAttributes<HTMLInputElement>, "type" | "size"> {
4
+ label?: string;
5
+ description?: string;
6
+ size?: "sm" | "md";
7
+ }
8
+
9
+ export function Toggle(props: ToggleProps) {
10
+ const [local, rest] = splitProps(props, ["label", "description", "size", "class", "disabled"]);
11
+ const size = () => local.size ?? "md";
12
+ const classes = () =>
13
+ `toggle ${size() === "sm" ? "toggle-sm" : ""} ${local.disabled ? "toggle-disabled" : ""} ${local.class ?? ""}`.trim();
14
+
15
+ return (
16
+ <label class={classes()}>
17
+ <input type="checkbox" class="toggle-input" role="switch" disabled={local.disabled} {...rest} />
18
+ <span class="toggle-track">
19
+ <span class="toggle-knob" />
20
+ </span>
21
+ {(local.label || local.description) && (
22
+ <span class="toggle-content">
23
+ {local.label && <span class="toggle-label">{local.label}</span>}
24
+ {local.description && <span class="toggle-description">{local.description}</span>}
25
+ </span>
26
+ )}
27
+ </label>
28
+ );
29
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,65 @@
1
+ // Components
2
+ export { Button, type ButtonProps, type ButtonVariant, type ButtonSize } from "./components/Button";
3
+ export { Badge, type BadgeProps, type BadgeVariant } from "./components/Badge";
4
+ export {
5
+ Card,
6
+ CardHeader,
7
+ CardTitle,
8
+ CardDescription,
9
+ CardContent,
10
+ CardFooter,
11
+ type CardProps,
12
+ } from "./components/Card";
13
+ export { Input, Textarea, Select, type InputProps, type TextareaProps, type SelectProps } from "./components/Input";
14
+ export { Status, type StatusProps, type StatusState } from "./components/Status";
15
+ export { Stat, type StatProps } from "./components/Stat";
16
+ export { Spinner, type SpinnerProps, type SpinnerSize } from "./components/Spinner";
17
+ export { Chevron, type ChevronProps, type ChevronFacing } from "./components/Chevron";
18
+ export { Empty, type EmptyProps } from "./components/Empty";
19
+ export {
20
+ Modal,
21
+ ModalHeader,
22
+ ModalTitle,
23
+ ModalBody,
24
+ ModalFooter,
25
+ type ModalProps,
26
+ } from "./components/Modal";
27
+ export {
28
+ Dropdown,
29
+ DropdownTrigger,
30
+ DropdownMenu,
31
+ DropdownItem,
32
+ DropdownDivider,
33
+ type DropdownProps,
34
+ type DropdownTriggerProps,
35
+ type DropdownMenuProps,
36
+ type DropdownItemProps,
37
+ } from "./components/Dropdown";
38
+ export { Clamp, type ClampProps } from "./components/Clamp";
39
+ export { Collapsible, type CollapsibleProps } from "./components/Collapsible";
40
+ export {
41
+ Stepper,
42
+ Step,
43
+ type StepperProps,
44
+ type StepProps,
45
+ type StepStatus,
46
+ } from "./components/Stepper";
47
+ export {
48
+ Tabs,
49
+ TabList,
50
+ Tab,
51
+ TabPanel,
52
+ type TabsProps,
53
+ type TabListProps,
54
+ type TabProps,
55
+ type TabPanelProps,
56
+ } from "./components/Tabs";
57
+ export { Checkbox, type CheckboxProps } from "./components/Checkbox";
58
+ export { Toggle, type ToggleProps } from "./components/Toggle";
59
+ export { FormField, type FormFieldProps } from "./components/FormField";
60
+ export {
61
+ Timeline,
62
+ type TimelineProps,
63
+ type TimelineItem,
64
+ type TimelineItemVariant,
65
+ } from "./components/Timeline";