@interactive-inc/docs-client 0.0.1 → 0.0.2

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 (87) hide show
  1. package/build/index.html +13 -0
  2. package/package.json +5 -2
  3. package/.tanstack/tmp/router-generator-Eq20Yx/50b11a0f488e61cf92364a7e585c1448 +0 -123
  4. package/.tanstack/tmp/router-generator-KQIYSa/aecb094e4891f9475b91de590abc8809 +0 -117
  5. package/.tanstack/tmp/router-generator-ekW2pI/aecb094e4891f9475b91de590abc8809 +0 -117
  6. package/app/components/archived-file-list-view.tsx +0 -99
  7. package/app/components/directory-file-list-view.tsx +0 -32
  8. package/app/components/directory-file-tree-node.tsx +0 -96
  9. package/app/components/directory-layout-sidebar.tsx +0 -181
  10. package/app/components/directory-page-view.tsx +0 -208
  11. package/app/components/directory-table-view.tsx +0 -383
  12. package/app/components/editable-table-cell/boolean-editable-cell.tsx +0 -44
  13. package/app/components/editable-table-cell/multi-boolean-editable-cell.tsx +0 -59
  14. package/app/components/editable-table-cell/multi-number-editable-cell.tsx +0 -62
  15. package/app/components/editable-table-cell/multi-relation-editable-cell.tsx +0 -19
  16. package/app/components/editable-table-cell/multi-select-number-editable-cell.tsx +0 -82
  17. package/app/components/editable-table-cell/multi-select-text-editable-cell.tsx +0 -82
  18. package/app/components/editable-table-cell/multi-text-editable-cell.tsx +0 -62
  19. package/app/components/editable-table-cell/number-editable-cell.tsx +0 -54
  20. package/app/components/editable-table-cell/relation-editable-cell.tsx +0 -20
  21. package/app/components/editable-table-cell/select-number-editable-cell.tsx +0 -43
  22. package/app/components/editable-table-cell/select-text-editable-cell.tsx +0 -33
  23. package/app/components/editable-table-cell/string-editable-cell.tsx +0 -42
  24. package/app/components/editable-table-cell.tsx +0 -129
  25. package/app/components/file-card.tsx +0 -120
  26. package/app/components/file-tree-icon.tsx +0 -26
  27. package/app/components/file-view/archive-restore-banner.tsx +0 -86
  28. package/app/components/file-view/csv-file-view.tsx +0 -50
  29. package/app/components/file-view/default-file-view.tsx +0 -11
  30. package/app/components/file-view/editable-csv-cell.tsx +0 -37
  31. package/app/components/file-view/editable-front-matter-view.tsx +0 -105
  32. package/app/components/file-view/file-csv-columns-view.tsx +0 -181
  33. package/app/components/file-view/file-csv-table.tsx +0 -142
  34. package/app/components/file-view/file-header.tsx +0 -115
  35. package/app/components/file-view/file-page-view.tsx +0 -165
  36. package/app/components/file-view/front-matter-input-field.tsx +0 -157
  37. package/app/components/file-view/front-matter-view.tsx +0 -30
  38. package/app/components/file-view/json-file-editor.tsx +0 -38
  39. package/app/components/file-view/markdown-file-view.tsx +0 -71
  40. package/app/components/file-view/multi-relation-select.tsx +0 -129
  41. package/app/components/file-view/render-front-matter-value.tsx +0 -57
  42. package/app/components/file-view/single-relation-select.tsx +0 -41
  43. package/app/components/file-view/sortable-csv-row.tsx +0 -57
  44. package/app/components/file-view/sortable-header.tsx +0 -30
  45. package/app/components/file-view/utils.ts +0 -8
  46. package/app/components/milestones-editor.tsx +0 -95
  47. package/app/components/page-header.tsx +0 -20
  48. package/app/components/project-view/feature-item.tsx +0 -139
  49. package/app/components/project-view/project-page-group.tsx +0 -125
  50. package/app/components/project-view/project-view.tsx +0 -230
  51. package/app/components/project-view/unlinked-features-section.tsx +0 -73
  52. package/app/components/providers.tsx +0 -21
  53. package/app/components/reload-button.tsx +0 -25
  54. package/app/components/sidebar-button.tsx +0 -13
  55. package/app/components/ui/alert.tsx +0 -66
  56. package/app/components/ui/badge.tsx +0 -46
  57. package/app/components/ui/button.tsx +0 -59
  58. package/app/components/ui/card.tsx +0 -92
  59. package/app/components/ui/dialog.tsx +0 -133
  60. package/app/components/ui/emoji-picker.tsx +0 -133
  61. package/app/components/ui/icons.tsx +0 -3
  62. package/app/components/ui/input.tsx +0 -21
  63. package/app/components/ui/select.tsx +0 -183
  64. package/app/components/ui/separator.tsx +0 -26
  65. package/app/components/ui/sheet.tsx +0 -137
  66. package/app/components/ui/sidebar.tsx +0 -724
  67. package/app/components/ui/simple-sidebar.tsx +0 -50
  68. package/app/components/ui/skeleton.tsx +0 -13
  69. package/app/components/ui/sonner.tsx +0 -23
  70. package/app/components/ui/table.tsx +0 -114
  71. package/app/components/ui/tabs.tsx +0 -53
  72. package/app/components/ui/textarea.tsx +0 -18
  73. package/app/components/ui/tooltip.tsx +0 -59
  74. package/app/components/vscode-button.tsx +0 -23
  75. package/app/hooks/use-directory-query.ts +0 -22
  76. package/app/hooks/use-file-properties-mutation.ts +0 -38
  77. package/app/hooks/use-file-query.ts +0 -21
  78. package/app/hooks/use-mobile.ts +0 -19
  79. package/app/main.css +0 -122
  80. package/app/main.tsx +0 -38
  81. package/app/routes/$.index.tsx +0 -25
  82. package/app/routes/$.tsx +0 -20
  83. package/app/routes/__root.tsx +0 -20
  84. package/app/routes/_index.tsx +0 -17
  85. package/app/routes/apps.client.$.tsx +0 -21
  86. package/components.json +0 -21
  87. package/tsconfig.index.json +0 -17
