srcdev-nuxt-components 9.0.18 → 9.1.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.
Files changed (27) hide show
  1. package/.claude/settings.json +2 -1
  2. package/.claude/skills/component-inline-action-button.md +79 -0
  3. package/.claude/skills/components/input-copy-core.md +66 -0
  4. package/.claude/skills/components/treatment-consultant.md +128 -0
  5. package/.claude/skills/icon-sets.md +45 -0
  6. package/.claude/skills/index.md +7 -1
  7. package/.claude/skills/performance-review.md +105 -0
  8. package/.claude/skills/robots-env-aware.md +69 -0
  9. package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_error.css +1 -1
  10. package/app/assets/styles/setup/02.colours/_amber.css +2 -2
  11. package/app/assets/styles/setup/03.theming/default/_dark.css +20 -2
  12. package/app/assets/styles/setup/03.theming/default/_light.css +11 -1
  13. package/app/assets/styles/setup/03.theming/error/_dark.css +1 -1
  14. package/app/components/01.atoms/text-blocks/eyebrow-text/EyebrowText.vue +15 -12
  15. package/app/components/01.atoms/text-blocks/hero-text/HeroText.vue +3 -1
  16. package/app/components/forms/form-errors/InputError.vue +104 -103
  17. package/app/components/forms/input-copy/InputCopyCore.vue +132 -0
  18. package/app/components/forms/input-copy/stories/InputCopyCore.stories.ts +89 -0
  19. package/app/components/forms/input-copy/tests/InputCopyCore.spec.ts +212 -0
  20. package/app/components/forms/input-copy/tests/__snapshots__/InputCopyCore.spec.ts.snap +28 -0
  21. package/app/pages/index.vue +0 -5
  22. package/modules/icon-sets.ts +53 -0
  23. package/nuxt.config.ts +1 -0
  24. package/package.json +44 -1
  25. package/app/components/03.organisms/treatment-consultant/TreatmentConsultant.vue +0 -2204
  26. package/app/components/03.organisms/treatment-consultant/stories/TreatmentConsultant.stories.ts +0 -38
  27. package/app/pages/ui/services/treatment-consultant.vue +0 -39
@@ -93,8 +93,10 @@ const normalisedContent = computed(() =>
93
93
  }
94
94
 
95
95
  .accent {
96
+ background-clip: text;
97
+ background-image: var(--hero-text-bg-img);
96
98
  font-style: italic;
97
- color: var(--colour-text-accent);
99
+ color: transparent;
98
100
  }
99
101
  }
100
102
  }
@@ -55,154 +55,155 @@ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
55
55
 
56
56
  <style lang="css">
