@kopexa/grc 0.0.3 → 0.0.4

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/index.js CHANGED
@@ -597,15 +597,18 @@ function ImpactCard({
597
597
  showAuthenticity = false,
598
598
  readOnly = false,
599
599
  scale = "risk",
600
- title
600
+ title,
601
+ variant = "card"
601
602
  }) {
602
603
  var _a, _b;
603
604
  const intl = (0, import_i18n6.useSafeIntl)();
605
+ const isInline = variant === "inline";
604
606
  const [isEditing, setIsEditing] = (0, import_react.useState)(false);
605
607
  const [editValues, setEditValues] = (0, import_react.useState)(
606
608
  value || defaultImpact
607
609
  );
608
- const styles = (0, import_theme3.impactCard)({ editing: isEditing });
610
+ const effectiveIsEditing = isInline ? !readOnly : isEditing;
611
+ const styles = (0, import_theme3.impactCard)({ editing: !isInline && isEditing });
609
612
  const scaleConfig = typeof scale === "string" ? getScale(scale) : scale;
610
613
  const formatLabel = (level) => {
611
614
  const config = scaleConfig[level];
@@ -641,18 +644,28 @@ function ImpactCard({
641
644
  setEditValues(value || defaultImpact);
642
645
  setIsEditing(true);
643
646
  };
644
- const currentImpact = isEditing ? editValues : value || defaultImpact;
647
+ const currentImpact = isInline ? value || defaultImpact : isEditing ? editValues : value || defaultImpact;
645
648
  const handleLevelChange = (key) => (level) => {
646
- setEditValues((prev) => ({
647
- ...prev,
649
+ const newValues = {
650
+ ...isInline ? value || defaultImpact : editValues,
648
651
  [key]: level
649
- }));
652
+ };
653
+ if (isInline) {
654
+ onChange == null ? void 0 : onChange(newValues);
655
+ } else {
656
+ setEditValues(newValues);
657
+ }
650
658
  };
651
659
  const handleJustificationChange = (justification) => {
652
- setEditValues((prev) => ({
653
- ...prev,
660
+ const newValues = {
661
+ ...isInline ? value || defaultImpact : editValues,
654
662
  impactJustification: justification || void 0
655
- }));
663
+ };
664
+ if (isInline) {
665
+ onChange == null ? void 0 : onChange(newValues);
666
+ } else {
667
+ setEditValues(newValues);
668
+ }
656
669
  };
657
670
  const highestImpact = Math.max(
658
671
  currentImpact.impactConfidentiality,
@@ -664,6 +677,87 @@ function ImpactCard({
664
677
  const justificationHint = intl.formatMessage(messages3.justification_hint, {
665
678
  level: highestLabel
666
679
  });
680
+ const impactRows = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
681
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
682
+ ImpactItemRow,
683
+ {
684
+ label: t.confidentiality,
685
+ shortLabel: "C",
686
+ value: currentImpact.impactConfidentiality,
687
+ isEditing: effectiveIsEditing,
688
+ scale: scaleConfig,
689
+ formatLabel,
690
+ onLevelChange: handleLevelChange("impactConfidentiality")
691
+ }
692
+ ),
693
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
694
+ ImpactItemRow,
695
+ {
696
+ label: t.integrity,
697
+ shortLabel: "I",
698
+ value: currentImpact.impactIntegrity,
699
+ isEditing: effectiveIsEditing,
700
+ scale: scaleConfig,
701
+ formatLabel,
702
+ onLevelChange: handleLevelChange("impactIntegrity")
703
+ }
704
+ ),
705
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
706
+ ImpactItemRow,
707
+ {
708
+ label: t.availability,
709
+ shortLabel: "A",
710
+ value: currentImpact.impactAvailability,
711
+ isEditing: effectiveIsEditing,
712
+ scale: scaleConfig,
713
+ formatLabel,
714
+ onLevelChange: handleLevelChange("impactAvailability")
715
+ }
716
+ ),
717
+ showAuthenticity && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
718
+ ImpactItemRow,
719
+ {
720
+ label: t.authenticity,
721
+ shortLabel: "Au",
722
+ value: (_b = currentImpact.impactAuthenticity) != null ? _b : 0,
723
+ isEditing: effectiveIsEditing,
724
+ scale: scaleConfig,
725
+ formatLabel,
726
+ onLevelChange: handleLevelChange("impactAuthenticity")
727
+ }
728
+ )
729
+ ] });
730
+ const justificationContent = showJustification && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: styles.justificationSection(), children: [
731
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
732
+ "label",
733
+ {
734
+ htmlFor: "impact-justification",
735
+ className: styles.justificationLabel(),
736
+ children: [
737
+ t.justification,
738
+ highestImpact > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: styles.justificationHint(), children: justificationHint })
739
+ ]
740
+ }
741
+ ),
742
+ effectiveIsEditing ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
743
+ import_sight3.Textarea,
744
+ {
745
+ id: "impact-justification",
746
+ value: currentImpact.impactJustification || "",
747
+ onChange: (e) => handleJustificationChange(e.target.value),
748
+ placeholder: t.justificationPlaceholder,
749
+ rows: 3,
750
+ className: "text-sm"
751
+ }
752
+ ) : currentImpact.impactJustification ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: styles.justificationText(), children: currentImpact.impactJustification }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: styles.justificationEmpty(), children: t.noJustification })
753
+ ] });
754
+ if (isInline) {
755
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: styles.wrapper(), children: [
756
+ title && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: styles.inlineHeader(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sight3.Heading, { level: "h4", className: "text-sm font-medium", children: title }) }),
757
+ impactRows,
758
+ justificationContent
759
+ ] });
760
+ }
667
761
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sight3.Card.Root, { className: styles.root(), children: [
668
762
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sight3.Card.Header, { className: "flex flex-row items-center justify-between", children: [
669
763
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", children: [
@@ -685,78 +779,8 @@ function ImpactCard({
685
779
  ] }))
686
780
  ] }),
