@hydralms/components 0.1.3 → 0.2.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 (199) hide show
  1. package/dist/ForumBoard-CHXU3mjC.js +2207 -0
  2. package/dist/ForumBoard-d1w5-r6n.cjs +1 -0
  3. package/dist/assessment-toolbar/assessment-toolbar.d.ts +1 -1
  4. package/dist/assessment-toolbar/index.d.ts +5 -1
  5. package/dist/assessment-toolbar/question-header-bar.d.ts +2 -0
  6. package/dist/assessment-toolbar/question-materials-drawer.d.ts +2 -0
  7. package/dist/assessment-toolbar/question-navigator.d.ts +1 -1
  8. package/dist/assessment-toolbar/types.d.ts +52 -4
  9. package/dist/assessment-toolbar/use-countdown.d.ts +43 -0
  10. package/dist/common/index.d.ts +2 -1
  11. package/dist/common/stepper.d.ts +6 -0
  12. package/dist/common/types.d.ts +37 -0
  13. package/dist/components.css +1 -1
  14. package/dist/content/attachment-list.d.ts +6 -0
  15. package/dist/content/content-block.d.ts +1 -1
  16. package/dist/content/index.d.ts +2 -1
  17. package/dist/content/types.d.ts +39 -0
  18. package/dist/curriculum/curriculum-item.d.ts +1 -1
  19. package/dist/index.cjs +1 -1
  20. package/dist/index.js +551 -312
  21. package/dist/modules/AssignmentModule/AssignmentModule.d.ts +8 -0
  22. package/dist/modules/AssignmentModule/types.d.ts +65 -0
  23. package/dist/modules/CertificateModule/CertificateModule.d.ts +9 -0
  24. package/dist/modules/CertificateModule/types.d.ts +49 -0
  25. package/dist/modules/DiscussionModule/DiscussionModule.d.ts +8 -0
  26. package/dist/modules/DiscussionModule/types.d.ts +47 -0
  27. package/dist/modules/ExamModule/ExamModule.d.ts +8 -0
  28. package/dist/modules/ExamModule/types.d.ts +64 -0
  29. package/dist/modules/GradeCenterModule/GradeCenterModule.d.ts +9 -0
  30. package/dist/modules/GradeCenterModule/types.d.ts +54 -0
  31. package/dist/modules/QuizModule/QuizModule.d.ts +1 -1
  32. package/dist/modules/QuizModule/types.d.ts +6 -1
  33. package/dist/modules/SurveyModule/SurveyModule.d.ts +7 -0
  34. package/dist/modules/SurveyModule/types.d.ts +49 -0
  35. package/dist/modules/index.d.ts +12 -0
  36. package/dist/modules.cjs +1 -0
  37. package/dist/modules.js +1422 -0
  38. package/dist/progress/achievement-badge.d.ts +6 -0
  39. package/dist/progress/activity-timeline.d.ts +6 -0
  40. package/dist/progress/index.d.ts +4 -1
  41. package/dist/progress/stat-card.d.ts +1 -1
  42. package/dist/progress/streak-badge.d.ts +6 -0
  43. package/dist/progress/types.d.ts +97 -0
  44. package/dist/questions/essay.d.ts +1 -1
  45. package/dist/questions/hotspot.d.ts +21 -0
  46. package/dist/questions/index.d.ts +9 -1
  47. package/dist/questions/inline-choice.d.ts +21 -0
  48. package/dist/questions/matching.d.ts +22 -0
  49. package/dist/questions/numeric.d.ts +11 -0
  50. package/dist/questions/ordering.d.ts +12 -0
  51. package/dist/questions/scenario.d.ts +23 -0
  52. package/dist/questions/scoring.d.ts +22 -0
  53. package/dist/questions/spreadsheet.d.ts +29 -0
  54. package/dist/questions/types.d.ts +106 -1
  55. package/dist/questions/use-drag-reorder.d.ts +17 -0
  56. package/dist/sections/CertificateViewer/types.d.ts +7 -5
  57. package/dist/sections/ExamSession/ExamSession.d.ts +1 -1
  58. package/dist/sections/ExamSession/types.d.ts +6 -1
  59. package/dist/sections/ForumBoard/ForumBoard.d.ts +8 -0
  60. package/dist/sections/ForumBoard/types.d.ts +64 -0
  61. package/dist/sections/QuizSession/QuizSession.d.ts +1 -1
  62. package/dist/sections/QuizSession/types.d.ts +6 -1
  63. package/dist/sections/RequirementsChecklist/RequirementsChecklist.d.ts +8 -0
  64. package/dist/sections/RequirementsChecklist/types.d.ts +37 -0
  65. package/dist/sections/RubricView/RubricView.d.ts +9 -0
  66. package/dist/sections/RubricView/types.d.ts +50 -0
  67. package/dist/sections/index.d.ts +7 -1
  68. package/dist/sections.cjs +1 -1
  69. package/dist/sections.js +250 -1715
  70. package/dist/social/post-card.d.ts +1 -1
  71. package/dist/tabs-DRM2Iq_J.cjs +172 -0
  72. package/dist/tabs-Wf3h_Cx3.js +21580 -0
  73. package/dist/ui/alert.d.ts +1 -1
  74. package/dist/ui/badge.d.ts +1 -1
  75. package/dist/ui/button.d.ts +1 -1
  76. package/dist/ui/drawer.d.ts +84 -0
  77. package/dist/ui/index.d.ts +3 -0
  78. package/dist/ui/progress.d.ts +1 -1
  79. package/dist/ui/rich-text-editor.d.ts +30 -0
  80. package/dist/ui/rich-text-toolbar.d.ts +8 -0
  81. package/dist/utils/array-utils.d.ts +4 -0
  82. package/dist/utils/flatten-leaves.d.ts +6 -0
  83. package/dist/utils/format-file-size.d.ts +1 -0
  84. package/dist/utils/format-timestamp.d.ts +1 -0
  85. package/dist/utils/is-empty-html.d.ts +5 -0
  86. package/dist/utils/shuffle.d.ts +1 -0
  87. package/dist/utils/string-utils.d.ts +12 -0
  88. package/dist/video/video-bookmark.d.ts +1 -1
  89. package/dist/video/video-playlist-item.d.ts +1 -1
  90. package/package.json +92 -3
  91. package/src/assessment-toolbar/assessment-toolbar.tsx +54 -49
  92. package/src/assessment-toolbar/index.ts +6 -0
  93. package/src/assessment-toolbar/question-header-bar.tsx +61 -0
  94. package/src/assessment-toolbar/question-materials-drawer.tsx +55 -0
  95. package/src/assessment-toolbar/question-navigator.tsx +3 -31
  96. package/src/assessment-toolbar/timer-display.tsx +2 -2
  97. package/src/assessment-toolbar/types.ts +54 -4
  98. package/src/assessment-toolbar/use-countdown.ts +153 -0
  99. package/src/common/index.ts +3 -0
  100. package/src/common/search-input.tsx +7 -6
  101. package/src/common/stepper.tsx +100 -0
  102. package/src/common/types.ts +39 -0
  103. package/src/content/attachment-list.tsx +90 -0
  104. package/src/content/content-block.tsx +4 -2
  105. package/src/content/file-upload-zone.tsx +1 -6
  106. package/src/content/index.ts +3 -0
  107. package/src/content/types.ts +41 -0
  108. package/src/curriculum/curriculum-item.tsx +7 -3
  109. package/src/feedback/feedback-banner.tsx +12 -14
  110. package/src/flashcards/flashcard-deck.tsx +1 -9
  111. package/src/flashcards/flashcard.tsx +1 -1
  112. package/src/modules/AssignmentModule/AssignmentModule.tsx +305 -0
  113. package/src/modules/AssignmentModule/types.ts +73 -0
  114. package/src/modules/CertificateModule/CertificateModule.tsx +161 -0
  115. package/src/modules/CertificateModule/types.ts +47 -0
  116. package/src/modules/CoursePlayer/CoursePlayer.tsx +44 -48
  117. package/src/modules/DiscussionModule/DiscussionModule.tsx +110 -0
  118. package/src/modules/DiscussionModule/types.ts +54 -0
  119. package/src/modules/ExamModule/ExamModule.tsx +285 -0
  120. package/src/modules/ExamModule/types.ts +66 -0
  121. package/src/modules/FlashcardLab/FlashcardLab.tsx +29 -16
  122. package/src/modules/GradeCenterModule/GradeCenterModule.tsx +169 -0
  123. package/src/modules/GradeCenterModule/types.ts +63 -0
  124. package/src/modules/QuizModule/QuizModule.tsx +88 -88
  125. package/src/modules/QuizModule/types.ts +6 -1
  126. package/src/modules/SurveyModule/SurveyModule.tsx +180 -0
  127. package/src/modules/SurveyModule/types.ts +51 -0
  128. package/src/modules/index.ts +24 -0
  129. package/src/progress/achievement-badge.tsx +52 -0
  130. package/src/progress/activity-timeline.tsx +84 -0
  131. package/src/progress/index.ts +7 -0
  132. package/src/progress/stat-card.tsx +30 -18
  133. package/src/progress/streak-badge.tsx +35 -0
  134. package/src/progress/types.ts +101 -0
  135. package/src/questions/choice.tsx +7 -9
  136. package/src/questions/essay.tsx +23 -25
  137. package/src/questions/fill-in-the-blank.tsx +13 -16
  138. package/src/questions/hotspot.tsx +154 -0
  139. package/src/questions/index.ts +16 -0
  140. package/src/questions/inline-choice.tsx +151 -0
  141. package/src/questions/matching.tsx +228 -0
  142. package/src/questions/multiple-choice.tsx +7 -9
  143. package/src/questions/numeric.tsx +102 -0
  144. package/src/questions/ordering.tsx +159 -0
  145. package/src/questions/question-renderer.tsx +21 -0
  146. package/src/questions/scenario.tsx +140 -0
  147. package/src/questions/scoring.ts +201 -0
  148. package/src/questions/spreadsheet.tsx +259 -0
  149. package/src/questions/true-false.tsx +7 -9
  150. package/src/questions/types.ts +123 -1
  151. package/src/questions/use-drag-reorder.ts +80 -0
  152. package/src/sections/AnnouncementFeed/AnnouncementFeed.tsx +2 -15
  153. package/src/sections/AssessmentReview/AssessmentReview.tsx +13 -2
  154. package/src/sections/AssignmentSubmission/AssignmentSubmission.tsx +7 -5
  155. package/src/sections/CertificateViewer/CertificateViewer.tsx +409 -56
  156. package/src/sections/CertificateViewer/types.ts +13 -5
  157. package/src/sections/CourseOutline/CourseOutline.tsx +4 -14
  158. package/src/sections/DiscussionThread/DiscussionThread.tsx +13 -10
  159. package/src/sections/ExamSession/ExamSession.tsx +44 -7
  160. package/src/sections/ExamSession/types.ts +6 -1
  161. package/src/sections/ForumBoard/ForumBoard.tsx +284 -0
  162. package/src/sections/ForumBoard/types.ts +67 -0
  163. package/src/sections/GradebookTable/GradebookTable.tsx +1 -1
  164. package/src/sections/LecturePlayer/LecturePlayer.tsx +1 -1
  165. package/src/sections/LessonPage/LessonPage.tsx +5 -9
  166. package/src/sections/PracticeQuiz/PracticeQuiz.tsx +15 -26
  167. package/src/sections/ProgressDashboard/ProgressDashboard.tsx +65 -65
  168. package/src/sections/QuizSession/QuizSession.tsx +67 -8
  169. package/src/sections/QuizSession/types.ts +6 -1
  170. package/src/sections/RequirementsChecklist/RequirementsChecklist.tsx +107 -0
  171. package/src/sections/RequirementsChecklist/types.ts +38 -0
  172. package/src/sections/ResourceLibrary/ResourceLibrary.tsx +4 -9
  173. package/src/sections/RubricView/RubricView.tsx +138 -0
  174. package/src/sections/RubricView/types.ts +52 -0
  175. package/src/sections/ScrollableQuiz/ScrollableQuiz.tsx +23 -9
  176. package/src/sections/SurveyForm/SurveyForm.tsx +8 -5
  177. package/src/sections/index.ts +20 -1
  178. package/src/social/post-card.tsx +8 -19
  179. package/src/social/user-avatar.tsx +1 -0
  180. package/src/styles/globals.css +13 -0
  181. package/src/ui/drawer.tsx +600 -0
  182. package/src/ui/index.ts +19 -0
  183. package/src/ui/rich-text-editor.tsx +109 -0
  184. package/src/ui/rich-text-toolbar.tsx +156 -0
  185. package/src/utils/array-utils.ts +17 -0
  186. package/src/utils/flatten-leaves.ts +17 -0
  187. package/src/utils/format-file-size.ts +5 -0
  188. package/src/utils/format-timestamp.ts +13 -0
  189. package/src/utils/is-empty-html.ts +7 -0
  190. package/src/utils/shuffle.ts +8 -0
  191. package/src/utils/string-utils.ts +30 -0
  192. package/src/video/video-bookmark.tsx +4 -3
  193. package/src/video/video-chapter-list.tsx +9 -4
  194. package/src/video/video-player.tsx +11 -4
  195. package/src/video/video-playlist-item.tsx +8 -3
  196. package/src/video/video-thumbnail-card.tsx +4 -0
  197. package/src/video/video-transcript.tsx +8 -5
  198. package/dist/table-BrS5cDQu.js +0 -2510
  199. package/dist/table-D6AkBBEo.cjs +0 -1
