@itiseeron/component-lib 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eeron Grant
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,278 @@
1
+ # @itiseeron/component-lib
2
+
3
+ Eeron Grant's personal React (17) component library. Every component follows the same
4
+ conventions:
5
+
6
+ - **Style props are mutable, with sensible defaults.** Each visual piece has its own
7
+ `*Style` prop (e.g. `containerStyle`, `itemStyle`) backed by a default `CSSProperties`
8
+ object — override only what you need.
9
+ - **Selection-driven components share one behavior engine.** `HamburgerMenu`,
10
+ `RadioButtonGroup`, and `ScrollMenu` are all presentational wrappers around
11
+ `NavigationBar` — they reuse its selection rules (one item always selected, disabled
12
+ or already-active items are a no-op) rather than reimplementing them, and swap in
13
+ their own look via a `render` prop.
14
+
15
+ ```bash
16
+ npm install @itiseeron/component-lib
17
+ ```
18
+
19
+ ```tsx
20
+ import { Card, ThinButton } from '@itiseeron/component-lib';
21
+ ```
22
+
23
+ ## Components
24
+
25
+ ### HelloWorld
26
+
27
+ A placeholder component from the project template.
28
+
29
+ ```tsx
30
+ <HelloWorld />
31
+ ```
32
+
33
+ ### ThinButton
34
+
35
+ A bordered, clickable button. Renders `text` unless `children` are given.
36
+
37
+ ```tsx
38
+ <ThinButton text="Submit" onclick={() => console.log('clicked')} />
39
+ ```
40
+
41
+ | Prop | Type | Default | Notes |
42
+ | --- | --- | --- | --- |
43
+ | `text` | `string` | `'Click Me'` | Ignored if `children` is given |
44
+ | `onclick` | `() => any` | no-op | |
45
+ | `containerStyle` / `buttonStyle` / `buttonTextStyle` | `CSSProperties` | — | |
46
+
47
+ ### Card
48
+
49
+ A white, rounded, shadowed surface with an optional title/subtitle and close button.
50
+
51
+ ```tsx
52
+ <Card titleText="Title" subtitleText="Subtitle" showCloseButton onClickCloseButton={close}>
53
+ <p>Body content</p>
54
+ </Card>
55
+ ```
56
+
57
+ | Prop | Type | Notes |
58
+ | --- | --- | --- |
59
+ | `titleText` / `subtitleText` | `string` | Omit to skip rendering |
60
+ | `showCloseButton` | `boolean` | Default `false` |
61
+ | `onClickCloseButton` | `() => any` | |
62
+ | `containerStyle` / `wrapperStyle` / `titleStyle` / `subtitleStyle` / `titleContainerStyle` / `closeButtonStyle` / `closeButtonContainerStyle` | `CSSProperties` | |
63
+
64
+ ### InputForm
65
+
66
+ A labeled text field. Uncontrolled by default (seeded from `defaultValue`); pass `value`
67
+ + `onInput` together to control it from a parent, the same duality `NavigationBar` uses
68
+ for selection.
69
+
70
+ ```tsx
71
+ <InputForm
72
+ name="Email"
73
+ placeholder="you@example.com"
74
+ onInput={(value) => console.log(value)}
75
+ />
76
+
77
+ // Controlled, with validation
78
+ <InputForm
79
+ name="Full Name"
80
+ value={name}
81
+ onInput={setName}
82
+ errorMessage={name.length < 3 ? 'Too short' : undefined}
83
+ />
84
+ ```
85
+
86
+ | Prop | Type | Notes |
87
+ | --- | --- | --- |
88
+ | `name` | `string` | Renders a label above the field |
89
+ | `value` / `defaultValue` / `onInput` | — | Controlled/uncontrolled duality |
90
+ | `secureTextEntry` | `boolean` | Renders `type="password"` |
91
+ | `multiline` / `numberOfLines` | `boolean` / `number` | Renders a `<textarea>` |
92
+ | `maxLength` | `number` | Default `200` |
93
+ | `trim` | `boolean` | Trims the value passed to `onInput` (not what's displayed while typing) |
94
+ | `errorMessage` | `string` | Shown under the field whenever set |
95
+ | `containerStyle` / `formTitleStyle` / `inputStyle` / `errorStyle` | `CSSProperties` | |
96
+
97
+ ### NavigationBar
98
+
99
+ A tab strip. Owns selection *behavior* only — one item is always selected (defaulting
100
+ to the first), and a disabled or already-selected item is a no-op. Items are plain data
101
+ (`{ key, label, icon?, disabled? }`), never elements with their own click handlers, so
102
+ no presentation can bypass that rule.
103
+
104
+ ```tsx
105
+ <NavigationBar
106
+ items={[
107
+ { key: 'home', label: 'Home' },
108
+ { key: 'settings', label: 'Settings' },
109
+ { key: 'admin', label: 'Admin', disabled: true },
110
+ ]}
111
+ onSelect={(key) => setPage(key)}
112
+ />
113
+ ```
114
+
115
+ Uncontrolled by default (`defaultSelectedKey`); pass `selectedKey` + `onSelect` together
116
+ to control it. Swap the look entirely with `render`:
117
+
118
+ ```tsx
119
+ <NavigationBar
120
+ items={items}
121
+ render={({ items, selectedKey, onItemSelect }) => (
122
+ <MyTabs items={items} activeKey={selectedKey} onClick={onItemSelect} />
123
+ )}
124
+ />
125
+ ```
126
+
127
+ `NavigationBarView` is the default presentation, also exported on its own if you want
128
+ its look without going through `render`.
129
+
130
+ ### HamburgerMenu
131
+
132
+ `NavigationBar`'s items behind a toggle button and a collapsible panel, instead of shown
133
+ inline. Same selection guarantees, purely a different presentation
134
+ (`HamburgerMenuView`, which itself reuses `NavigationBarView` for the panel's items).
135
+
136
+ ```tsx
137
+ <HamburgerMenu
138
+ items={items}
139
+ onSelect={(key) => setPage(key)}
140
+ icon={<MyMenuIcon />} // optional, defaults to "☰"
141
+ />
142
+ ```
143
+
144
+ ### RadioButtonGroup
145
+
146
+ `NavigationBar`'s items as a segmented radio control (default styling mirrors
147
+ homepairsUI's `AccountTypeRadioButton`: accent fill on the selected segment, rounded
148
+ outer edges only).
149
+
150
+ ```tsx
151
+ <RadioButtonGroup
152
+ title="Account Type"
153
+ items={[
154
+ { key: 'tenant', label: 'Tenant' },
155
+ { key: 'pm', label: 'Property Manager' },
156
+ ]}
157
+ onSelect={(key) => setAccountType(key)}
158
+ />
159
+ ```
160
+
161
+ ### ScrollMenu
162
+
163
+ `NavigationBar`'s items as a horizontally scrollable strip of thumbnail tiles. Give an
164
+ item a thumbnail via `icon`; without one it falls back to its `label` as centered text.
165
+
166
+ ```tsx
167
+ <ScrollMenu
168
+ items={[
169
+ { key: 'p1', label: 'Acme Plumbing', icon: <img src={logo} style={{width: '100%', height: '100%', objectFit: 'cover'}} /> },
170
+ { key: 'p2', label: 'No Logo Co' },
171
+ ]}
172
+ onSelect={(key) => setSelectedProvider(key)}
173
+ />
174
+ ```
175
+
176
+ ### Tile
177
+
178
+ `ImageTile` and `TextTile`, the standalone rounded-tile primitives `ScrollMenu`'s
179
+ fallback logic is conceptually built on (used independently here, not shared —
180
+ `ScrollMenu` keeps a single unified look across image/text items, while `TextTile`'s
181
+ own default is deliberately two-toned like the original).
182
+
183
+ ```tsx
184
+ <ImageTile image={logoUrl} /> {/* 70x70 */}
185
+ <ImageTile image={logoUrl} enlarge /> {/* 100x100 */}
186
+ <TextTile text="+" /> {/* fontSize defaults to 30 */}
187
+ ```
188
+
189
+ ### Loading
190
+
191
+ A small centered spinner panel, built on `Card`. Takes `children` for a status message.
192
+
193
+ ```tsx
194
+ <Loading>
195
+ <span>Loading properties...</span>
196
+ </Loading>
197
+ ```
198
+
199
+ ### SearchBar
200
+
201
+ Wraps `InputForm` (no styling of its own) and adds filtering: on every keystroke,
202
+ `objects` is filtered and the result is handed to `onResults`.
203
+
204
+ ```tsx
205
+ <SearchBar
206
+ placeholder="Search providers..."
207
+ objects={providers}
208
+ keys={['name']}
209
+ onResults={setFilteredProviders}
210
+ />
211
+ ```
212
+
213
+ | Prop | Type | Default | Notes |
214
+ | --- | --- | --- | --- |
215
+ | `objects` | `T[]` | `[]` | The list to search |
216
+ | `keys` | `string[]` | — | Restrict matching to these top-level fields; omit to recurse through every nested value instead |
217
+ | `onResults` | `(filtered: T[]) => any` | no-op | Not called on mount — only once the user searches, or `objects` changes |
218
+ | `matchStrategy` | see below | `'fuzzy'` | |
219
+ | `caseSensitive` | `boolean` | `false` | |
220
+ | `ignoreAccents` | `boolean` | `false` | e.g. so `"cafe"` matches `"café"` |
221
+ | `rank` | `boolean` | `true` | Sorts survivors by match score; a no-op for the boolean strategies |
222
+ | `debounceMs` | `number` | `0` | Delays re-filtering after the last keystroke, never delays what's shown in the field |
223
+
224
+ `matchStrategy` options:
225
+
226
+ | Strategy | Behavior |
227
+ | --- | --- |
228
+ | `'fuzzy'` (default) | Subsequence match — query characters found in order, not necessarily together. Scores tighter (more contiguous) matches higher. |
229
+ | `'substring'` | Plain `includes()` — exact and cheap. |
230
+ | `'prefix'` | Matches the start of the text, or the start of any word in it. |
231
+ | `'tokenized'` | Every word in the query must appear somewhere, in any order. |
232
+ | `'regex'` | Query is compiled as a `RegExp`; an invalid pattern just matches nothing. |
233
+ | custom function | `(query, text) => number \| null` — return `null` to exclude, a number (higher = better) to include and rank. Receives the same case/accent-normalized query and text the built-ins do. |
234
+
235
+ ```tsx
236
+ <SearchBar
237
+ objects={providers}
238
+ keys={['name']}
239
+ matchStrategy={(query, text) => (text.split(/\s+/).includes(query) ? 0 : null)}
240
+ onResults={setFilteredProviders}
241
+ />
242
+ ```
243
+
244
+ ## Development
245
+
246
+ ```bash
247
+ npm install
248
+ npm run storybook
249
+ ```
250
+
251
+ ## Testing
252
+
253
+ ```bash
254
+ npm test
255
+ ```
256
+
257
+ Runs the Jest + React Testing Library suite (`*.test.tsx` files co-located with each
258
+ component). Test files are excluded from the type-checked/shipped build (see
259
+ `tsconfig.json`'s `exclude`), so they're free to use Jest/Testing Library globals
260
+ without affecting `dist/`.
261
+
262
+ ## Build
263
+
264
+ ```bash
265
+ npm run build
266
+ ```
267
+
268
+ Outputs cjs, esm, and type declarations to `dist/`.
269
+
270
+ ## Publish
271
+
272
+ ```bash
273
+ npm login # one-time, per machine
274
+ npm publish # runs the build automatically via prepublishOnly
275
+ ```
276
+
277
+ Published as a public scoped package (`publishConfig.access: public` is already set, so
278
+ `npm publish` works without extra flags once logged in).