PluginProbe ʕ •ᴥ•ʔ
Presto Player / 4.4.0
Presto Player v4.4.0
4.4.1 4.4.0 4.3.3 4.3.2 4.3.1 4.3.0 4.2.4 4.2.3 4.2.2 4.2.0 4.2.1 trunk 1.10.0 1.10.1 1.10.2 1.11.0 1.12.0 1.13.0 1.14.0 1.14.1 1.5.10 1.5.11 1.5.12 1.5.13 1.5.14 1.5.15 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.10 1.6.11 1.6.12 1.6.13 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.16 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.2.0 2.2.1 2.2.2 2.2.3 2.2.3-beta1 2.3.0 2.3.1 2.3.2 2.3.3 3.0.0 3.0.0-beta1 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.1.0 3.1.1 3.1.2 3.1.3 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4
presto-player / src / admin / dashboard / pages / learn / StepItem.js
presto-player / src / admin / dashboard / pages / learn Last commit date
ChapterCard.js 1 week ago LearnPage.js 1 week ago LearnPageSkeleton.js 3 months ago ProgressPill.js 3 months ago ScreenshotDialog.js 3 months ago SetupCompleteCard.js 3 months ago StepItem.js 1 week ago useLearnProgress.js 3 months ago
StepItem.js
160 lines
1 import { useState } from '@wordpress/element';
2 import { Button } from '@bsf/force-ui';
3 import {
4 ArrowUpRight,
5 Check,
6 ChevronDown,
7 ChevronUp,
8 Circle,
9 Info,
10 Sparkles,
11 } from 'lucide-react';
12 import ScreenshotDialog from './ScreenshotDialog';
13
14 const { __ } = wp.i18n;
15
16 const StepItem = ( { chapterId, step, onToggle } ) => {
17 const [ open, setOpen ] = useState( false );
18 const [ screenshotOpen, setScreenshotOpen ] = useState( false );
19 const completed = !! step.completed;
20
21 const toggleCompleted = ( e ) => {
22 e.stopPropagation();
23 onToggle( chapterId, step.id, ! completed );
24 };
25
26 return (
27 <div className="rounded-lg border border-solid border-border-subtle bg-background-primary">
28 { /* Header */ }
29 <button
30 type="button"
31 onClick={ () => setOpen( ( v ) => ! v ) }
32 aria-expanded={ open }
33 className="w-full flex items-center gap-3 px-4 py-3 bg-transparent border-0 cursor-pointer text-left hover:bg-background-secondary/40 transition-colors"
34 >
35 <span
36 role="button"
37 tabIndex={ 0 }
38 onClick={ toggleCompleted }
39 onKeyDown={ ( e ) => {
40 if ( e.key === 'Enter' || e.key === ' ' ) {
41 toggleCompleted( e );
42 }
43 } }
44 aria-label={
45 completed
46 ? __( 'Mark step incomplete', 'presto-player' )
47 : __( 'Mark step complete', 'presto-player' )
48 }
49 className={ `shrink-0 inline-flex items-center justify-center w-5 h-5 rounded-full border border-solid transition-colors cursor-pointer ${
50 completed
51 ? 'bg-brand-primary-600 border-brand-primary-600 text-white'
52 : 'bg-background-primary border-border-strong text-transparent hover:border-brand-400'
53 }` }
54 >
55 { completed ? (
56 <Check className="w-3 h-3" />
57 ) : (
58 <Circle className="w-0 h-0" />
59 ) }
60 </span>
61
62 <span className="flex-1 text-sm font-medium text-text-primary">
63 { step.title }
64 </span>
65
66 { step.isPro && (
67 <span className="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-semibold uppercase tracking-wide bg-background-secondary text-text-secondary border border-solid border-border-subtle">
68 { __( 'Pro', 'presto-player' ) }
69 </span>
70 ) }
71
72 { open ? (
73 <ChevronUp className="w-5 h-5 text-text-secondary shrink-0" />
74 ) : (
75 <ChevronDown className="w-5 h-5 text-text-secondary shrink-0" />
76 ) }
77 </button>
78
79 { /* Body */ }
80 { open && (
81 <div className="border-0 border-t border-solid border-border-subtle px-4 py-4 flex items-start justify-between gap-4">
82 <div className="flex-1">
83 <p className="m-0 text-sm text-text-secondary leading-relaxed">
84 { step.description }
85 </p>
86
87 { Array.isArray( step.prompts ) && step.prompts.length > 0 && (
88 <div className="mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2">
89 { step.prompts.map( ( prompt, i ) => (
90 <div
91 key={ i }
92 className="group flex items-center gap-2.5 rounded-lg border border-solid border-border-subtle bg-background-primary px-3 py-2.5 transition-colors hover:border-brand-primary-600 hover:bg-brand-primary-50"
93 >
94 <span className="flex size-6 shrink-0 items-center justify-center rounded-md bg-brand-primary-50 text-brand-primary-600">
95 <Sparkles className="size-3.5" aria-hidden="true" />
96 </span>
97 <span className="text-sm leading-snug text-text-primary">
98 { prompt }
99 </span>
100 </div>
101 ) ) }
102 </div>
103 ) }
104
105 { step.docsUrl && (
106 <a
107 href={ step.docsUrl }
108 target="_blank"
109 rel="noopener noreferrer"
110 className="inline-flex items-center gap-1 mt-2 text-xs font-medium text-brand-primary-600 hover:text-brand-hover-700 no-underline"
111 >
112 { __( 'Read documentation', 'presto-player' ) }
113 <ArrowUpRight className="w-3.5 h-3.5" />
114 </a>
115 ) }
116 </div>
117
118 <div className="shrink-0 flex items-center gap-2">
119 { step.screenshot?.url && (
120 <button
121 type="button"
122 onClick={ () => setScreenshotOpen( true ) }
123 aria-label={ __( 'View screenshot', 'presto-player' ) }
124 className="inline-flex items-center justify-center w-8 h-8 rounded-full border-0 bg-background-primary text-text-secondary hover:text-brand-primary-600 cursor-pointer transition-colors"
125 >
126 <Info className="w-4 h-4" />
127 </button>
128 ) }
129
130 { step.headerAction?.url && (
131 <Button
132 variant="primary"
133 size="sm"
134 onClick={ () => {
135 window.open(
136 step.headerAction.url,
137 '_blank',
138 'noopener,noreferrer'
139 );
140 } }
141 >
142 { step.headerAction.label }
143 </Button>
144 ) }
145 </div>
146 </div>
147 ) }
148
149 <ScreenshotDialog
150 open={ screenshotOpen }
151 onOpenChange={ setScreenshotOpen }
152 title={ step.title }
153 screenshot={ step.screenshot }
154 />
155 </div>
156 );
157 };
158
159 export default StepItem;
160