@nocobase/portal-template-default 1.0.4 → 1.0.6

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 (139) hide show
  1. package/README.MD +77 -0
  2. package/package.json +23 -17
  3. package/registry/nocobase-acl/demo/prompt-generator.tsx +6 -39
  4. package/registry/nocobase-acl/demo/role-switcher-prompt-generator.tsx +6 -42
  5. package/registry/nocobase-ai/demo/page-context-prompt-generator.tsx +1 -1
  6. package/registry/nocobase-ai/demo/prompt-card.tsx +7 -28
  7. package/registry/nocobase-ai/demo/prompt-generator.tsx +9 -30
  8. package/registry/nocobase-ai/demo/shortcut.tsx +1 -1
  9. package/registry/nocobase-ai/demo/tool-cards.tsx +10 -30
  10. package/registry/nocobase-auth-cas/README.md +6 -0
  11. package/registry/nocobase-auth-cas/cas-sign-in-button.tsx +41 -0
  12. package/registry/nocobase-auth-cas/demo.tsx +20 -0
  13. package/registry/nocobase-auth-cas/extension.tsx +46 -0
  14. package/registry/nocobase-auth-cas/index.ts +1 -0
  15. package/registry/nocobase-auth-cas/use-cas-sign-in.ts +29 -0
  16. package/registry/nocobase-auth-dingtalk/README.md +7 -0
  17. package/registry/nocobase-auth-dingtalk/auto-login-provider.tsx +154 -0
  18. package/registry/nocobase-auth-dingtalk/demo.tsx +23 -0
  19. package/registry/nocobase-auth-dingtalk/dingtalk-sign-in-button.tsx +45 -0
  20. package/registry/nocobase-auth-dingtalk/extension.tsx +55 -0
  21. package/registry/nocobase-auth-dingtalk/index.ts +4 -0
  22. package/registry/nocobase-auth-dingtalk/use-dingtalk-sign-in.ts +64 -0
  23. package/registry/nocobase-auth-ldap/README.md +6 -0
  24. package/registry/nocobase-auth-ldap/demo.tsx +21 -0
  25. package/registry/nocobase-auth-ldap/extension.tsx +46 -0
  26. package/registry/nocobase-auth-ldap/index.ts +1 -0
  27. package/registry/nocobase-auth-ldap/ldap-sign-in-form.tsx +69 -0
  28. package/registry/nocobase-auth-ldap/use-ldap-sign-in.ts +5 -0
  29. package/registry/nocobase-auth-oidc/README.md +11 -0
  30. package/registry/nocobase-auth-oidc/auto-redirect-provider.tsx +13 -0
  31. package/registry/nocobase-auth-oidc/demo.tsx +20 -0
  32. package/registry/nocobase-auth-oidc/extension.tsx +51 -0
  33. package/registry/nocobase-auth-oidc/index.ts +1 -0
  34. package/registry/nocobase-auth-oidc/oidc-sign-in-button.tsx +44 -0
  35. package/registry/nocobase-auth-oidc/use-oidc-sign-in.ts +58 -0
  36. package/registry/nocobase-auth-saml/README.md +6 -0
  37. package/registry/nocobase-auth-saml/auto-redirect-provider.tsx +13 -0
  38. package/registry/nocobase-auth-saml/demo.tsx +20 -0
  39. package/registry/nocobase-auth-saml/extension.tsx +51 -0
  40. package/registry/nocobase-auth-saml/index.ts +1 -0
  41. package/registry/nocobase-auth-saml/saml-sign-in-button.tsx +43 -0
  42. package/registry/nocobase-auth-saml/use-saml-sign-in.ts +51 -0
  43. package/registry/nocobase-auth-sms/README.md +7 -0
  44. package/registry/nocobase-auth-sms/demo.tsx +25 -0
  45. package/registry/nocobase-auth-sms/extension.tsx +46 -0
  46. package/registry/nocobase-auth-sms/index.ts +1 -0
  47. package/registry/nocobase-auth-sms/sms-sign-in-form.tsx +107 -0
  48. package/registry/nocobase-auth-sms/use-sms-sign-in.ts +88 -0
  49. package/registry/nocobase-auth-wecom/README.md +6 -0
  50. package/registry/nocobase-auth-wecom/auto-login-provider.tsx +21 -0
  51. package/registry/nocobase-auth-wecom/demo.tsx +23 -0
  52. package/registry/nocobase-auth-wecom/extension.tsx +49 -0
  53. package/registry/nocobase-auth-wecom/index.ts +1 -0
  54. package/registry/nocobase-auth-wecom/use-wecom-sign-in.ts +62 -0
  55. package/registry/nocobase-auth-wecom/wecom-sign-in-button.tsx +70 -0
  56. package/registry/nocobase-i18n/demo/prompt-generator.tsx +30 -54
  57. package/registry/nocobase-route-surfaces/demo/prompt-generator.tsx +18 -21
  58. package/registry.config.json +112 -0
  59. package/scripts/build-html.mjs +106 -0
  60. package/src/App.tsx +34 -8
  61. package/src/app/extension.ts +4 -0
  62. package/src/app/extensions.tsx +24 -1
  63. package/src/components/auth/authenticator-adapters.ts +20 -0
  64. package/src/components/auth/auto-redirect-provider.tsx +84 -0
  65. package/src/components/auth/basic-sign-in-form.tsx +101 -0
  66. package/src/components/auth/default-sign-in-page.tsx +20 -0
  67. package/src/components/auth/demo/auth-demo-page.tsx +236 -0
  68. package/src/components/auth/demo/auth-demo-prompt-generator.tsx +93 -0
  69. package/src/components/auth/demo/auth-demo-route.tsx +11 -0
  70. package/src/components/auth/demo/auth-method-demo.tsx +48 -0
  71. package/src/components/auth/demo/index.ts +4 -0
  72. package/src/components/auth/dynamic-sign-in.tsx +191 -0
  73. package/src/components/auth/forgot-password-form.tsx +13 -5
  74. package/src/components/auth/index.ts +13 -0
  75. package/src/components/auth/sign-in-form.tsx +1 -111
  76. package/src/components/auth/sign-up-form.tsx +14 -37
  77. package/src/components/auth/types.ts +38 -0
  78. package/src/components/auth/use-authenticator-sign-in.ts +14 -0
  79. package/src/components/auth/use-public-authenticators.ts +26 -0
  80. package/src/components/data-table/data-table-filter.tsx +107 -153
  81. package/src/components/demo/prompt-output.tsx +71 -0
  82. package/src/components/ui/alert-dialog.tsx +2 -0
  83. package/src/components/ui/attachment.tsx +207 -0
  84. package/src/components/ui/bubble.tsx +128 -0
  85. package/src/components/ui/button-group.tsx +87 -0
  86. package/src/components/ui/button.tsx +6 -6
  87. package/src/components/ui/calendar.tsx +0 -2
  88. package/src/components/ui/card.tsx +1 -1
  89. package/src/components/ui/carousel.tsx +2 -0
  90. package/src/components/ui/combobox.tsx +297 -0
  91. package/src/components/ui/command.tsx +43 -41
  92. package/src/components/ui/dialog.tsx +0 -2
  93. package/src/components/ui/direction.tsx +4 -0
  94. package/src/components/ui/drawer.tsx +2 -0
  95. package/src/components/ui/dropdown-menu.tsx +0 -2
  96. package/src/components/ui/empty.tsx +104 -0
  97. package/src/components/ui/field.tsx +238 -0
  98. package/src/components/ui/hover-card.tsx +2 -0
  99. package/src/components/ui/input-group.tsx +0 -2
  100. package/src/components/ui/input-otp.tsx +0 -2
  101. package/src/components/ui/input.tsx +1 -1
  102. package/src/components/ui/item.tsx +201 -0
  103. package/src/components/ui/kbd.tsx +26 -0
  104. package/src/components/ui/label.tsx +2 -0
  105. package/src/components/ui/marker.tsx +71 -0
  106. package/src/components/ui/message-scroller.tsx +131 -0
  107. package/src/components/ui/message.tsx +92 -0
  108. package/src/components/ui/native-select.tsx +61 -0
  109. package/src/components/ui/popover.tsx +0 -2
  110. package/src/components/ui/progress.tsx +2 -0
  111. package/src/components/ui/radio-group.tsx +0 -2
  112. package/src/components/ui/resizable.tsx +2 -0
  113. package/src/components/ui/scroll-area.tsx +0 -2
  114. package/src/components/ui/select.tsx +6 -4
  115. package/src/components/ui/separator.tsx +0 -2
  116. package/src/components/ui/sheet.tsx +21 -22
  117. package/src/components/ui/sidebar.tsx +1 -3
  118. package/src/components/ui/sonner.tsx +2 -0
  119. package/src/components/ui/spinner.tsx +10 -0
  120. package/src/components/ui/switch.tsx +0 -2
  121. package/src/components/ui/table.tsx +4 -2
  122. package/src/components/ui/tabs.tsx +0 -2
  123. package/src/components/ui/textarea.tsx +1 -1
  124. package/src/components/ui/toggle-group.tsx +2 -0
  125. package/src/components/ui/toggle.tsx +2 -0
  126. package/src/components/ui/tooltip.tsx +0 -2
  127. package/src/index.tsx +3 -0
  128. package/src/lib/nocobase/client.ts +32 -2
  129. package/src/lib/utils.ts +2 -1
  130. package/src/locales/en-US.ts +4 -2
  131. package/src/locales/zh-CN.ts +4 -2
  132. package/src/pages/login/index.tsx +2 -2
  133. package/src/pages/users/list.tsx +144 -16
  134. package/src/providers/auth-callback.ts +21 -0
  135. package/src/providers/auth.ts +108 -22
  136. package/src/providers/constants.ts +4 -1
  137. package/src/providers/runtime-config.ts +19 -0
  138. package/src/vite-env.d.ts +5 -0
  139. package/vite.config.ts +55 -2
