@asgard-js/react 0.0.43-canary.9 → 0.0.43

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asgard-js/react",
3
- "version": "0.0.43-canary.9",
3
+ "version": "0.0.43",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -54,7 +54,7 @@
54
54
  "vitest": "^1.6.0"
55
55
  },
56
56
  "peerDependencies": {
57
- "@asgard-js/core": "^0.0.42-canary.1",
57
+ "@asgard-js/core": "^0.0.43",
58
58
  "react": "^18.0.0",
59
59
  "react-dom": "^18.0.0"
60
60
  },
Binary file
@@ -1,53 +1,61 @@
1
- .container {
2
- width: 220px;
1
+ .api_key_input {
2
+ width: 360px;
3
+ max-width: calc(100vw - 32px);
3
4
  background-color: var(--asg-color-bg);
4
5
  border-radius: 12px;
5
- padding: 12px 20px;
6
+ padding: 24px;
6
7
  border: 0.5px solid var(--asg-color-border);
8
+
9
+ @media (max-width: 480px) {
10
+ width: 100%;
11
+ max-width: none;
12
+ padding: 20px;
13
+ margin: 0 16px;
14
+ }
7
15
  }
8
16
 
9
- .header {
17
+ .api_key_input__header {
10
18
  display: flex;
11
19
  flex-direction: column;
12
- gap: 4px;
20
+ gap: 8px;
13
21
  align-items: center;
14
- margin-bottom: 12px;
22
+ margin-bottom: 20px;
15
23
  }
16
24
 
17
- .icon {
18
- width: 24px;
19
- height: 24px;
25
+ .api_key_input__icon {
26
+ width: 40px;
27
+ height: 40px;
20
28
  }
21
29
 
22
- .title {
30
+ .api_key_input__title {
23
31
  margin: 0;
24
- font-size: 18px;
32
+ font-size: 20px;
25
33
  font-weight: 500;
26
34
  color: white;
27
35
  }
28
36
 
29
- .form {
37
+ .api_key_input__form {
30
38
  width: 100%;
31
39
  display: flex;
32
40
  flex-direction: column;
33
41
  gap: 20px;
34
42
  }
35
43
 
36
- .label {
44
+ .api_key_input__label {
37
45
  display: block;
38
46
  font-size: 14px;
39
47
  color: rgba(255, 255, 255, 0.7);
40
48
  margin-bottom: 8px;
41
49
  }
42
50
 
43
- .inputWrapper {
51
+ .api_key_input__input_wrapper {
44
52
  position: relative;
45
53
  width: 100%;
46
54
  }
47
55
 
48
- .input {
56
+ .api_key_input__input {
49
57
  width: 100%;
50
- height: 42px;
58
+ height: 48px;
51
59
  padding: 0 40px 0 12px;
52
60
  font-size: 14px;
53
61
  font-weight: 400;
@@ -59,6 +67,11 @@
59
67
  transition: all 0.2s ease;
60
68
  box-sizing: border-box;
61
69
 
70
+ @media (max-width: 480px) {
71
+ height: 44px;
72
+ font-size: 16px;
73
+ }
74
+
62
75
  &::placeholder {
63
76
  color: rgba(255, 255, 255, 0.4);
64
77
  }
@@ -68,18 +81,18 @@
68
81
  background: rgba(255, 255, 255, 0.08);
69
82
  }
70
83
 
71
- &.error {
72
- border-color: rgba(255, 69, 58, 0.6);
73
- background: rgba(255, 69, 58, 0.05);
84
+ &--error {
85
+ border-color: #FF4D4F;
86
+ background: rgba(255, 77, 79, 0.05);
74
87
  }
75
88
 
76
- &.disabled {
89
+ &--disabled {
77
90
  opacity: 0.5;
78
91
  cursor: not-allowed;
79
92
  }
80
93
  }
81
94
 
82
- .toggleButton {
95
+ .api_key_input__toggle_button {
83
96
  position: absolute;
84
97
  right: 8px;
85
98
  top: 50%;
@@ -106,23 +119,23 @@
106
119
  }
107
120
  }
108
121
 
109
- .toggleIcon {
122
+ .api_key_input__toggle_icon {
110
123
  width: 16px;
111
124
  height: 16px;
112
125
  }
113
126
 
114
- .errorMessage {
127
+ .api_key_input__error_message {
115
128
  margin-top: 6px;
116
129
  font-size: 12px;
117
- color: rgba(255, 69, 58, 0.8);
130
+ color: #FF4D4F;
118
131
  }
119
132
 
120
- .submitButton {
133
+ .api_key_input__submit_button {
121
134
  width: 100%;
122
- height: 42px;
123
- background: #5856d6;
135
+ height: 48px;
124
136
  border: none;
125
137
  border-radius: 6px;
138
+ background-color: #5856d6;
126
139
  color: white;
127
140
  font-size: 14px;
128
141
  font-weight: 500;
@@ -130,13 +143,9 @@
130
143
  transition: all 0.2s ease;
131
144
  outline: none;
132
145
 
133
- &:hover:not(:disabled) {
134
- background: #4845c7;
135
- transform: translateY(-1px);
136
- }
137
-
138
- &:active:not(:disabled) {
139
- transform: translateY(0);
146
+ @media (max-width: 480px) {
147
+ height: 44px;
148
+ font-size: 16px;
140
149
  }
141
150
 
142
151
  &:disabled {
@@ -144,41 +153,4 @@
144
153
  cursor: not-allowed;
145
154
  transform: none;
146
155
  }
147
-
148
- &.loading {
149
- position: relative;
150
- color: transparent;
151
-
152
- &::after {
153
- content: '';
154
- position: absolute;
155
- top: 50%;
156
- left: 50%;
157
- width: 16px;
158
- height: 16px;
159
- margin-left: -8px;
160
- margin-top: -8px;
161
- border: 2px solid transparent;
162
- border-top: 2px solid white;
163
- border-radius: 50%;
164
- animation: spin 1s linear infinite;
165
- }
166
- }
167
- }
168
-
169
- @keyframes spin {
170
- 0% {
171
- transform: rotate(0deg);
172
- }
173
- 100% {
174
- transform: rotate(360deg);
175
- }
176
- }
177
-
178
- // 響應式設計
179
- @media (max-width: 280px) {
180
- .container {
181
- width: 100%;
182
- min-width: 200px;
183
- }
184
156
  }
@@ -3,6 +3,8 @@ import clsx from 'clsx';
3
3
  import { useAsgardThemeContext } from '../../../context/asgard-theme-context';
4
4
  import { useAsgardContext } from '../../../context/asgard-service-context';
5
5
  import { ProfileIcon } from '../profile-icon';
6
+ import EyeSvg from '../../../icons/eye.svg?react';
7
+ import EyeOffSvg from '../../../icons/eye-off.svg?react';
6
8
  import styles from './api-key-input.module.scss';
7
9
 
8
10
  export interface ApiKeyInputProps {
@@ -46,29 +48,29 @@ export function ApiKeyInput({
46
48
 
47
49
  return (
48
50
  <div
49
- className={clsx(styles.container, className)}
51
+ className={clsx(styles.api_key_input, className)}
50
52
  style={{
51
53
  backgroundColor: chatbot.backgroundColor,
52
54
  borderColor: chatbot.borderColor,
53
55
  }}
54
56
  >
55
- <div className={styles.header}>
57
+ <div className={styles.api_key_input__header}>
56
58
  <ProfileIcon avatar={avatar} />
57
- <h2 className={styles.title} style={chatbot?.header?.title?.style}>{title}</h2>
59
+ <h2 className={styles.api_key_input__title} style={chatbot?.header?.title?.style}>{title}</h2>
58
60
  </div>
59
61
 
60
- <form onSubmit={handleSubmit} className={styles.form}>
61
- <div className={styles.inputGroup}>
62
- <label className={styles.label}>Key</label>
63
- <div className={styles.inputWrapper}>
62
+ <form onSubmit={handleSubmit} className={styles.api_key_input__form}>
63
+ <div>
64
+ <label className={styles.api_key_input__label}>Key</label>
65
+ <div className={styles.api_key_input__input_wrapper}>
64
66
  <input
65
67
  type={showPassword ? 'text' : 'password'}
66
68
  value={apiKey}
67
69
  onChange={handleInputChange}
68
70
  placeholder={placeholder}
69
- className={clsx(styles.input, {
70
- [styles.error]: error,
71
- [styles.disabled]: loading,
71
+ className={clsx(styles.api_key_input__input, {
72
+ [styles['api_key_input__input--error']]: error,
73
+ [styles['api_key_input__input--disabled']]: loading,
72
74
  })}
73
75
  disabled={loading}
74
76
  autoComplete="off"
@@ -77,48 +79,28 @@ export function ApiKeyInput({
77
79
  <button
78
80
  type="button"
79
81
  onClick={togglePasswordVisibility}
80
- className={styles.toggleButton}
82
+ className={styles.api_key_input__toggle_button}
81
83
  disabled={loading}
82
84
  aria-label={showPassword ? 'Hide password' : 'Show password'}
83
85
  >
84
- <svg
85
- className={styles.toggleIcon}
86
- width="16"
87
- height="16"
88
- viewBox="0 0 24 24"
89
- fill="none"
90
- stroke="currentColor"
91
- strokeWidth="2"
92
- strokeLinecap="round"
93
- strokeLinejoin="round"
94
- >
95
- {showPassword ? (
96
- <>
97
- <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
98
- <line x1="1" y1="1" x2="23" y2="23" />
99
- </>
100
- ) : (
101
- <>
102
- <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
103
- <circle cx="12" cy="12" r="3" />
104
- </>
105
- )}
106
- </svg>
86
+ {showPassword ? (
87
+ <EyeOffSvg className={styles.api_key_input__toggle_icon} />
88
+ ) : (
89
+ <EyeSvg className={styles.api_key_input__toggle_icon} />
90
+ )}
107
91
  </button>
108
92
  )}
109
93
  </div>
110
- {error && <div className={styles.errorMessage}>{error}</div>}
94
+ {error && <div className={styles.api_key_input__error_message}>{error}</div>}
111
95
  </div>
112
96
 
113
97
  <button
114
98
  type="submit"
115
99
  disabled={!apiKey.trim() || loading}
116
- className={clsx(styles.submitButton, {
117
- [styles.loading]: loading,
118
- })}
100
+ className={styles.api_key_input__submit_button}
119
101
  style={{
120
- backgroundColor: chatbot?.primaryComponent?.mainColor,
121
- color: chatbot?.primaryComponent?.secondaryColor,
102
+ backgroundColor: chatbot?.mainColor,
103
+ color: chatbot?.secondaryColor,
122
104
  }}
123
105
  >
124
106
  {loading ? 'Loading...' : 'Continue'}
@@ -20,12 +20,12 @@ export function ChatbotFullScreenContainer(
20
20
 
21
21
  usePreventOverScrolling(containerRef);
22
22
 
23
- useOnScreenKeyboardScrollFix();
24
-
25
23
  const [, height] = useViewportSize() ?? [];
26
24
 
27
25
  const isOnScreenKeyboardOpen = useIsOnScreenKeyboardOpen();
28
26
 
27
+ useOnScreenKeyboardScrollFix(isOnScreenKeyboardOpen);
28
+
29
29
  const styles = useMemo(() => {
30
30
  return Object.assign(
31
31
  theme?.chatbot?.backgroundColor
@@ -0,0 +1,24 @@
1
+ .chatbot__auth_state_container {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ flex: 1;
6
+ padding: 20px;
7
+ }
8
+
9
+ .chatbot__error_state {
10
+ color: #ff6b6b;
11
+ }
12
+
13
+ .chatbot__error_state__content {
14
+ text-align: center;
15
+ }
16
+
17
+ .chatbot__error_state__icon {
18
+ font-size: 16px;
19
+ margin-bottom: 8px;
20
+ }
21
+
22
+ .chatbot__error_state__message {
23
+ // No additional styles needed - uses default text styling
24
+ }
@@ -17,13 +17,14 @@ import {
17
17
  AsgardAppInitializationContextProvider,
18
18
  AsgardServiceContextProviderProps,
19
19
  } from '../../context';
20
+ import { AuthState } from '@asgard-js/core';
21
+ import clsx from 'clsx';
20
22
  import { ApiKeyInput } from './api-key-input';
21
23
  import { ChatbotHeader } from './chatbot-header';
22
24
  import { ChatbotBody } from './chatbot-body';
23
25
  import { ChatbotFooter } from './chatbot-footer';
24
26
  import { ChatbotContainer } from './chatbot-container/chatbot-container';
25
-
26
- type AuthState = 'loading' | 'needApiKey' | 'authenticated' | 'error';
27
+ import styles from './chatbot.module.scss';
27
28
 
28
29
  interface ChatbotProps extends AsgardTemplateContextValue {
29
30
  className?: string;
@@ -95,47 +96,40 @@ export const Chatbot = forwardRef(function Chatbot(
95
96
  switch (authState) {
96
97
  case 'loading':
97
98
  return (
98
- <div style={{
99
- display: 'flex',
100
- alignItems: 'center',
101
- justifyContent: 'center',
102
- flex: 1,
103
- padding: '20px'
104
- }}>
99
+ <div className={styles.chatbot__auth_state_container}>
105
100
  {loadingComponent || <div>Loading...</div>}
106
101
  </div>
107
102
  );
108
103
 
109
104
  case 'needApiKey':
110
105
  return (
111
- <div style={{
112
- display: 'flex',
113
- alignItems: 'center',
114
- justifyContent: 'center',
115
- flex: 1,
116
- padding: '20px'
117
- }}>
106
+ <div className={styles.chatbot__auth_state_container}>
107
+ <ApiKeyInput
108
+ title={title}
109
+ onSubmit={onApiKeySubmit || (() => {})}
110
+ placeholder="Enter your key"
111
+ />
112
+ </div>
113
+ );
114
+
115
+ case 'invalidApiKey':
116
+ return (
117
+ <div className={styles.chatbot__auth_state_container}>
118
118
  <ApiKeyInput
119
119
  title={title}
120
120
  onSubmit={onApiKeySubmit || (() => {})}
121
121
  placeholder="Enter your key"
122
+ error="Please check if the key is correct."
122
123
  />
123
124
  </div>
124
125
  );
125
126
 
126
127
  case 'error':
127
128
  return (
128
- <div style={{
129
- display: 'flex',
130
- alignItems: 'center',
131
- justifyContent: 'center',
132
- flex: 1,
133
- padding: '20px',
134
- color: '#ff6b6b'
135
- }}>
136
- <div style={{ textAlign: 'center' }}>
137
- <div style={{ fontSize: '16px', marginBottom: '8px' }}>⚠️</div>
138
- <div>Something went wrong. Please try again later.</div>
129
+ <div className={clsx(styles.chatbot__auth_state_container, styles.chatbot__error_state)}>
130
+ <div className={styles.chatbot__error_state__content}>
131
+ <div className={styles.chatbot__error_state__icon}>⚠️</div>
132
+ <div className={styles.chatbot__error_state__message}>Something went wrong. Please try again later.</div>
139
133
  </div>
140
134
  </div>
141
135
  );
@@ -159,7 +153,7 @@ export const Chatbot = forwardRef(function Chatbot(
159
153
  };
160
154
 
161
155
  // Don't initialize SSE connection when explicitly needing API key or in error state
162
- if (authState !== 'needApiKey' && authState !== 'error') {
156
+ if (authState !== 'needApiKey' && authState !== 'error' && authState !== 'invalidApiKey') {
163
157
  return (
164
158
  <AsgardAppInitializationContextProvider
165
159
  enabled={enableLoadConfigFromService}
@@ -31,6 +31,8 @@ export interface AsgardThemeContextValue {
31
31
  backgroundColor?: CSSProperties['backgroundColor'];
32
32
  borderColor?: CSSProperties['borderColor'];
33
33
  inactiveColor?: CSSProperties['color'];
34
+ mainColor?: CSSProperties['color'];
35
+ secondaryColor?: CSSProperties['color'];
34
36
  primaryComponent?: {
35
37
  mainColor?: CSSProperties['color'];
36
38
  secondaryColor?: CSSProperties['color'];
@@ -267,6 +269,9 @@ export function AsgardThemeContextProvider(
267
269
  chatbot: {
268
270
  backgroundColor: themeFromAnnotations.chatbot?.backgroundColor,
269
271
  borderColor: themeFromAnnotations.chatbot?.borderColor,
272
+ mainColor: themeFromAnnotations.chatbot?.primaryComponent?.mainColor,
273
+ secondaryColor: themeFromAnnotations.chatbot?.primaryComponent?.secondaryColor,
274
+
270
275
  header: {
271
276
  style: {
272
277
  borderBottomColor: themeFromAnnotations.chatbot?.borderColor,
@@ -407,6 +412,8 @@ export function AsgardThemeContextProvider(
407
412
  chatbot: {
408
413
  backgroundColor: propsTheme.chatbot?.backgroundColor,
409
414
  borderColor: propsTheme.chatbot?.borderColor,
415
+ mainColor: propsTheme.chatbot?.primaryComponent?.mainColor,
416
+ secondaryColor: propsTheme.chatbot?.primaryComponent?.secondaryColor,
410
417
  header: {
411
418
  style: {
412
419
  borderBottomColor: propsTheme.chatbot?.borderColor,
@@ -1,7 +1,9 @@
1
1
  import { useEffect } from 'react';
2
2
 
3
- export function useOnScreenKeyboardScrollFix(): void {
3
+ export function useOnScreenKeyboardScrollFix(isOnScreenKeyboardOpen: boolean): void {
4
4
  useEffect(() => {
5
+ if (!isOnScreenKeyboardOpen) return;
6
+
5
7
  function handleScroll(): void {
6
8
  window.scrollTo(0, 0);
7
9
  }
@@ -11,5 +13,5 @@ export function useOnScreenKeyboardScrollFix(): void {
11
13
  return (): void => {
12
14
  window.removeEventListener('scroll', handleScroll);
13
15
  };
14
- }, []);
16
+ }, [isOnScreenKeyboardOpen]);
15
17
  }
@@ -0,0 +1,4 @@
1
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
3
+ <line x1="1" y1="1" x2="23" y2="23"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
3
+ <circle cx="12" cy="12" r="3"/>
4
+ </svg>
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import 'highlight.js/styles/atom-one-dark.css';
2
2
  export * from './components';
3
3
  export * from './context';
4
- export * from './hooks';
4
+ export * from './hooks';