@stamcat/craftsman 0.0.23-alpha.10 → 0.0.23-alpha.11

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/AGENTS.md CHANGED
@@ -12,6 +12,7 @@ Use these imports:
12
12
  import { Button } from "@stamcat/craftsman/Button";
13
13
  import { Input } from "@stamcat/craftsman/Input";
14
14
  import { Loader } from "@stamcat/craftsman/Loader";
15
+ import { Modal } from "@stamcat/craftsman/Modal";
15
16
  ```
16
17
 
17
18
  Do not assume a root export like `@stamcat/craftsman` unless that export is explicitly added to package `exports`.
@@ -37,12 +38,25 @@ Props:
37
38
 
38
39
  - Inherits all native `<button>` props.
39
40
  - `variant?: "primary" | "default" | "text"` (default: `"default"`)
41
+ - `size?: number` (default visual scale is `1`)
40
42
  - `styles?: SerializedStyles` (Emotion override)
41
43
 
42
44
  Behavior notes:
43
45
 
44
46
  - `type` defaults to `"button"`.
45
47
  - For `variant !== "default"`, variant is appended to `className` (for example `"primary"`).
48
+ - Theme class merge: if `theme.components.button` is a string, it is appended to the class list.
49
+ - `className` is preserved and merged after variant/theme classes.
50
+ - If `children` is empty (per `isEmpty`), the component renders nothing.
51
+ - `size` is clamped to `[0.1, 10]` before styling is applied.
52
+ - When `size` is provided, Button scales:
53
+ - `border-radius: calc(var(--btn-border-radius) * size)`
54
+ - `padding: calc(var(--btn-pad-y) * size) calc(var(--btn-pad-x) * size)`
55
+ - `font-size: max(10px, calc(var(--w-text) * size))`
56
+ - There is no dedicated icon/loading prop. Icons, loaders, and mixed content are passed as `children`.
57
+ - Supports both real disable (`disabled={true}`) and style-only disable (`className="disabled"`).
58
+ - Native button modes are supported (`type="button" | "submit" | "reset"`).
59
+ - Accessibility props such as `aria-label` pass through unchanged.
46
60
 
47
61
  Example:
48
62
 
@@ -50,6 +64,33 @@ Example:
50
64
  <Button variant="primary" onClick={onSave}>Save</Button>
51
65
  ```
52
66
 
67
+ Story-aligned usage examples:
68
+
69
+ ```tsx
70
+ // Scaled compact button (size is clamped to [0.1, 10])
71
+ <Button variant="primary" size={0.5}>Mini Action</Button>
72
+
73
+ // Icon content
74
+ <Button variant="primary">
75
+ <TruckIcon size={20} /> <span>Ship</span>
76
+ </Button>
77
+
78
+ // Loading content
79
+ <Button aria-label="Saving">
80
+ <Loader type="boxy" width={32} color="#de13ca" />
81
+ </Button>
82
+
83
+ // Style-only disabled appearance while still allowing click handlers
84
+ <Button className="disabled" onClick={onClick}>Disabled Look</Button>
85
+
86
+ // Native submit behavior
87
+ <Button type="submit" aria-label="Save form">Save</Button>
88
+ ```
89
+
90
+ Implementation caution:
91
+
92
+ - Size scaling depends on CSS variables provided by the package global styles (`--btn-border-radius`, `--btn-pad-y`, `--btn-pad-x`, `--w-text`).
93
+
53
94
  ### Input
54
95
 
55
96
  Import:
@@ -68,6 +109,54 @@ Example:
68
109
  <Input type="email" placeholder="you@company.com" required />
