@tribepad/themis 1.0.5 → 1.0.7
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/Tabs/Tabs.d.ts +2 -2
- package/dist/elements/Tabs/Tabs.d.ts.map +1 -1
- package/dist/elements/Tabs/Tabs.types.d.ts +3 -0
- package/dist/elements/Tabs/Tabs.types.d.ts.map +1 -1
- package/dist/elements/Tabs/index.js +1 -1
- package/dist/elements/Tabs/index.js.map +1 -1
- package/dist/elements/Tabs/index.mjs +1 -1
- package/dist/elements/Tabs/index.mjs.map +1 -1
- 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 +15 -15
- 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/src/elements/Tabs/Tabs.stories.tsx +231 -2
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { SituationalJudgement } from './SituationalJudgement';
|
|
4
|
+
|
|
5
|
+
const SAMPLE_RESPONSES = [
|
|
6
|
+
{ value: 'a', text: 'Speak to the colleague privately about the issue.', ordinal: 0 },
|
|
7
|
+
{ value: 'b', text: 'Report the situation to your manager immediately.', ordinal: 1 },
|
|
8
|
+
{ value: 'c', text: 'Ignore the situation and focus on your own work.', ordinal: 2 },
|
|
9
|
+
{ value: 'd', text: 'Discuss the matter in the next team meeting.', ordinal: 3 },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const SAMPLE_SCENARIO = '<p>You observe a colleague making a <strong>significant error</strong> on an important client report. The report is due to be submitted tomorrow morning. Your colleague seems unaware of the mistake.</p>';
|
|
13
|
+
|
|
14
|
+
const meta = {
|
|
15
|
+
title: 'Elements/SituationalJudgement',
|
|
16
|
+
component: SituationalJudgement,
|
|
17
|
+
parameters: {
|
|
18
|
+
layout: 'centered',
|
|
19
|
+
docs: {
|
|
20
|
+
description: {
|
|
21
|
+
component:
|
|
22
|
+
'Assessment component presenting a scenario with 3-6 response options. Composes existing RadioGroup + Radio for responses and Accordion for collapsible scenarios. Includes built-in HTML sanitiser for XSS protection.',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
tags: ['autodocs'],
|
|
27
|
+
argTypes: {
|
|
28
|
+
size: {
|
|
29
|
+
control: 'select',
|
|
30
|
+
options: ['sm', 'default', 'lg'],
|
|
31
|
+
description: 'Component size',
|
|
32
|
+
table: { defaultValue: { summary: 'default' } },
|
|
33
|
+
},
|
|
34
|
+
collapsibleScenario: { control: 'boolean' },
|
|
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
|
+
},
|
|
43
|
+
} satisfies Meta<typeof SituationalJudgement>;
|
|
44
|
+
|
|
45
|
+
export default meta;
|
|
46
|
+
type Story = StoryObj<typeof meta>;
|
|
47
|
+
|
|
48
|
+
// =============================================================================
|
|
49
|
+
// Default
|
|
50
|
+
// =============================================================================
|
|
51
|
+
|
|
52
|
+
export const Default: Story = {
|
|
53
|
+
args: {
|
|
54
|
+
label: 'Question 1',
|
|
55
|
+
scenarioText: SAMPLE_SCENARIO,
|
|
56
|
+
responses: SAMPLE_RESPONSES,
|
|
57
|
+
},
|
|
58
|
+
decorators: [(Story) => <div className="w-[600px]"><Story /></div>],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// =============================================================================
|
|
62
|
+
// Collapsible Scenario
|
|
63
|
+
// =============================================================================
|
|
64
|
+
|
|
65
|
+
export const CollapsibleScenario: Story = {
|
|
66
|
+
args: {
|
|
67
|
+
label: 'Question 2',
|
|
68
|
+
scenarioText: SAMPLE_SCENARIO,
|
|
69
|
+
responses: SAMPLE_RESPONSES,
|
|
70
|
+
collapsibleScenario: true,
|
|
71
|
+
},
|
|
72
|
+
decorators: [(Story) => <div className="w-[600px]"><Story /></div>],
|
|
73
|
+
parameters: {
|
|
74
|
+
docs: {
|
|
75
|
+
description: {
|
|
76
|
+
story: 'Scenario panel can be collapsed to save space. Uses the existing Accordion component.',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// =============================================================================
|
|
83
|
+
// States
|
|
84
|
+
// =============================================================================
|
|
85
|
+
|
|
86
|
+
export const Required: Story = {
|
|
87
|
+
args: {
|
|
88
|
+
label: 'Question 3',
|
|
89
|
+
scenarioText: SAMPLE_SCENARIO,
|
|
90
|
+
responses: SAMPLE_RESPONSES,
|
|
91
|
+
isRequired: true,
|
|
92
|
+
},
|
|
93
|
+
decorators: [(Story) => <div className="w-[600px]"><Story /></div>],
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const Invalid: Story = {
|
|
97
|
+
args: {
|
|
98
|
+
label: 'Question 4',
|
|
99
|
+
scenarioText: SAMPLE_SCENARIO,
|
|
100
|
+
responses: SAMPLE_RESPONSES,
|
|
101
|
+
isInvalid: true,
|
|
102
|
+
errorMessage: 'Please select a response before continuing.',
|
|
103
|
+
},
|
|
104
|
+
decorators: [(Story) => <div className="w-[600px]"><Story /></div>],
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const Disabled: Story = {
|
|
108
|
+
args: {
|
|
109
|
+
label: 'Question 5',
|
|
110
|
+
scenarioText: SAMPLE_SCENARIO,
|
|
111
|
+
responses: SAMPLE_RESPONSES,
|
|
112
|
+
value: 'a',
|
|
113
|
+
isDisabled: true,
|
|
114
|
+
},
|
|
115
|
+
decorators: [(Story) => <div className="w-[600px]"><Story /></div>],
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const ReadOnly: Story = {
|
|
119
|
+
args: {
|
|
120
|
+
label: 'Question 6 (Submitted)',
|
|
121
|
+
scenarioText: SAMPLE_SCENARIO,
|
|
122
|
+
responses: SAMPLE_RESPONSES,
|
|
123
|
+
value: 'b',
|
|
124
|
+
isReadOnly: true,
|
|
125
|
+
},
|
|
126
|
+
decorators: [(Story) => <div className="w-[600px]"><Story /></div>],
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// =============================================================================
|
|
130
|
+
// With Description
|
|
131
|
+
// =============================================================================
|
|
132
|
+
|
|
133
|
+
export const WithDescription: Story = {
|
|
134
|
+
args: {
|
|
135
|
+
label: 'Question 7',
|
|
136
|
+
description: 'Choose the most effective response to the scenario below.',
|
|
137
|
+
scenarioText: SAMPLE_SCENARIO,
|
|
138
|
+
responses: SAMPLE_RESPONSES,
|
|
139
|
+
},
|
|
140
|
+
decorators: [(Story) => <div className="w-[600px]"><Story /></div>],
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// =============================================================================
|
|
144
|
+
// Sizes
|
|
145
|
+
// =============================================================================
|
|
146
|
+
|
|
147
|
+
export const SmallSize: Story = {
|
|
148
|
+
args: {
|
|
149
|
+
label: 'Small',
|
|
150
|
+
scenarioText: '<p>A brief scenario.</p>',
|
|
151
|
+
responses: SAMPLE_RESPONSES.slice(0, 3),
|
|
152
|
+
size: 'sm',
|
|
153
|
+
},
|
|
154
|
+
decorators: [(Story) => <div className="w-[600px]"><Story /></div>],
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const LargeSize: Story = {
|
|
158
|
+
args: {
|
|
159
|
+
label: 'Large',
|
|
160
|
+
scenarioText: '<p>A brief scenario.</p>',
|
|
161
|
+
responses: SAMPLE_RESPONSES.slice(0, 3),
|
|
162
|
+
size: 'lg',
|
|
163
|
+
},
|
|
164
|
+
decorators: [(Story) => <div className="w-[600px]"><Story /></div>],
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// =============================================================================
|
|
168
|
+
// Controlled
|
|
169
|
+
// =============================================================================
|
|
170
|
+
|
|
171
|
+
export const ControlledExample: Story = {
|
|
172
|
+
render: () => {
|
|
173
|
+
const [value, setValue] = useState<string | null>(null);
|
|
174
|
+
return (
|
|
175
|
+
<div className="w-[600px]">
|
|
176
|
+
<SituationalJudgement
|
|
177
|
+
label="Question 8"
|
|
178
|
+
scenarioText={SAMPLE_SCENARIO}
|
|
179
|
+
responses={SAMPLE_RESPONSES}
|
|
180
|
+
value={value}
|
|
181
|
+
onChange={setValue}
|
|
182
|
+
/>
|
|
183
|
+
<p className="mt-3 text-sm text-[var(--muted-foreground)]">
|
|
184
|
+
Selected: {value ?? 'None'}
|
|
185
|
+
</p>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// =============================================================================
|
|
192
|
+
// Six Responses
|
|
193
|
+
// =============================================================================
|
|
194
|
+
|
|
195
|
+
export const SixResponses: Story = {
|
|
196
|
+
args: {
|
|
197
|
+
label: 'Question 9',
|
|
198
|
+
scenarioText: '<p>A complex workplace scenario with multiple viable approaches.</p>',
|
|
199
|
+
responses: [
|
|
200
|
+
{ value: 'a', text: 'Address the issue directly with the person involved.', ordinal: 0 },
|
|
201
|
+
{ value: 'b', text: 'Consult with HR before taking any action.', ordinal: 1 },
|
|
202
|
+
{ value: 'c', text: 'Document the incident and report to management.', ordinal: 2 },
|
|
203
|
+
{ value: 'd', text: 'Seek advice from a trusted colleague.', ordinal: 3 },
|
|
204
|
+
{ value: 'e', text: 'Wait to see if the situation resolves itself.', ordinal: 4 },
|
|
205
|
+
{ value: 'f', text: 'Escalate immediately to senior leadership.', ordinal: 5 },
|
|
206
|
+
],
|
|
207
|
+
},
|
|
208
|
+
decorators: [(Story) => <div className="w-[600px]"><Story /></div>],
|
|
209
|
+
parameters: {
|
|
210
|
+
docs: {
|
|
211
|
+
description: { story: 'Maximum of 6 response options.' },
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// =============================================================================
|
|
217
|
+
// Rich Scenario HTML
|
|
218
|
+
// =============================================================================
|
|
219
|
+
|
|
220
|
+
export const RichScenarioHTML: Story = {
|
|
221
|
+
args: {
|
|
222
|
+
label: 'Question 10',
|
|
223
|
+
scenarioText: `
|
|
224
|
+
<p>During a team project, you discover that:</p>
|
|
225
|
+
<ul>
|
|
226
|
+
<li>A team member has been <strong>consistently missing deadlines</strong></li>
|
|
227
|
+
<li>Their work quality has <em>noticeably declined</em></li>
|
|
228
|
+
<li>Other team members are having to <strong>cover their tasks</strong></li>
|
|
229
|
+
</ul>
|
|
230
|
+
<p>The project deadline is in <strong>two weeks</strong> and the team is already behind schedule.</p>
|
|
231
|
+
`,
|
|
232
|
+
responses: [
|
|
233
|
+
{ value: 'a', text: 'Have a private, supportive conversation with the team member.', ordinal: 0 },
|
|
234
|
+
{ value: 'b', text: 'Redistribute the workload among remaining team members.', ordinal: 1 },
|
|
235
|
+
{ value: 'c', text: 'Escalate to the project manager with documented concerns.', ordinal: 2 },
|
|
236
|
+
],
|
|
237
|
+
collapsibleScenario: true,
|
|
238
|
+
},
|
|
239
|
+
decorators: [(Story) => <div className="w-[600px]"><Story /></div>],
|
|
240
|
+
parameters: {
|
|
241
|
+
docs: {
|
|
242
|
+
description: {
|
|
243
|
+
story: 'Scenario HTML supports formatting: bold, italic, lists, paragraphs. Dangerous HTML (scripts, iframes) is automatically sanitised.',
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// =============================================================================
|
|
250
|
+
// Assessment Form
|
|
251
|
+
// =============================================================================
|
|
252
|
+
|
|
253
|
+
export const AssessmentForm: Story = {
|
|
254
|
+
render: () => {
|
|
255
|
+
const [answers, setAnswers] = useState<Record<string, string | null>>({
|
|
256
|
+
q1: null,
|
|
257
|
+
q2: null,
|
|
258
|
+
});
|
|
259
|
+
return (
|
|
260
|
+
<div className="w-[600px] rounded-lg border border-[var(--border)] bg-[var(--content-background)] p-6">
|
|
261
|
+
<h2 className="mb-6 text-xl font-bold text-[var(--content-foreground)]">
|
|
262
|
+
Situational Judgement Assessment
|
|
263
|
+
</h2>
|
|
264
|
+
<div className="flex flex-col gap-8">
|
|
265
|
+
<SituationalJudgement
|
|
266
|
+
label="Question 1 of 2"
|
|
267
|
+
description="Select the most appropriate response."
|
|
268
|
+
scenarioText="<p>A customer calls to complain about a <strong>delayed delivery</strong>. They are upset and demanding an immediate resolution.</p>"
|
|
269
|
+
responses={[
|
|
270
|
+
{ value: 'a', text: 'Apologise sincerely and offer a refund or replacement.', ordinal: 0 },
|
|
271
|
+
{ value: 'b', text: 'Explain the delivery policy and ask them to wait.', ordinal: 1 },
|
|
272
|
+
{ value: 'c', text: 'Transfer the call to a supervisor.', ordinal: 2 },
|
|
273
|
+
]}
|
|
274
|
+
value={answers.q1}
|
|
275
|
+
onChange={(v) => setAnswers((a) => ({ ...a, q1: v }))}
|
|
276
|
+
isRequired
|
|
277
|
+
/>
|
|
278
|
+
<hr className="border-[var(--border)]" />
|
|
279
|
+
<SituationalJudgement
|
|
280
|
+
label="Question 2 of 2"
|
|
281
|
+
description="Select the most appropriate response."
|
|
282
|
+
scenarioText="<p>You notice a <em>security vulnerability</em> in the company's customer-facing application during routine testing.</p>"
|
|
283
|
+
responses={[
|
|
284
|
+
{ value: 'a', text: 'Report it immediately to the security team.', ordinal: 0 },
|
|
285
|
+
{ value: 'b', text: 'Try to fix it yourself before reporting.', ordinal: 1 },
|
|
286
|
+
{ value: 'c', text: 'Document it and include in the next sprint.', ordinal: 2 },
|
|
287
|
+
{ value: 'd', text: 'Verify the severity and then escalate accordingly.', ordinal: 3 },
|
|
288
|
+
]}
|
|
289
|
+
value={answers.q2}
|
|
290
|
+
onChange={(v) => setAnswers((a) => ({ ...a, q2: v }))}
|
|
291
|
+
isRequired
|
|
292
|
+
collapsibleScenario
|
|
293
|
+
/>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
);
|
|
297
|
+
},
|
|
298
|
+
parameters: {
|
|
299
|
+
docs: {
|
|
300
|
+
description: {
|
|
301
|
+
story: 'Full assessment form with multiple SJT questions.',
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
};
|