@archbase/kanban 4.0.37
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/LICENSE +13 -0
- package/README.md +630 -0
- package/dist/archbase-kanban-4.0.37.tgz +0 -0
- package/dist/components/Card/Card.d.ts +26 -0
- package/dist/components/Card/index.d.ts +1 -0
- package/dist/components/CardSkeleton/CardSkeleton.d.ts +9 -0
- package/dist/components/CardSkeleton/index.d.ts +2 -0
- package/dist/components/Column/Column.d.ts +47 -0
- package/dist/components/Column/index.d.ts +1 -0
- package/dist/components/ColumnAdder/ColumnAdder.d.ts +6 -0
- package/dist/components/ColumnAdder/index.d.ts +1 -0
- package/dist/components/ColumnContent/ColumnContent.d.ts +24 -0
- package/dist/components/ColumnContent/index.d.ts +1 -0
- package/dist/components/ColumnHeader/ColumnHeader.d.ts +10 -0
- package/dist/components/ColumnHeader/index.d.ts +1 -0
- package/dist/components/DefaultCard/DefaultCard.d.ts +3 -0
- package/dist/components/DefaultCard/index.d.ts +1 -0
- package/dist/components/GenericItem/GenericItem.d.ts +26 -0
- package/dist/components/GenericItem/index.d.ts +1 -0
- package/dist/components/Kanban.d.ts +3 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/types.d.ts +124 -0
- package/dist/context/KanbanContext.d.ts +5 -0
- package/dist/global/dnd/dropManager.d.ts +16 -0
- package/dist/global/dnd/useCardDnd.d.ts +22 -0
- package/dist/global/dnd/useColumnDnd.d.ts +29 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1041 -0
- package/dist/utils/columnsUtils.d.ts +4 -0
- package/dist/utils/getPrefix.d.ts +2 -0
- package/dist/utils/getSharedProps.d.ts +8 -0
- package/dist/utils/infinite-scroll.d.ts +4 -0
- package/dist/utils/mergeRefs.d.ts +5 -0
- package/dist/utils/scroll.d.ts +2 -0
- package/package.json +66 -0
- package/src/common/_index.scss +0 -0
- package/src/components/Card/Card.tsx +215 -0
- package/src/components/Card/_Card.scss +11 -0
- package/src/components/Card/_index.scss +1 -0
- package/src/components/Card/index.ts +1 -0
- package/src/components/CardSkeleton/CardSkeleton.tsx +45 -0
- package/src/components/CardSkeleton/_CardSkeleton.scss +245 -0
- package/src/components/CardSkeleton/_index.scss +1 -0
- package/src/components/CardSkeleton/index.ts +2 -0
- package/src/components/Column/Column.tsx +246 -0
- package/src/components/Column/_Column.scss +44 -0
- package/src/components/Column/_index.scss +1 -0
- package/src/components/Column/index.ts +1 -0
- package/src/components/ColumnAdder/ColumnAdder.tsx +18 -0
- package/src/components/ColumnAdder/_ColumnAdder.scss +0 -0
- package/src/components/ColumnAdder/_index.scss +1 -0
- package/src/components/ColumnAdder/index.ts +1 -0
- package/src/components/ColumnContent/ColumnContent.tsx +238 -0
- package/src/components/ColumnContent/_ColumnContent.scss +23 -0
- package/src/components/ColumnContent/_index.scss +1 -0
- package/src/components/ColumnContent/index.ts +1 -0
- package/src/components/ColumnHeader/ColumnHeader.tsx +43 -0
- package/src/components/ColumnHeader/_ColumnHeader.scss +26 -0
- package/src/components/ColumnHeader/_index.scss +1 -0
- package/src/components/ColumnHeader/index.ts +1 -0
- package/src/components/DefaultCard/DefaultCard.tsx +9 -0
- package/src/components/DefaultCard/_DefaultCard.scss +14 -0
- package/src/components/DefaultCard/_index.scss +1 -0
- package/src/components/DefaultCard/index.ts +1 -0
- package/src/components/GenericItem/GenericItem.tsx +132 -0
- package/src/components/GenericItem/_GenericItem.scss +4 -0
- package/src/components/GenericItem/_index.scss +1 -0
- package/src/components/GenericItem/index.ts +1 -0
- package/src/components/Kanban.tsx +102 -0
- package/src/components/_Kanban.scss +8 -0
- package/src/components/_index.scss +9 -0
- package/src/components/index.ts +2 -0
- package/src/components/types.ts +155 -0
- package/src/context/KanbanContext.tsx +19 -0
- package/src/global/_index.scss +3 -0
- package/src/global/assets/styles/abstracts/_breakpoints.scss +71 -0
- package/src/global/assets/styles/abstracts/_colors.scss +85 -0
- package/src/global/assets/styles/abstracts/_functions.scss +3 -0
- package/src/global/assets/styles/abstracts/_index.scss +5 -0
- package/src/global/assets/styles/abstracts/_mixins.scss +49 -0
- package/src/global/assets/styles/abstracts/_padding.scss +1 -0
- package/src/global/assets/styles/abstracts/_variables.scss +8 -0
- package/src/global/assets/styles/base/_index.scss +34 -0
- package/src/global/dnd/dropManager.ts +301 -0
- package/src/global/dnd/useCardDnd.tsx +225 -0
- package/src/global/dnd/useColumnDnd.tsx +231 -0
- package/src/global/theme-default.scss +137 -0
- package/src/index.ts +6 -0
- package/src/utils/columnsUtils.ts +19 -0
- package/src/utils/getPrefix.ts +7 -0
- package/src/utils/getSharedProps.ts +18 -0
- package/src/utils/infinite-scroll.ts +18 -0
- package/src/utils/mergeRefs.ts +23 -0
- package/src/utils/scroll.ts +34 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { BoardData, BoardItem } from '../components';
|
|
2
|
+
export declare const getColumnsFromDataSource: (dataSource: BoardData) => BoardItem[];
|
|
3
|
+
export declare const getColumnChildren: (column: BoardItem, dataSource: BoardData) => BoardItem[];
|
|
4
|
+
export declare const getHeaderHeight: (header: HTMLDivElement) => number;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BoardProps } from '../components';
|
|
2
|
+
export declare const getSharedProps: (props: BoardProps) => {
|
|
3
|
+
viewOnly: boolean;
|
|
4
|
+
virtualization: boolean;
|
|
5
|
+
cardsGap: number;
|
|
6
|
+
allowColumnAdder: boolean;
|
|
7
|
+
allowListFooter: (column: import('../components').BoardItem) => boolean;
|
|
8
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
type CallbackRef<T> = (ref: T | null) => void;
|
|
3
|
+
type Ref<T> = React.MutableRefObject<T> | CallbackRef<T>;
|
|
4
|
+
export default function mergeRefs<T>(refA?: Ref<T | null> | null, refB?: Ref<T | null> | null): React.RefCallback<T>;
|
|
5
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@archbase/kanban",
|
|
3
|
+
"version": "4.0.37",
|
|
4
|
+
"description": "Kanban board components for Archbase React, based on react-kanban-kit with Archbase packaging",
|
|
5
|
+
"author": "Edson Martins <edsonmartins2005@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./dist/index.css": "./dist/index.css",
|
|
18
|
+
"./dist/cjs/index.css": "./dist/index.css",
|
|
19
|
+
"./dist/esm/index.css": "./dist/index.css"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"src",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"react": "^18.3.0 || ^19.2.0",
|
|
29
|
+
"react-dom": "^18.3.0 || ^19.2.0"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@atlaskit/pragmatic-drag-and-drop": "^1.5.0",
|
|
33
|
+
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.0",
|
|
34
|
+
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
|
|
35
|
+
"@atlaskit/pragmatic-drag-and-drop-react-beautiful-dnd-autoscroll": "^2.0.0",
|
|
36
|
+
"@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^2.1.0",
|
|
37
|
+
"classes": "^0.3.0",
|
|
38
|
+
"classnames": "^2.5.1",
|
|
39
|
+
"virtua": "^0.40.4"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.16.1",
|
|
43
|
+
"@types/react": "^19.0.6",
|
|
44
|
+
"@types/react-dom": "^19.0.2",
|
|
45
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
46
|
+
"react": "^19.2.3",
|
|
47
|
+
"react-dom": "^19.2.3",
|
|
48
|
+
"sass-embedded": "^1.80.7",
|
|
49
|
+
"typescript": "~5.7.2",
|
|
50
|
+
"vite": "^6.0.3",
|
|
51
|
+
"vite-plugin-dts": "^4.5.4",
|
|
52
|
+
"vitest": "^2.1.8"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"registry": "http://192.168.1.110:4873"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"dev": "NODE_OPTIONS=\"--max-old-space-size=16384\" vite build --watch",
|
|
59
|
+
"build": "NODE_OPTIONS=\"--max-old-space-size=16384\" vite build",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"test:watch": "vitest",
|
|
62
|
+
"test:coverage": "vitest run --coverage",
|
|
63
|
+
"lint": "eslint . --max-warnings 0",
|
|
64
|
+
"clean": "rm -rf dist .turbo node_modules"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { withPrefix } from "@/utils/getPrefix";
|
|
2
|
+
import React, { Fragment, memo, useMemo } from "react";
|
|
3
|
+
import { BoardItem, DndState } from "../types";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
import { TaskCardState, useCardDnd } from "@/global/dnd/useCardDnd";
|
|
6
|
+
|
|
7
|
+
export const CardShadow = memo(
|
|
8
|
+
({
|
|
9
|
+
height,
|
|
10
|
+
customIndicator,
|
|
11
|
+
}: {
|
|
12
|
+
height: number;
|
|
13
|
+
customIndicator?: React.ReactNode;
|
|
14
|
+
}) => {
|
|
15
|
+
return (
|
|
16
|
+
<div className={withPrefix("card-shadow-container")}>
|
|
17
|
+
{customIndicator || (
|
|
18
|
+
<div
|
|
19
|
+
className={withPrefix("card-shadow")}
|
|
20
|
+
style={{ height: `${height - 2}px` }}
|
|
21
|
+
/>
|
|
22
|
+
)}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const CardDisplay = (props: {
|
|
29
|
+
outerRef?: React.RefObject<HTMLDivElement>;
|
|
30
|
+
innerRef?: React.RefObject<HTMLDivElement>;
|
|
31
|
+
state: TaskCardState;
|
|
32
|
+
data: BoardItem;
|
|
33
|
+
column: BoardItem;
|
|
34
|
+
index: number;
|
|
35
|
+
isDraggable: boolean;
|
|
36
|
+
render: (props: {
|
|
37
|
+
data: BoardItem;
|
|
38
|
+
column: BoardItem;
|
|
39
|
+
index: number;
|
|
40
|
+
isDraggable: boolean;
|
|
41
|
+
}) => React.ReactNode;
|
|
42
|
+
onClick?: (e: React.MouseEvent<HTMLDivElement>, card: BoardItem) => void;
|
|
43
|
+
cardsGap?: number;
|
|
44
|
+
renderCardDragIndicator?: (card: BoardItem, info: any) => React.ReactNode;
|
|
45
|
+
renderGap?: (column: BoardItem) => React.ReactNode;
|
|
46
|
+
}) => {
|
|
47
|
+
const {
|
|
48
|
+
outerRef,
|
|
49
|
+
innerRef,
|
|
50
|
+
state,
|
|
51
|
+
data,
|
|
52
|
+
column,
|
|
53
|
+
index,
|
|
54
|
+
isDraggable,
|
|
55
|
+
cardsGap,
|
|
56
|
+
render,
|
|
57
|
+
onClick,
|
|
58
|
+
renderCardDragIndicator,
|
|
59
|
+
} = props;
|
|
60
|
+
|
|
61
|
+
const containerStyle = useMemo(() => {
|
|
62
|
+
const styles: React.CSSProperties = {};
|
|
63
|
+
if (state.type === "is-dragging-and-left-self") {
|
|
64
|
+
styles.display = "none";
|
|
65
|
+
}
|
|
66
|
+
return styles;
|
|
67
|
+
}, [state.type]);
|
|
68
|
+
|
|
69
|
+
const innerStyle = useMemo(() => {
|
|
70
|
+
if (state.type === "is-dragging") {
|
|
71
|
+
return { opacity: 0.6 };
|
|
72
|
+
}
|
|
73
|
+
if (state.type === "preview") {
|
|
74
|
+
return {
|
|
75
|
+
width: state.dragging.width,
|
|
76
|
+
height: state.dragging.height,
|
|
77
|
+
transform: "rotate(4deg)",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return {};
|
|
81
|
+
}, [state]);
|
|
82
|
+
|
|
83
|
+
const showTopShadow = state.type === "is-over" && state.closestEdge === "top";
|
|
84
|
+
const showBottomShadow =
|
|
85
|
+
state.type === "is-over" && state.closestEdge === "bottom";
|
|
86
|
+
const shadowHeight = state.type === "is-over" ? state.dragging.height : 0;
|
|
87
|
+
const renderContent = render({ data, column, index, isDraggable });
|
|
88
|
+
const customIndicator = renderCardDragIndicator?.(
|
|
89
|
+
state.type === "is-dragging" ? data : null,
|
|
90
|
+
{
|
|
91
|
+
height: shadowHeight,
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<Fragment>
|
|
97
|
+
<div
|
|
98
|
+
ref={outerRef}
|
|
99
|
+
className={withPrefix("card-outer")}
|
|
100
|
+
onClick={(e) => onClick?.(e, data)}
|
|
101
|
+
style={{
|
|
102
|
+
...containerStyle,
|
|
103
|
+
...(cardsGap !== undefined ? { marginBottom: cardsGap } : {}),
|
|
104
|
+
}}
|
|
105
|
+
data-test-id={data?.id}
|
|
106
|
+
data-rkk-column={column?.id}
|
|
107
|
+
data-rkk-index={index}
|
|
108
|
+
>
|
|
109
|
+
{showTopShadow && (
|
|
110
|
+
<CardShadow height={shadowHeight} customIndicator={customIndicator} />
|
|
111
|
+
)}
|
|
112
|
+
<div
|
|
113
|
+
ref={innerRef}
|
|
114
|
+
className={withPrefix("card-inner")}
|
|
115
|
+
style={{
|
|
116
|
+
...innerStyle,
|
|
117
|
+
marginBottom: showBottomShadow ? cardsGap : 0,
|
|
118
|
+
marginTop: showTopShadow ? cardsGap : 0,
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
{renderContent}
|
|
122
|
+
</div>
|
|
123
|
+
{showBottomShadow && (
|
|
124
|
+
<CardShadow height={shadowHeight} customIndicator={customIndicator} />
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
{/* {renderGap?.(column)} */}
|
|
128
|
+
</Fragment>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
interface Props {
|
|
133
|
+
render: (props: {
|
|
134
|
+
data: BoardItem;
|
|
135
|
+
column: BoardItem;
|
|
136
|
+
index: number;
|
|
137
|
+
isDraggable: boolean;
|
|
138
|
+
}) => React.ReactNode;
|
|
139
|
+
data: BoardItem;
|
|
140
|
+
column: BoardItem;
|
|
141
|
+
index: number;
|
|
142
|
+
isDraggable: boolean;
|
|
143
|
+
onClick?: (e: React.MouseEvent<HTMLDivElement>, card: BoardItem) => void;
|
|
144
|
+
cardsGap?: number;
|
|
145
|
+
renderGap?: (column: BoardItem) => React.ReactNode;
|
|
146
|
+
onCardDndStateChange?: (info: DndState) => void;
|
|
147
|
+
renderCardDragIndicator?: (card: BoardItem, info: any) => React.ReactNode;
|
|
148
|
+
renderCardDragPreview?: (card: BoardItem, info: any) => React.ReactNode;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const Card = (props: Props) => {
|
|
152
|
+
const {
|
|
153
|
+
render,
|
|
154
|
+
data,
|
|
155
|
+
column,
|
|
156
|
+
index,
|
|
157
|
+
isDraggable,
|
|
158
|
+
cardsGap,
|
|
159
|
+
onClick,
|
|
160
|
+
onCardDndStateChange,
|
|
161
|
+
renderCardDragIndicator,
|
|
162
|
+
renderCardDragPreview,
|
|
163
|
+
renderGap,
|
|
164
|
+
} = props;
|
|
165
|
+
const { outerRef, innerRef, state } = useCardDnd(
|
|
166
|
+
data,
|
|
167
|
+
column,
|
|
168
|
+
index,
|
|
169
|
+
isDraggable,
|
|
170
|
+
onCardDndStateChange,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<>
|
|
175
|
+
<CardDisplay
|
|
176
|
+
outerRef={outerRef}
|
|
177
|
+
innerRef={innerRef}
|
|
178
|
+
state={state}
|
|
179
|
+
data={data}
|
|
180
|
+
column={column}
|
|
181
|
+
index={index}
|
|
182
|
+
isDraggable={isDraggable}
|
|
183
|
+
render={render}
|
|
184
|
+
onClick={onClick}
|
|
185
|
+
cardsGap={cardsGap}
|
|
186
|
+
renderCardDragIndicator={renderCardDragIndicator}
|
|
187
|
+
renderGap={renderGap}
|
|
188
|
+
/>
|
|
189
|
+
|
|
190
|
+
{state.type === "preview"
|
|
191
|
+
? createPortal(
|
|
192
|
+
renderCardDragPreview?.(data, {
|
|
193
|
+
state,
|
|
194
|
+
data,
|
|
195
|
+
column,
|
|
196
|
+
index,
|
|
197
|
+
isDraggable,
|
|
198
|
+
}) || (
|
|
199
|
+
<CardDisplay
|
|
200
|
+
state={state}
|
|
201
|
+
data={data}
|
|
202
|
+
column={column}
|
|
203
|
+
index={index}
|
|
204
|
+
isDraggable={isDraggable}
|
|
205
|
+
render={render}
|
|
206
|
+
/>
|
|
207
|
+
),
|
|
208
|
+
state.container,
|
|
209
|
+
)
|
|
210
|
+
: null}
|
|
211
|
+
</>
|
|
212
|
+
);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
export default Card;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
@use "../../global/assets/styles/abstracts/variables" as *;
|
|
2
|
+
|
|
3
|
+
.#{$prefix}-card-shadow-container {
|
|
4
|
+
position: relative;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.#{$prefix}-card-shadow {
|
|
8
|
+
border-radius: 8px;
|
|
9
|
+
background-color: #ffffffb7;
|
|
10
|
+
border: 1px solid var(--Schemes-Outline-Variant, #e8ecf5);
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@forward "./Card";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Card";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { withPrefix } from "@/utils/getPrefix";
|
|
3
|
+
|
|
4
|
+
export type SkeletonAnimationType = "shimmer" | "pulse" | "wave";
|
|
5
|
+
|
|
6
|
+
interface CardSkeletonProps {
|
|
7
|
+
className?: string;
|
|
8
|
+
style?: React.CSSProperties;
|
|
9
|
+
animationType?: SkeletonAnimationType;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const CardSkeleton: React.FC<CardSkeletonProps> = ({
|
|
13
|
+
className,
|
|
14
|
+
style,
|
|
15
|
+
animationType = "shimmer",
|
|
16
|
+
}) => {
|
|
17
|
+
const skeletonClass = `${withPrefix("skeleton")} ${
|
|
18
|
+
animationType === "pulse" ? withPrefix("skeleton-pulse") : ""
|
|
19
|
+
} ${animationType === "wave" ? withPrefix("skeleton-wave") : ""} ${
|
|
20
|
+
className || ""
|
|
21
|
+
}`.trim();
|
|
22
|
+
|
|
23
|
+
const renderDefaultSkeleton = () => (
|
|
24
|
+
<div className={withPrefix("skeleton-content")}>
|
|
25
|
+
<div className={withPrefix("skeleton-title")}></div>
|
|
26
|
+
|
|
27
|
+
<div className={withPrefix("skeleton-description")}>
|
|
28
|
+
<div className={withPrefix("skeleton-line")}></div>
|
|
29
|
+
<div
|
|
30
|
+
className={`${withPrefix("skeleton-line")} ${withPrefix(
|
|
31
|
+
"skeleton-line-short"
|
|
32
|
+
)}`}
|
|
33
|
+
></div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className={skeletonClass} style={style}>
|
|
40
|
+
{renderDefaultSkeleton()}
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default CardSkeleton;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
@use "../../global/assets/styles/abstracts/variables" as *;
|
|
2
|
+
|
|
3
|
+
// Shimmer animation keyframes
|
|
4
|
+
@keyframes skeleton-shimmer {
|
|
5
|
+
0% {
|
|
6
|
+
background-position: -200px 0;
|
|
7
|
+
}
|
|
8
|
+
100% {
|
|
9
|
+
background-position: calc(200px + 100%) 0;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Wave animation keyframes
|
|
14
|
+
@keyframes skeleton-wave {
|
|
15
|
+
0% {
|
|
16
|
+
transform: translateX(-100%);
|
|
17
|
+
}
|
|
18
|
+
50% {
|
|
19
|
+
transform: translateX(100%);
|
|
20
|
+
}
|
|
21
|
+
100% {
|
|
22
|
+
transform: translateX(100%);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Pulse animation keyframes
|
|
27
|
+
@keyframes skeleton-pulse {
|
|
28
|
+
0%,
|
|
29
|
+
100% {
|
|
30
|
+
opacity: 1;
|
|
31
|
+
}
|
|
32
|
+
50% {
|
|
33
|
+
opacity: 0.4;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.#{$prefix}-skeleton {
|
|
38
|
+
border-radius: 8px;
|
|
39
|
+
border: 1px solid #e8ecf5;
|
|
40
|
+
background: #fff;
|
|
41
|
+
padding: 16px;
|
|
42
|
+
margin-bottom: 8px;
|
|
43
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
44
|
+
transition: box-shadow 0.2s ease;
|
|
45
|
+
position: relative;
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
|
|
48
|
+
&:hover {
|
|
49
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&-content {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
gap: 12px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Base skeleton element styles
|
|
59
|
+
%skeleton-base {
|
|
60
|
+
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
|
61
|
+
background-size: 200px 100%;
|
|
62
|
+
animation: skeleton-shimmer 1.5s infinite ease-in-out;
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
position: relative;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Wave animation base
|
|
68
|
+
%skeleton-wave-base {
|
|
69
|
+
background: #f0f0f0;
|
|
70
|
+
position: relative;
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
border-radius: 4px;
|
|
73
|
+
|
|
74
|
+
&::after {
|
|
75
|
+
position: absolute;
|
|
76
|
+
top: 0;
|
|
77
|
+
right: 0;
|
|
78
|
+
bottom: 0;
|
|
79
|
+
left: 0;
|
|
80
|
+
transform: translateX(-100%);
|
|
81
|
+
background: linear-gradient(
|
|
82
|
+
90deg,
|
|
83
|
+
rgba(255, 255, 255, 0) 0,
|
|
84
|
+
rgba(255, 255, 255, 0.2) 20%,
|
|
85
|
+
rgba(255, 255, 255, 0.5) 60%,
|
|
86
|
+
rgba(255, 255, 255, 0)
|
|
87
|
+
);
|
|
88
|
+
animation: skeleton-wave 2s infinite;
|
|
89
|
+
content: "";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Title skeleton
|
|
94
|
+
&-title {
|
|
95
|
+
@extend %skeleton-base;
|
|
96
|
+
height: 20px;
|
|
97
|
+
width: 80%;
|
|
98
|
+
border-radius: 6px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Description section
|
|
102
|
+
&-description {
|
|
103
|
+
display: flex;
|
|
104
|
+
flex-direction: column;
|
|
105
|
+
gap: 8px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
&-line {
|
|
109
|
+
@extend %skeleton-base;
|
|
110
|
+
height: 14px;
|
|
111
|
+
width: 100%;
|
|
112
|
+
|
|
113
|
+
&-short {
|
|
114
|
+
width: 65%;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Priority indicator
|
|
119
|
+
&-priority {
|
|
120
|
+
@extend %skeleton-base;
|
|
121
|
+
height: 4px;
|
|
122
|
+
width: 40px;
|
|
123
|
+
border-radius: 2px;
|
|
124
|
+
margin-bottom: 4px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Tags section
|
|
128
|
+
&-tags {
|
|
129
|
+
display: flex;
|
|
130
|
+
gap: 8px;
|
|
131
|
+
flex-wrap: wrap;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
&-tag {
|
|
135
|
+
@extend %skeleton-base;
|
|
136
|
+
height: 20px;
|
|
137
|
+
width: 60px;
|
|
138
|
+
border-radius: 12px;
|
|
139
|
+
|
|
140
|
+
&:nth-child(2) {
|
|
141
|
+
width: 45px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
&:nth-child(3) {
|
|
145
|
+
width: 55px;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Footer section
|
|
150
|
+
&-footer {
|
|
151
|
+
display: flex;
|
|
152
|
+
justify-content: space-between;
|
|
153
|
+
align-items: center;
|
|
154
|
+
margin-top: 4px;
|
|
155
|
+
|
|
156
|
+
&-left {
|
|
157
|
+
display: flex;
|
|
158
|
+
align-items: center;
|
|
159
|
+
gap: 8px;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
&-avatar {
|
|
164
|
+
@extend %skeleton-base;
|
|
165
|
+
width: 24px;
|
|
166
|
+
height: 24px;
|
|
167
|
+
border-radius: 50%;
|
|
168
|
+
flex-shrink: 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
&-assignee {
|
|
172
|
+
@extend %skeleton-base;
|
|
173
|
+
height: 12px;
|
|
174
|
+
width: 60px;
|
|
175
|
+
border-radius: 3px;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
&-date {
|
|
179
|
+
@extend %skeleton-base;
|
|
180
|
+
height: 12px;
|
|
181
|
+
width: 80px;
|
|
182
|
+
border-radius: 3px;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Wave animation variant
|
|
187
|
+
.#{$prefix}-skeleton-wave {
|
|
188
|
+
.#{$prefix}-skeleton-title,
|
|
189
|
+
.#{$prefix}-skeleton-line,
|
|
190
|
+
.#{$prefix}-skeleton-tag,
|
|
191
|
+
.#{$prefix}-skeleton-avatar,
|
|
192
|
+
.#{$prefix}-skeleton-assignee,
|
|
193
|
+
.#{$prefix}-skeleton-date,
|
|
194
|
+
.#{$prefix}-skeleton-priority {
|
|
195
|
+
@extend %skeleton-wave-base;
|
|
196
|
+
animation: none; // Remove shimmer animation
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Pulse animation variant
|
|
201
|
+
.#{$prefix}-skeleton-pulse {
|
|
202
|
+
.#{$prefix}-skeleton-title,
|
|
203
|
+
.#{$prefix}-skeleton-line,
|
|
204
|
+
.#{$prefix}-skeleton-tag,
|
|
205
|
+
.#{$prefix}-skeleton-avatar,
|
|
206
|
+
.#{$prefix}-skeleton-assignee,
|
|
207
|
+
.#{$prefix}-skeleton-date,
|
|
208
|
+
.#{$prefix}-skeleton-priority {
|
|
209
|
+
background: #f0f0f0;
|
|
210
|
+
animation: skeleton-pulse 1.5s ease-in-out infinite;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Responsive design for smaller screens
|
|
215
|
+
@media (max-width: 768px) {
|
|
216
|
+
.#{$prefix}-skeleton {
|
|
217
|
+
padding: 12px;
|
|
218
|
+
|
|
219
|
+
&-content {
|
|
220
|
+
gap: 10px;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
&-title {
|
|
224
|
+
height: 18px;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
&-line {
|
|
228
|
+
height: 12px;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
&-tag {
|
|
232
|
+
height: 18px;
|
|
233
|
+
width: 50px;
|
|
234
|
+
|
|
235
|
+
&:nth-child(2) {
|
|
236
|
+
width: 40px;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
&-avatar {
|
|
241
|
+
width: 20px;
|
|
242
|
+
height: 20px;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@forward "./CardSkeleton";
|