@@ -1,129 +0,0 @@
1
- import { X } from "lucide-react"
2
- import { Button } from "@/components/ui/button"
3
- import {
4
- Select,
5
- SelectContent,
6
- SelectItem,
7
- SelectTrigger,
8
- SelectValue,
9
- } from "@/components/ui/select"
10
- import type { DocRelationFile } from "@/lib/types"
11
-
12
- type Props = {
13
- value: string[]
14
- onValueChange: (value: string[]) => void
15
- relationOptions?: DocRelationFile[]
16
- wrap?: boolean
17
- }
18
-
19
- /**
20
- * 複数リレーション用のSelectコンポーネント
21
- */
22
- export function MultiRelationSelect(props: Props) {
23
- const options = props.relationOptions || []
24
- const selectedValues = Array.isArray(props.value) ? props.value : []
25
-
26
- // 選択済みの値を除外した利用可能なオプション
27
- const availableOptions = options.filter(
28
- (option) => !selectedValues.includes(option.name),
29
- )
30
-
31
- const addValue = (newValue: string) => {
32
- if (newValue && !selectedValues.includes(newValue)) {
33
- const updatedValues = [...selectedValues, newValue]
34
- props.onValueChange(updatedValues)
35
- }
36
- }
37
-
38
- const removeValue = (valueToRemove: string) => {
39
- const updatedValues = selectedValues.filter((val) => val !== valueToRemove)
40
- props.onValueChange(updatedValues)
41
- }
42
-
43
- // wrapありの場合(ファイル詳細用)
44
- if (props.wrap) {
45
- return (
46
- <div className="space-y-2">
47
- {/* 新しい値を追加するSelect */}
48
- {availableOptions.length > 0 && (
49
- <Select onValueChange={addValue}>
50
- <SelectTrigger className="w-full">
51
- <SelectValue placeholder="項目を追加" />
52
- </SelectTrigger>
53
- <SelectContent>
54
- {availableOptions.map((option) => (
55
- <SelectItem key={option.name} value={option.name}>
56
- {option.label}
57
- </SelectItem>
58
- ))}
59
- </SelectContent>
60
- </Select>
61
- )}
62
- {/* 選択済みの値を表示(wrap対応) */}
63
- <div className="flex flex-wrap gap-2">
64
- {selectedValues.toReversed().map((value) => {
65
- const option = options.find((opt) => opt.name === value)
66
- const label = option?.label || value
67
- return (
68
- <Button
69
- key={value}
70
- variant={"outline"}
71
- onClick={() => removeValue(value)}
72
- className="h-auto break-words text-left"
73
- >
74
- <span className="flex-1">{label}</span>
75
- <X className="ml-2 w-4 flex-shrink-0 text-muted-foreground" />
76
- </Button>
77
- )
78
- })}
79
- </div>
80
- {selectedValues.length === 0 && availableOptions.length === 0 && (
81
- <div className="text-muted-foreground text-sm">
82
- 利用可能なオプションがありません
83
- </div>
84
- )}
85
- </div>
86
- )
87
- }
88
-
89
- // wrapなしの場合(テーブル用)
90
- return (
91
- <div className="flex gap-2">
92
- {/* 新しい値を追加するSelect */}
93
- {availableOptions.length > 0 && (
94
- <Select onValueChange={addValue}>
95
- <SelectTrigger className="min-w-24">
96
- <SelectValue placeholder="項目を追加" />
97
- </SelectTrigger>
98
- <SelectContent>
99
- {availableOptions.map((option) => (
100
- <SelectItem key={option.name} value={option.name}>
101
- {option.label}
102
- </SelectItem>
103
- ))}
104
- </SelectContent>
105
- </Select>
106
- )}
107
- {/* 選択済みの値を表示(横並び) */}
108
- {selectedValues.toReversed().map((value) => {
109
- const option = options.find((opt) => opt.name === value)
110
- const label = option?.label || value
111
- return (
112
- <Button
113
- key={value}
114
- variant={"outline"}
115
- onClick={() => removeValue(value)}
116
- >
117
- <span>{label}</span>
118
- <X className="w-4 text-muted-foreground" />
119
- </Button>
120
- )
121
- })}
122
- {selectedValues.length === 0 && availableOptions.length === 0 && (
123
- <div className="text-muted-foreground text-sm">
124
- 利用可能なオプションがありません
125
- </div>
126
- )}
127
- </div>
128
- )
129
- }
@@ -1,57 +0,0 @@
1
- type Props = {
2
- value: unknown
3
- }
4
-
5
- /**
6
- * フロントマターの値を再帰的にレンダリングする
7
- */
8
- export function RenderFrontMatterValue(props: Props) {
9
- const value = props.value
10
-
11
- if (value === null || value === undefined) {
12
- return <span>null</span>
13
- }
14
-
15
- if (typeof value === "boolean") {
16
- return <span>{value.toString()}</span>
17
- }
18
-
19
- if (typeof value === "number") {
20
- return <span>{value}</span>
21
- }
22
-
23
- if (typeof value === "string") {
24
- return <span>"{value}"</span>
25
- }
26
-
27
- if (Array.isArray(value)) {
28
- return (
29
- <div>
30
- {value.map((item, index) => (
31
- <div
32
- key={`item-${index}-${String(item).substring(0, 8)}`}
33
- className="flex"
34
- >
35
- <span className="mr-2">-</span>
36
- <RenderFrontMatterValue value={item} />
37
- </div>
38
- ))}
39
- </div>
40
- )
41
- }
42
-
43
- if (typeof value === "object") {
44
- return (
45
- <div>
46
- {Object.entries(value as Record<string, unknown>).map(([key, val]) => (
47
- <div key={key} className="flex">
48
- <span className="mr-2">{key}:</span>
49
- <RenderFrontMatterValue value={val} />
50
- </div>
51
- ))}
52
- </div>
53
- )
54
- }
55
-
56
- return <span>{String(value)}</span>
57
- }
@@ -1,41 +0,0 @@
1
- import {
2
- Select,
3
- SelectContent,
4
- SelectItem,
5
- SelectTrigger,
6
- SelectValue,
7
- } from "@/components/ui/select"
8
-
9
- import type { DocRelationFile } from "@/lib/types"
10
-
11
- type Props = {
12
- value: string
13
- onValueChange: (value: string) => void
14
- relationOptions?: DocRelationFile[]
15
- }
16
-
17
- /**
18
- * 単一リレーション用のSelectコンポーネント
19
- */
20
- export function SingleRelationSelect(props: Props) {
21
- const options = props.relationOptions || []
22
-
23
- const handleValueChange = (value: string) => {
24
- props.onValueChange(value)
25
- }
26
-
27
- return (
28
- <Select value={props.value || ""} onValueChange={handleValueChange}>
29
- <SelectTrigger className="w-full bg-background">
30
- <SelectValue placeholder="リレーションを選択" />
31
- </SelectTrigger>
32
- <SelectContent>
33
- {options.map((option) => (
34
- <SelectItem key={option.name} value={option.name}>
35
- {option.label}
36
- </SelectItem>
37
- ))}
38
- </SelectContent>
39
- </Select>
40
- )
41
- }
@@ -1,57 +0,0 @@
1
- import { useSortable } from "@dnd-kit/sortable"
2
- import { CSS } from "@dnd-kit/utilities"
3
- import { GripVertical } from "lucide-react"
4
- import { TableCell, TableRow } from "@/components/ui/table"
5
- import { EditableCsvCell } from "./editable-csv-cell"
6
-
7
- type Props = {
8
- id: string
9
- row: string[]
10
- rowIndex: number
11
- onCellUpdate: (rowIndex: number, columnIndex: number, value: string) => void
12
- }
13
-
14
- export function SortableCsvRow(props: Props) {
15
- const sortable = useSortable({
16
- id: props.id,
17
- attributes: {
18
- role: "button",
19
- tabIndex: 0,
20
- },
21
- })
22
-
23
- const style = {
24
- transform: CSS.Transform.toString(sortable.transform),
25
- opacity: sortable.isDragging ? 0.5 : 1,
26
- touchAction: "none",
27
- }
28
-
29
- function handleCellChange(
30
- rowIndex: number,
31
- columnIndex: number,
32
- value: string,
33
- ) {
34
- props.onCellUpdate(rowIndex, columnIndex, value)
35
- }
36
-
37
- return (
38
- <TableRow
39
- ref={sortable.setNodeRef}
40
- style={style}
41
- className="hover:opacity-60"
42
- >
43
- <TableCell {...sortable.attributes} {...sortable.listeners}>
44
- <GripVertical className="size-4" />
45
- </TableCell>
46
- {props.row.map((cell: string, cellIndex: number) => (
47
- <EditableCsvCell
48
- key={`cell-${props.rowIndex}-${cellIndex}-${cell}`}
49
- content={cell}
50
- rowIndex={props.rowIndex}
51
- columnIndex={cellIndex}
52
- onCellChange={handleCellChange}
53
- />
54
- ))}
55
- </TableRow>
56
- )
57
- }
@@ -1,30 +0,0 @@
1
- import { useSortable } from "@dnd-kit/sortable"
2
- import { CSS } from "@dnd-kit/utilities"
3
-
4
- type Props = {
5
- id: string
6
- index: number
7
- children: React.ReactNode
8
- }
9
-
10
- export function CsvSortableHeader({ id, index, children }: Props) {
11
- const { attributes, listeners, setNodeRef, transform, transition } =
12
- useSortable({ id })
13
-
14
- const style = {
15
- transform: CSS.Transform.toString(transform),
16
- transition,
17
- }
18
-
19
- return (
20
- <th
21
- ref={setNodeRef}
22
- style={style}
23
- {...attributes}
24
- {...listeners}
25
- className="cursor-grab border border-gray-600 p-2 text-sm"
26
- >
27
- {children}
28
- </th>
29
- )
30
- }
@@ -1,8 +0,0 @@
1
- export function generateUniqueId(
2
- prefix: string,
3
- value: string,
4
- index: number,
5
- ): string {
6
- // 単純な値とインデックスの組み合わせ
7
- return `${prefix}-${value.slice(0, 10).replace(/\s+/g, "-")}-${index}`
8
- }
@@ -1,95 +0,0 @@
1
- import { useSuspenseQuery } from "@tanstack/react-query"
2
- import { Card } from "@/components/ui/card"
3
- import { apiClient } from "@/lib/api-client"
4
- import type { DocFile, DocFileMd } from "@/lib/types"
5
-
6
- type Props = {
7
- project: string
8
- }
9
-
10
- export function MilestonesEditor(props: Props) {
11
- const { data: milestonesData } = useSuspenseQuery({
12
- queryKey: ["directories", `products/${props.project}/milestones`],
13
- queryFn: async () => {
14
- const response = await apiClient.api.directories[":path{.+}"].$get({
15
- param: { path: `products/${props.project}/milestones` },
16
- })
17
- if (!response.ok) {
18
- throw new Error("Failed to fetch milestones")
19
- }
20
- return response.json()
21
- },
22
- })
23
-
24
- const { data: featuresData } = useSuspenseQuery({
25
- queryKey: ["directories", `products/${props.project}/features`],
26
- queryFn: async () => {
27
- const response = await apiClient.api.directories[":path{.+}"].$get({
28
- param: { path: `products/${props.project}/features` },
29
- })
30
- if (!response.ok) {
31
- throw new Error("Failed to fetch features")
32
- }
33
- return response.json()
34
- },
35
- })
36
-
37
- function isDocFileMd(file: DocFile): file is DocFileMd {
38
- return file.type === 'markdown'
39
- }
40
-
41
- function hasMarkdownContent(file: { content: unknown }): file is { content: { title: string; description: string; frontMatter: Record<string, unknown> } } {
42
- return typeof file.content === 'object' && file.content !== null && 'frontMatter' in file.content
43
- }
44
-
45
- const milestones = (milestonesData.files || []).filter(isDocFileMd)
46
- const features = (featuresData.files || []).filter(isDocFileMd)
47
-
48
- function getFeaturesByMilestone(milestoneId: string) {
49
- return features.filter(
50
- (feature) => {
51
- if (!hasMarkdownContent(feature)) return false
52
- const frontMatter = feature.content.frontMatter
53
- return typeof frontMatter === 'object' && frontMatter !== null && 'milestone' in frontMatter && frontMatter.milestone === milestoneId
54
- }
55
- )
56
- }
57
-
58
- return (
59
- <div className="grid gap-2">
60
- {milestones.map((milestone) => {
61
- const milestoneFeatures = getFeaturesByMilestone(milestone.path.name)
62
- return (
63
- <Card key={milestone.path.name} className="gap-2 overflow-hidden p-2">
64
- <div className="space-y-1">
65
- <h2 className="font-semibold text-xl">
66
- {hasMarkdownContent(milestone) ? milestone.content.title || milestone.path.name : milestone.path.name}
67
- </h2>
68
- <p className="text-muted-foreground text-sm">
69
- {hasMarkdownContent(milestone) ? milestone.content.description : ''}
70
- </p>
71
- </div>
72
- {milestoneFeatures.length > 0 ? (
73
- <div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
74
- {milestoneFeatures.map((feature) => (
75
- <div key={feature.path.name} className="rounded border p-2">
76
- <h3 className="font-medium">
77
- {hasMarkdownContent(feature) ? feature.content.title || feature.path.name : feature.path.name}
78
- </h3>
79
- <p className="text-muted-foreground text-sm">
80
- {hasMarkdownContent(feature) ? feature.content.description : ''}
81
- </p>
82
- </div>
83
- ))}
84
- </div>
85
- ) : (
86
- <p className="py-4 text-center text-muted-foreground">
87
- このマイルストーンに関連する機能はありません
88
- </p>
89
- )}
90
- </Card>
91
- )
92
- })}
93
- </div>
94
- )
95
- }
@@ -1,20 +0,0 @@
1
- import type { ReactNode } from "react"
2
- import { SidebarButton } from "@/components/sidebar-button"
3
- import { Input } from "@/components/ui/input"
4
-
5
- type Props = {
6
- filePath: string
7
- }
8
-
9
- export function PageHeader(props: Props): ReactNode {
10
- const paths = props.filePath.split("/")
11
-
12
- const fileName = paths[paths.length - 1]
13
-
14
- return (
15
- <div className="flex flex-1 items-center gap-2">
16
- <SidebarButton />
17
- <Input value={fileName} readOnly />
18
- </div>
19
- )
20
- }
@@ -1,139 +0,0 @@
1
- import { CheckCircle, X } from "lucide-react"
2
- import { SingleRelationSelect } from "@/components/file-view/single-relation-select"
3
- import { Button } from "@/components/ui/button"
4
- import type { DocFile, DocFileMd, DocRelationFile } from "@/lib/types"
5
- import { cn } from "@/lib/utils"
6
-
7
- function isDocFileMd(file: DocFile): file is DocFileMd {
8
- return file.type === 'markdown'
9
- }
10
-
11
- type Priority = "high" | "medium" | "low"
12
-
13
- type Props = {
14
- feature: DocFile
15
- milestoneOptions?: DocRelationFile[]
16
- onMilestoneUpdate?: (featurePath: string, milestone: string) => void
17
- onPropertyUpdate?: (
18
- featurePath: string,
19
- field: string,
20
- value: unknown,
21
- ) => void
22
- onFeatureRemove?: (featurePath: string) => void
23
- }
24
-
25
- export function FeatureItem(props: Props) {
26
- if (!isDocFileMd(props.feature)) {
27
- return null
28
- }
29
-
30
- const frontMatter = props.feature.content.frontMatter as Record<string, unknown>
31
-
32
- const isDone = (frontMatter?.["is-done"] as boolean) === true
33
-
34
- const priorityNumber = (frontMatter?.priority as number) || 0
35
-
36
- const priority =
37
- priorityNumber === 2 ? "high" : priorityNumber === 1 ? "medium" : "low"
38
-
39
- const milestone = frontMatter?.milestone as string
40
-
41
- const getCurrentValue = () => {
42
- const matchingOption = props.milestoneOptions?.find(
43
- (option) => option.label === milestone,
44
- )
45
- return matchingOption?.name || ""
46
- }
47
-
48
- const handleMilestoneChange = (value: string) => {
49
- if (!value) {
50
- props.onMilestoneUpdate?.(props.feature.path.path, "")
51
- return
52
- }
53
-
54
- const selectedOption = props.milestoneOptions?.find(
55
- (option) => option.name === value,
56
- )
57
- const labelToSave = selectedOption?.label || value
58
- props.onMilestoneUpdate?.(props.feature.path.path, labelToSave)
59
- }
60
-
61
- const handleDoneToggle = () => {
62
- props.onPropertyUpdate?.(props.feature.path.path, "is-done", !isDone)
63
- }
64
-
65
- const handlePriorityClick = () => {
66
- const currentValue = (frontMatter?.priority as number) || 0
67
- const nextValue = (currentValue + 1) % 3
68
- props.onPropertyUpdate?.(props.feature.path.path, "priority", nextValue)
69
- }
70
-
71
- const getPriorityColor = (priority: Priority): string => {
72
- switch (priority) {
73
- case "high":
74
- return "border-red-500"
75
- case "medium":
76
- return "border-yellow-500"
77
- case "low":
78
- return "border-green-500"
79
- default:
80
- return "border-gray-500"
81
- }
82
- }
83
-
84
- const getPriorityLabel = (priority: Priority): string => {
85
- if (priority === "high") return "高"
86
- if (priority === "medium") return "中"
87
- return "低"
88
- }
89
-
90
- return (
91
- <div
92
- className={cn("space-y-2 rounded-md border p-2", isDone && "opacity-75")}
93
- >
94
- <div className="flex items-center gap-3">
95
- <Button
96
- variant={isDone ? "default" : "secondary"}
97
- size="sm"
98
- onClick={handleDoneToggle}
99
- >
100
- <CheckCircle className="size-4" />
101
- </Button>
102
- <h3 className={`font-bold ${isDone ? "line-through opacity-80" : ""}`}>
103
- {props.feature.content.title || props.feature.path.name}
104
- </h3>
105
- </div>
106
- {props.feature.content.description && (
107
- <p className="text-sm opacity-80">{props.feature.content.description}</p>
108
- )}
109
- <div className="flex flex-col items-end justify-between gap-2 lg:flex-row">
110
- <div className="text-xs opacity-50">{props.feature.path.name}</div>
111
- <div className="flex gap-2">
112
- <SingleRelationSelect
113
- value={getCurrentValue()}
114
- relationOptions={props.milestoneOptions}
115
- onValueChange={handleMilestoneChange}
116
- />
117
- <Button
118
- variant="outline"
119
- className={cn("w-8", getPriorityColor(priority))}
120
- onClick={handlePriorityClick}
121
- >
122
- {getPriorityLabel(priority)}
123
- </Button>
124
- {props.onFeatureRemove && (
125
- <Button
126
- variant="outline"
127
- className="w-8"
128
- onClick={() => {
129
- props.onFeatureRemove?.(props.feature.path.path)
130
- }}
131
- >
132
- <X className="size-3" />
133
- </Button>
134
- )}
135
- </div>
136
- </div>
137
- </div>
138
- )
139
- }