@@ -1,14 +1,13 @@
1
1
  "use client";
2
2
 
3
- import { useState, useEffect, useMemo } from "react";
3
+ import { lazy, Suspense, useState, useEffect, useMemo } from "react";
4
4
  import { useTranslate, type CrudOperators } from "@refinedev/core";
5
5
  import type { Column, Table as ReactTable } from "@tanstack/react-table";
6
6
  import type { DateRange } from "react-day-picker";
7
- import { Check, ChevronsUpDown, ListFilter, X } from "lucide-react";
7
+ import { ChevronsUpDown, ListFilter, X } from "lucide-react";
8
8
 
9
9
  import { Button } from "@/components/ui/button";
10
10
  import { Input } from "@/components/ui/input";
11
- import { Badge } from "@/components/ui/badge";
12
11
  import {
13
12
  Popover,
14
13
  PopoverContent,
@@ -22,9 +21,15 @@ import {
22
21
  CommandList,
23
22
  } from "@/components/ui/command";
24
23
  import { Separator } from "@/components/ui/separator";
25
- import { Calendar } from "@/components/ui/calendar";
24
+ import { LoadingState } from "@/components/app-shell/loading-state";
26
25
  import { cn } from "@/lib/utils";
27
26
 
27
+ const Calendar = lazy(() =>
28
+ import("@/components/ui/calendar").then((module) => ({
29
+ default: module.Calendar,
30
+ }))
31
+ );
32
+
28
33
  export type DataTableFilterDropdownProps<TData> = {
29
34
  column: Column<TData>;
30
35
  contentClassName?: string;
@@ -54,22 +59,29 @@ export function DataTableFilterDropdown<TData>({
54
59
  variant="ghost"
55
60
  size="icon"
56
61
  className={cn(
57
- "data-[state=open]:bg-accent",
58
62
  "w-5 h-5",
59
63
  {
60
- "text-primary": isFiltered,
61
- "text-muted-foreground": !isFiltered,
64
+ "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90 data-[state=open]:bg-primary/90 data-[state=open]:text-primary-foreground":
65
+ isFiltered,
66
+ "text-muted-foreground data-[state=open]:bg-accent":
67
+ !isFiltered,
62
68
  },
63
69
  triggerClassName
64
70
  )}
65
71
  />
66
72
  }
67
73
  >
68
- <ListFilter className={cn("!h-3", "!w-3")} />
74
+ <ListFilter
75
+ className={cn("!h-3", "!w-3", isFiltered && "stroke-[2.5]")}
76
+ />
69
77
  </PopoverTrigger>
70
78
  <PopoverContent
71
79
  align="start"
72
- className={cn("w-full", "shadow-sm", contentClassName)}
80
+ className={cn(
81
+ "w-80 max-w-[calc(100vw-2rem)]",
82
+ "shadow-sm",
83
+ contentClassName
84
+ )}
73
85
  >
74
86
  {children({ isOpen, setIsOpen })}
75
87
  </PopoverContent>
@@ -287,12 +299,10 @@ export function DataTableFilterCombobox<TData>({
287
299
  }
288
300
  };
289
301
 
290
- const handleRemove = (optionValue: string) => {
291
- if (multiple) {
292
- const newValues = currentValues.filter((v) => v !== optionValue);
293
- onChange(newValues);
294
- }
295
- };
302
+ const selectedLabels = currentValues.map((value) => {
303
+ const option = options.find((item) => item.value === value);
304
+ return option?.label ?? value;
305
+ });
296
306
 
297
307
  const getDisplayText = () => {
298
308
  if (currentValues.length === 0) {
@@ -302,7 +312,7 @@ export function DataTableFilterCombobox<TData>({
302
312
  }
303
313
 
304
314
  if (multiple) {
305
- return `${currentValues.length} selected`;
315
+ return selectedLabels.join(", ");
306
316
  }
307
317
 
308
318
  const selectedOption = options.find(
@@ -311,13 +321,6 @@ export function DataTableFilterCombobox<TData>({
311
321
  return selectedOption ? selectedOption.label : currentValues[0];
312
322
  };
313
323
 
314
- const getSelectedLabels = () => {
315
- return currentValues.map((val) => {
316
- const option = options.find((opt) => opt.value === val);
317
- return { label: option ? option.label : val, value: val };
318
- });
319
- };
320
-
321
324
  return (
322
325
  <Popover open={isOpen} onOpenChange={setIsOpen}>
323
326
  <PopoverTrigger
@@ -328,93 +331,37 @@ export function DataTableFilterCombobox<TData>({
328
331
  aria-expanded={isOpen}
329
332
  className={cn(
330
333
  "w-full",
331
- "min-w-48",
332
- "max-w-80",
334
+ "min-w-0",
333
335
  "justify-start",
334
- "h-auto",
335
- "min-h-9"
336
+ "h-8"
336
337
  )}
337
338
  />
338
339
  }
339
340
  >
340
- <div className={cn("flex", "gap-2", "w-full")}>
341
- {multiple && currentValues.length > 0 ? (
342
- <div className={cn("flex", "flex-wrap", "gap-1", "flex-1")}>
343
- {getSelectedLabels()
344
- .slice(0, 3)
345
- .map(({ label, value: val }) => (
346
- <Badge
347
- key={val}
348
- variant="outline"
349
- className={cn(
350
- "inline-flex",
351
- "items-center",
352
- "gap-0",
353
- "h-4",
354
- "pr-0.5",
355
- "rounded-sm"
356
- )}
357
- >
358
- <span className={cn("text-[10px]", "leading-4")}>
359
- {label}
360
- </span>
361
- <span
362
- className={cn(
363
- "inline-flex",
364
- "items-center",
365
- "justify-center",
366
- "p-0",
367
- "w-4",
368
- "h-full",
369
- "text-muted-foreground",
370
- "hover:text-destructive",
371
- "rounded-sm",
372
- "cursor-pointer",
373
- "transition-colors"
374
- )}
375
- onClick={(e) => {
376
- e.preventDefault();
377
- e.stopPropagation();
378
- handleRemove(val);
379
- }}
380
- >
381
- <X className={cn("!h-2", "!w-2")} />
382
- </span>
383
- </Badge>
384
- ))}
385
- {currentValues.length > 3 && (
386
- <span
387
- className={cn(
388
- "text-xs",
389
- "text-muted-foreground",
390
- "px-1"
391
- )}
392
- >
393
- +{currentValues.length - 3} more
394
- </span>
395
- )}
396
- </div>
397
- ) : (
398
- <span
399
- className={cn(
400
- "truncate",
401
- "flex-1",
402
- "text-start",
403
- "text-xs",
404
- currentValues.length === 0 && "text-muted-foreground"
405
- )}
406
- >
407
- {getDisplayText()}
408
- </span>
341
+ <div className={cn("flex", "min-w-0", "w-full", "gap-2")}>
342
+ <span
343
+ className={cn(
344
+ "min-w-0",
345
+ "truncate",
346
+ "flex-1",
347
+ "text-start",
348
+ "text-xs",
349
+ currentValues.length === 0 && "text-muted-foreground"
409
350
  )}
351
+ >
352
+ {getDisplayText()}
353
+ </span>
410
354
 
411
- <ChevronsUpDown
412
- className={cn("h-4", "w-4", "shrink-0", "opacity-50")}
413
- />
414
- </div>
355
+ <ChevronsUpDown
356
+ className={cn("h-4", "w-4", "shrink-0", "opacity-50")}
357
+ />
358
+ </div>
415
359
  </PopoverTrigger>
416
- <PopoverContent className={cn("w-[200px]", "p-0")} align="start">
417
- <Command items={options}>
360
+ <PopoverContent
361
+ className="w-(--anchor-width) min-w-56 p-0"
362
+ align="start"
363
+ >
364
+ <Command>
418
365
  <CommandInput
419
366
  placeholder={t("table.filter.combobox.search", "Search...")}
420
367
  />
@@ -426,25 +373,16 @@ export function DataTableFilterCombobox<TData>({
426
373
  )}
427
374
  </CommandEmpty>
428
375
  <CommandList>
429
- {(option: { label: string; value: string }) => (
376
+ {options.map((option) => (
430
377
  <CommandItem
431
378
  key={option.value}
432
- value={option}
433
- onClick={() => handleSelect(option.value)}
379
+ value={`${option.label} ${option.value}`}
380
+ data-checked={currentValues.includes(option.value)}
381
+ onSelect={() => handleSelect(option.value)}
434
382
  >
435
383
  {option.label}
436
- <Check
437
- className={cn(
438
- "ml-auto",
439
- "h-4",
440
- "w-4",
441
- currentValues.includes(option.value)
442
- ? "opacity-100"
443
- : "opacity-0"
444
- )}
445
- />
446
384
  </CommandItem>
447
- )}
385
+ ))}
448
386
  </CommandList>
449
387
  </Command>
450
388
  </PopoverContent>
@@ -518,13 +456,15 @@ export function DataTableFilterDropdownDateSinglePicker<TData>({
518
456
  }
519
457
  }}
520
458
  >
521
- <Calendar
522
- mode="single"
523
- selected={filterValue}
524
- onSelect={(date) => {
525
- setFilterValue(date);
526
- }}
527
- />
459
+ <Suspense fallback={<LoadingState className="min-h-72" />}>
460
+ <Calendar
461
+ mode="single"
462
+ selected={filterValue}
463
+ onSelect={(date) => {
464
+ setFilterValue(date);
465
+ }}
466
+ />
467
+ </Suspense>
528
468
 
529
469
  <div className={cn("w-full")}>
530
470
  <Separator />
@@ -626,17 +566,19 @@ export function DataTableFilterDropdownDateRangePicker<TData>({
626
566
  }
627
567
  }}
628
568
  >
629
- <Calendar
630
- mode="range"
631
- numberOfMonths={2}
632
- selected={filterValue}
633
- onSelect={(date) => {
634
- setFilterValue({
635
- from: date?.from,
636
- to: date?.to,
637
- });
638
- }}
639
- />
569
+ <Suspense fallback={<LoadingState className="min-h-72" />}>
570
+ <Calendar
571
+ mode="range"
572
+ numberOfMonths={2}
573
+ selected={filterValue}
574
+ onSelect={(date) => {
575
+ setFilterValue({
576
+ from: date?.from,
577
+ to: date?.to,
578
+ });
579
+ }}
580
+ />
581
+ </Suspense>
640
582
 
641
583
  <div className={cn("w-full")}>
642
584
  <Separator />
@@ -680,6 +622,9 @@ export function DataTableFilterInput<TData>({
680
622
  defaultOperator: defaultOperatorFromProps,
681
623
  renderInput,
682
624
  }: DataTableFilterInputProps<TData>) {
625
+ const hasOperatorSelect = Boolean(
626
+ operatorsFromProps && operatorsFromProps.length > 1
627
+ );
683
628
  const [filterValue, setFilterValue] = useState(
684
629
  (columnFromProps.getFilterValue() as string | string[]) || ""
685
630
  );
@@ -720,7 +665,14 @@ export function DataTableFilterInput<TData>({
720
665
  };
721
666
 
722
667
  return (
723
- <DataTableFilterDropdown column={columnFromProps}>
668
+ <DataTableFilterDropdown
669
+ column={columnFromProps}
670
+ contentClassName={
671
+ hasOperatorSelect
672
+ ? "w-[30rem] max-w-[calc(100vw-2rem)]"
673
+ : undefined
674
+ }
675
+ >
724
676
  {({ setIsOpen }) => {
725
677
  return (
726
678
  <div
@@ -739,9 +691,15 @@ export function DataTableFilterInput<TData>({
739
691
  }}
740
692
  >
741
693
  <div
742
- className={cn("grid", "grid-cols-1", "md:grid-cols-2", "gap-2")}
694
+ className={cn(
695
+ "grid",
696
+ "grid-cols-1",
697
+ "w-full",
698
+ "gap-2",
699
+ hasOperatorSelect && "md:grid-cols-2"
700
+ )}
743
701
  >
744
- {operatorsFromProps && operatorsFromProps.length > 1 && (
702
+ {hasOperatorSelect && (
745
703
  <DataTableFilterOperatorSelect
746
704
  value={operator}
747
705
  operators={operatorsFromProps}
@@ -791,11 +749,11 @@ const CRUD_OPERATOR_LABELS: Record<
791
749
  },
792
750
  in: {
793
751
  i18nKey: "table.filter.operator.in",
794
- defaultLabel: "Includes in an array",
752
+ defaultLabel: "Includes",
795
753
  },
796
754
  nin: {
797
755
  i18nKey: "table.filter.operator.nin",
798
- defaultLabel: "Not includes in an array",
756
+ defaultLabel: "Excludes",
799
757
  },
800
758
  ina: {
801
759
  i18nKey: "table.filter.operator.ina",
@@ -948,31 +906,27 @@ export function DataTableFilterOperatorSelect({
948
906
  className={cn("ml-2", "h-4", "w-4", "shrink-0", "opacity-50")}
949
907
  />
950
908
  </PopoverTrigger>
951
- <PopoverContent className={cn("p-0", contentClassName)} keepMounted>
952
- <Command items={operatorOptions}>
909
+ <PopoverContent
910
+ className={cn("w-(--anchor-width) p-0", contentClassName)}
911
+ keepMounted
912
+ >
913
+ <Command>
953
914
  <CommandInput placeholder={placeholderText} />
954
915
  <CommandEmpty>{noResultsText}</CommandEmpty>
955
916
  <CommandList>
956
- {(option: { value: CrudOperators; label: string }) => (
917
+ {operatorOptions.map((option) => (
957
918
  <CommandItem
958
919
  key={option.value}
959
- value={option}
960
- onClick={() => {
920
+ value={`${option.label} ${option.value}`}
921
+ data-checked={value === option.value}
922
+ onSelect={() => {
961
923
  onValueChange(option.value);
962
924
  setOpen(false);
963
925
  }}
964
926
  >
965
- <Check
966
- className={cn(
967
- "mr-2",
968
- "h-4",
969
- "w-4",
970
- value === option.value ? "opacity-100" : "opacity-0"
971
- )}
972
- />
973
927
  {option.label}
974
928
  </CommandItem>
975
- )}
929
+ ))}
976
930
  </CommandList>
977
931
  </Command>
978
932
  </PopoverContent>
@@ -0,0 +1,71 @@
1
+ import { useState, type ReactNode } from "react";
2
+ import { Check, Copy } from "lucide-react";
3
+
4
+ import { Button } from "@/components/ui/button";
5
+ import { Card } from "@/components/ui/card";
6
+ import { cn } from "@/lib/utils";
7
+
8
+ export function PromptOutput({
9
+ className,
10
+ copiedLabel = "Copied",
11
+ copyErrorLabel = "Clipboard access failed. Select the prompt and copy it manually.",
12
+ copyLabel = "Copy prompt",
13
+ description,
14
+ prompt,
15
+ promptClassName,
16
+ title = "Generated prompt",
17
+ }: {
18
+ className?: string;
19
+ copiedLabel?: ReactNode;
20
+ copyErrorLabel?: ReactNode;
21
+ copyLabel?: ReactNode;
22
+ description: ReactNode;
23
+ prompt: string;
24
+ promptClassName?: string;
25
+ title?: ReactNode;
26
+ }) {
27
+ const [copied, setCopied] = useState(false);
28
+ const [copyError, setCopyError] = useState(false);
29
+
30
+ return (
31
+ <Card className={cn("min-w-0 gap-0 overflow-hidden py-0", className)}>
32
+ <div className="flex items-center justify-between gap-3 border-b px-4 py-3">
33
+ <div className="min-w-0">
34
+ <div className="text-sm font-medium">{title}</div>
35
+ <div className="text-xs text-muted-foreground">{description}</div>
36
+ </div>
37
+ <Button
38
+ type="button"
39
+ size="sm"
40
+ className="shrink-0"
41
+ onClick={async () => {
42
+ try {
43
+ await navigator.clipboard.writeText(prompt);
44
+ setCopyError(false);
45
+ setCopied(true);
46
+ window.setTimeout(() => setCopied(false), 1500);
47
+ } catch {
48
+ setCopyError(true);
49
+ }
50
+ }}
51
+ >
52
+ {copied ? <Check /> : <Copy />}
53
+ {copied ? copiedLabel : copyLabel}
54
+ </Button>
55
+ </div>
56
+ {copyError ? (
57
+ <p className="border-b px-4 py-2 text-xs text-destructive">
58
+ {copyErrorLabel}
59
+ </p>
60
+ ) : null}
61
+ <pre
62
+ className={cn(
63
+ "max-h-[560px] overflow-auto whitespace-pre-wrap bg-muted/25 p-5 font-mono text-xs leading-5 text-muted-foreground",
64
+ promptClassName
65
+ )}
66
+ >
67
+ {prompt}
68
+ </pre>
69
+ </Card>
70
+ );
71
+ }
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"
3
5
 
@@ -0,0 +1,207 @@
1
+ import * as React from "react"
2
+ import { mergeProps } from "@base-ui/react/merge-props"
3
+ import { useRender } from "@base-ui/react/use-render"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+
6
+ import { cn } from "@/lib/utils"
7
+ import { Button } from "@/components/ui/button"
8
+
9
+ const attachmentVariants = cva(
10
+ "group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed",
11
+ {
12
+ variants: {
13
+ size: {
14
+ default:
15
+ "gap-2 text-sm has-data-[slot=attachment-content]:px-2.5 has-data-[slot=attachment-content]:py-2 has-data-[slot=attachment-media]:p-2",
16
+ sm: "gap-2.5 text-xs has-data-[slot=attachment-content]:px-2 has-data-[slot=attachment-content]:py-1.5 has-data-[slot=attachment-media]:p-1.5",
17
+ xs: "gap-1.5 rounded-lg text-xs has-data-[slot=attachment-content]:px-1.5 has-data-[slot=attachment-content]:py-1 has-data-[slot=attachment-media]:p-1",
18
+ },
19
+ orientation: {
20
+ horizontal: "min-w-40 items-center",
21
+ vertical: "w-24 flex-col has-data-[slot=attachment-content]:w-30",
22
+ },
23
+ },
24
+ }
25
+ )
26
+
27
+ function Attachment({
28
+ className,
29
+ state = "done",
30
+ size = "default",
31
+ orientation = "horizontal",
32
+ ...props
33
+ }: React.ComponentProps<"div"> &
34
+ VariantProps<typeof attachmentVariants> & {
35
+ state?: "idle" | "uploading" | "processing" | "error" | "done"
36
+ }) {
37
+ return (
38
+ <div
39
+ data-slot="attachment"
40
+ data-state={state}
41
+ data-size={size}
42
+ data-orientation={orientation}
43
+ className={cn(attachmentVariants({ size, orientation }), className)}
44
+ {...props}
45
+ />
46
+ )
47
+ }
48
+
49
+ const attachmentMediaVariants = cva(
50
+ "relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted text-foreground group-data-[orientation=vertical]/attachment:w-full group-data-[size=sm]/attachment:w-8 group-data-[size=xs]/attachment:w-7 group-data-[size=xs]/attachment:rounded-md group-data-[state=error]/attachment:bg-destructive/10 group-data-[state=error]/attachment:text-destructive group-data-[orientation=vertical]/attachment:*:data-[slot=spinner]:size-6! [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 group-data-[orientation=vertical]/attachment:[&_svg:not([class*='size-'])]:size-6 group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5",
51
+ {
52
+ variants: {
53
+ variant: {
54
+ icon: "",
55
+ image:
56
+ "opacity-60 group-data-[state=done]/attachment:opacity-100 group-data-[state=idle]/attachment:opacity-100 *:[img]:aspect-square *:[img]:w-full *:[img]:object-cover",
57
+ },
58
+ },
59
+ defaultVariants: {
60
+ variant: "icon",
61
+ },
62
+ }
63
+ )
64
+
65
+ function AttachmentMedia({
66
+ className,
67
+ variant = "icon",
68
+ ...props
69
+ }: React.ComponentProps<"div"> & VariantProps<typeof attachmentMediaVariants>) {
70
+ return (
71
+ <div
72
+ data-slot="attachment-media"
73
+ data-variant={variant}
74
+ className={cn(attachmentMediaVariants({ variant }), className)}
75
+ {...props}
76
+ />
77
+ )
78
+ }
79
+
80
+ function AttachmentContent({
81
+ className,
82
+ ...props
83
+ }: React.ComponentProps<"div">) {
84
+ return (
85
+ <div
86
+ data-slot="attachment-content"
87
+ className={cn(
88
+ "max-w-full min-w-0 flex-1 leading-tight group-data-[orientation=vertical]/attachment:px-1",
89
+ className
90
+ )}
91
+ {...props}
92
+ />
93
+ )
94
+ }
95
+
96
+ function AttachmentTitle({
97
+ className,
98
+ ...props
99
+ }: React.ComponentProps<"span">) {
100
+ return (
101
+ <span
102
+ data-slot="attachment-title"
103
+ className={cn(
104
+ "block max-w-full min-w-0 truncate font-medium group-data-[state=processing]/attachment:shimmer group-data-[state=uploading]/attachment:shimmer",
105
+ className
106
+ )}
107
+ {...props}
108
+ />
109
+ )
110
+ }
111
+
112
+ function AttachmentDescription({
113
+ className,
114
+ ...props
115
+ }: React.ComponentProps<"span">) {
116
+ return (
117
+ <span
118
+ data-slot="attachment-description"
119
+ className={cn(
120
+ "mt-0.5 block min-w-0 truncate text-xs text-muted-foreground group-data-[state=error]/attachment:text-destructive/80",
121
+ "max-w-full",
122
+ className
123
+ )}
124
+ {...props}
125
+ />
126
+ )
127
+ }
128
+
129
+ function AttachmentActions({
130
+ className,
131
+ ...props
132
+ }: React.ComponentProps<"div">) {
133
+ return (
134
+ <div
135
+ data-slot="attachment-actions"
136
+ className={cn(
137
+ "relative z-20 flex shrink-0 items-center group-data-[orientation=vertical]/attachment:absolute group-data-[orientation=vertical]/attachment:top-3 group-data-[orientation=vertical]/attachment:right-3 group-data-[orientation=vertical]/attachment:gap-1",
138
+ className
139
+ )}
140
+ {...props}
141
+ />
142
+ )
143
+ }
144
+
145
+ function AttachmentAction({
146
+ className,
147
+ variant,
148
+ size = "icon-xs",
149
+ ...props
150
+ }: React.ComponentProps<typeof Button>) {
151
+ return (
152
+ <Button
153
+ data-slot="attachment-action"
154
+ variant={variant ?? "ghost"}
155
+ size={size}
156
+ className={cn(className)}
157
+ {...props}
158
+ />
159
+ )
160
+ }
161
+
162
+ function AttachmentTrigger({
163
+ className,
164
+ render,
165
+ type,
166
+ ...props
167
+ }: useRender.ComponentProps<"button">) {
168
+ return useRender({
169
+ defaultTagName: "button",
170
+ props: mergeProps<"button">(
171
+ {
172
+ type: render ? type : (type ?? "button"),
173
+ className: cn("absolute inset-0 z-10 outline-none", className),
174
+ },
175
+ props
176
+ ),
177
+ render,
178
+ state: {
179
+ slot: "attachment-trigger",
180
+ },
181
+ })
182
+ }
183
+
184
+ function AttachmentGroup({ className, ...props }: React.ComponentProps<"div">) {
185
+ return (
186
+ <div
187
+ data-slot="attachment-group"
188
+ className={cn(
189
+ "flex min-w-0 scroll-fade-x snap-x snap-mandatory scroll-px-1 scrollbar-none gap-3 overflow-x-auto overscroll-x-contain py-1 *:data-[slot=attachment]:flex-none *:data-[slot=attachment]:snap-start",
190
+ className
191
+ )}
192
+ {...props}
193
+ />
194
+ )
195
+ }
196
+
197
+ export {
198
+ Attachment,
199
+ AttachmentGroup,
200
+ AttachmentMedia,
201
+ AttachmentContent,
202
+ AttachmentTitle,
203
+ AttachmentDescription,
204
+ AttachmentActions,
205
+ AttachmentAction,
206
+ AttachmentTrigger,
207
+ }