69
110
  ```
70
111
 
112
+ ### Modal
113
+
114
+ Import:
115
+
116
+ ```tsx
117
+ import { Modal } from "@stamcat/craftsman/Modal";
118
+ ```
119
+
120
+ Props:
121
+
122
+ - Inherits all native `<div>` props.
123
+ - `visible?: boolean` (when falsy, component renders nothing)
124
+ - `onDismiss?: () => void`
125
+ - `type?: "dialog" | "panel"` (default: `"dialog"`)
126
+ - `header?: string | React.ReactNode`
127
+ - `backgroundDismiss?: boolean` (default behavior: `true`)
128
+ - `hideDismissIcon?: boolean` (default behavior: close icon is shown)
129
+ - `footer?: React.ReactNode`
130
+ - `styles?: SerializedStyles` (applies to outer modal wrapper)
131
+
132
+ Behavior notes:
133
+
134
+ - Modal is controlled; parent owns open/close state via `visible` and `onDismiss`.
135
+ - Close icon and background click both dismiss through `onDismiss`.
136
+ - Background dismiss only runs when `backgroundDismiss` is `true` or `undefined`.
137
+ - Dismiss uses a short close animation before calling `onDismiss` (~280ms timeout).
138
+ - `type="dialog"` renders centered responsive dialog sizing; `type="panel"` renders a right-side panel.
139
+ - `footer` is rendered in an action row container below modal content.
140
+
141
+ Example:
142
+
143
+ ```tsx
144
+ const [open, setOpen] = useState(false);
145
+
146
+ <>
147
+ <Button variant="primary" onClick={() => setOpen(true)}>Open</Button>
148
+ <Modal
149
+ visible={open}
150
+ onDismiss={() => setOpen(false)}
151
+ type="dialog"
152
+ header="Confirm Action"
153
+ footer={<><Button onClick={() => setOpen(false)}>Cancel</Button><Button variant="primary">Confirm</Button></>}
154
+ >
155
+ <p>Are you sure?</p>
156
+ </Modal>
157
+ </>
158
+ ```
159
+
71
160
  ### Loader
72
161
 
73
162
  Import:
@@ -151,12 +240,12 @@ if (arr.length === 0) { ... }
151
240
 
152
241
  1. README is minimal; treat this guide as the source of truth for agent usage.
153
242
  2. Theme utilities exist in source but are not guaranteed public package exports.
154
- 3. Additional components in source (for example `Progress`) may not yet be exported.
243
+ 3. `Progress` exists in source but is incomplete and intentionally omitted from this guide for now.
155
244
 
156
245
  ## Safe Fallback Strategy for Agents
157
246
 
158
247
  If uncertain about available exports:
159
248
 
160
- 1. Use only `Button`, `Input`, and `Loader` from their component entry points.
249
+ 1. Use only `Button`, `Input`, `Loader`, and `Modal` from their component entry points.
161
250
  2. Do not invent package APIs.
162
251
  3. Prefer native HTML elements for anything not explicitly exported.
@@ -2,6 +2,7 @@ import { SerializedStyles } from '@emotion/react';
2
2
  import { ButtonType } from '../styles/global/components/button';
3
3
  export type ButtonProps = React.ComponentProps<"button"> & {
4
4
  variant?: ButtonType;
5
+ size?: number;
5
6
  styles?: SerializedStyles;
6
7
  };
7
8
  export declare const Button: React.FC<ButtonProps>;
@@ -18,6 +18,6 @@ export declare class Modal extends React.PureComponent<ModalProps, ModalState> {
18
18
  readonly state: ModalState;
19
19
  onDismiss: () => void;
20
20
  onClickBackground: () => void;
21
- render(): React.JSX.Element | null;
21
+ render(): React.JSX.Element;
22
22
  }
23
23
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamcat/craftsman",
3
- "version": "0.0.23-alpha.10",
3
+ "version": "0.0.23-alpha.11",
4
4
  "type": "module",
5
5
  "description": "A powerful, lightweight framework for design systems",
6
6
  "repository": {
@@ -1,20 +1,27 @@
1
- import { useTheme as e } from "@emotion/react";
2
- import t from "@emotion/styled";
3
- import { jsx as n } from "react/jsx-runtime";
1
+ import { isEmpty as e } from "../utilities/validations.esm.js";
2
+ import { css as t, useTheme as n } from "@emotion/react";
3
+ import r from "@emotion/styled";
4
+ import { Fragment as i, jsx as a } from "react/jsx-runtime";
4
5
  //#region src/components/Button.tsx
5
- var r = t.button`
6
+ var o = r.button`
6
7
  ${(e) => e.styles}
7
- `, i = (t) => {
8
- let { type: i = "button", variant: a = "default", className: o, ...s } = t, c = e(), l = typeof c?.components?.button == "string" ? c.components.button : void 0;
9
- return /* @__PURE__ */ n(r, {
10
- type: i,
11
- className: [
12
- a === "default" ? void 0 : a,
13
- l,
14
- o
15
- ].filter(Boolean).join(" ") || void 0,
16
- ...s
8
+ ${((e) => e.size && t`
9
+ border-radius: calc(var(--btn-border-radius) * ${e.size});
10
+ padding: calc(var(--btn-pad-y) * ${e.size}) calc(var(--btn-pad-x) * ${e.size});
11
+ font-size: max(10px, calc(var(--w-text) * ${e.size}));
12
+ `)}
13
+ `, s = (t) => {
14
+ let { type: r = "button", variant: s = "default", className: c, size: l, ...u } = t, d = typeof l == "number" ? Math.min(10, Math.max(.1, l)) : void 0, f = n(), p = typeof f?.components?.button == "string" ? f.components.button : void 0, m = [
15
+ s === "default" ? void 0 : s,
16
+ p,
17
+ c
18
+ ].filter(Boolean).join(" ") || void 0;
19
+ return e(t.children) ? /* @__PURE__ */ a(i, {}) : /* @__PURE__ */ a(o, {
20
+ type: r,
21
+ size: d,
22
+ className: m,
23
+ ...u
17
24
  });
18
25
  };
19
26
  //#endregion
20
- export { i as Button };
27
+ export { s as Button };
@@ -104,7 +104,6 @@ var d = a`
104
104
  }
105
105
  `, C = i`
106
106
  text-decoration: none;
107
- // padding: 0 calc(var(--w-gutter) * 0.25) 0 var(--w-gutter);
108
107
  height: 36px;
109
108
  width: 36px;
110
109
  border-radius: 50%;
@@ -122,8 +121,7 @@ var d = a`
122
121
  }, 280));
123
122
  };
124
123
  onClickBackground = () => {
125
- let e = this.props.backgroundDismiss;
126
- (e === !0 || e === void 0) && this.onDismiss();
124
+ (this.props.backgroundDismiss || !0) === !0 && this.onDismiss();
127
125
  };
128
126
  render() {
129
127
  return this.props.visible ? /* @__PURE__ */ l(h, {
@@ -149,7 +147,7 @@ var d = a`
149
147
  isClosing: this.state.isClosing,
150
148
  onClick: this.onClickBackground
151
149
  })]
152
- }) : null;
150
+ }) : /* @__PURE__ */ c(s, {});
153
151
  }
154
152
  };
155
153
  //#endregion
@@ -0,0 +1,7 @@
1
+ //#region src/utilities/validations.ts
2
+ /* #__PURE__ */
3
+ function e(e) {
4
+ return e == null || typeof e == "object" && Object.keys(e).length === 0 || typeof e == "string" && e.trim().length === 0 || typeof e == "string" && e === " ";
5
+ }
6
+ //#endregion
7
+ export { e as isEmpty };
@@ -6,6 +6,9 @@ type Story = StoryObj<typeof meta>;
6
6
  export declare const Default: Story;
7
7
  export declare const Primary: Story;
8
8
  export declare const Text: Story;
9
+ export declare const WithIcon: Story;
10
+ export declare const Mini: Story;
11
+ export declare const XL: Story;
9
12
  export declare const Disabled: Story;
10
13
  export declare const FullyDisabled: Story;
11
14
  export declare const Loading: Story;