687
781
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sight3.Card.Body, { className: "space-y-3", children: [
688
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
689
- ImpactItemRow,
690
- {
691
- label: t.confidentiality,
692
- shortLabel: "C",
693
- value: currentImpact.impactConfidentiality,
694
- isEditing,
695
- scale: scaleConfig,
696
- formatLabel,
697
- onLevelChange: handleLevelChange("impactConfidentiality")
698
- }
699
- ),
700
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
701
- ImpactItemRow,
702
- {
703
- label: t.integrity,
704
- shortLabel: "I",
705
- value: currentImpact.impactIntegrity,
706
- isEditing,
707
- scale: scaleConfig,
708
- formatLabel,
709
- onLevelChange: handleLevelChange("impactIntegrity")
710
- }
711
- ),
712
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
713
- ImpactItemRow,
714
- {
715
- label: t.availability,
716
- shortLabel: "A",
717
- value: currentImpact.impactAvailability,
718
- isEditing,
719
- scale: scaleConfig,
720
- formatLabel,
721
- onLevelChange: handleLevelChange("impactAvailability")
722
- }
723
- ),
724
- showAuthenticity && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
725
- ImpactItemRow,
726
- {
727
- label: t.authenticity,
728
- shortLabel: "Au",
729
- value: (_b = currentImpact.impactAuthenticity) != null ? _b : 0,
730
- isEditing,
731
- scale: scaleConfig,
732
- formatLabel,
733
- onLevelChange: handleLevelChange("impactAuthenticity")
734
- }
735
- ),
736
- showJustification && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: styles.justificationSection(), children: [
737
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
738
- "label",
739
- {
740
- htmlFor: "impact-justification",
741
- className: styles.justificationLabel(),
742
- children: [
743
- t.justification,
744
- highestImpact > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: styles.justificationHint(), children: justificationHint })
745
- ]
746
- }
747
- ),
748
- isEditing ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
749
- import_sight3.Textarea,
750
- {
751
- id: "impact-justification",
752
- value: currentImpact.impactJustification || "",
753
- onChange: (e) => handleJustificationChange(e.target.value),
754
- placeholder: t.justificationPlaceholder,
755
- rows: 3,
756
- className: "text-sm"
757
- }
758
- ) : currentImpact.impactJustification ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: styles.justificationText(), children: currentImpact.impactJustification }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: styles.justificationEmpty(), children: t.noJustification })
759
- ] })
782
+ impactRows,
783
+ justificationContent
760
784
  ] })
