@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 +21 -0
- package/README.md +278 -0
- package/dist/index.d.mts +744 -0
- package/dist/index.d.ts +744 -0
- package/dist/index.js +1259 -0
- package/dist/index.mjs +1208 -0
- package/package.json +62 -0
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).
|