@salla.sa/ui-ai-kit-core 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.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/ai-card.cjs.entry.js +25 -0
  3. package/dist/cjs/ai-chat-container.cjs.entry.js +138 -0
  4. package/dist/cjs/ai-chat-header.cjs.entry.js +79 -0
  5. package/dist/cjs/ai-chat-message.cjs.entry.js +164 -0
  6. package/dist/cjs/ai-icon.cjs.entry.js +25 -0
  7. package/dist/cjs/ai-link.cjs.entry.js +34 -0
  8. package/dist/cjs/ai-loading.cjs.entry.js +77 -0
  9. package/dist/cjs/ai-message-input.cjs.entry.js +65 -0
  10. package/dist/cjs/ai-rating.cjs.entry.js +57 -0
  11. package/dist/cjs/ai-suggestion.cjs.entry.js +31 -0
  12. package/dist/cjs/ai-voice-input.cjs.entry.js +233 -0
  13. package/dist/cjs/icon-registry-dmfLA-Dj.js +82 -0
  14. package/dist/cjs/index-DLJcLHFH.js +1622 -0
  15. package/dist/cjs/index.cjs.js +7 -0
  16. package/dist/cjs/loader.cjs.js +12 -0
  17. package/dist/cjs/ui-ai-kit.cjs.js +24 -0
  18. package/dist/collection/collection-manifest.json +23 -0
  19. package/dist/collection/components/ai-card/ai-card.css +40 -0
  20. package/dist/collection/components/ai-card/ai-card.js +70 -0
  21. package/dist/collection/components/ai-card/ai-card.stories.js +52 -0
  22. package/dist/collection/components/ai-chat-container/ai-chat-container.css +137 -0
  23. package/dist/collection/components/ai-chat-container/ai-chat-container.js +270 -0
  24. package/dist/collection/components/ai-chat-container/ai-chat-container.stories.js +160 -0
  25. package/dist/collection/components/ai-chat-header/ai-chat-header.css +186 -0
  26. package/dist/collection/components/ai-chat-header/ai-chat-header.js +311 -0
  27. package/dist/collection/components/ai-chat-header/ai-chat-header.stories.js +138 -0
  28. package/dist/collection/components/ai-chat-message/ai-chat-message.css +304 -0
  29. package/dist/collection/components/ai-chat-message/ai-chat-message.js +379 -0
  30. package/dist/collection/components/ai-chat-message/ai-chat-message.stories.js +164 -0
  31. package/dist/collection/components/ai-icon/ai-icon.css +9 -0
  32. package/dist/collection/components/ai-icon/ai-icon.js +76 -0
  33. package/dist/collection/components/ai-link/ai-link.css +62 -0
  34. package/dist/collection/components/ai-link/ai-link.js +119 -0
  35. package/dist/collection/components/ai-link/ai-link.stories.js +79 -0
  36. package/dist/collection/components/ai-loading/ai-loading.css +202 -0
  37. package/dist/collection/components/ai-loading/ai-loading.js +244 -0
  38. package/dist/collection/components/ai-loading/ai-loading.stories.js +145 -0
  39. package/dist/collection/components/ai-message-input/ai-message-input.css +175 -0
  40. package/dist/collection/components/ai-message-input/ai-message-input.js +192 -0
  41. package/dist/collection/components/ai-message-input/ai-message-input.stories.js +125 -0
  42. package/dist/collection/components/ai-rating/ai-rating.css +145 -0
  43. package/dist/collection/components/ai-rating/ai-rating.js +176 -0
  44. package/dist/collection/components/ai-rating/ai-rating.stories.js +78 -0
  45. package/dist/collection/components/ai-suggestion/ai-suggestion.css +69 -0
  46. package/dist/collection/components/ai-suggestion/ai-suggestion.js +93 -0
  47. package/dist/collection/components/ai-suggestion/ai-suggestion.stories.js +62 -0
  48. package/dist/collection/components/ai-voice-input/ai-voice-input.css +136 -0
  49. package/dist/collection/components/ai-voice-input/ai-voice-input.js +341 -0
  50. package/dist/collection/components/ai-voice-input/ai-voice-input.stories.js +118 -0
  51. package/dist/collection/index.js +10 -0
  52. package/dist/collection/utils/icon-registry.js +78 -0
  53. package/dist/collection/utils/utils.js +3 -0
  54. package/dist/components/ai-card.d.ts +11 -0
  55. package/dist/components/ai-card.js +1 -0
  56. package/dist/components/ai-chat-container.d.ts +11 -0
  57. package/dist/components/ai-chat-container.js +1 -0
  58. package/dist/components/ai-chat-header.d.ts +11 -0
  59. package/dist/components/ai-chat-header.js +1 -0
  60. package/dist/components/ai-chat-message.d.ts +11 -0
  61. package/dist/components/ai-chat-message.js +1 -0
  62. package/dist/components/ai-icon.d.ts +11 -0
  63. package/dist/components/ai-icon.js +1 -0
  64. package/dist/components/ai-link.d.ts +11 -0
  65. package/dist/components/ai-link.js +1 -0
  66. package/dist/components/ai-loading.d.ts +11 -0
  67. package/dist/components/ai-loading.js +1 -0
  68. package/dist/components/ai-message-input.d.ts +11 -0
  69. package/dist/components/ai-message-input.js +1 -0
  70. package/dist/components/ai-rating.d.ts +11 -0
  71. package/dist/components/ai-rating.js +1 -0
  72. package/dist/components/ai-suggestion.d.ts +11 -0
  73. package/dist/components/ai-suggestion.js +1 -0
  74. package/dist/components/ai-voice-input.d.ts +11 -0
  75. package/dist/components/ai-voice-input.js +1 -0
  76. package/dist/components/index.d.ts +35 -0
  77. package/dist/components/index.js +1 -0
  78. package/dist/components/p-CWjXxYJI.js +1 -0
  79. package/dist/components/p-CY6emva2.js +1 -0
  80. package/dist/components/p-DYv5ef4M.js +1 -0
  81. package/dist/esm/ai-card.entry.js +23 -0
  82. package/dist/esm/ai-chat-container.entry.js +136 -0
  83. package/dist/esm/ai-chat-header.entry.js +77 -0
  84. package/dist/esm/ai-chat-message.entry.js +162 -0
  85. package/dist/esm/ai-icon.entry.js +23 -0
  86. package/dist/esm/ai-link.entry.js +32 -0
  87. package/dist/esm/ai-loading.entry.js +75 -0
  88. package/dist/esm/ai-message-input.entry.js +63 -0
  89. package/dist/esm/ai-rating.entry.js +55 -0
  90. package/dist/esm/ai-suggestion.entry.js +29 -0
  91. package/dist/esm/ai-voice-input.entry.js +231 -0
  92. package/dist/esm/icon-registry-DYv5ef4M.js +80 -0
  93. package/dist/esm/index-7hrZ8FOQ.js +1612 -0
  94. package/dist/esm/index.js +5 -0
  95. package/dist/esm/loader.js +10 -0
  96. package/dist/esm/ui-ai-kit.js +20 -0
  97. package/dist/index.cjs.js +1 -0
  98. package/dist/index.js +1 -0
  99. package/dist/types/components/ai-card/ai-card.d.ts +7 -0
  100. package/dist/types/components/ai-card/ai-card.stories.d.ts +7 -0
  101. package/dist/types/components/ai-chat-container/ai-chat-container.d.ts +28 -0
  102. package/dist/types/components/ai-chat-container/ai-chat-container.stories.d.ts +7 -0
  103. package/dist/types/components/ai-chat-header/ai-chat-header.d.ts +38 -0
  104. package/dist/types/components/ai-chat-header/ai-chat-header.stories.d.ts +8 -0
  105. package/dist/types/components/ai-chat-message/ai-chat-message.d.ts +27 -0
  106. package/dist/types/components/ai-chat-message/ai-chat-message.stories.d.ts +10 -0
  107. package/dist/types/components/ai-icon/ai-icon.d.ts +8 -0
  108. package/dist/types/components/ai-link/ai-link.d.ts +12 -0
  109. package/dist/types/components/ai-link/ai-link.stories.d.ts +8 -0
  110. package/dist/types/components/ai-loading/ai-loading.d.ts +33 -0
  111. package/dist/types/components/ai-loading/ai-loading.stories.d.ts +10 -0
  112. package/dist/types/components/ai-message-input/ai-message-input.d.ts +22 -0
  113. package/dist/types/components/ai-message-input/ai-message-input.stories.d.ts +13 -0
  114. package/dist/types/components/ai-rating/ai-rating.d.ts +17 -0
  115. package/dist/types/components/ai-rating/ai-rating.stories.d.ts +8 -0
  116. package/dist/types/components/ai-suggestion/ai-suggestion.d.ts +10 -0
  117. package/dist/types/components/ai-suggestion/ai-suggestion.stories.d.ts +8 -0
  118. package/dist/types/components/ai-voice-input/ai-voice-input.d.ts +43 -0
  119. package/dist/types/components/ai-voice-input/ai-voice-input.stories.d.ts +9 -0
  120. package/dist/types/components.d.ts +860 -0
  121. package/dist/types/index.d.ts +11 -0
  122. package/dist/types/stencil-public-runtime.d.ts +1860 -0
  123. package/dist/types/utils/icon-registry.d.ts +5 -0
  124. package/dist/types/utils/utils.d.ts +1 -0
  125. package/dist/ui-ai-kit/index.esm.js +1 -0
  126. package/dist/ui-ai-kit/p-11facfad.entry.js +1 -0
  127. package/dist/ui-ai-kit/p-128a2ed4.entry.js +1 -0
  128. package/dist/ui-ai-kit/p-227bdb8f.entry.js +1 -0
  129. package/dist/ui-ai-kit/p-455daa17.entry.js +1 -0
  130. package/dist/ui-ai-kit/p-56163e8c.entry.js +1 -0
  131. package/dist/ui-ai-kit/p-6d21d0fd.entry.js +1 -0
  132. package/dist/ui-ai-kit/p-6ddcd77b.entry.js +1 -0
  133. package/dist/ui-ai-kit/p-7hrZ8FOQ.js +2 -0
  134. package/dist/ui-ai-kit/p-8e90143e.entry.js +1 -0
  135. package/dist/ui-ai-kit/p-9938c277.entry.js +1 -0
  136. package/dist/ui-ai-kit/p-DYv5ef4M.js +1 -0
  137. package/dist/ui-ai-kit/p-dc5b4a7f.entry.js +1 -0
  138. package/dist/ui-ai-kit/p-fb1702de.entry.js +1 -0
  139. package/dist/ui-ai-kit/ui-ai-kit.css +1 -0
  140. package/dist/ui-ai-kit/ui-ai-kit.esm.js +1 -0
  141. package/loader/cdn.js +1 -0
  142. package/loader/index.cjs.js +1 -0
  143. package/loader/index.d.ts +24 -0
  144. package/loader/index.es2017.js +1 -0
  145. package/loader/index.js +2 -0
  146. package/package.json +77 -0
  147. package/readme.md +111 -0