761
785
  ] });
762
786
  }
package/dist/index.mjs CHANGED
@@ -22,7 +22,7 @@ import "./chunk-QS5S6V26.mjs";
22
22
  import "./chunk-GFABGXAO.mjs";
23
23
  import {
24
24
  ImpactCard
25
- } from "./chunk-TW3S4OE2.mjs";
25
+ } from "./chunk-AGASJJ7X.mjs";
26
26
  import {
27
27
  assetScale,
28
28
  getScale,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kopexa/grc",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "GRC (Governance, Risk, Compliance) components for Kopexa",
5
5
  "sideEffects": false,
6
6
  "main": "dist/index.js",
@@ -42,15 +42,15 @@
42
42
  "react": ">=19.0.0-rc.0",
43
43
  "react-dom": ">=19.0.0-rc.0",
44
44
  "react-intl": "^7.1.14",
45
- "@kopexa/theme": "17.7.0",
46
- "@kopexa/sight": "17.1.1"
45
+ "@kopexa/theme": "17.8.0",
46
+ "@kopexa/sight": "17.2.0"
47
47
  },
48
48
  "dependencies": {
49
49
  "@kopexa/krn": "^1.0.1",
50
- "@kopexa/react-utils": "17.0.13",
51
- "@kopexa/icons": "17.2.1",
52
- "@kopexa/i18n": "17.2.0",
53
- "@kopexa/shared-utils": "17.0.13"
50
+ "@kopexa/react-utils": "17.0.14",
51
+ "@kopexa/shared-utils": "17.0.14",
52
+ "@kopexa/icons": "17.2.2",
53
+ "@kopexa/i18n": "17.3.0"
54
54
  },
55
55
  "clean-package": "../../clean-package.config.json",