@@ -0,0 +1,52 @@
1
+ /**
2
+ * RubricView section — displays a grading rubric with criteria and proficiency levels.
3
+ *
4
+ * Shows criteria as rows with level columns. When `selectedLevels` is provided,
5
+ * highlights the scored level per criterion and shows total score.
6
+ *
7
+ * @example
8
+ * <RubricView
9
+ * criteria={rubricCriteria}
10
+ * selectedLevels={{ "c1": "l2", "c2": "l3" }}
11
+ * totalScore={85}
12
+ * maxScore={100}
13
+ * />
14
+ */
15
+ export interface RubricViewProps {
16
+ /** Rubric criteria (rows) */
17
+ criteria: RubricCriterion[];
18
+ /** Selected level UID per criterion UID — highlights scored cells */
19
+ selectedLevels?: Record<string, string>;
20
+ /** Overall rubric score (sum of selected levels) */
21
+ totalScore?: number;
22
+ /** Maximum possible score */
23
+ maxScore?: number;
24
+ /** Instructor feedback text */
25
+ feedback?: string;
26
+ /** CSS class name for the root element */
27
+ className?: string;
28
+ /** Inline styles for the root element */
29
+ style?: React.CSSProperties;
30
+ }
31
+
32
+ export interface RubricCriterion {
33
+ /** Unique identifier */
34
+ uid: string;
35
+ /** Criterion name (e.g. "Organization", "Evidence") */
36
+ name: string;
37
+ /** Optional description of what this criterion evaluates */
38
+ description?: string;
39
+ /** Proficiency levels from lowest to highest */
40
+ levels: RubricLevel[];
41
+ }
42
+
43
+ export interface RubricLevel {
44
+ /** Unique identifier */
45
+ uid: string;
46
+ /** Level label (e.g. "Excellent", "Good", "Needs Work") */
47
+ label: string;
48
+ /** Description of what this level looks like */
49
+ description: string;
50
+ /** Point value for this level */
51
+ points: number;
52
+ }
@@ -3,6 +3,7 @@ import { QuestionRenderer } from "../../questions";
3
3
  import type { SessionAnswer } from "../../questions/types";