@@ -0,0 +1,192 @@
1
+ import { Host, h } from "@stencil/core";
2
+ export class MessageInput {
3
+ el;
4
+ /** Placeholder text for the textarea */
5
+ placeholder = 'ايش في بالك؟';
6
+ /** Whether the input is disabled */
7
+ disabled = false;
8
+ /** Whether to show the voice recording button */
9
+ showVoiceButton = true;
10
+ inputValue = '';
11
+ textareaRef;
12
+ /** Event emitted when a message is sent */
13
+ sendMessage;
14
+ /** Event emitted when the voice button is clicked — caller controls rendering the voice recorder */
15
+ voiceButtonClick;
16
+ /** Set the textarea value programmatically (e.g. after speech-to-text transcription) */
17
+ async setInputValue(value) {
18
+ this.inputValue = value;
19
+ if (this.textareaRef) {
20
+ this.textareaRef.style.height = 'auto';
21
+ this.textareaRef.style.height = `${this.textareaRef.scrollHeight}px`;
22
+ this.textareaRef.focus();
23
+ }
24
+ }
25
+ handleSendMessage = () => {
26
+ if (!this.inputValue.trim() || this.disabled)
27
+ return;
28
+ this.sendMessage.emit(this.inputValue.trim());
29
+ this.inputValue = '';
30
+ if (this.textareaRef) {
31
+ this.textareaRef.style.height = 'auto';
32
+ }
33
+ };
34
+ handleKeyPress = (e) => {
35
+ if (e.key === 'Enter' && !e.shiftKey) {
36
+ e.preventDefault();
37
+ this.handleSendMessage();
38
+ }
39
+ };
40
+ handleInputChange = (e) => {
41
+ const target = e.target;
42
+ this.inputValue = target.value;
43
+ if (this.textareaRef) {
44
+ this.textareaRef.style.height = 'auto';
45
+ this.textareaRef.style.height = `${this.textareaRef.scrollHeight}px`;
46
+ }
47
+ };
48
+ render() {
49
+ const canSend = !!this.inputValue.trim() && !this.disabled;
50
+ return (h(Host, { key: '97dfe627b89657d36e4f7281a8bdb708cc913374' }, h("div", { key: 'a3efb2a0b95ec3a86cc5f575d0fdb2bb6dd2eb6c', part: "wrapper", class: "wrapper" }, h("div", { key: '5986fa2946a44757c143999a72b25ea3b6e7c336', part: "glow", class: "glow", "aria-hidden": "true" }), h("div", { key: 'd5b9d346495de00e8b0e0900ef3e766756844626', part: "container", class: "input-container" }, h("div", { key: '8313695b2e4d7557a5362e9b9d2a5c5803a4ec0c', class: "input-row" }, h("div", { key: '9c184f8097e28527b5d18fccf92010fb36b14a9c', class: "actions" }, h("button", { key: '39ad3feb24a53fd5a08e8a8388f7bcee71413a91', part: "send-button", class: `send-button${canSend ? ' active' : ''}`, onClick: this.handleSendMessage, disabled: !canSend, type: "button", "aria-label": "Send message" }, h("ai-icon", { key: '1fe555709288d33551696312fb3329b1c656d063', name: "send", size: 16 })), this.showVoiceButton && !this.inputValue && (h("button", { key: 'a7bcc3b9ca5f5e59c32a34ab33c2fbbd607bb298', part: "voice-button", class: "voice-button", onClick: () => this.voiceButtonClick.emit(), disabled: this.disabled, type: "button", "aria-label": "Record voice" }, h("ai-icon", { key: '98fea04c79e841e4a2b8ee50a480bb554212b055', name: "mic", size: 16 })))), h("textarea", { key: '798af74ca2298a31f37459dfa1c176182d740839', part: "textarea", ref: el => (this.textareaRef = el), value: this.inputValue, onInput: this.handleInputChange, onKeyPress: this.handleKeyPress, disabled: this.disabled, placeholder: this.placeholder, rows: 1, class: "textarea" }))))));
51
+ }
52
+ static get is() { return "ai-message-input"; }
53
+ static get encapsulation() { return "shadow"; }
54
+ static get originalStyleUrls() {
55
+ return {
56
+ "$": ["ai-message-input.css"]
57
+ };
58
+ }
59
+ static get styleUrls() {
60
+ return {
61
+ "$": ["ai-message-input.css"]
62
+ };
63
+ }
64
+ static get properties() {
65
+ return {
66
+ "placeholder": {
67
+ "type": "string",
68
+ "mutable": false,
69
+ "complexType": {
70
+ "original": "string",
71
+ "resolved": "string",
72
+ "references": {}
73
+ },
74
+ "required": false,
75
+ "optional": false,
76
+ "docs": {
77
+ "tags": [],
78
+ "text": "Placeholder text for the textarea"
79
+ },
80
+ "getter": false,
81
+ "setter": false,
82
+ "reflect": false,
83
+ "attribute": "placeholder",
84
+ "defaultValue": "'\u0627\u064A\u0634 \u0641\u064A \u0628\u0627\u0644\u0643\u061F'"
85
+ },
86
+ "disabled": {
87
+ "type": "boolean",
88
+ "mutable": false,
89
+ "complexType": {
90
+ "original": "boolean",
91
+ "resolved": "boolean",
92
+ "references": {}
93
+ },
94
+ "required": false,
95
+ "optional": false,
96
+ "docs": {
97
+ "tags": [],
98
+ "text": "Whether the input is disabled"
99
+ },
100
+ "getter": false,
101
+ "setter": false,
102
+ "reflect": false,
103
+ "attribute": "disabled",
104
+ "defaultValue": "false"
105
+ },
106
+ "showVoiceButton": {
107
+ "type": "boolean",
108
+ "mutable": false,
109
+ "complexType": {
110
+ "original": "boolean",
111
+ "resolved": "boolean",
112
+ "references": {}
113
+ },
114
+ "required": false,
115
+ "optional": false,
116
+ "docs": {
117
+ "tags": [],
118
+ "text": "Whether to show the voice recording button"
119
+ },
120
+ "getter": false,
121
+ "setter": false,
122
+ "reflect": false,
123
+ "attribute": "show-voice-button",
124
+ "defaultValue": "true"
125
+ }
126
+ };
127
+ }
128
+ static get states() {
129
+ return {
130
+ "inputValue": {}
131
+ };
132
+ }
133
+ static get events() {
134
+ return [{
135
+ "method": "sendMessage",
136
+ "name": "sendMessage",
137
+ "bubbles": true,
138
+ "cancelable": true,
139
+ "composed": true,
140
+ "docs": {
141
+ "tags": [],
142
+ "text": "Event emitted when a message is sent"
143
+ },
144
+ "complexType": {
145
+ "original": "string",
146
+ "resolved": "string",
147
+ "references": {}
148
+ }
149
+ }, {
150
+ "method": "voiceButtonClick",
151
+ "name": "voiceButtonClick",
152
+ "bubbles": true,
153
+ "cancelable": true,
154
+ "composed": true,
155
+ "docs": {
156
+ "tags": [],
157
+ "text": "Event emitted when the voice button is clicked \u2014 caller controls rendering the voice recorder"
158
+ },
159
+ "complexType": {
160
+ "original": "void",
161
+ "resolved": "void",
162
+ "references": {}
163
+ }
164
+ }];
165
+ }
166
+ static get methods() {
167
+ return {
168
+ "setInputValue": {
169
+ "complexType": {
170
+ "signature": "(value: string) => Promise<void>",
171
+ "parameters": [{
172
+ "name": "value",
173
+ "type": "string",
174
+ "docs": ""
175
+ }],
176
+ "references": {
177
+ "Promise": {
178
+ "location": "global",
179
+ "id": "global::Promise"
180
+ }
181
+ },
182
+ "return": "Promise<void>"
183
+ },
184
+ "docs": {
185
+ "text": "Set the textarea value programmatically (e.g. after speech-to-text transcription)",
186
+ "tags": []
187
+ }
188
+ }
189
+ };
190
+ }
191
+ static get elementRef() { return "el"; }
192
+ }
@@ -0,0 +1,125 @@
1
+ import { html } from "lit";
2
+ const meta = {
3
+ title: 'Chat/Message Input',
4
+ component: 'ai-message-input',
5
+ tags: ['autodocs'],
6
+ argTypes: {
7
+ placeholder: {
8
+ control: 'text',
9
+ description: 'Placeholder text for the textarea',
10
+ },
11
+ disabled: {
12
+ control: 'boolean',
13
+ description: 'Whether the input is disabled',
14
+ },
15
+ showVoiceButton: {
16
+ control: 'boolean',
17
+ description: 'Show the mic button — emits voiceButtonClick when pressed so the caller can render ai-voice-input',
18
+ },
19
+ },
20
+ parameters: {
21
+ docs: {
22
+ description: {
23
+ component: `
24
+ A pill-shaped chat input component.
25
+
26
+ ## Events
27
+ - \`sendMessage\` — emitted with the trimmed message string when the user sends
28
+ - \`voiceButtonClick\` — emitted when the mic button is pressed; the caller is responsible for showing \`ai-voice-input\`
29
+
30
+ ## Methods
31
+ - \`setInputValue(text: string)\` — populates the textarea programmatically (e.g. after speech-to-text transcription)
32
+ `,
33
+ },
34
+ },
35
+ },
36
+ };
37
+ export default meta;
38
+ export const Default = {
39
+ render: args => html `
40
+ <ai-message-input
41
+ placeholder=${args.placeholder || 'ايش في بالك؟'}
42
+ ?disabled=${args.disabled}
43
+ ?show-voice-button=${args.showVoiceButton !== false}
44
+ @sendMessage=${(e) => console.log('sendMessage:', e.detail)}
45
+ @voiceButtonClick=${() => console.log('voiceButtonClick')}
46
+ ></ai-message-input>
47
+ `,
48
+ args: {
49
+ placeholder: 'ايش في بالك؟',
50
+ disabled: false,
51
+ showVoiceButton: true,
52
+ },
53
+ };
54
+ export const Disabled = {
55
+ render: () => html `<ai-message-input placeholder="ايش في بالك؟" disabled></ai-message-input>`,
56
+ };
57
+ /**
58
+ * Demonstrates the full voice toggle pattern:
59
+ * clicking the mic button hides the message input and shows ai-voice-input.
60
+ * After recording (or cancel) the voice input is hidden and message input is restored.
61
+ * When audio is recorded, setInputValue() pre-fills the textarea for review.
62
+ */
63
+ export const WithVoiceToggle = {
64
+ render: () => html `
65
+ <div id="voice-toggle-demo" style="max-width: 600px;">
66
+ <ai-message-input
67
+ id="msg-input"
68
+ placeholder="اكتب رسالة أو اضغط على الميكروفون"
69
+ @sendMessage=${(e) => {
70
+ const log = document.getElementById('toggle-log');
71
+ if (log)
72
+ log.innerHTML += `<div style="color:#10b981;">✓ Message sent: "${e.detail}"</div>`;
73
+ }}
74
+ @voiceButtonClick=${() => {
75
+ const msg = document.getElementById('msg-input');
76
+ const voice = document.getElementById('voice-recorder');
77
+ if (msg)
78
+ msg.style.display = 'none';
79
+ if (voice) {
80
+ voice.style.display = 'block';
81
+ voice.autoStart = true;
82
+ }
83
+ }}
84
+ ></ai-message-input>
85
+
86
+ <ai-voice-input
87
+ id="voice-recorder"
88
+ auto-start
89
+ style="display:none;"
90
+ @audioRecorded=${(e) => {
91
+ const msg = document.getElementById('msg-input');
92
+ const voice = document.getElementById('voice-recorder');
93
+ if (voice)
94
+ voice.style.display = 'none';
95
+ if (msg) {
96
+ msg.style.display = 'block';
97
+ // In a real app: send e.detail.blob to Whisper, then call setInputValue()
98
+ msg.setInputValue(`[Audio ${e.detail.duration}s — paste transcription here]`);
99
+ }
100
+ const log = document.getElementById('toggle-log');
101
+ if (log)
102
+ log.innerHTML += `<div style="color:#8b5cf6;">🎤 Audio recorded (${e.detail.duration}s, ${(e.detail.blob.size / 1024).toFixed(1)} KB)</div>`;
103
+ }}
104
+ @recordingCancel=${() => {
105
+ const msg = document.getElementById('msg-input');
106
+ const voice = document.getElementById('voice-recorder');
107
+ if (voice)
108
+ voice.style.display = 'none';
109
+ if (msg)
110
+ msg.style.display = 'block';
111
+ const log = document.getElementById('toggle-log');
112
+ if (log)
113
+ log.innerHTML += `<div style="color:#737373;">✕ Recording cancelled</div>`;
114
+ }}
115
+ ></ai-voice-input>
116
+
117
+ <div
118
+ id="toggle-log"
119
+ style="margin-top:1rem;padding:1rem;background:#f5f5f5;border-radius:8px;font-family:monospace;font-size:13px;min-height:80px;max-height:200px;overflow-y:auto;"
120
+ >
121
+ <div style="color:#666;font-weight:bold;margin-bottom:4px;">Event log:</div>
122
+ </div>
123
+ </div>
124
+ `,
125
+ };
@@ -0,0 +1,145 @@
1
+ /* ─── Custom Properties ──────────────────────────────────────────────────── */
2
+ :host {
3
+ --ai-rating-question-color: var(--ai-text-primary, #333333);
4
+ --ai-rating-subtitle-color: var(--ai-text-secondary, #737373);
5
+ --ai-rating-question-size: 16px;
6
+ --ai-rating-subtitle-size: 14px;
7
+ --ai-rating-gap: 8px;
8
+ --ai-rating-emoji-size: 24px;
9
+ --ai-rating-btn-padding: 6px;
10
+ --ai-rating-btn-border: 1px solid var(--ai-border-light, #f4f4f4);
11
+ --ai-rating-btn-radius: 9999px;
12
+
13
+ /* hover */
14
+ --ai-rating-btn-bg-hover: var(--ai-bg-surface, #f9fafb);
15
+ --ai-rating-btn-border-hover: 1px solid var(--ai-border-default);
16
+
17
+ /* selected — matches Figma warning palette */
18
+ --ai-rating-btn-bg-active: var(--ai-warning-bg, #fff9eb);
19
+ --ai-rating-btn-border-active: 1px solid var(--ai-warning-border, #ffaf44);
20
+ --ai-rating-btn-shadow-active: 0px 4px 6px -1px rgba(18, 18, 23, 0.08),
21
+ 0px 2px 4px -1px rgba(18, 18, 23, 0.06);
22
+
23
+ /* selected label */
24
+ --ai-rating-label-color: var(--ai-text-primary, #333333);
25
+ --ai-rating-label-size: 16px;
26
+
27
+ display: block;
28
+ direction: rtl;
29
+ font-family: var(--ai-font-family, "PingARLT", sans-serif);
30
+ }
31
+
32
+ /* ─── Wrapper ────────────────────────────────────────────────────────────── */
33
+ .rating {
34
+ display: flex;
35
+ flex-direction: column;
36
+ align-items: center;
37
+ gap: var(--ai-rating-gap);
38
+ width: 100%;
39
+ }
40
+
41
+ /* ─── Text block ─────────────────────────────────────────────────────────── */
42
+ .rating__text {
43
+ display: flex;
44
+ flex-direction: column;
45
+ align-items: center;
46
+ gap: 2px;
47
+ width: 100%;
48
+ text-align: center;
49
+ }
50
+
51
+ .rating__question {
52
+ margin: 0;
53
+ font-size: var(--ai-rating-question-size);
54
+ font-weight: 500;
55
+ line-height: 24px;
56
+ color: var(--ai-rating-question-color);
57
+ }
58
+
59
+ .rating__subtitle {
60
+ margin: 0;
61
+ font-size: var(--ai-rating-subtitle-size);
62
+ font-weight: 400;
63
+ line-height: 20px;
64
+ color: var(--ai-rating-subtitle-color);
65
+ }
66
+
67
+ /* ─── Emoji row ──────────────────────────────────────────────────────────── */
68
+ .rating__icons {
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ gap: 10px;
73
+ width: 100%;
74
+ flex-wrap: wrap;
75
+ }
76
+
77
+ /* ─── Emoji button ───────────────────────────────────────────────────────── */
78
+ .emoji-btn {
79
+ display: inline-flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ padding: var(--ai-rating-btn-padding);
83
+ border: var(--ai-rating-btn-border);
84
+ border-radius: var(--ai-rating-btn-radius);
85
+ background: transparent;
86
+ cursor: pointer;
87
+ transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
88
+ outline: none;
89
+ }
90
+
91
+ .emoji-btn:focus-visible {
92
+ outline: 2px solid var(--ai-warning-border);
93
+ outline-offset: 2px;
94
+ }
95
+
96
+ .emoji-btn:hover:not(:disabled),
97
+ .emoji-btn--hovered {
98
+ background: var(--ai-rating-btn-bg-hover);
99
+ border: var(--ai-rating-btn-border-hover);
100
+ transform: scale(1.1);
101
+ }
102
+
103
+ /* ─── Selected state ─────────────────────────────────────────────────────── */
104
+ .emoji-btn--active {
105
+ background: var(--ai-rating-btn-bg-active) !important;
106
+ border: var(--ai-rating-btn-border-active) !important;
107
+ box-shadow: var(--ai-rating-btn-shadow-active);
108
+ transform: scale(1.18) !important;
109
+ }
110
+
111
+ /* ─── Disabled ───────────────────────────────────────────────────────────── */
112
+ .emoji-btn--disabled {
113
+ opacity: 0.45;
114
+ cursor: not-allowed;
115
+ pointer-events: none;
116
+ }
117
+
118
+ /* ─── Emoji icon ─────────────────────────────────────────────────────────── */
119
+ .emoji-btn__icon {
120
+ font-size: var(--ai-rating-emoji-size);
121
+ line-height: 1;
122
+ display: inline-flex;
123
+ align-items: center;
124
+ justify-content: center;
125
+ width: var(--ai-rating-emoji-size);
126
+ height: var(--ai-rating-emoji-size);
127
+ pointer-events: none;
128
+ }
129
+
130
+ /* ─── Selected label ─────────────────────────────────────────────────────── */
131
+ .rating__selected-label {
132
+ margin: 0;
133
+ font-size: var(--ai-rating-label-size);
134
+ font-weight: 500;
135
+ line-height: 24px;
136
+ color: var(--ai-rating-label-color);
137
+ text-align: center;
138
+ width: 100%;
139
+ animation: fadeIn 0.15s ease;
140
+ }
141
+
142
+ @keyframes fadeIn {
143
+ from { opacity: 0; transform: translateY(4px); }
144
+ to { opacity: 1; transform: translateY(0); }
145
+ }
@@ -0,0 +1,176 @@
1
+ import { Host, h } from "@stencil/core";
2
+ const EMOJI_OPTIONS = [
3
+ { value: 1, emoji: '😠', label: 'سيئ جداً' },
4
+ { value: 2, emoji: '☹️', label: 'سيئ' },
5
+ { value: 3, emoji: '😐', label: 'مقبول' },
6
+ { value: 4, emoji: '🙂', label: 'جيد' },
7
+ { value: 5, emoji: '😍', label: 'ممتاز' },
8
+ ];
9
+ export class AiRating {
10
+ /** Main question label */
11
+ question = 'كيف كانت تجربتك؟';
12
+ /** Sub-label below the question */
13
+ subtitle = 'رأيك يساعدنا نحسن الخدمة';
14
+ /** Currently selected rating value (1–5) */
15
+ value = null;
16
+ /** Disable interaction */
17
+ disabled = false;
18
+ hovered = null;
19
+ ratingChange;
20
+ select(val) {
21
+ if (this.disabled)
22
+ return;
23
+ this.value = this.value === val ? null : val;
24
+ if (this.value !== null)
25
+ this.ratingChange.emit(this.value);
26
+ }
27
+ get selectedLabel() {
28
+ if (this.value === null)
29
+ return null;
30
+ return EMOJI_OPTIONS.find(o => o.value === this.value)?.label ?? null;
31
+ }
32
+ render() {
33
+ const label = this.selectedLabel;
34
+ return (h(Host, { key: 'e540c94762552fbf2bc8a60f37a9059e1f9b6610' }, h("div", { key: '0e5ff97267ee692a19e6e1ba5d6f32b456d6401e', class: "rating" }, h("div", { key: '00c30cb515c0d51d7c1f9e8393f15b1d0475ffc7', class: "rating__text" }, h("p", { key: '2a390c9465eed30fac0d8f8291f6a7c2dab35a47', class: "rating__question" }, this.question), h("p", { key: '09a1010df00e31aa05946e6a1cf1fc23d9fa77a0', class: "rating__subtitle" }, this.subtitle)), h("div", { key: '777a46e4157d1c1bc3691bdd0783a7023f7e3d8d', class: "rating__icons", role: "group", "aria-label": this.question }, EMOJI_OPTIONS.map(opt => {
35
+ const isActive = this.value === opt.value;
36
+ const isHovered = this.hovered === opt.value;
37
+ return (h("button", { key: opt.value, class: {
38
+ 'emoji-btn': true,
39
+ 'emoji-btn--active': isActive,
40
+ 'emoji-btn--hovered': isHovered && !isActive,
41
+ 'emoji-btn--disabled': this.disabled,
42
+ }, "aria-label": opt.label, "aria-pressed": isActive ? 'true' : 'false', disabled: this.disabled, onClick: () => this.select(opt.value), onMouseEnter: () => (this.hovered = opt.value), onMouseLeave: () => (this.hovered = null) }, h("span", { class: "emoji-btn__icon", "aria-hidden": "true" }, opt.emoji)));
43
+ })), label && h("p", { key: 'bba5b2ff2bcdd371cb3aefc761a5d7653bbf8630', class: "rating__selected-label" }, label))));
44
+ }
45
+ static get is() { return "ai-rating"; }
46
+ static get encapsulation() { return "shadow"; }
47
+ static get originalStyleUrls() {
48
+ return {
49
+ "$": ["ai-rating.css"]
50
+ };
51
+ }
52
+ static get styleUrls() {
53
+ return {
54
+ "$": ["ai-rating.css"]
55
+ };
56
+ }
57
+ static get properties() {
58
+ return {
59
+ "question": {
60
+ "type": "string",
61
+ "mutable": false,
62
+ "complexType": {
63
+ "original": "string",
64
+ "resolved": "string",
65
+ "references": {}
66
+ },
67
+ "required": false,
68
+ "optional": false,
69
+ "docs": {
70
+ "tags": [],
71
+ "text": "Main question label"
72
+ },
73
+ "getter": false,
74
+ "setter": false,
75
+ "reflect": false,
76
+ "attribute": "question",
77
+ "defaultValue": "'\u0643\u064A\u0641 \u0643\u0627\u0646\u062A \u062A\u062C\u0631\u0628\u062A\u0643\u061F'"
78
+ },
79
+ "subtitle": {
80
+ "type": "string",
81
+ "mutable": false,
82
+ "complexType": {
83
+ "original": "string",
84
+ "resolved": "string",
85
+ "references": {}
86
+ },
87
+ "required": false,
88
+ "optional": false,
89
+ "docs": {
90
+ "tags": [],
91
+ "text": "Sub-label below the question"
92
+ },
93
+ "getter": false,
94
+ "setter": false,
95
+ "reflect": false,
96
+ "attribute": "subtitle",
97
+ "defaultValue": "'\u0631\u0623\u064A\u0643 \u064A\u0633\u0627\u0639\u062F\u0646\u0627 \u0646\u062D\u0633\u0646 \u0627\u0644\u062E\u062F\u0645\u0629'"
98
+ },
99
+ "value": {
100
+ "type": "number",
101
+ "mutable": true,
102
+ "complexType": {
103
+ "original": "RatingValue | null",
104
+ "resolved": "1 | 2 | 3 | 4 | 5",
105
+ "references": {
106
+ "RatingValue": {
107
+ "location": "local",
108
+ "path": "/home/ahmed/Desktop/Salla/Dev/multi-agent/ui-ai-kit/packages/core/src/components/ai-rating/ai-rating.tsx",
109
+ "id": "src/components/ai-rating/ai-rating.tsx::RatingValue"
110
+ }
111
+ }
112
+ },
113
+ "required": false,
114
+ "optional": false,
115
+ "docs": {
116
+ "tags": [],
117
+ "text": "Currently selected rating value (1\u20135)"
118
+ },
119
+ "getter": false,
120
+ "setter": false,
121
+ "reflect": false,
122
+ "attribute": "value",
123
+ "defaultValue": "null"
124
+ },
125
+ "disabled": {
126
+ "type": "boolean",
127
+ "mutable": false,
128
+ "complexType": {
129
+ "original": "boolean",
130
+ "resolved": "boolean",
131
+ "references": {}
132
+ },
133
+ "required": false,
134
+ "optional": false,
135
+ "docs": {
136
+ "tags": [],
137
+ "text": "Disable interaction"
138
+ },
139
+ "getter": false,
140
+ "setter": false,
141
+ "reflect": false,
142
+ "attribute": "disabled",
143
+ "defaultValue": "false"
144
+ }
145
+ };
146
+ }
147
+ static get states() {
148
+ return {
149
+ "hovered": {}
150
+ };
151
+ }
152
+ static get events() {
153
+ return [{
154
+ "method": "ratingChange",
155
+ "name": "ratingChange",
156
+ "bubbles": true,
157
+ "cancelable": true,
158
+ "composed": true,
159
+ "docs": {
160
+ "tags": [],
161
+ "text": ""
162
+ },
163
+ "complexType": {
164
+ "original": "RatingValue",
165
+ "resolved": "1 | 2 | 3 | 4 | 5",
166
+ "references": {
167
+ "RatingValue": {
168
+ "location": "local",
169
+ "path": "/home/ahmed/Desktop/Salla/Dev/multi-agent/ui-ai-kit/packages/core/src/components/ai-rating/ai-rating.tsx",
170
+ "id": "src/components/ai-rating/ai-rating.tsx::RatingValue"
171
+ }
172
+ }
173
+ }
174
+ }];
175
+ }
176
+ }