56
56
  "publishConfig": {
@@ -145,6 +145,12 @@ export interface ImpactCardProps {
145
145
  scale?: ImpactScalePreset | ImpactScaleConfig;
146
146
  /** Custom title for the card */
147
147
  title?: string;
148
+ /**
149
+ * Display variant:
150
+ * - `card`: Wrapped in a Card with edit/save/cancel buttons (default)
151
+ * - `inline`: No card wrapper, always editable, changes propagate immediately
152
+ */
153
+ variant?: "card" | "inline";
148
154
  }
149
155
 
150
156
  const defaultImpact: ImpactValue = {
@@ -162,14 +168,19 @@ export function ImpactCard({
162
168
  readOnly = false,
163
169
  scale = "risk",
164
170
  title,
171
+ variant = "card",
165
172
  }: ImpactCardProps) {
166
173
  const intl = useSafeIntl();
174
+ const isInline = variant === "inline";
167
175
  const [isEditing, setIsEditing] = useState(false);
168
176
  const [editValues, setEditValues] = useState<ImpactValue>(
169
177
  value || defaultImpact,
170
178
  );
171
179
 
172
- const styles = impactCard({ editing: isEditing });
180
+ // For inline variant, always show as editing (form mode)
181
+ const effectiveIsEditing = isInline ? !readOnly : isEditing;
182
+
183
+ const styles = impactCard({ editing: !isInline && isEditing });
173
184
 
174
185
  // Resolve scale config
175
186
  const scaleConfig: ImpactScaleConfig =
@@ -218,7 +229,12 @@ export function ImpactCard({
218
229
  setIsEditing(true);
219
230
  };
220
231
 
221
- const currentImpact = isEditing ? editValues : value || defaultImpact;
232
+ // In inline mode, always use value; in card mode, use editValues when editing
233
+ const currentImpact = isInline
234
+ ? value || defaultImpact
235
+ : isEditing
236
+ ? editValues
237
+ : value || defaultImpact;
222
238
 
223
239
  const handleLevelChange =
224
240
  (
@@ -229,17 +245,29 @@ export function ImpactCard({
229
245
  | "impactAuthenticity",
230
246
  ) =>
231
247
  (level: ImpactLevel) => {
232
- setEditValues((prev) => ({
233
- ...prev,
248
+ const newValues = {
249
+ ...(isInline ? value || defaultImpact : editValues),
234
250
  [key]: level,
235
- }));
251
+ };
252
+ if (isInline) {
253
+ // Inline mode: propagate changes immediately
254
+ onChange?.(newValues);
255
+ } else {
256
+ setEditValues(newValues);
257
+ }
236
258
  };
237
259
 
238
260
  const handleJustificationChange = (justification: string) => {
239
- setEditValues((prev) => ({
240
- ...prev,
261
+ const newValues = {
262
+ ...(isInline ? value || defaultImpact : editValues),
241
263
  impactJustification: justification || undefined,
242
- }));
264
+ };
265
+ if (isInline) {
266
+ // Inline mode: propagate changes immediately
267
+ onChange?.(newValues);
268
+ } else {
269
+ setEditValues(newValues);
270
+ }
243
271
  };
244
272
 
245
273
  // Calculate highest impact for justification hint
@@ -255,6 +283,100 @@ export function ImpactCard({
255
283
  level: highestLabel,
256
284
  });
257
285
 
286
+ // Shared content for both variants
287
+ const impactRows = (
288
+ <>
289
+ <ImpactItemRow
290
+ label={t.confidentiality}
291
+ shortLabel="C"
292
+ value={currentImpact.impactConfidentiality}
293
+ isEditing={effectiveIsEditing}
294
+ scale={scaleConfig}
295
+ formatLabel={formatLabel}
296
+ onLevelChange={handleLevelChange("impactConfidentiality")}
297
+ />
298
+ <ImpactItemRow
299
+ label={t.integrity}
300
+ shortLabel="I"
301
+ value={currentImpact.impactIntegrity}
302
+ isEditing={effectiveIsEditing}
303
+ scale={scaleConfig}
304
+ formatLabel={formatLabel}
305
+ onLevelChange={handleLevelChange("impactIntegrity")}
306
+ />
307
+ <ImpactItemRow
308
+ label={t.availability}
309
+ shortLabel="A"
310
+ value={currentImpact.impactAvailability}
311
+ isEditing={effectiveIsEditing}
312
+ scale={scaleConfig}
313
+ formatLabel={formatLabel}
314
+ onLevelChange={handleLevelChange("impactAvailability")}
315
+ />
316
+ {showAuthenticity && (
317
+ <ImpactItemRow
318
+ label={t.authenticity}
319
+ shortLabel="Au"
320
+ value={currentImpact.impactAuthenticity ?? 0}
321
+ isEditing={effectiveIsEditing}
322
+ scale={scaleConfig}
323
+ formatLabel={formatLabel}
324
+ onLevelChange={handleLevelChange("impactAuthenticity")}
325
+ />
326
+ )}
327
+ </>
328
+ );
329
+
330
+ const justificationContent = showJustification && (
331
+ <div className={styles.justificationSection()}>
332
+ <label
333
+ htmlFor="impact-justification"
334
+ className={styles.justificationLabel()}
335
+ >
336
+ {t.justification}
337
+ {highestImpact > 0 && (
338
+ <span className={styles.justificationHint()}>
339
+ {justificationHint}
340
+ </span>
341
+ )}
342
+ </label>
343
+ {effectiveIsEditing ? (
344
+ <Textarea
345
+ id="impact-justification"
346
+ value={currentImpact.impactJustification || ""}
347
+ onChange={(e) => handleJustificationChange(e.target.value)}
348
+ placeholder={t.justificationPlaceholder}
349
+ rows={3}
350
+ className="text-sm"
351
+ />
352
+ ) : currentImpact.impactJustification ? (
353
+ <p className={styles.justificationText()}>
354
+ {currentImpact.impactJustification}
355
+ </p>
356
+ ) : (
357
+ <p className={styles.justificationEmpty()}>{t.noJustification}</p>
358
+ )}
359
+ </div>
360
+ );
361
+
362
+ // Inline variant: no card wrapper, always editable
363
+ if (isInline) {
364
+ return (
365
+ <div className={styles.wrapper()}>
366
+ {title && (
367
+ <div className={styles.inlineHeader()}>
368
+ <Heading level="h4" className="text-sm font-medium">
369
+ {title}
370
+ </Heading>
371
+ </div>
372
+ )}
373
+ {impactRows}
374
+ {justificationContent}
375
+ </div>
376
+ );
377
+ }
378
+
379
+ // Card variant: wrapped in Card with edit/save/cancel
258
380
  return (
259
381
  <Card.Root className={styles.root()}>
260
382
  <Card.Header className="flex flex-row items-center justify-between">
@@ -290,77 +412,8 @@ export function ImpactCard({
290
412
  ))}
291
413
  </Card.Header>
292
414
  <Card.Body className="space-y-3">
293
- <ImpactItemRow
294
- label={t.confidentiality}
295
- shortLabel="C"
296
- value={currentImpact.impactConfidentiality}
297
- isEditing={isEditing}
298
- scale={scaleConfig}
299
- formatLabel={formatLabel}
300
- onLevelChange={handleLevelChange("impactConfidentiality")}
301
- />
302
- <ImpactItemRow
303
- label={t.integrity}
304
- shortLabel="I"
305
- value={currentImpact.impactIntegrity}
306
- isEditing={isEditing}
307
- scale={scaleConfig}
308
- formatLabel={formatLabel}
309
- onLevelChange={handleLevelChange("impactIntegrity")}
310
- />
311
- <ImpactItemRow
312
- label={t.availability}
313
- shortLabel="A"
314
- value={currentImpact.impactAvailability}
315
- isEditing={isEditing}
316
- scale={scaleConfig}
317
- formatLabel={formatLabel}
318
- onLevelChange={handleLevelChange("impactAvailability")}
319
- />
320
- {showAuthenticity && (
321
- <ImpactItemRow
322
- label={t.authenticity}
323
- shortLabel="Au"
324
- value={currentImpact.impactAuthenticity ?? 0}
325
- isEditing={isEditing}
326
- scale={scaleConfig}
327
- formatLabel={formatLabel}
328
- onLevelChange={handleLevelChange("impactAuthenticity")}
329
- />
330
- )}
331
-
332
- {/* Justification - single field for all dimensions */}
333
- {showJustification && (
334
- <div className={styles.justificationSection()}>
335
- <label
336
- htmlFor="impact-justification"
337
- className={styles.justificationLabel()}
338
- >
339
- {t.justification}
340
- {highestImpact > 0 && (
341
- <span className={styles.justificationHint()}>
342
- {justificationHint}
343
- </span>
344
- )}
345
- </label>
346
- {isEditing ? (
347
- <Textarea
348
- id="impact-justification"
349
- value={currentImpact.impactJustification || ""}
350
- onChange={(e) => handleJustificationChange(e.target.value)}
351
- placeholder={t.justificationPlaceholder}
352
- rows={3}
353
- className="text-sm"
354
- />
355
- ) : currentImpact.impactJustification ? (
356
- <p className={styles.justificationText()}>
357
- {currentImpact.impactJustification}
358
- </p>
359
- ) : (
360
- <p className={styles.justificationEmpty()}>{t.noJustification}</p>
361
- )}
362
- </div>
363
- )}
415
+ {impactRows}
416
+ {justificationContent}
364
417
  </Card.Body>
365
418
  </Card.Root>
366
419
  );