@tribepad/themis 1.0.4 → 1.0.6
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/dist/elements/MatrixGrid/MatrixGrid.d.ts +31 -0
- package/dist/elements/MatrixGrid/MatrixGrid.d.ts.map +1 -0
- package/dist/elements/MatrixGrid/MatrixGrid.styles.d.ts +48 -0
- package/dist/elements/MatrixGrid/MatrixGrid.styles.d.ts.map +1 -0
- package/dist/elements/MatrixGrid/MatrixGrid.types.d.ts +75 -0
- package/dist/elements/MatrixGrid/MatrixGrid.types.d.ts.map +1 -0
- package/dist/elements/MatrixGrid/index.d.ts +5 -0
- package/dist/elements/MatrixGrid/index.d.ts.map +1 -0
- package/dist/elements/MatrixGrid/index.js +3 -0
- package/dist/elements/MatrixGrid/index.js.map +1 -0
- package/dist/elements/MatrixGrid/index.mjs +3 -0
- package/dist/elements/MatrixGrid/index.mjs.map +1 -0
- package/dist/elements/RatingScale/RatingScale.d.ts +32 -0
- package/dist/elements/RatingScale/RatingScale.d.ts.map +1 -0
- package/dist/elements/RatingScale/RatingScale.styles.d.ts +52 -0
- package/dist/elements/RatingScale/RatingScale.styles.d.ts.map +1 -0
- package/dist/elements/RatingScale/RatingScale.types.d.ts +73 -0
- package/dist/elements/RatingScale/RatingScale.types.d.ts.map +1 -0
- package/dist/elements/RatingScale/index.d.ts +5 -0
- package/dist/elements/RatingScale/index.d.ts.map +1 -0
- package/dist/elements/RatingScale/index.js +3 -0
- package/dist/elements/RatingScale/index.js.map +1 -0
- package/dist/elements/RatingScale/index.mjs +3 -0
- package/dist/elements/RatingScale/index.mjs.map +1 -0
- package/dist/elements/SituationalJudgement/SituationalJudgement.d.ts +36 -0
- package/dist/elements/SituationalJudgement/SituationalJudgement.d.ts.map +1 -0
- package/dist/elements/SituationalJudgement/SituationalJudgement.styles.d.ts +19 -0
- package/dist/elements/SituationalJudgement/SituationalJudgement.styles.d.ts.map +1 -0
- package/dist/elements/SituationalJudgement/SituationalJudgement.types.d.ts +67 -0
- package/dist/elements/SituationalJudgement/SituationalJudgement.types.d.ts.map +1 -0
- package/dist/elements/SituationalJudgement/index.d.ts +5 -0
- package/dist/elements/SituationalJudgement/index.d.ts.map +1 -0
- package/dist/elements/SituationalJudgement/index.js +3 -0
- package/dist/elements/SituationalJudgement/index.js.map +1 -0
- package/dist/elements/SituationalJudgement/index.mjs +3 -0
- package/dist/elements/SituationalJudgement/index.mjs.map +1 -0
- package/dist/elements/index.d.ts +6 -0
- package/dist/elements/index.d.ts.map +1 -1
- package/dist/elements/index.js +1 -1
- package/dist/elements/index.js.map +1 -1
- package/dist/elements/index.mjs +1 -1
- package/dist/elements/index.mjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -2
- package/src/elements/MatrixGrid/MatrixGrid.stories.tsx +292 -0
- package/src/elements/RatingScale/RatingScale.stories.tsx +379 -0
- package/src/elements/SituationalJudgement/SituationalJudgement.stories.tsx +305 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tribepad/themis",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Accessible React component library built on React Aria primitives",
|
|
5
5
|
"author": "Tribepad <mbasford@tribepad.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,7 +50,10 @@
|
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
"./styles/defaults.css": "./dist/styles/defaults.css",
|
|
53
|
-
"./tailwind-source.css":
|
|
53
|
+
"./tailwind-source.css": {
|
|
54
|
+
"style": "./dist/tailwind-source.css",
|
|
55
|
+
"default": "./dist/tailwind-source.css"
|
|
56
|
+
},
|
|
54
57
|
"./utils": {
|
|
55
58
|
"import": {
|
|
56
59
|
"types": "./dist/utils/index.d.ts",
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { MatrixGrid } from './MatrixGrid';
|
|
4
|
+
|
|
5
|
+
const LIKERT_COLUMNS = [
|
|
6
|
+
{ id: 'sd', label: 'Strongly Disagree' },
|
|
7
|
+
{ id: 'd', label: 'Disagree' },
|
|
8
|
+
{ id: 'n', label: 'Neutral' },
|
|
9
|
+
{ id: 'a', label: 'Agree' },
|
|
10
|
+
{ id: 'sa', label: 'Strongly Agree' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const LIKERT_ROWS = [
|
|
14
|
+
{ id: 'q1', label: 'The product meets my needs' },
|
|
15
|
+
{ id: 'q2', label: 'The product is easy to use' },
|
|
16
|
+
{ id: 'q3', label: 'The product is reliable' },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const meta = {
|
|
20
|
+
title: 'Elements/MatrixGrid',
|
|
21
|
+
component: MatrixGrid,
|
|
22
|
+
parameters: {
|
|
23
|
+
layout: 'centered',
|
|
24
|
+
docs: {
|
|
25
|
+
description: {
|
|
26
|
+
component:
|
|
27
|
+
'Grid-style survey question with rows (statements) and columns (rating options). Each row has a single radio selection. Built with role="grid" ARIA pattern for 2D keyboard navigation.',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
tags: ['autodocs'],
|
|
32
|
+
argTypes: {
|
|
33
|
+
size: {
|
|
34
|
+
control: 'select',
|
|
35
|
+
options: ['sm', 'default', 'lg'],
|
|
36
|
+
description: 'Component size',
|
|
37
|
+
table: { defaultValue: { summary: 'default' } },
|
|
38
|
+
},
|
|
39
|
+
isDisabled: { control: 'boolean' },
|
|
40
|
+
isReadOnly: { control: 'boolean' },
|
|
41
|
+
isRequired: { control: 'boolean' },
|
|
42
|
+
isInvalid: { control: 'boolean' },
|
|
43
|
+
label: { control: 'text' },
|
|
44
|
+
description: { control: 'text' },
|
|
45
|
+
errorMessage: { control: 'text' },
|
|
46
|
+
},
|
|
47
|
+
} satisfies Meta<typeof MatrixGrid>;
|
|
48
|
+
|
|
49
|
+
export default meta;
|
|
50
|
+
type Story = StoryObj<typeof meta>;
|
|
51
|
+
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// Default
|
|
54
|
+
// =============================================================================
|
|
55
|
+
|
|
56
|
+
export const Default: Story = {
|
|
57
|
+
args: {
|
|
58
|
+
label: 'Product Evaluation',
|
|
59
|
+
rows: LIKERT_ROWS,
|
|
60
|
+
columns: LIKERT_COLUMNS,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// =============================================================================
|
|
65
|
+
// Large Matrix
|
|
66
|
+
// =============================================================================
|
|
67
|
+
|
|
68
|
+
export const LargeMatrix: Story = {
|
|
69
|
+
args: {
|
|
70
|
+
label: 'Comprehensive Service Assessment',
|
|
71
|
+
rows: [
|
|
72
|
+
{ id: 'q1', label: 'Quality of service' },
|
|
73
|
+
{ id: 'q2', label: 'Value for money' },
|
|
74
|
+
{ id: 'q3', label: 'Staff friendliness' },
|
|
75
|
+
{ id: 'q4', label: 'Wait time' },
|
|
76
|
+
{ id: 'q5', label: 'Cleanliness of facility' },
|
|
77
|
+
{ id: 'q6', label: 'Ease of scheduling' },
|
|
78
|
+
{ id: 'q7', label: 'Communication clarity' },
|
|
79
|
+
{ id: 'q8', label: 'Follow-up care' },
|
|
80
|
+
],
|
|
81
|
+
columns: [
|
|
82
|
+
{ id: 'vp', label: 'Very Poor' },
|
|
83
|
+
{ id: 'p', label: 'Poor' },
|
|
84
|
+
{ id: 'f', label: 'Fair' },
|
|
85
|
+
{ id: 'g', label: 'Good' },
|
|
86
|
+
{ id: 'vg', label: 'Very Good' },
|
|
87
|
+
{ id: 'e', label: 'Excellent' },
|
|
88
|
+
{ id: 'na', label: 'N/A' },
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
parameters: {
|
|
92
|
+
docs: {
|
|
93
|
+
description: { story: 'Large matrix with 8 rows and 7 columns. Scrolls horizontally on small screens.' },
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// =============================================================================
|
|
99
|
+
// Sizes
|
|
100
|
+
// =============================================================================
|
|
101
|
+
|
|
102
|
+
export const AllSizes: Story = {
|
|
103
|
+
render: () => (
|
|
104
|
+
<div className="flex flex-col gap-8">
|
|
105
|
+
<MatrixGrid
|
|
106
|
+
label="Small"
|
|
107
|
+
rows={LIKERT_ROWS.slice(0, 2)}
|
|
108
|
+
columns={LIKERT_COLUMNS}
|
|
109
|
+
size="sm"
|
|
110
|
+
/>
|
|
111
|
+
<MatrixGrid
|
|
112
|
+
label="Default"
|
|
113
|
+
rows={LIKERT_ROWS.slice(0, 2)}
|
|
114
|
+
columns={LIKERT_COLUMNS}
|
|
115
|
+
size="default"
|
|
116
|
+
/>
|
|
117
|
+
<MatrixGrid
|
|
118
|
+
label="Large"
|
|
119
|
+
rows={LIKERT_ROWS.slice(0, 2)}
|
|
120
|
+
columns={LIKERT_COLUMNS}
|
|
121
|
+
size="lg"
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
),
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// =============================================================================
|
|
128
|
+
// States
|
|
129
|
+
// =============================================================================
|
|
130
|
+
|
|
131
|
+
export const Disabled: Story = {
|
|
132
|
+
args: {
|
|
133
|
+
label: 'Survey (Closed)',
|
|
134
|
+
rows: LIKERT_ROWS,
|
|
135
|
+
columns: LIKERT_COLUMNS,
|
|
136
|
+
value: { q1: 'a', q2: 'sa', q3: 'n' },
|
|
137
|
+
isDisabled: true,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const ReadOnly: Story = {
|
|
142
|
+
args: {
|
|
143
|
+
label: 'Previous Responses',
|
|
144
|
+
rows: LIKERT_ROWS,
|
|
145
|
+
columns: LIKERT_COLUMNS,
|
|
146
|
+
value: { q1: 'sa', q2: 'a', q3: 'n' },
|
|
147
|
+
isReadOnly: true,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const Required: Story = {
|
|
152
|
+
args: {
|
|
153
|
+
label: 'Required Assessment',
|
|
154
|
+
rows: LIKERT_ROWS,
|
|
155
|
+
columns: LIKERT_COLUMNS,
|
|
156
|
+
isRequired: true,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const Invalid: Story = {
|
|
161
|
+
args: {
|
|
162
|
+
label: 'Assessment',
|
|
163
|
+
rows: LIKERT_ROWS,
|
|
164
|
+
columns: LIKERT_COLUMNS,
|
|
165
|
+
isInvalid: true,
|
|
166
|
+
errorMessage: 'Please answer all rows before continuing.',
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export const WithDescription: Story = {
|
|
171
|
+
args: {
|
|
172
|
+
label: 'Training Effectiveness',
|
|
173
|
+
description: 'Rate each aspect of the training programme you attended.',
|
|
174
|
+
rows: LIKERT_ROWS,
|
|
175
|
+
columns: LIKERT_COLUMNS,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// =============================================================================
|
|
180
|
+
// Controlled Example
|
|
181
|
+
// =============================================================================
|
|
182
|
+
|
|
183
|
+
export const ControlledExample: Story = {
|
|
184
|
+
render: () => {
|
|
185
|
+
const [value, setValue] = useState<Record<string, string | null>>({});
|
|
186
|
+
return (
|
|
187
|
+
<div className="max-w-2xl">
|
|
188
|
+
<MatrixGrid
|
|
189
|
+
label="Controlled Matrix"
|
|
190
|
+
rows={LIKERT_ROWS}
|
|
191
|
+
columns={LIKERT_COLUMNS}
|
|
192
|
+
value={value}
|
|
193
|
+
onChange={setValue}
|
|
194
|
+
/>
|
|
195
|
+
<pre className="mt-4 rounded bg-[var(--muted)] p-3 text-xs">
|
|
196
|
+
{JSON.stringify(value, null, 2)}
|
|
197
|
+
</pre>
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
},
|
|
201
|
+
parameters: {
|
|
202
|
+
docs: {
|
|
203
|
+
description: { story: 'Controlled component with state displayed below.' },
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// =============================================================================
|
|
209
|
+
// Real-world Examples
|
|
210
|
+
// =============================================================================
|
|
211
|
+
|
|
212
|
+
export const SatisfactionSurvey: Story = {
|
|
213
|
+
render: () => {
|
|
214
|
+
const [value, setValue] = useState<Record<string, string | null>>({});
|
|
215
|
+
const rows = [
|
|
216
|
+
{ id: 'onboarding', label: 'Onboarding process' },
|
|
217
|
+
{ id: 'tools', label: 'Tools and resources' },
|
|
218
|
+
{ id: 'management', label: 'Management support' },
|
|
219
|
+
{ id: 'culture', label: 'Company culture' },
|
|
220
|
+
{ id: 'growth', label: 'Growth opportunities' },
|
|
221
|
+
];
|
|
222
|
+
const columns = [
|
|
223
|
+
{ id: 'vs', label: 'Very Unsatisfied' },
|
|
224
|
+
{ id: 's', label: 'Unsatisfied' },
|
|
225
|
+
{ id: 'n', label: 'Neutral' },
|
|
226
|
+
{ id: 'st', label: 'Satisfied' },
|
|
227
|
+
{ id: 'vst', label: 'Very Satisfied' },
|
|
228
|
+
];
|
|
229
|
+
const answered = Object.keys(value).length;
|
|
230
|
+
return (
|
|
231
|
+
<div className="max-w-2xl rounded-lg border border-[var(--border)] bg-[var(--content-background)] p-6">
|
|
232
|
+
<MatrixGrid
|
|
233
|
+
label="Employee Satisfaction Survey"
|
|
234
|
+
description="Please rate your satisfaction with each aspect of your role."
|
|
235
|
+
rows={rows}
|
|
236
|
+
columns={columns}
|
|
237
|
+
value={value}
|
|
238
|
+
onChange={setValue}
|
|
239
|
+
isRequired
|
|
240
|
+
/>
|
|
241
|
+
<p className="mt-3 text-sm text-[var(--muted-foreground)]">
|
|
242
|
+
{answered} of {rows.length} answered
|
|
243
|
+
</p>
|
|
244
|
+
</div>
|
|
245
|
+
);
|
|
246
|
+
},
|
|
247
|
+
parameters: {
|
|
248
|
+
docs: {
|
|
249
|
+
description: { story: 'Employee satisfaction survey with progress indicator.' },
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
export const EmployeeFeedback: Story = {
|
|
255
|
+
args: {
|
|
256
|
+
label: 'Peer Feedback',
|
|
257
|
+
description: 'Rate your colleague on the following competencies.',
|
|
258
|
+
rows: [
|
|
259
|
+
{ id: 'comm', label: 'Communication' },
|
|
260
|
+
{ id: 'team', label: 'Teamwork' },
|
|
261
|
+
{ id: 'init', label: 'Initiative' },
|
|
262
|
+
{ id: 'quality', label: 'Quality of work' },
|
|
263
|
+
{ id: 'time', label: 'Time management' },
|
|
264
|
+
],
|
|
265
|
+
columns: [
|
|
266
|
+
{ id: 'ni', label: 'Needs Improvement' },
|
|
267
|
+
{ id: 'me', label: 'Meets Expectations' },
|
|
268
|
+
{ id: 'ee', label: 'Exceeds Expectations' },
|
|
269
|
+
{ id: 'ex', label: 'Exceptional' },
|
|
270
|
+
],
|
|
271
|
+
isRequired: true,
|
|
272
|
+
},
|
|
273
|
+
parameters: {
|
|
274
|
+
docs: {
|
|
275
|
+
description: { story: '360-degree feedback form with 4-point competency scale.' },
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
export const CustomStyling: Story = {
|
|
281
|
+
args: {
|
|
282
|
+
label: 'Custom Styled Grid',
|
|
283
|
+
rows: LIKERT_ROWS.slice(0, 2),
|
|
284
|
+
columns: LIKERT_COLUMNS.slice(0, 3),
|
|
285
|
+
className: 'p-4 rounded-lg border border-[var(--border)] bg-[var(--content-background)]',
|
|
286
|
+
},
|
|
287
|
+
parameters: {
|
|
288
|
+
docs: {
|
|
289
|
+
description: { story: 'Demonstrates custom className pass-through for container styling.' },
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
};
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { RatingScale } from './RatingScale';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Elements/RatingScale',
|
|
7
|
+
component: RatingScale,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: 'centered',
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
'Configurable rating scale with 3 display styles (buttons, stars, slider). Built on React Aria RadioGroup and Slider primitives for full keyboard and screen reader support.',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
tags: ['autodocs'],
|
|
18
|
+
argTypes: {
|
|
19
|
+
displayStyle: {
|
|
20
|
+
control: 'select',
|
|
21
|
+
options: ['buttons', 'stars', 'slider'],
|
|
22
|
+
description: 'Visual display style of the scale',
|
|
23
|
+
table: { defaultValue: { summary: 'buttons' } },
|
|
24
|
+
},
|
|
25
|
+
size: {
|
|
26
|
+
control: 'select',
|
|
27
|
+
options: ['sm', 'default', 'lg'],
|
|
28
|
+
description: 'Component size',
|
|
29
|
+
table: { defaultValue: { summary: 'default' } },
|
|
30
|
+
},
|
|
31
|
+
min: { control: 'number', description: 'Minimum value', table: { defaultValue: { summary: '1' } } },
|
|
32
|
+
max: { control: 'number', description: 'Maximum value', table: { defaultValue: { summary: '5' } } },
|
|
33
|
+
step: { control: 'number', description: 'Step increment', table: { defaultValue: { summary: '1' } } },
|
|
34
|
+
showNA: { control: 'boolean', description: 'Show N/A button', table: { defaultValue: { summary: 'false' } } },
|
|
35
|
+
isDisabled: { control: 'boolean' },
|
|
36
|
+
isReadOnly: { control: 'boolean' },
|
|
37
|
+
isRequired: { control: 'boolean' },
|
|
38
|
+
isInvalid: { control: 'boolean' },
|
|
39
|
+
label: { control: 'text' },
|
|
40
|
+
description: { control: 'text' },
|
|
41
|
+
errorMessage: { control: 'text' },
|
|
42
|
+
lowLabel: { control: 'text' },
|
|
43
|
+
highLabel: { control: 'text' },
|
|
44
|
+
},
|
|
45
|
+
} satisfies Meta<typeof RatingScale>;
|
|
46
|
+
|
|
47
|
+
export default meta;
|
|
48
|
+
type Story = StoryObj<typeof meta>;
|
|
49
|
+
|
|
50
|
+
// =============================================================================
|
|
51
|
+
// Display Styles
|
|
52
|
+
// =============================================================================
|
|
53
|
+
|
|
54
|
+
export const Buttons: Story = {
|
|
55
|
+
args: {
|
|
56
|
+
label: 'How would you rate this experience?',
|
|
57
|
+
displayStyle: 'buttons',
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const Stars: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
label: 'Rate this product',
|
|
64
|
+
displayStyle: 'stars',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const Slider: Story = {
|
|
69
|
+
args: {
|
|
70
|
+
label: 'Satisfaction level',
|
|
71
|
+
displayStyle: 'slider',
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const AllDisplayStyles: Story = {
|
|
76
|
+
render: () => (
|
|
77
|
+
<div className="flex flex-col gap-8 w-[400px]">
|
|
78
|
+
<RatingScale label="Buttons" displayStyle="buttons" />
|
|
79
|
+
<RatingScale label="Stars" displayStyle="stars" />
|
|
80
|
+
<RatingScale label="Slider" displayStyle="slider" />
|
|
81
|
+
</div>
|
|
82
|
+
),
|
|
83
|
+
parameters: {
|
|
84
|
+
docs: {
|
|
85
|
+
description: { story: 'Comparison of all three display styles.' },
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// =============================================================================
|
|
91
|
+
// Sizes
|
|
92
|
+
// =============================================================================
|
|
93
|
+
|
|
94
|
+
export const SmallSize: Story = {
|
|
95
|
+
args: {
|
|
96
|
+
label: 'Small rating',
|
|
97
|
+
size: 'sm',
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const LargeSize: Story = {
|
|
102
|
+
args: {
|
|
103
|
+
label: 'Large rating',
|
|
104
|
+
size: 'lg',
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const AllSizes: Story = {
|
|
109
|
+
render: () => (
|
|
110
|
+
<div className="flex flex-col gap-8 w-[400px]">
|
|
111
|
+
<RatingScale label="Small" size="sm" />
|
|
112
|
+
<RatingScale label="Default" size="default" />
|
|
113
|
+
<RatingScale label="Large" size="lg" />
|
|
114
|
+
</div>
|
|
115
|
+
),
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// =============================================================================
|
|
119
|
+
// Labels
|
|
120
|
+
// =============================================================================
|
|
121
|
+
|
|
122
|
+
export const WithEndpointLabels: Story = {
|
|
123
|
+
args: {
|
|
124
|
+
label: 'Job satisfaction',
|
|
125
|
+
lowLabel: 'Very unsatisfied',
|
|
126
|
+
highLabel: 'Very satisfied',
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const WithValueLabels: Story = {
|
|
131
|
+
render: () => {
|
|
132
|
+
const [value, setValue] = useState<number | null>(null);
|
|
133
|
+
return (
|
|
134
|
+
<div className="w-[400px]">
|
|
135
|
+
<RatingScale
|
|
136
|
+
label="How likely are you to recommend us?"
|
|
137
|
+
value={value}
|
|
138
|
+
onChange={setValue}
|
|
139
|
+
valueLabels={{
|
|
140
|
+
'1': 'Not at all likely',
|
|
141
|
+
'2': 'Unlikely',
|
|
142
|
+
'3': 'Neutral',
|
|
143
|
+
'4': 'Likely',
|
|
144
|
+
'5': 'Very likely',
|
|
145
|
+
}}
|
|
146
|
+
/>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
},
|
|
150
|
+
parameters: {
|
|
151
|
+
docs: {
|
|
152
|
+
description: { story: 'Value labels provide context for each score.' },
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const WithDescription: Story = {
|
|
158
|
+
args: {
|
|
159
|
+
label: 'Technical skills',
|
|
160
|
+
description: 'Rate the candidate\'s technical proficiency from 1-5.',
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// =============================================================================
|
|
165
|
+
// N/A Support
|
|
166
|
+
// =============================================================================
|
|
167
|
+
|
|
168
|
+
export const WithNA: Story = {
|
|
169
|
+
render: () => {
|
|
170
|
+
const [value, setValue] = useState<number | null>(null);
|
|
171
|
+
return (
|
|
172
|
+
<div className="w-[400px]">
|
|
173
|
+
<RatingScale
|
|
174
|
+
label="Communication skills"
|
|
175
|
+
showNA
|
|
176
|
+
value={value}
|
|
177
|
+
onChange={setValue}
|
|
178
|
+
lowLabel="Poor"
|
|
179
|
+
highLabel="Excellent"
|
|
180
|
+
/>
|
|
181
|
+
<p className="mt-2 text-sm text-[var(--muted-foreground)]">
|
|
182
|
+
Selected: {value === null ? 'N/A' : value ?? 'None'}
|
|
183
|
+
</p>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
},
|
|
187
|
+
parameters: {
|
|
188
|
+
docs: {
|
|
189
|
+
description: { story: 'N/A button allows opting out of rating.' },
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// =============================================================================
|
|
195
|
+
// States
|
|
196
|
+
// =============================================================================
|
|
197
|
+
|
|
198
|
+
export const Required: Story = {
|
|
199
|
+
args: {
|
|
200
|
+
label: 'Rating',
|
|
201
|
+
isRequired: true,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export const Invalid: Story = {
|
|
206
|
+
args: {
|
|
207
|
+
label: 'Rating',
|
|
208
|
+
isInvalid: true,
|
|
209
|
+
errorMessage: 'Please select a rating before continuing.',
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export const Disabled: Story = {
|
|
214
|
+
args: {
|
|
215
|
+
label: 'Rating',
|
|
216
|
+
value: 3,
|
|
217
|
+
isDisabled: true,
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export const ReadOnly: Story = {
|
|
222
|
+
args: {
|
|
223
|
+
label: 'Previous rating',
|
|
224
|
+
value: 4,
|
|
225
|
+
isReadOnly: true,
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// =============================================================================
|
|
230
|
+
// Custom Ranges
|
|
231
|
+
// =============================================================================
|
|
232
|
+
|
|
233
|
+
export const TenPointScale: Story = {
|
|
234
|
+
args: {
|
|
235
|
+
label: 'NPS Score',
|
|
236
|
+
min: 0,
|
|
237
|
+
max: 10,
|
|
238
|
+
lowLabel: 'Not likely',
|
|
239
|
+
highLabel: 'Very likely',
|
|
240
|
+
displayStyle: 'buttons',
|
|
241
|
+
},
|
|
242
|
+
parameters: {
|
|
243
|
+
docs: {
|
|
244
|
+
description: { story: '0-10 Net Promoter Score scale.' },
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export const StarsOutOfTen: Story = {
|
|
250
|
+
args: {
|
|
251
|
+
label: 'Movie rating',
|
|
252
|
+
min: 1,
|
|
253
|
+
max: 10,
|
|
254
|
+
displayStyle: 'stars',
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
export const SliderWithEndpoints: Story = {
|
|
259
|
+
args: {
|
|
260
|
+
label: 'Pain level',
|
|
261
|
+
min: 0,
|
|
262
|
+
max: 10,
|
|
263
|
+
displayStyle: 'slider',
|
|
264
|
+
lowLabel: 'No pain',
|
|
265
|
+
highLabel: 'Worst pain',
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// =============================================================================
|
|
270
|
+
// Controlled Example
|
|
271
|
+
// =============================================================================
|
|
272
|
+
|
|
273
|
+
export const ControlledExample: Story = {
|
|
274
|
+
render: () => {
|
|
275
|
+
const [value, setValue] = useState<number | null>(3);
|
|
276
|
+
return (
|
|
277
|
+
<div className="w-[400px]">
|
|
278
|
+
<RatingScale
|
|
279
|
+
label="Controlled rating"
|
|
280
|
+
value={value}
|
|
281
|
+
onChange={setValue}
|
|
282
|
+
displayStyle="buttons"
|
|
283
|
+
showNA
|
|
284
|
+
lowLabel="Low"
|
|
285
|
+
highLabel="High"
|
|
286
|
+
valueLabels={{
|
|
287
|
+
'1': 'Poor',
|
|
288
|
+
'2': 'Below average',
|
|
289
|
+
'3': 'Average',
|
|
290
|
+
'4': 'Above average',
|
|
291
|
+
'5': 'Excellent',
|
|
292
|
+
}}
|
|
293
|
+
/>
|
|
294
|
+
<div className="mt-4 flex gap-2">
|
|
295
|
+
<button
|
|
296
|
+
className="rounded border px-3 py-1 text-sm"
|
|
297
|
+
onClick={() => setValue(1)}
|
|
298
|
+
>
|
|
299
|
+
Set to 1
|
|
300
|
+
</button>
|
|
301
|
+
<button
|
|
302
|
+
className="rounded border px-3 py-1 text-sm"
|
|
303
|
+
onClick={() => setValue(5)}
|
|
304
|
+
>
|
|
305
|
+
Set to 5
|
|
306
|
+
</button>
|
|
307
|
+
<button
|
|
308
|
+
className="rounded border px-3 py-1 text-sm"
|
|
309
|
+
onClick={() => setValue(null)}
|
|
310
|
+
>
|
|
311
|
+
Set N/A
|
|
312
|
+
</button>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
);
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// =============================================================================
|
|
320
|
+
// Real-world Examples
|
|
321
|
+
// =============================================================================
|
|
322
|
+
|
|
323
|
+
export const InterviewScorecard: Story = {
|
|
324
|
+
render: () => {
|
|
325
|
+
const [scores, setScores] = useState<Record<string, number | null>>({
|
|
326
|
+
technical: null,
|
|
327
|
+
communication: null,
|
|
328
|
+
leadership: null,
|
|
329
|
+
});
|
|
330
|
+
return (
|
|
331
|
+
<div className="w-[500px] rounded-lg border border-[var(--border)] bg-[var(--content-background)] p-6">
|
|
332
|
+
<h3 className="mb-4 text-lg font-semibold text-[var(--content-foreground)]">
|
|
333
|
+
Interview Scorecard
|
|
334
|
+
</h3>
|
|
335
|
+
<div className="flex flex-col gap-6">
|
|
336
|
+
<RatingScale
|
|
337
|
+
label="Technical Skills"
|
|
338
|
+
displayStyle="stars"
|
|
339
|
+
value={scores.technical}
|
|
340
|
+
onChange={(v) => setScores((s) => ({ ...s, technical: v }))}
|
|
341
|
+
showNA
|
|
342
|
+
/>
|
|
343
|
+
<RatingScale
|
|
344
|
+
label="Communication"
|
|
345
|
+
displayStyle="buttons"
|
|
346
|
+
value={scores.communication}
|
|
347
|
+
onChange={(v) => setScores((s) => ({ ...s, communication: v }))}
|
|
348
|
+
lowLabel="Poor"
|
|
349
|
+
highLabel="Excellent"
|
|
350
|
+
valueLabels={{
|
|
351
|
+
'1': 'Poor',
|
|
352
|
+
'2': 'Below expectations',
|
|
353
|
+
'3': 'Meets expectations',
|
|
354
|
+
'4': 'Exceeds expectations',
|
|
355
|
+
'5': 'Outstanding',
|
|
356
|
+
}}
|
|
357
|
+
/>
|
|
358
|
+
<RatingScale
|
|
359
|
+
label="Leadership Potential"
|
|
360
|
+
displayStyle="slider"
|
|
361
|
+
value={scores.leadership}
|
|
362
|
+
onChange={(v) => setScores((s) => ({ ...s, leadership: v }))}
|
|
363
|
+
min={1}
|
|
364
|
+
max={10}
|
|
365
|
+
lowLabel="Low"
|
|
366
|
+
highLabel="High"
|
|
367
|
+
/>
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
);
|
|
371
|
+
},
|
|
372
|
+
parameters: {
|
|
373
|
+
docs: {
|
|
374
|
+
description: {
|
|
375
|
+
story: 'Interview scorecard showing all three display styles for different evaluation criteria.',
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
};
|