4
4
  import { Button } from "../../ui/button";
5
5
  import { Card, CardContent } from "../../ui/card";
6
+ import { Separator } from "../../ui/separator";
6
7
  import { cn } from "../../lib/utils";
7
8
  import type { ScrollableQuizProps } from "./types";
8
9
 
@@ -23,10 +24,20 @@ export function ScrollableQuiz({
23
24
  const [activeUid, setActiveUid] = useState<string | null>(questions[0]?.uid ?? null);
24
25
  const questionRefs = useRef<Map<string, HTMLElement>>(new Map());
25
26
 
26
- const answeredCount = useMemo(() => {
27
- const answered = new Set(sessionAnswers.map((a) => a.uid));
28
- return questions.filter((q) => answered.has(q.uid)).length;
29
- }, [questions, sessionAnswers]);
27
+ const answersByQuestion = useMemo(() => {
28
+ const map = new Map<string, SessionAnswer[]>();
29
+ for (const a of sessionAnswers) {
30
+ const list = map.get(a.uid);
31
+ if (list) list.push(a);
32
+ else map.set(a.uid, [a]);
33
+ }
34
+ return map;
35
+ }, [sessionAnswers]);
36
+
37
+ const answeredCount = useMemo(
38
+ () => questions.filter((q) => answersByQuestion.has(q.uid)).length,
39
+ [questions, answersByQuestion],
40
+ );
30
41
 
31
42
  useEffect(() => {
32
43
  const observer = new IntersectionObserver(
@@ -90,7 +101,7 @@ export function ScrollableQuiz({
90
101
  {/* Main content */}
91
102
  <div className="flex-1 min-w-0">
92
103
  {orderedQuestions.map((group, gi) => (
93
- <div key={gi}>
104
+ <div key={group.label ?? `ungrouped-${gi}`}>
94
105
  {group.label && (
95
106
  <p className={cn("text-lg font-semibold mb-2 text-foreground", gi > 0 && "mt-4")}>
96
107
  {group.label}
@@ -113,7 +124,7 @@ export function ScrollableQuiz({
113
124
  )}
114
125
  <QuestionRenderer
115
126
  question={q}
116
- sessionAnswers={sessionAnswers.filter((a) => a.uid === q.uid)}
127
+ sessionAnswers={answersByQuestion.get(q.uid) ?? []}
117
128
  onAnswer={(answers) => handleAnswer(q.uid, answers)}
118
129
  readOnly={readOnly}
119
130
  />
@@ -123,7 +134,8 @@ export function ScrollableQuiz({
123
134
  })}
124
135
  </div>
125
136
  ))}
126
- <div className="mt-3 flex justify-between items-center">
137
+ <Separator className="my-3" />
138
+ <div className="flex justify-between items-center">
127
139
  <span className="text-sm text-muted-foreground">
128
140
  {answeredCount} of {questions.length} answered
129
141
  </span>
@@ -138,11 +150,12 @@ export function ScrollableQuiz({
138
150
 
139
151
  {/* Sidebar navigator */}
140
152
  {showNavigator && (
141
- <Card className="hidden md:block w-50 shrink-0 sticky top-4 self-start p-3">
153
+ <Card className="hidden md:block w-50 shrink-0 sticky top-4 self-start">
154
+ <CardContent className="p-3">
142
155
  <p className="font-semibold text-sm mb-1 text-foreground">Questions</p>
143
156
  <div className="flex flex-wrap gap-1">
144
157
  {questions.map((q, i) => {
145
- const isAnswered = sessionAnswers.some((a) => a.uid === q.uid);
158
+ const isAnswered = answersByQuestion.has(q.uid);
146
159
  const isActive = activeUid === q.uid;
147
160
  return (
148
161
  <button
@@ -163,6 +176,7 @@ export function ScrollableQuiz({
163
176
  );
164
177
  })}
165
178
  </div>
179
+ </CardContent>
166
180
  </Card>
167
181
  )}
168
182
  </div>
@@ -3,7 +3,8 @@ import { LikertScale, StarRating } from "../../feedback";
3
3
  import { Button } from "../../ui/button";
4
4
  import { Card, CardContent } from "../../ui/card";
5
5
  import { Progress } from "../../ui/progress";
6
- import { Textarea } from "../../ui/textarea";
6
+ import { Separator } from "../../ui/separator";
7
+ import { RichTextEditor } from "../../ui/rich-text-editor";
7
8
  import type { SurveyFormProps, SurveyAnswer } from "./types";
8
9
 
9
10
  export function SurveyForm({
@@ -95,12 +96,13 @@ export function SurveyForm({
95
96
  )}
96
97
 
97
98
  {q.type === "open_text" && (
98
- <Textarea
99
+ <RichTextEditor
99
100
  placeholder="Type your response..."
100
101
  value={(answer?.value as string) ?? ""}
101
- onChange={(e) => setAnswer(q.uid, e.target.value)}
102
- disabled={readOnly}
102
+ onChange={(html) => setAnswer(q.uid, html)}
103
+ readOnly={readOnly}
103
104
  className="min-h-24"
105
+ variant="minimal"
104
106
  />
105
107
  )}
106
108
 
@@ -164,7 +166,8 @@ export function SurveyForm({
164
166
  );
165
167
  })}
166
168
 
167
- <div className="flex justify-between items-center mt-3">
169
+ <Separator className="my-3" />
170
+ <div className="flex justify-between items-center">
168
171
  <span className="text-sm text-muted-foreground">
169
172
  {answeredCount} of {questions.length} answered
170
173
  </span>
@@ -87,4 +87,23 @@ export type {
87
87
  } from "./ProgressDashboard/types";
88
88
 
89
89
  export { CertificateViewer } from "./CertificateViewer/CertificateViewer";
90
- export type { CertificateViewerProps } from "./CertificateViewer/types";
90
+ export type {
91
+ CertificateViewerProps,
92
+ CertificateVariant,
93
+ } from "./CertificateViewer/types";
94
+
95
+ export { RubricView } from "./RubricView/RubricView";
96
+ export type {
97
+ RubricViewProps,
98
+ RubricCriterion,
99
+ RubricLevel,
100
+ } from "./RubricView/types";
101
+
102
+ export { RequirementsChecklist } from "./RequirementsChecklist/RequirementsChecklist";
103
+ export type {
104
+ RequirementsChecklistProps,
105
+ Requirement,
106
+ } from "./RequirementsChecklist/types";
107
+
108
+ export { ForumBoard } from "./ForumBoard/ForumBoard";
109
+ export type { ForumBoardProps, ForumTopic, ForumSortOrder } from "./ForumBoard/types";
@@ -1,29 +1,17 @@
1
+ import { memo } from "react";
1
2
  import { Pin } from "lucide-react";
2
3
  import { UserAvatar } from "./user-avatar";
3
4
  import { Separator } from "../ui/separator";
4
5
  import type { PostCardProps } from "./types";
5
6
  import { cn } from "../lib/utils";
6
-
7
- function formatTimestamp(iso: string): string {
8
- const date = new Date(iso);
9
- const now = new Date();
10
- const diffMs = now.getTime() - date.getTime();
11
- const diffMins = Math.floor(diffMs / 60000);
12
- if (diffMins < 1) return "Just now";
13
- if (diffMins < 60) return `${diffMins}m ago`;
14
- const diffHours = Math.floor(diffMins / 60);
15
- if (diffHours < 24) return `${diffHours}h ago`;
16
- const diffDays = Math.floor(diffHours / 24);
17
- if (diffDays < 7) return `${diffDays}d ago`;
18
- return date.toLocaleDateString();
19
- }
7
+ import { formatTimestamp } from "../utils/format-timestamp";
20
8
 
21
9
  const HIGHLIGHT_BORDERS: Record<string, string> = {
22
10
  pinned: "var(--warning)",
23
11
  answer: "var(--success)",
24
12
  };
25
13
 
26
- export function PostCard({
14
+ export const PostCard = memo(function PostCard({
27
15
  author,
28
16
  content,
29
17
  createdAt,
@@ -73,9 +61,10 @@ export function PostCard({
73
61
  )}
74
62
  {highlight === "pinned" && <Pin size={14} />}
75
63
  </div>
76
- <p className="mt-1 text-sm whitespace-pre-wrap">
77
- {content}
78
- </p>
64
+ <div
65
+ className="mt-1 text-sm [&_p]:mb-1 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5 [&_a]:text-primary [&_a]:underline"
66
+ dangerouslySetInnerHTML={{ __html: content }}
67
+ />
79
68
  {actions && (
80
69
  <>
81
70
  <Separator className="my-2" />
@@ -88,4 +77,4 @@ export function PostCard({
88
77
  </div>
89
78
  </div>
90
79
  );
91
- }
80
+ });
@@ -59,6 +59,7 @@ export function UserAvatar({
59
59
  className="size-full object-cover"
60
60
  src={avatarUrl}
61
61
  alt={displayName}
62
+ loading="lazy"
62
63
  onError={() => setImgError(true)}
63
64
  />
64
65
  ) : (
@@ -123,3 +123,16 @@
123
123
  .flashcard-back-content p {
124
124
  margin: 0.25em 0;
125
125
  }
126
+
127
+ /* Tiptap rich text editor */
128
+ .tiptap:focus {
129
+ outline: none;
130
+ }
131
+
132
+ .tiptap p.is-editor-empty:first-child::before {
133
+ color: var(--muted-foreground);
134
+ content: attr(data-placeholder);
135
+ float: left;
136
+ height: 0;
137
+ pointer-events: none;
138
+ }