57
57
  @layer components {
58
- .input-error-message {
59
- grid-row: 2;
60
- grid-column: 1;
61
- display: grid;
62
- grid-template-rows: 0fr;
58
+ .input-error-message {
59
+ grid-row: 2;
60
+ grid-column: 1;
61
+ display: grid;
62
+ grid-template-rows: 0fr;
63
63
 
64
- color: var(--input-error-color);
65
- background-color: var(--theme-error-surface);
66
- opacity: 0;
64
+ color: var(--input-error-color);
65
+ background-color: var(--theme-error-surface);
66
+ opacity: 0;
67
67
 
68
- transition:
69
- grid-template-rows var(--theme-form-transition-duration) linear,
70
- opacity var(--theme-form-transition-duration) linear,
71
- margin-block-start var(--theme-form-transition-duration) linear;
68
+ transition:
69
+ grid-template-rows var(--theme-form-transition-duration) linear,
70
+ opacity var(--theme-form-transition-duration) linear,
71
+ margin-block-start var(--theme-form-transition-duration) linear;
72
72
 
73
- transition-behavior: allow-discrete;
73
+ transition-behavior: allow-discrete;
74
74
 
75
- border-radius: 0;
76
- border: var(--form-element-border-width) solid transparent;
77
- outline: var(--form-element-outline-width) solid transparent;
75
+ border-radius: 0;
76
+ border: var(--form-element-border-width) solid transparent;
77
+ outline: var(--form-element-outline-width) solid transparent;
78
78
 
79
- background-clip: padding-box;
79
+ background-clip: padding-box;
80
80
 
81
- translate: 0 calc(-1 * calc(var(--form-element-border-width) + var(--form-input-border-radius)));
81
+ translate: 0 calc(-1 * calc(var(--form-element-border-width) + var(--form-input-border-radius)));
82
82
 
83
- margin-block-start: var(--input-error-margin-block-start);
83
+ margin-block-start: var(--input-error-margin-block-start);
84
84
 
85
- &.underlined {
86
- outline-color: transparent;
87
- }
85
+ &.underlined {
86
+ outline-color: transparent;
87
+ }
88
88
 
89
- &.detached {
90
- margin-block-start: 0rem;
89
+ &.detached {
90
+ margin-block-start: 0rem;
91
91
 
92
- border-top: var(--form-element-border-width) solid var(--theme-error-surface);
93
- border-right: var(--form-element-border-width) solid var(--theme-error-surface);
94
- border-bottom: var(--form-element-border-width) solid var(--red-08);
95
- border-left: var(--form-element-border-width) solid var(--theme-error-surface);
92
+ border-top: var(--form-element-border-width) solid var(--theme-error-surface);
93
+ border-right: var(--form-element-border-width) solid var(--theme-error-surface);
94
+ border-bottom: var(--form-element-border-width) solid var(--red-08);
95
+ border-left: var(--form-element-border-width) solid var(--theme-error-surface);
96
96
 
97
- border-radius: var(--form-input-border-radius);
97
+ border-radius: var(--form-input-border-radius);
98
98
 
99
- &.underlined {
100
- border-radius: 0;
99
+ &.underlined {
100
+ border-radius: 0;
101
+ }
101
102
  }
102
- }
103
103
 
104
- &.show {
105
- grid-template-rows: 1fr;
106
- display: grid;
107
- opacity: 1;
104
+ &.show {
105
+ grid-template-rows: 1fr;
106
+ display: grid;
107
+ opacity: 1;
108
108
 
109
- border: var(--form-element-border-width) solid var(--theme-error-border);
110
- outline: var(--form-element-outline-width) solid var(--theme-error-outline);
109
+ border: var(--form-element-border-width) solid var(--theme-error-border);
110
+ outline: var(--form-element-outline-width) solid var(--theme-error-outline);
111
111
 
112
- &:not(.underlined) {
113
- border-bottom-left-radius: var(--form-input-border-radius);
114
- border-bottom-right-radius: var(--form-input-border-radius);
115
- }
112
+ &:not(.underlined) {
113
+ border-bottom-left-radius: var(--form-input-border-radius);
114
+ border-bottom-right-radius: var(--form-input-border-radius);
115
+ }
116
116
 
117
- &.detached {
118
- margin-block-start: 2rem;
117
+ &.detached {
118
+ margin-block-start: 2rem;
119
+ }
119
120
  }
120
- }
121
121
 
122
- .inner {
123
- align-items: center;
124
- overflow: hidden;
125
-
126
- .inner-content {
127
- display: flex;
122
+ .inner {
128
123
  align-items: center;
124
+ overflow: hidden;
129
125
 
130
- .inner-icon {
131
- display: inline-block;
132
- padding-left: 1.2rem;
126
+ .inner-content {
127
+ display: flex;
128
+ align-items: center;
133
129
 
134
- .icon {
135
- color: white;
136
- transform: translateY(3px);
137
- }
138
- }
130
+ .inner-icon {
131
+ display: inline-block;
132
+ padding-left: 1.2rem;
139
133
 
140
- .message {
141
- display: inline-block;
142
- flex-grow: 1;
143
- font-family: var(--font-family);
144
- font-size: 1.6rem;
145
- font-weight: 500;
146
- padding-block: 1rem 1rem;
147
- padding-inline: 1.2rem;
148
-
149
- .message-single {
150
- color: var(--input-error-color);
134
+ .icon {
135
+ color: white;
136
+ transform: translateY(3px);
137
+ }
151
138
  }
152
139
 
153
- .message-list {
154
- list-style-type: none;
155
- padding-inline-start: 0;
156
- margin-block-start: 0;
157
- margin-block-end: 0;
158
-
159
- .message-list-item {
160
- color: white;
140
+ .message {
141
+ display: inline-block;
142
+ flex-grow: 1;
143
+ font-family: var(--font-family);
144
+ font-size: 1.6rem;
145
+ font-weight: 500;
146
+ padding-block: 1rem 1rem;
147
+ padding-inline: 1.2rem;
148
+
149
+ .message-single {
150
+ color: var(--input-error-color);
161
151
  }
162
152
 
163
- .message-list-item + .message-list-item {
164
- margin-block-start: 0.6rem;
153
+ .message-list {
154
+ list-style-type: none;
155
+ padding-inline-start: 0;
156
+ margin-block-start: 0;
157
+ margin-block-end: 0;
158
+
159
+ .message-list-item {
160
+ color: white;
161
+ }
162
+
163
+ .message-list-item + .message-list-item {
164
+ margin-block-start: 0.6rem;
165
+ }
165
166
  }
166
167
  }
167
168
  }
168
169
  }
169
- }
170
170
 
171
- /* Modifiers for input variants: */
172
- &:is(.detached) {
173
- &.normal {
174
- .inner {
175
- .inner-content {
176
- .message {
177
- padding-block: 1.2rem 1rem;
171
+ /* Modifiers for input variants: */
172
+ &:is(.detached) {
173
+ &.normal {
174
+ .inner {
175
+ .inner-content {
176
+ .message {
177
+ padding-block: 1.2rem 1rem;
178
+ }
178
179
  }
179
180
  }
180
181
  }
181
182
  }
182
- }
183
183
 
184
- &:not(.detached) {
185
- &.normal {
186
- .inner {
187
- padding-block-start: var(--form-input-border-radius);
184
+ &:not(.detached) {
185
+ &.normal {
186
+ .inner {
187
+ padding-block-start: var(--form-input-border-radius);
188
188
 
189
- .inner-content {
190
- .message {
191
- padding-block: 1.2rem 1rem;
189
+ .inner-content {
190
+ .message {
191
+ padding-block: 1.2rem 1rem;
192
+ }
192
193
  }
193
194
  }
194
195
  }
195
- }
196
- &.underlined {
197
- .inner {
198
- .inner-content {
199
- .message {
200
- padding-block: 1rem 1rem;
196
+ &.underlined {
197
+ outline-color: transparent;
198
+ .inner {
199
+ .inner-content {
200
+ .message {
201
+ padding-block: 1rem 1rem;
202
+ }
201
203
  }
202
204
  }
203
205
  }
204
206
  }
205
207
  }
206
208
  }
207
- }
208
209
  </style>
@@ -0,0 +1,132 @@
1
+ <template>
2
+ <div
3
+ class="input-copy-core"
4
+ data-testid="input-copy-core"
5
+ :class="[elementClasses, { copied: isCopied }]"
6
+ >
7
+ <input
8
+ :id
9
+ :value
10
+ type="text"
11
+ readonly
12
+ aria-readonly="true"
13
+ class="input-copy-field"
14
+ />
15
+ <InputButtonCore
16
+ type="button"
17
+ variant="inline"
18
+ class="input-copy-button"
19
+ :button-text="isCopied ? copiedLabel : copyLabel"
20
+ :aria-label="isCopied ? copiedLabel : copyLabel"
21
+ @click="handleCopy"
22
+ >
23
+ <template v-if="slots.icon" #left>
24
+ <slot name="icon"></slot>
25
+ </template>
26
+ </InputButtonCore>
27
+ </div>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ interface Props {
32
+ id: string;
33
+ value: string;
34
+ copyLabel?: string;
35
+ copiedLabel?: string;
36
+ feedbackDuration?: number;
37
+ styleClassPassthrough?: string | string[];
38
+ }
39
+
40
+ const props = withDefaults(defineProps<Props>(), {
41
+ copyLabel: "Copy",
42
+ copiedLabel: "Copied!",
43
+ feedbackDuration: 2000,
44
+ styleClassPassthrough: () => [],
45
+ });
46
+
47
+ const emit = defineEmits<{
48
+ copy: [value: string];
49
+ }>();
50
+
51
+ const slots = useSlots();
52
+ const isCopied = ref(false);
53
+
54
+ const handleCopy = async () => {
55
+ try {
56
+ await navigator.clipboard.writeText(props.value);
57
+ isCopied.value = true;
58
+ emit("copy", props.value);
59
+ await useSleep(props.feedbackDuration);
60
+ isCopied.value = false;
61
+ } catch {
62
+ // Clipboard API unavailable — fail silently
63
+ }
64
+ };
65
+
66
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
67
+
68
+ watch(
69
+ () => props.styleClassPassthrough,
70
+ () => {
71
+ resetElementClasses(props.styleClassPassthrough);
72
+ }
73
+ );
74
+ </script>
75
+
76
+ <style lang="css">
77
+ @layer components {
78
+ .input-copy-core {
79
+ display: flex;
80
+ align-items: stretch;
81
+ overflow: hidden;
82
+ border: var(--form-element-border-width) solid var(--theme-input-border);
83
+ border-radius: var(--form-input-border-radius);
84
+ background-color: var(--theme-input-surface);
85
+ transition: all var(--theme-form-transition-duration) ease-in-out;
86
+
87
+ .input-copy-field {
88
+ all: unset;
89
+ flex-grow: 1;
90
+ padding-block: var(--input-padding-block);
91
+ padding-inline: var(--input-padding-inline);
92
+ font-family: var(--font-family);
93
+ font-size: var(--input-font-size);
94
+ color: var(--theme-input-text-color-normal);
95
+ cursor: default;
96
+ overflow: hidden;
97
+ text-overflow: ellipsis;
98
+ white-space: nowrap;
99
+ min-width: 0;
100
+ }
101
+
102
+ .input-copy-button.input-button-core {
103
+ border-radius: 0;
104
+ border-inline-start: var(--form-element-border-width) solid var(--theme-input-border);
105
+ padding-inline: var(--input-padding-inline);
106
+ min-width: var(--input-min-height);
107
+ background-color: var(--theme-button-secondary-surface);
108
+ color: var(--theme-button-secondary-text);
109
+
110
+ &:hover {
111
+ background-color: var(--theme-button-primary-surface);
112
+ color: var(--theme-button-primary-text);
113
+ }
114
+
115
+ &:focus-visible {
116
+ outline: var(--form-element-outline-width-focus) solid var(--theme-input-outline-focus);
117
+ outline-offset: -4px;
118
+ }
119
+ }
120
+
121
+ &.copied {
122
+ --_copy-success-surface: light-dark(var(--green-01), var(--green-09));
123
+ --_copy-success-text: light-dark(var(--green-08), var(--green-01));
124
+
125
+ .input-copy-button.input-button-core {
126
+ background-color: var(--_copy-success-surface);
127
+ color: var(--_copy-success-text);
128
+ }
129
+ }
130
+ }
131
+ }
132
+ </style>
@@ -0,0 +1,89 @@
1
+ import InputCopyCore from "../InputCopyCore.vue";
2
+ import type { Meta, StoryObj } from "@nuxtjs/storybook";
3
+
4
+ const meta: Meta<typeof InputCopyCore> = {
5
+ title: "Components/Forms/Input Copy/InputCopyCore",
6
+ component: InputCopyCore,
7
+ argTypes: {
8
+ id: {
9
+ control: "text",
10
+ description: "The id attribute for the input element",
11
+ table: { category: "Content" },
12
+ },
13
+ value: {
14
+ control: "text",
15
+ description: "The text value to display and copy",
16
+ table: { category: "Content" },
17
+ },
18
+ copyLabel: {
19
+ control: "text",
20
+ description: "Button label before copying",
21
+ table: { category: "Behaviour" },
22
+ },
23
+ copiedLabel: {
24
+ control: "text",
25
+ description: "Button label shown after a successful copy",
26
+ table: { category: "Behaviour" },
27
+ },
28
+ feedbackDuration: {
29
+ control: { type: "number", min: 500, max: 5000, step: 500 },
30
+ description: "How long (ms) to show the copied state before resetting",
31
+ table: { category: "Behaviour" },
32
+ },
33
+ styleClassPassthrough: {
34
+ control: "object",
35
+ description: "Additional CSS classes applied to the root element",
36
+ table: { category: "Styling" },
37
+ },
38
+ },
39
+ args: {
40
+ id: "copy-input",
41
+ value: "sk_live_abc123def456",
42
+ copyLabel: "Copy",
43
+ copiedLabel: "Copied!",
44
+ feedbackDuration: 2000,
45
+ styleClassPassthrough: [],
46
+ },
47
+ };
48
+
49
+ export default meta;
50
+ type Story = StoryObj<typeof InputCopyCore>;
51
+
52
+ export const Default: Story = {
53
+ render: (args) => ({
54
+ components: { InputCopyCore },
55
+ setup() {
56
+ return { args };
57
+ },
58
+ template: `<div style="max-width: 420px; padding: 40px;"><InputCopyCore v-bind="args" /></div>`,
59
+ }),
60
+ };
61
+
62
+ export const LongValue: Story = {
63
+ name: "Long value",
64
+ args: {
65
+ value: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b21lcklkIjoiY3VzdF8wMDEiLCJkb21haW5zIjpbImV4YW1wbGUuY29tIl0sInBsYW4iOiJzaW5nbGUtc2l0ZSIsImlhdCI6MTcwMDAwMDAwMCwiZXhwIjoxNzMxNjI3MjAwfQ",
66
+ },
67
+ render: (args) => ({
68
+ components: { InputCopyCore },
69
+ setup() {
70
+ return { args };
71
+ },
72
+ template: `<div style="max-width: 420px; padding: 40px;"><InputCopyCore v-bind="args" /></div>`,
73
+ }),
74
+ };
75
+
76
+ export const CustomLabels: Story = {
77
+ name: "Custom labels",
78
+ args: {
79
+ copyLabel: "Copy key",
80
+ copiedLabel: "Key copied!",
81
+ },
82
+ render: (args) => ({
83
+ components: { InputCopyCore },
84
+ setup() {
85
+ return { args };
86
+ },
87
+ template: `<div style="max-width: 420px; padding: 40px;"><InputCopyCore v-bind="args" /></div>`,
88
+ }),
89
+ };